首页 > 技术 > 比特币为什么那么分叉|DeFi YAM 一行代码如何蒸发数亿美元
慢雾安全团队  

比特币为什么那么分叉|DeFi YAM 一行代码如何蒸发数亿美元

摘要:By :?yudan @?慢雾安全团队前言2020 年 8 月 13 日,知名以太坊?DeFi 项目 YAM 官方通过 Twitter 发文表明发现合约中存在漏

By :?yudan @?慢雾安全团队

前言

4a60f8635f5a11570ceb9f366847aad.png

2020 年 8 月 13 日,知名以太坊?DeFi 项目 YAM 官方通过 Twitter 发文表明发现合约中存在漏洞,24 小时内价格暴跌 99% 。慢雾安全团队在收到情报后快速进行了相关的跟进及分析,以下是详细的技术细节。

发生了什么?

1.jpg

以上是?YAM 官方对本次事件的简短说明(来源:

https://medium.com/@yamfinance/save-yam-245598d81cec)。

简单来说就是官方在合约中发现负责调整供应量的函数发生了问题,这个问题导致多余的 YAM 代币放进了 YAM 的 reserves 合约中,并且如果不修正这个问题,将会导致 YAM 的后续治理变为不可能。同时,官方给出了此次漏洞的具体问题代码,如下:

2.jpg

从上图可知,由于编码不规范,YAM 合约在调整 totalSupply 的时候,本应将最后的结果除以 BASE 变量,但是在实际开发过程中却忽略了,导致 totoalSupply 计算不正确,比原来的值要大 10^18 倍。但是代币供应量问题和治理是怎么扯上关系呢?这需要我们针对代码做进一步的分析。

YAM 会变成怎样?

为了深入了解此次漏洞造成的影响,需要对 YAM 项目代码进行深入的了解。根据官方给出的问题代码及项目 Github 地址(https://github.com/yam-finance/yam-protocol),可以定位出调整供应量的?rebase?函数位于 YAMDelegator.sol 合约中,具体代码如下:

function rebase( ? ? ? ?

uint256 epoch, ? ? ? ?

uint256 indexDelta, ? ? ? ?

bool positive ? ?) ? ? ? ?

external ? ? ? ?

returns (uint256) ? ?

{ ? ? ? ?

epoch; indexDelta; positive; ? ? ? ?

delegateAndReturn(); ? ?

}

通过跟踪?rebase?函数,发现?rebase?函数最终调用了 delegateAndReturn 函数,代码如下:

function delegateAndReturn() private returns (bytes memory) { ? ? ? ?

(bool success, ) = implementation.delegatecall(msg.data); ? ? ? ?

assembly { ? ? ? ? ? ?

let free_mem_ptr := mload(0x40) ? ? ? ? ??

?returndatacopy(free_mem_ptr, 0, returndatasize) ? ? ? ? ? ?

switch success ? ? ? ? ? ?

case 0 { revert(free_mem_ptr, returndatasize) } ? ? ? ? ? ?

default { return(free_mem_ptr, returndatasize) } ? ? ? ?

} ? ?

}

通过分析代码,可以发现 delegateAndReturn 函数最终使用 delegatecall 的方式调用了 implementation 地址中的逻辑,也就是说,这是一个可升级的合约模型。而真正的?rebase?逻辑位于 YAM.sol 中, 继续跟进 rebase 函数的具体逻辑,如下:

function rebase( ? ? ? ?

uint256 epoch, ? ? ? ?

uint256 indexDelta, ? ? ? ?

bool positive) ? ? ? ?

external ? ? ? ?

onlyRebaser ? ? ? ?

returns (uint256) ? ?

{ ? ? ? ?

if (indexDelta == 0) { ? ? ? ? ?

emit Rebase(epoch, yamsScalingFactor, yamsScalingFactor); ? ? ? ? ?

return totalSupply; ? ? ? ?

} ? ? ? ?

uint256 prevYamsScalingFactor = yamsScalingFactor; ? ? ??

if (!positive) { ? ? ? ? ??

yamsScalingFactor = yamsScalingFactor.mul(BASE.sub(indexDelta)).div(BASE); ? ? ? ?

} else { ? ? ? ? ? ?

uint256 newScalingFactor = yamsScalingFactor.mul(BASE.add(indexDelta)).div(BASE); ? ? ? ? ? ?

if (newScalingFactor < _maxScalingFactor()) { ? ? ? ? ? ? ? ?

yamsScalingFactor = newScalingFactor; ? ? ? ? ? ?

}?

else { ? ? ? ? ? ? ?

yamsScalingFactor = _maxScalingFactor(); ? ? ? ? ? ?

} ? ? ? ?

} ? ? ? ?

//SlowMist// 问题代码 ? ? ? ?

totalSupply = initSupply.mul(yamsScalingFactor); ? ? ? ?

emit Rebase(epoch, prevYamsScalingFactor, yamsScalingFactor); ? ? ? ?

return totalSupply; ? ?

}

}

通过分析最终的?rebase?函数的逻辑,不难发现代码中根据 yamsScalingFactor 来对 totalSupply 进行调整,由于 yamsScalingFactor 是一个高精度的值,在调整完成后应当除以 BASE 来去除计算过程中的精度,获得正确的值。但是项目方在对 totalSupply 进行调整时,竟忘记了对计算结果进行调整,导致了 totalSupply 意外变大,计算出错误的结果。

分析到这里还没结束,要将漏洞和社区治理关联起来,需要对代码进行进一步的分析。通过观察?rebase?函数的修饰器,不难发现此处限定了只能是 rebaser 进行调用。而 rebaser 是 YAM 中用与实现供应量相关逻辑的合约,也就是说,是 rebaser 合约最终调用了 YAM.sol 合约中的?rebase?函数。通过跟踪相关代码,发现 rebaser 合约中对应供应量调整的逻辑为?rebase?函数,代码如下:

function rebase() ? ? ? ?

public ? ?

{ ? ? ? ?

// EOA only ? ? ? ?

require(msg.sender == tx.origin); ? ? ? ?

// ensure rebasing at correct time ? ? ??

?_inRebaseWindow(); ? ? ? ?

// This comparison also ensures there is no reentrancy.? ? ? ??

require(lastRebaseTimestampSec.add(minRebaseTimeIntervalSec) < now);? ? ? ??

// Snap the rebase time to the start of this window. ? ? ? ?

lastRebaseTimestampSec = now.sub(

now.mod(minRebaseTimeIntervalSec)).add(rebaseWindowOffsetSec); ? ? ? ?

epoch = epoch.add(1); ? ? ??

// get twap from uniswap v2; ? ? ? ?

uint256 exchangeRate = getTWAP(); ? ? ? ?

// calculates % change to supply ? ? ? ?

(uint256 offPegPerc, bool positive) = computeOffPegPerc(exchangeRate); ? ? ? ?

uint256 indexDelta = offPegPerc; ? ? ? ?

// Apply the Dampening factor. ? ? ? ?

indexDelta = indexDelta.div(rebaseLag); ? ? ? ?

YAMTokenInterface yam = YAMTokenInterface(yamAddress); ? ? ? ?

if (positive) {? ? ? ? ? ?

require(yam.yamsScalingFactor().mul(uint256(10**18).add(indexDelta)).div(10**18)

yam.maxScalingFactor(), "new scaling factor will be too big"); ? ? ? ?

} ? ? ? ?

//SlowMist// 取当前 YAM 代币的供应量 ? ? ? ?

uint256 currSupply = yam.totalSupply(); ? ? ? ?

uint256 mintAmount; ? ? ? ?

// reduce indexDelta to account for minting ? ? ? ?

//SlowMist// 计算要调整的供应量 ? ? ? ?

if (positive) { ? ? ? ? ? ?

uint256 mintPerc = indexDelta.mul(rebaseMintPerc).div(10**18); ? ? ? ? ? ?

indexDelta = indexDelta.sub(mintPerc); ? ? ? ? ? ?

mintAmount = currSupply.mul(mintPerc).div(10**18); ? ? ? ?

} ? ? ? ?

// rebase ? ? ? ?

//SlowMist// 调用 YAM 的rebase 逻辑 ? ? ? ?

uint256 supplyAfterRebase = yam.rebase(epoch, indexDelta, positive);? ? ? ?

assert(yam.yamsScalingFactor() <= yam.maxScalingFactor()); ? ? ? ?

// perform actions after rebase ? ? ? ?

//SlowMist// 进入调整逻辑 ? ? ? ?

afterRebase(mintAmount, offPegPerc); ? ?}

通过分析代码,可以发现函数在进行了一系列的检查后,首先获取了当前 YAM 的供应量,计算此次的铸币数量,然后再调用 YAM.sol 中的?rebase?函数对 totalSupply 进行调整,也就是说 rebase 过后的对 totalSupply 的影响要在下一次调用 rebaser 合约的?rebase?函数才会生效。最后?rebase?函数调用了 afterRebase 函数。我们继续跟进 afterRebase 函数中的代码:

function afterRebase( ? ? ? ?

uint256 mintAmount, ? ? ? ?

uint256 offPegPerc) ? ? ? ?

internal ? ?

{ ? ? ? ?

// update uniswap ? ? ? ?

UniswapPair(uniswap_pair).sync(); ? ? ? ?

//SlowMist// 通过 uniswap 购买 yCRV 代币 ?? ? ? ?

if (mintAmount > 0) { ? ? ? ? ? ?

buyReserveAndTransfer( ? ? ? ? ? ? ? ?

mintAmount, ? ? ? ? ? ? ? ?

offPegPerc ? ? ? ? ? ?

); ? ? ? ?

} ? ? ? ?

// call any extra functions ? ? ? ?

//SlowMist// 社区管理调用 ? ? ? ?

for (uint i = 0; i < transactions.length; i++) { ? ? ? ? ? ?

Transaction storage t = transactions[i]; ? ? ? ? ? ?

if (t.enabled) { ? ? ? ? ? ? ? ?

bool result = ? ? ? ? ? ? ? ? ? ?

externalCall(t.destination, t.data); ? ? ? ? ? ? ??

?if (!result) { ? ? ? ? ? ? ? ? ? ?

emit TransactionFailed(t.destination, i, t.data); ? ? ? ? ? ? ? ? ??

?revert("Transaction Failed"); ? ? ? ? ? ? ? ?

} ? ? ? ? ??

?} ? ? ??

?} ??

?}

通过分析发现, afterRebase 函数主要的逻辑在 buyReserveAndTransfer 函数中,此函数用于将增发出来的代币的一部分用于到 Uniswap 中购买 yCRV 代币。跟踪 buyReserveAndTransfer 函数,代码如下:

function buyReserveAndTransfer( ? ? ? ?

uint256 mintAmount, ? ? ? ?

uint256 offPegPerc ? ?

) ? ? ? ?

internal ? ?

{ ? ? ? ?

UniswapPair pair = UniswapPair(uniswap_pair); ? ? ? ?

YAMTokenInterface yam = YAMTokenInterface(yamAddress); ? ? ? ?

// get reserves ? ? ? ?

(uint256 token0Reserves, uint256 token1Reserves, ) = pair.getReserves(); ? ? ? ?

// check if protocol has excess yam in the reserve ? ? ??

?uint256 excess = yam.balanceOf(reservesContract); ? ? ? ?

//SlowMist// 计算用于 Uniswap 中兑换的 YAM 数量 ? ? ? ?

uint256 tokens_to_max_slippage = uniswapMaxSlippage(token0Reserves, token1Reserves, offPegPerc); ? ? ? ?

UniVars memory uniVars = UniVars({ ? ? ? ? ?

yamsToUni: tokens_to_max_slippage, // how many yams uniswap needs ? ? ? ? ?

amountFromReserves: excess, // how much of yamsToUni comes from reserves? ? ? ? ?

mintToReserves: 0 // how much yams protocol mints to reserves ? ? ? ?

}); ? ? ? ?

// tries to sell all mint + excess ? ? ? ?

// falls back to selling some of mint and all of excess ? ? ? ?

// if all else fails, sells portion of excess ? ? ? ?

// upon pair.swap, `uniswapV2Call` is called by the uniswap pair contract ? ? ? ?

if (isToken0) { ? ? ? ? ? ?

if (tokens_to_max_slippage > mintAmount.add(excess)) { ? ? ? ? ? ? ??

?// we already have performed a safemath check on mintAmount+excess ? ? ? ? ? ? ? ?

// so we dont need to continue using it in this code path ? ? ? ? ? ? ? ?

// can handle selling all of reserves and mint ? ? ? ? ? ? ? ?

uint256 buyTokens = getAmountOut(mintAmount + excess, token0Reserves, token1Reserves); ? ? ? ? ? ? ? ?

uniVars.yamsToUni = mintAmount + excess; ? ? ? ? ? ? ??

?uniVars.amountFromReserves = excess; ? ? ? ? ? ? ? ?

// call swap using entire mint amount and excess; mint 0 to reserves ? ? ? ? ? ? ? ?

pair.swap(0, buyTokens, address(this), abi.encode(uniVars)); ? ? ? ? ? ?

} else { ? ? ? ? ? ? ? ?

if (tokens_to_max_slippage > excess) { ? ? ? ? ? ? ? ? ? ?

// uniswap can handle entire reserves? ? ? ? ? ? ? ? ? ?

uint256 buyTokens = getAmountOut(tokens_to_max_slippage, token0Reserves, token1Reserves); ? ? ? ? ? ? ? ? ? ?

// swap up to slippage limit, taking entire yam reserves, and minting part of total ? ? ? ? ? ? ? ? ? ?//SlowMist// 将多余代币铸给 reserves 合约 ? ? ? ? ? ? ? ? ? ?

uniVars.mintToReserves = mintAmount.sub((tokens_to_max_slippage - excess)); ? ? ? ? ? ? ? ? ? ?//SlowMist// Uniswap代币交换 ? ? ? ? ? ? ? ? ? ?

pair.swap(0, buyTokens, address(this), abi.encode(uniVars)); ? ? ? ? ? ? ? ?

} else { ? ? ? ? ? ? ? ? ? ?

// uniswap cant handle all of excess ? ? ? ? ? ? ? ? ? ?

uint256 buyTokens = getAmountOut(tokens_to_max_slippage, token0Reserves, token1Reserves); ? ? ? ? ? ? ? ? ? ?

uniVars.amountFromReserves = tokens_to_max_slippage; ? ? ? ? ? ? ? ? ? ?

uniVars.mintToReserves = mintAmount; ? ? ? ? ? ? ? ? ? ?

// swap up to slippage limit, taking excess - remainingExcess from reserves, and minting full amount ? ? ? ? ? ? ? ? ? ?

// to reserves ? ? ? ? ? ? ? ? ? ?

pair.swap(0, buyTokens, address(this), abi.encode(uniVars)); ? ? ? ? ? ? ??

?} ? ? ? ? ??

?} ? ? ??

?} else { ? ? ? ? ? ?

if (tokens_to_max_slippage > mintAmount.add(excess)) { ? ? ? ? ? ? ? ?

// can handle all of reserves and mint ? ? ? ? ? ? ? ?

uint256 buyTokens = getAmountOut(mintAmount + excess, token1Reserves, token0Reserves); ? ? ? ? ? ? ? ?

uniVars.yamsToUni = mintAmount + excess; ? ? ? ? ? ? ? ?

uniVars.amountFromReserves = excess; ? ? ? ? ? ? ??

?// call swap using entire mint amount and excess; mint 0 to reserves ? ? ? ? ? ? ? ?

pair.swap(buyTokens, 0, address(this), abi.encode(uniVars)); ? ? ? ? ? ?

} else { ? ? ? ? ? ? ? ?

if (tokens_to_max_slippage > excess) { ? ? ? ? ? ? ? ? ? ?

// uniswap can handle entire reserves ? ? ? ? ? ? ? ? ? ?

uint256 buyTokens = getAmountOut(tokens_to_max_slippage, token1Reserves, token0Reserves); ? ? ? ? ? ? ? ? ? ?

// swap up to slippage limit, taking entire yam reserves, and minting part of total ? ? ? ? ? ? ? ? ? ?

//SlowMist// 增发的多余的代币给 reserves 合约 ? ? ? ? ? ? ? ? ? ?

uniVars.mintToReserves = mintAmount.sub( (tokens_to_max_slippage - excess)); ? ? ? ? ? ? ? ? ? ?

// swap up to slippage limit, taking entire yam reserves, and minting part of total ? ? ? ? ? ? ? ? ? ?

//Slowist// 在 uniswap 中进行兑换,并最终调用 rebase 合约的 uniswapV2Call 函数 ? ? ? ? ? ? ? ? ? ?

pair.swap(buyTokens, 0, address(this), abi.encode(uniVars)); ? ? ? ? ? ? ? ?

} else { ? ? ? ? ? ? ? ? ? ?

// uniswap cant handle all of excess ? ? ? ? ? ? ? ? ? ?

uint256 buyTokens = getAmountOut(tokens_to_max_slippage, token1Reserves, token0Reserves); ? ? ? ? ? ? ? ? ??

?uniVars.amountFromReserves = tokens_to_max_slippage; ? ? ? ? ? ? ? ? ??

?uniVars.mintToReserves = mintAmount; ? ? ? ? ? ? ? ? ??

?// swap up to slippage limit, taking excess - remainingExcess from reserves, and minting full amount ? ? ? ? ? ? ? ? ? ?

// to reserves ? ? ? ? ? ? ? ? ? ?

pair.swap(buyTokens, 0, address(this), abi.encode(uniVars)); ? ? ? ? ? ? ??

?} ? ? ? ? ??

?} ? ? ??

?} ??

?}

通过对代码分析,buyReserveAndTransfer 首先会计算在 Uniswap 中用于兑换 yCRV 的 YAM 的数量,如果该数量少于 YAM 的铸币数量,则会将多余的增发的 YAM 币给 reserves 合约,这一步是通过 Uniswap 合约调用?rebase?合约的 uniswapV2Call 函数实现的,具体的代码如下:

function uniswapV2Call( ? ? ? ?

address sender, ? ? ? ?

uint256 amount0, ? ? ??

?uint256 amount1, ? ? ? ?

bytes memory data) ? ? ? ?

public{ ? ? ? ?

// enforce that it is coming from uniswap ? ? ? ?

require(msg.sender == uniswap_pair, "bad msg.sender"); ? ? ? ?

// enforce that this contract called uniswap ? ? ? ?

require(sender == address(this), "bad origin"); ? ? ? ?

(UniVars memory uniVars) = abi.decode(data, (UniVars)); ? ? ? ?

YAMTokenInterface yam = YAMTokenInterface(yamAddress); ? ? ? ?

if (uniVars.amountFromReserves > 0) { ? ? ? ? ? ?

// transfer from reserves and mint to uniswap ? ? ? ? ? ?

yam.transferFrom(reservesContract, uniswap_pair, uniVars.amountFromReserves); ? ? ? ? ??

?if (uniVars.amountFromReserves < uniVars.yamsToUni) { ? ? ? ? ? ? ? ?

// if the amount from reserves > yamsToUni, we have fully paid for the yCRV tokens ? ? ? ? ? ? ? ?

// thus this number would be 0 so no need to mint ? ? ? ? ? ? ? ?

yam.mint(uniswap_pair, uniVars.yamsToUni.sub(uniVars.amountFromReserves)); ? ? ? ? ? ?

} ? ? ? ?

} else { ? ? ? ? ? ?

// mint to uniswap ? ? ? ? ? ?

yam.mint(uniswap_pair, uniVars.yamsToUni); ? ? ? ?

} ? ? ? ?

// mint unsold to mintAmount ? ? ??

?//SlowMist// 将多余的 YAM 代币分发给 reserves 合约 ? ? ? ?

if (uniVars.mintToReserves > 0) { ? ? ? ? ? ?

yam.mint(reservesContract, uniVars.mintToReserves); ? ? ? ?

} ? ? ??

?// transfer reserve token to reserves ? ? ? ?

if (isToken0) { ? ? ? ? ? ?

SafeERC20.safeTransfer(IERC20(reserveToken), reservesContract, amount1); ? ? ? ? ? ?

emit TreasuryIncreased(amount1, uniVars.yamsToUni, uniVars.amountFromReserves, uniVars.mintToReserves); ? ? ? ?

} else { ? ? ? ? ? ?

SafeERC20.safeTransfer(IERC20(reserveToken), reservesContract, amount0); ? ? ? ? ? ?

emit TreasuryIncreased(amount0, uniVars.yamsToUni, uniVars.amountFromReserves, uniVars.mintToReserves); ? ? ? ?

} ? ?}

分析到这里,一个完整的?rebase?流程就完成了,你可能看得很懵,我们用简单的流程图简化下:

3.jpg

也就是说,每次的?rebase,如果有多余的 YAM 代币,这些代币将会流到 reserves 合约中,那这和社区治理的关系是什么呢?

通过分析项目代码,发现治理相关的逻辑在 YAMGovernorAlpha.sol 中,其中发起提案的函数为 propose,具体代码如下:

function propose( ? ? ? ?

address[] memory targets, ? ? ? ?

uint[] memory values, ? ? ? ?

string[] memory signatures, ? ? ? ?

bytes[] memory calldatas, ? ? ? ?

string memory description) ? ? ? ?

public ? ? ? ?

returns (uint256){ ??

//SlowMist// 校验提案发起者的票数占比 ? ? ? ?

require(yam.getPriorVotes(msg.sender, sub256(block.number, 1)) > proposalThreshold(), "GovernorAlpha::propose: proposer votes below proposal threshold"); ? ? ? ?

require(targets.length == values.length && targets.length == signatures.length && targets.length == calldatas.length, "GovernorAlpha::propose: proposal function information arity mismatch"); ? ? ? ?

require(targets.length != 0, "GovernorAlpha::propose: must provide actions"); ? ? ? ?

require(targets.length <= proposalMaxOperations(), "GovernorAlpha::propose: too many actions"); ? ? ? ?

uint256 latestProposalId = latestProposalIds[msg.sender]; ? ? ??

?if (latestProposalId != 0) { ? ? ? ? ?

ProposalState proposersLatestProposalState = state(latestProposalId); ? ? ? ? ?

require(proposersLatestProposalState != ProposalState.Active, "GovernorAlpha::propose: one live proposal per proposer, found an already active proposal"); ? ? ? ? ?

require(proposersLatestProposalState != ProposalState.Pending, "GovernorAlpha::propose: one live proposal per proposer, found an already pending proposal"); ? ? ? ?

} ? ? ? ?

uint256 startBlock = add256(block.number, votingDelay()); ? ? ??

?uint256 endBlock = add256(startBlock, votingPeriod()); ? ? ? ?

proposalCount++; ? ? ??

?Proposal memory newProposal = Proposal({ ? ? ? ? ??

?id: proposalCount, ? ? ? ? ? ?

proposer: msg.sender, ? ? ? ? ??

?eta: 0, ? ? ? ? ? ?

targets: targets, ? ? ? ? ? ?

values: values, ? ? ? ? ? ?

signatures: signatures, ? ? ? ? ??

?calldatas: calldatas, ? ? ? ? ??

?startBlock: startBlock, ? ? ? ? ?

??endBlock: endBlock, ? ? ? ? ?

??forVotes: 0, ? ? ? ? ? ?

againstVotes: 0, ? ? ? ? ??

?canceled: false, ? ? ? ? ?

??executed: false ? ? ?

??}); ? ? ? ?

proposals[newProposal.id] = newProposal; ? ? ??

?latestProposalIds[newProposal.proposer] = newProposal.id;? ? ??

?emit ProposalCreated( ? ? ? ? ? ?

newProposal.id, ? ? ? ? ??

?msg.sender, ? ? ? ? ? ?

targets, ? ? ? ? ?

??values, ? ? ??

? ? ?signatures, ? ? ? ? ? ?

calldatas, ? ? ? ? ??

?startBlock,? ? ? ??

? ?endBlock, ? ? ? ? ?

??description ? ? ? ?

); ? ? ? ?

return newProposal.id; ? ?}

通过分析代码,可以发现在发起提案时,需要提案发起人拥有一定额度的票权利,这个值必须大于 proposalThreshold 计算得来的值,具体代码如下:

function?proposalThreshold()?public?view?returns?(uint256)?{? ?return?SafeMath.div(yam.initSupply(),?100);?}?//?1%?of?YAM

也就是说提案发起人的票权必须大于 initSupply 的 1% 才能发起提案。那 initSupply 受什么影响呢?答案是 YAM 代币的 mint 函数,代码如下:

function mint(address to, uint256 amount) ? ? ? ?

external ? ? ? ?

onlyMinter ? ? ? ?

returns (bool){ ? ? ? ?

_mint(to, amount); ? ? ??

?return true; ? ?

} ??

?function _mint(address to, uint256 amount) ? ? ? ?

internal{ ? ? ?

// increase totalSupply ? ? ?

totalSupply = totalSupply.add(amount); ? ? ?

// get underlying value ? ? ?

uint256 yamValue = amount.mul(internalDecimals).div(yamsScalingFactor); ? ??

?// increase initSupply ? ? ?

initSupply = initSupply.add(yamValue); ? ??

?// make sure the mint didnt push maxScalingFactor too low ? ??

?require(yamsScalingFactor <= _maxScalingFactor(), "max scaling factor too low"); ? ? ?

// add balance ? ? ?

_yamBalances[to] = _yamBalances[to].add(yamValue); ? ??

?// add delegates to the minter ? ??

?_moveDelegates(address(0), _delegates[to], yamValue); ? ? ?

emit Mint(to, amount); ??

?}

从代码可知,mint 函数在每次铸币时都会更新 initSupply 的值,而这个值是根据 amount 的值来计算的,也就是铸币的数量。

现在,我们已经分析完所有的流程了,剩下的就是把所有的分析串起来,看看这次的漏洞对 YAM 产生了什么影响,对上文的流程图做拓展,变成下面这样:

4.jpg

整个事件的分析如上图,由于?rebase?的时候取的是上一次的 totalSupply 的值,所以计算错误的 totalSupply 的值并不会立即通过 mint 作用到 initSupply 上,所以在下一次?rebase?前,社区仍有机会挽回这个错误,减少损失。但是一旦下一次?rebase?执行,整个失误将会变得无法挽回。

通过查询 Etherscan 上 YAM 代币合约的相关信息,可以看到 totalSupply 已经到了一个非常大的值,而 initSupply 还未受到影响。

5.jpg

6.jpg

前车之鉴

这次事件中官方已经给出了具体的修复方案,这里不再赘述。这次的事件充分暴露了未经审计 DeFi 合约中隐藏的巨大风险,虽然 YAM 开发者已经在 Github 中表明 YAM 合约的很多代码是参考了经过充分审计的 DeFi 项目如 Compound、Ampleforth、Synthetix 及 YEarn/YFI,但是仍无可避免地发生了意料之外的风险。

DeFi 项目 Yam Finance(YAM) 核心开发者 belmore 在推特上表示:“对不起,大家。我失败了。谢谢你们今天的大力支持。我太难过了。”,但是覆水已经难收,在此,慢雾安全团队给出如下建议

1、由于 DeFi 合约的高度复杂性,任何 DeFi 项目都需在经过专业的安全团队充分审计后再进行上线,降低合约发生意外的风险?。

2、项目中去中心化治理应循序渐进,在项目开始阶段,需要设置适当的权限以防发生黑天鹅事件。

免责声明
世链财经作为开放的信息发布平台,所有资讯仅代表作者个人观点,与世链财经无关。如文章、图片、音频或视频出现侵权、违规及其他不当言论,请提供相关材料,发送到:2785592653@qq.com。
风险提示:本站所提供的资讯不代表任何投资暗示。投资有风险,入市须谨慎。
世链粉丝群:提供最新热点新闻,空投糖果、红包等福利,微信:juu3644。