当前位置:首页区块链慢雾:一行代码怎么会蒸发上亿美元?

慢雾:一行代码怎么会蒸发上亿美元?

当红流动性挖矿项目的任正非披露了该项目的合同漏洞,慢雾科技对漏洞的细节进行了解释。

标题:“DeFi yam,一行代码如何蒸发数亿美元?》
Yudan@慢雾安全团队

据连文介绍,2020年8月13日,知名以太坊DFI项目的官方YAM通过Twitter发来消息,表明合同存在漏洞,24小时内价格暴跌99%。接到信息后,慢雾安保小组迅速进行了相关跟踪分析。以下是详细的技术细节。

慢雾:一行代码怎么会蒸发上亿美元?

怎么搞的?

慢雾:一行代码怎么会蒸发上亿美元?1

以上是YAM官方对事件的简要描述。

总之,这位官员发现,负责调整合同供应的职能部门存在问题。这个问题导致多余的YAM代币被放入YAM的储备合同中。如果这个问题得不到纠正,YAM的后续治理将变得不可能。同时,官方给出了该漏洞的具体问题代码如下:

慢雾:一行代码怎么会蒸发上亿美元?2

从上图可以看出,由于编码不规范,在调整YAM合同供应总量时,最终结果应该除以基变量,但在实际开发过程中却被忽略了。因此,总供应量计算不正确,比原值大10^18倍。但是代币供应和治理之间的关系是什么呢?这需要进一步分析代码。

YAM会变成什么样子?

为了了解此漏洞的影响,我们需要深入了解yam项目代码。根据项目的官方问题代码和GitHub地址(https://github.com/yam-finance/yam-protocol)用于调整供给的反向调整功能可位于yamdegator.SOL在合同中,具体代码如下:

函数rebase(uint256 epoch,uint256 indexDelta,bool positive)外部返回(uint256){epoch;indexDelta;positive;delegateAndReturn();}

通过对rebase函数的跟踪,发现rebase函数最终调用delegateandreturn函数。代码如下:

函数delegateAndReturn()私有返回(字节内存){(bool success,)=实现.delegatecall( 消息数据);assembly{let free{ptr:=mload(0x40)returndatacopy(free_mem_uptr,0,returndatasize)开关成功案例0{revert(free-mem_u-ptr,returndatasize)}默认{return(free-mem-ptr,returndatasize)}}}}

通过对代码的分析,可以发现delegateandreturn函数以delegatecall的方式调用实现地址中的逻辑,即这是一个可伸缩的契约模。真正的rebase逻辑位于YAM溶胶继续跟踪rebase函数的具体逻辑,如下所示:

函数rebase(uint256 epoch,uint256 indexDelta,bool positive)只外部返回(uint256){if(indexDelta==0){发出rebase(epoch,yamscalingfactor,yamscalingfactor);returntotalsupply;}uint256 prevyamscalingfactor=yamscalingfactor;如果(!阳性){yamscalingfactor=yamscalingfactor.mul( 基础.sub(indexDelta)).div(基);}else{uint256 newScalingFactor=yamscalingfactor.mul( 基础.add(indexDelta)).div(基本);if(newScalingFactorlt;maxScalingFactor()){yamscalingfactor=newScalingFactor;}else{yamscalingfactor=Maxscalingfactor();}}//slowlist//问题代码totalsupply=初始化供应.mul(yamscalingfactor);发出Rebase(epoch,previyamscalingfactor,yamscalingfactor);returntotalsupply;}

通过分析最终的rebase函数的逻辑,不难发现代码根据yamscalingfactor来调整总供给。由于yamscalingfactor是一个高精度的值,调整后,计算过程中的精度应通过除以基数来消除,以得到正确的值。然而,项目方在调整总供给时,却忘了调整计算结果,导致总供给量意外增加,计算结果出现错误。

分析还没有结束。为了将漏洞与社区治理联系起来,需要进一步分析代码。通过观察rebase函数的修饰符,不难发现这里只能调用rebar。Rebaaser是yam中用于实现供应相关逻辑的契约,也就是说,最终调用的是Rebaaser契约YAM溶胶合同中的回扣功能。通过对相关代码的跟踪,发现再baser合同中相应的供货调整的逻辑是rebase函数。代码如下:

函数rebase()public{//EOA只需要(消息发送者= = 发送来源);//确保在正确的时间重基inRebaseWindow();//此比较还确保不存在可重入。要求(lastRebaseTimestampSec.add(minRebaseTimeIntervalSec)lt;now);//将恢复基时间捕捉到此窗口的开头。LastRebaSetTimeStampSec=现在。sub( 现在.mod(minRebaSetTimeIntervalSec))。添加(RebaSeIndowOffsetSec);epoch=epoch.添加(1) ;//从Uniswap v2获取twap;uint256 exchangeRate=getTWAP();//计算供应变化百分比(uint256 offPegPERC,bool positive)=computeOffPegPERC(exchangeRate);uint256 indexDelta=offPegPERC;//应用阻尼因子。指数Delta=索引Delta.div(rebelag);YAMTokenInterface yam=YAMTokenInterface(yamAddress);if(正){require(yam.YamsCalingFactor公司().mul(uint256(10**18).add(indexDelta)).div(10**18)lt;YAM最大比例因子(),“新的缩放因子将太大”);}//slowmist//使用当前的yam代币电源uint256电流供应=总供应量();uint256 minthamount;//减少indexdelta以说明铸造//slowlist//如果(正)计算要调整的供应量{uint256 mintpERC=索引delta.mul(rebaseMintPERC).div(10**18);索引Delta=indexDelta.sub公司(薄荷糖);最小金额=货币供应.mul(薄荷糖)。Div(10**18);}//rebase//slowmist//调用yam的rebase逻辑uint256 supplyafterrebase=YAM(epoch,indexDelta,正数);断言(yam.YamsCalingFactor公司()lt;=YAM最大比例因子());//在恢复基之后执行操作//slowmist//输入rebase后的调整逻辑(minthamount,offpegpERC);}

通过分析代码,我们可以发现,经过一系列检查,函数首先获得当前的YAM供应量,计算硬币数量,然后调用YAM溶胶中的rebase函数用于调整总供给。也就是说,回扣对总供给的影响要等到下一次调用回扣合约的回扣函数时才会生效。最后,rebase函数调用afterrebase函数。我们继续遵循afterrebase函数中的代码:

函数after rebase(uint256 mintAmount,uint256 offPegPERC)internal{//更新Uniswap UniswapPair(Uniswap_uPair)。Sync();//slowlist//通过Uniswap购买ycrv代币if(minthamountgt;0){buyserveandtransfer(minthamount,offpegpERC);}//调用任何额外的函数//slowmist//社区管理调用(uint I=0;Ilt;事务处理.长度;i++){事务存储t=transactions[i];if(t.enabled){bool result=外部调用(t.destination,t.data);if(!结果){emit TransactionFailed(t.destination,i,t.data);revert(“Transaction Failed”);}}}}

通过分析发现,后补基函数的主要逻辑是buyserveandtransform函数。此函数用于购买Uniswap中的ycrv代币和部分附加代币。跟踪buyreserveandtransfer函数。代码如下:

*
函数buyReserveAndTransfer(uint256 mintAmount,uint256 offPegPERC)internal{UniswapPair pair=UniswapPair(Uniswap_u2;pair);YAMTokenInterface yam=YAMTokenInterface(yamAddress);//获取保留(uint256 token0Reserves,uint256 token1Reserves,)=pair.getReserves公司(); //检查协议在保留uint256 excess中是否有多余的yam=YAM平衡(reservesContract);//slowlist//计算Uniswap中使用的yam量,uint256 tokens_uto_umax_Upage=UniswapMaxSlippage(token0Reserves,token1Reserves,offPegPERC);UniVars memory UniVars=UniVars({yamsToUni:tokens_uto_umax_uLappage,//Uniswap需要多少YAM从储备中提取:过量,//有多少yamsToUni来自reserves mintToReserves:0//多少YAM协议薄荷糖要储备});//试图卖掉所有的薄荷+多余的//回到出售一部分薄荷和所有多余的//如果所有其他方法都失败了,出售一部分多余的//配对交换,`UniswapV2Call`由Uniswap pair contract if(isToken0){if(tokens_uuto_max_Uslipagegt;最小金额.add(过量)){//我们已经对mintAmount+excess执行了safemath检查//因此我们不需要在此代码路径中继续使用它//可以处理出售所有储备和mint uint256 buyTokens=getAmountOut(mintAmount+excess,token0Reserves,token1Reserves);亚姆斯托尼大学=最小金额+超额;从储备中提取的单位金额=excess;//使用整个mint amount和excess调用swap;mint 0 to reserves配对交换(0,buyTokens,地址(this),abi.编码(uniVars));}else{if(tokens_uto_umax_Ulagegt;excess){//Uniswap可以处理整个储量uint256 buyTokens=getAmountOut(tokens_uto_umax_uslipage,token0 reserves,token1 reserves);//交换到滑动限制,获取整个yam储量,并挖矿total的一部分。//slowmist//将多余的代币投到储量合同中uniVars.mintToReserves公司= 最小金额.sub((代币)_To_umax_uSliplage-excess));//Slowsmist//Uniswap代币交换配对交换(0,buyTokens,地址(this),abi.编码(uniVars));}else{//Uniswap无法处理所有多余的uint256 buyTokens=getAmountOut(tokens_uto_max_Ulappage,token0Reserves,token1Reserves); 从储备中提取的单位金额=代币-最大滑动;uniVars.mintToReserves公司=mintAmount;//交换到滑动限制,从储备中获取剩余超额,并将全部金额//转换为储备配对交换(0,buyTokens,地址(this),abi.编码(uniVars));}}}}else{if(tokens_uto_max_uSlipagegt;最小金额.add(超额)){//可以处理所有的储备,mint uint256 buyTokens=getAmountOut(mintAmount+超额,token1Reserves,token0Reserves);亚姆斯托尼大学=最小金额+超额;从储备中提取的单位金额=excess;//使用整个mint amount和excess调用swap;mint 0 to reserves配对交换(buyTokens,0,地址(this),abi.编码(uniVars));}else{if(tokens_uto_max_uSlipagegt;excess){//Uniswap可以处理整个储量uint256 buyTokens=getAmountOut(tokens_uto_umax_uslipage,token1 reserves,token0 reserves);//交换到滑动限制,获取整个yam储量,并挖矿total的一部分。//slowsmist//额外的额外代币被发放到储量合同中uniVars.mintToReserves公司 = 最小金额.sub((tokens_uto_xmax_Ulage-exception));//交换到滑动限制,取整个YAM储量,并在Uniswap中挖矿部分total//slow//交换,最后调用rebase contract的Uniswapv2call函数配对交换(buyTokens,0,地址(this),abi.编码(uniVars));}其他{//Uniswap无法处理所有多余的uint256 buyTokens=getAmountOut(tokens_uuto_umax_uLappage,token1Reserves,token0Reserves);从储备中提取的单位金额=代币-最大滑动;uniVars.mintToReserves公司=mintAmount;//交换到滑动限制,从储备中提取超额部分,并将其全部存入储备配对交换(buyTokens,0,地址(this),abi.编码(uniVars));}}}}}

通过代码分析,buyreserveandtransfer将首先计算Uniswap中用于交换YCLV的YAM数量。如果数量少于YAM币数量,则将剩余的YAM币发放给储备合约。这一步是通过Uniswap合同调用rebase contract的Uniswap v2call函数来实现的。具体代码如下:

函数UniswapV2Call(address sender,uint256 amount0,uint256 amount1,bytes memory data)public{//强制它来自Uniswap require(消息发送者==Uniswap_Upair,“坏消息发送者”); //强制此名为Uniswap的协定要求(sender==address(this),“bad origin”);(UniVars memory UniVars)=解码(数据,(UniVars));YAMTokenInterface yam=YAMTokenInterface(yamAddress);如果(从储备中提取的单位金额gt;0){//从reserves and mint转到Uniswap YAM转移自(订座合同,Uniswap®对,从储备中提取的单位金额);如果(从储备中提取的单位金额lt;亚姆斯托尼大学){//如果来自储备的金额gt;yamsToUni,我们已经全额支付了yCRV代币//因此该数字将为0,因此无需造币YAM薄荷(Uniswap®配对,uniVars.yamsToUni.sub公司( 从储备中提取的单位金额));}}else{//mint到UniswapYAM薄荷配对unisuwap,亚姆斯托尼大学);}//min未出售给minthamount//slowmist//如果(uniVars.mintToReserves公司gt;0){YAM薄荷(保留合同,uniVars.mintToReserves公司); }//将reserve token转移到reserves if(isToken0){SafeERC20.safeTransfer(IERC20(reserveToken),reservesContract,amount1);emit TreasuryIncreased(amount1,亚姆斯托尼大学, 联合储备基金, uniVars.mintToReserves公司); }else{SafeERC20.safeTransfer(IERC20(reserveToken),reservesContract,amount0);释放国债增加(amount0,亚姆斯托尼大学, 从储备中提取的单位金额, uniVars.mintToReserves公司); } }

在这里,完成了一个完整的重基过程。你可能会感到困惑。让我们用一个简单的流程图来简化它

慢雾:一行代码怎么会蒸发上亿美元?3

换句话说,如果每个回扣中都有多余的yam代币,这些代币将流入储备合约。这与社区治理有什么关系?

通过分析项目代码,我们发现与治理相关的逻辑在YAMGovernorAlpha.SOL公司发起提案的功能是提案,具体代码如下:

函数provide(address[]内存目标,uint[]内存值,string[]内存签名,bytes[]内存calldata,string memory description)公共返回(uint256){//slowmist//检查提议者的投票百分比require(yam.GetPrior投票( 消息发送者,低于256(区块编号,1))gt;建议阈值(),“GovernorAlpha::propose:提案人投票低于提案阈值”);要求(目标.长度= = 值.长度ampamp;目标.长度= = 签名.长度ampamp;目标.长度= = 调用数据长度,“GovernorAlpha::provide:建议函数信息arity不匹配”);require(目标.长度!=0,“GovernorAlpha::propose:必须提供操作”);需要(目标.长度lt;=proposalMaxOperations(),“GovernorAlpha::propose:太多操作”);uint256 latestProposalId=latestProposalIds[消息发送者];if(最新的proposalid!=0){ProposalState ProposalState=状态(latestProposalId);require(ProposerLateStProposalState!= 提议状态。活动,“GovernorAlpha::propose:每个提案人一个实时提案,找到一个已经激活的提案”);require(ProposerLatestProposalState!= 提案状态。待定,“GovernorAlpha::propose:每个提议者一个实时提案,找到一个已经挂起的提案”);}uint256 startBlock=add256(区块编号,votingDelay());uint256 endBlock=add256(startBlock,votingPeriod());proposalCount++;建议内存newProposal=Proposal({id:proposalCount,提案人:消息发送者,eta:0,目标:目标,值:值,签名:签名,调用数据:调用数据,开始块:开始块,结束块:结束块,For投票:0,反对投票:0,已取消:错误,已执行:false});建议[新提案.id]=新提案;最新提案[新提案人] = 新提案.id;发出建议已创建(新提案.id, 消息发送者,目标,值,签名,调用数据,startBlock,endBlock,description);返回新提案.id; }

通过对该准则的分析,可以发现,提案发起人在发起提案时,应当具有一定数量的表决权,其表决权必须大于提案阈值所计算的数值。具体代码如下:

函数proposalThreshold()公共视图返回(uint256){return安全数学.div( YAM初始供应(),100);}//1%YAM

换句话说,发起人的投票必须大于initsupply的1%才能发起提案。initsupply的影响是什么?答案是YAM代币的造币功能。代码如下:

*
函数mint(address to,uint256 amount)external onlyMinter返回(bool){mint(to,amount);return true;}函数mint(address to,uint256 amount)internal{//增加totalSupply totalSupply=总供给量.add(金额);//获取基础值uint256 yamValue=金额.mul(internalDecimals).div(yamscalingfactor);//增加initSupply initSupply=初始化供应.add(yamValue);//确保mint没有将maxScalingFactor推得太低(yamscalingfactorlt;=U maxScalingFactor(),“max scalingfactor too low”);//add balance UuYamBalances[to]=yamBalances[to].add(yamValue);//将委托添加到minter中moveDelegates(地址(0),u delegates[to],yamValue);emit Mint(to,amount);}

从代码中,每次铸币时,mint函数都会更新initsupply的值,这个值是根据amount的值,即硬币的数量来计算的。

现在我们已经分析了所有的过程,剩下的就是把所有的分析串在一起,看看这个漏洞对yam有什么影响。展开上面的流程图并将其转换为以下内容:

慢雾:一行代码怎么会蒸发上亿美元?4

整个事件的分析如上图所示。由于之前的totalsupply值是在rebase期间获取的,错误的totalsupply值不会立即通过mint应用到initsupply。因此,在下一次回基之前,社区仍然有机会恢复错误,减少损失。但是一旦执行下一个rebase,整个错误将变得不可恢复。

通过查询ETHerscan上的yam token合约信息,我们可以看到总供应量已经达到了非常大的值,而initsupply没有受到影响。

A602A606号

慢雾:一行代码怎么会蒸发上亿美元?5

从前面翻倒的大车上发出警告

在这起事件中,官方给出了具体的维修方案,这里不再赘述。这一事件充分暴露了非审计违约合同中隐藏的巨大风险。尽管yam开发者在GitHub中表示,yam合同的许多代码都涉及经过全面审计的DFI项目,如compound、Ampleforth、synthetix和year/yfi,但意外风险仍然不可避免地发生。

“对不起,各位,”DFI项目yam Finance(yam Finance)的核心开发者贝尔莫尔(Belmore)在推特上说。我失败了。感谢您今天的大力支持。我太伤心了。但是,很难收集覆盖的水。在此,慢雾安全小组给出以下建议:

1由于DeFi合同的高度复杂性,任何DeFi项目在上线前都要经过专业安全团队的全面审核,以降低合同事故的风险。审计人员可以联系慢雾安全小组([email protected])

2项目中的去中心化治理应逐步实施。在项目开始时,应设置适当的权限,以防止黑天鹅事件的发生。

温馨提示:

文章标题:慢雾:一行代码怎么会蒸发上亿美元?

文章链接:https://www.btchangqing.cn/95197.html

更新时间:2020年08月31日

本站大部分内容均收集于网络,若内容若侵犯到您的权益,请联系我们,我们将第一时间处理。

慢雾:一行代码怎么会蒸发上亿美元?6
区块链

第17期|波涛专访今年最热门赛道与东风迪菲

2020-8-31 21:25:47

区块链

在后疫病时代,新奥基金创造了一个新的全球泛旅游生态,赋予行业权力

2020-8-31 21:42:47

0 条回复 A文章作者 M管理员
    暂无讨论,说说你的看法吧
个人中心
购物车
优惠劵
今日签到
有新私信 私信列表
搜索