origin发生了些意料之外的事情删文件没有用(origin发生了些意料之外的事情)
慢雾:DeFi 当红项目 YAM 闪电折戟,一行代码如何蒸发数亿美元?
免责声明:本文旨在传递更多市场信息,不构成任何投资建议。文章仅代表作者观点,不代表火星财经官方立场。
小编:记得关注哦
来源:慢雾科技
原文标题:慢雾:DeFi 当红项目 YAM 闪电折戟,一行代码如何蒸发数亿美元?
当红流动性挖矿项目 YAM 创始人披露该项目合约漏洞,慢雾技术详解漏洞细节。
原文标题:《DeFi YAM,一行代码如何蒸发数亿美元?》 撰文:yudan @ 慢雾安全团队
发生了什么?以上是 YAM 官方对本次事件的 简短说明。
简单来说就是官方在合约中发现负责调整供应量的函数发生了问题,这个问题导致多余的 YAM 代币放进了 YAM 的 reserves 合约中,并且如果不修正这个问题,将会导致 YAM 的后续治理变为不可能。同时,官方给出了此次漏洞的具体问题代码,如下:
从上图可知,由于编码不规范,YAM 合约在调整 totalSupply 的时候,本应将最后的结果除以 BASE 变量,但是在实际开发过程中却忽略了,导致 totoalSupply 计算不正确,比原来的值要大 10^18 倍。但是代币供应量问题和治理是怎么扯上关系呢?这需要我们针对代码做进一步的分析。
YAM 会变成怎样?为了深入了解此次漏洞造成的影响,需要对 YAM 项目代码进行深入的了解。根据官方给出的问题代码及项目 Github 地址(https://github/yam-finance/yam-protocol),可以定位出调整供应量的rebase函数位于 YAMDelegatorl 合约中,具体代码如下:
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逻辑位于 YAMl 中 , 继续跟进 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 合约最终调用了 YAMl 合约中的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 的供应量,计算此次的铸币数量,然后再调用 YAMl 中的 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流程就完成了,你可能看得很懵,我们用简单的流程图简化下:
也就是说,每次的 rebase,如果有多余的 YAM 代币,这些代币将会流到 reserves 合约中,那这和社区治理的关系是什么呢?
通过分析项目代码,发现治理相关的逻辑在 YAMGovernorAlphal 中,其中发起提案的函数为 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[newProposalposer] = 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(yamitSupply, 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
从代码可知,mint 函数在每次铸币时都会更新 initSupply 的值,而这个值是根据 amount 的值来计算的,也就是铸币的数量。
现在,我们已经分析完所有的流程了,剩下的就是把所有的分析串起来,看看这次的漏洞对 YAM 产生了什么影响,对上文的流程图做拓展,变成下面这样:
整个事件的分析如上图,由于 rebase的时候取的是上一次的 totalSupply 的值,所以计算错误的 totalSupply 的值并不会立即通过 mint 作用到 initSupply 上,所以在下一次rebase前,社区仍有机会挽回这个错误,减少损失。但是一旦下一次rebase执行,整个失误将会变得无法挽回。
通过查询 Etherscan 上 YAM 代币合约的相关信息,可以看到 totalSupply 已经到了一个非常大的值,而 initSupply 还未受到影响。
前车之鉴这次事件中官方已经给出了具体的修复方案,这里不再赘述。这次的事件充分暴露了未经审计 DeFi 合约中隐藏的巨大风险,虽然 YAM 开发者已经在 Github 中表明 YAM 合约的很多代码是参考了经过充分审计的 DeFi 项目如 Compound、Ampleforth、Synthetix 及 YEarn/YFI,但是仍无可避免地发生了意料之外的风险。
DeFi 项目 Yam Finance (YAM) 核心开发者 belmore 在推特上表示:「对不起,大家。我失败了。谢谢你们今天的大力支持。我太难过了。」但是覆水已经难收,在此,慢雾安全团队给出如下建议:
1、由于 DeFi 合约的高度复杂性,任何 DeFi 项目都需在经过专业的安全团队充分审计后再进行上线,降低合约发生意外的风险 。审计可联系慢雾安全团队(team@slowmist)
2、项目中去中心化治理应循序渐进,在项目开始阶段,需要设置适当的权限以防发生黑天鹅事件。
来源链接:mp.weixin.qq
“Origin进不去、下载慢”的解决办法合集
玩儿烂橘子的游戏,喜闻乐见会见到下面这句话:
呃,发生了些意料之外的事情。其实,这还挺意料之中的。
▌进不去
方法①:
挂加速器。
加速器挂Steam或者Origin大厅一般是免费的,没效果就换一个节点、模式、加速器。
有时候挂了加速器也可能加载不出来,但其实可以右键任务栏的Origin,在快捷菜单里直接开游戏。(同:直接在游戏路径开游戏)
方法②:
改DNS。上一篇文章提到了改的方式和常见好用的DNS。
方法③:
最常见的解决办法,用不着重装,清空本地缓存即可。关闭橘子后,打开C:\Users\你的名字\AppData中的Local和Roaming目录,
也可以用[运行]%AppData%直达,把里面的Origin文件夹删除,重启烂橘子。这个方法能解决9成烂橘子问题。
方法④:
用Dogfight360开发的软件Origin_CDN或UsbEAm Hosts Editor改Hosts,点击左下角小手柄之后,选择对应的平台和连不上的地方,
系统会给出一排能用的节点,点左下角的[检测延迟]后,选择位置和延迟都比较合适的节电,点击[应用选中]。
方法⑤:
如果初次安装Origin,记得直接下载完整客户端。如果客户端明明早就安装好的,忽然有一天卡正在安装,特别慢的话,
一般是网络问题,还是需要挂加速器,基本可以秒进了。如果更新还是卡住,可以用下面这个老办法:
origin开始下载更新后,在C:\ProgramData\Origin\SelfUpdate里面会有这样一个文件OriginUpdate_X_X_X_XXX.zip.part
然后把.part去掉,前面的部分复制下来。粘贴到这后面https://download.dm.origin/origin/live/打开连接(这样一个连接https://download.dm.origin/origin/live/OriginUpdate_X_X_X_XXX.zip)下载下来(可以用IDM、迅雷之类的),关掉origin,放到刚才那个文件夹下面,打开origin。
方法⑥:
前两个月老张遇到了一个很尴尬的问题,Origin怎么都连的上,但好友列表卡在“已离线”,导致游戏能玩就是上不了线、看不到好友的问题...这个可以用UsbEAm解决,不过刚好搬家,直接肉身换小区和宽带,直接解决(同时还解决了其他迷之网络问题),看来这个根源在于小区网络环境,就很迷...
▌下载慢
方法①:
除了可以用改Hosts和挂加速器的方案外,也可以通过这些办法解决。找到Origin安装目录中的EACorei文件,做好备份之后,双击打开,
这个方案可以下个Origin_CDN代替,复制这段文字进去后保存:
[connection]
EnvironmentName=production
[Feature]
CdnOverride=akamai
方法②:
下载三大妈之类的分流包,一般可以满速下载,完了之后用Origin.Games.Reg.Tools工具打一下注册表就行(这个的工具一般分流包自带)。
方法③:
遇到下载被暂停,“Origin需要下载档案,并正等待获得许可”问题,这时候需要Win+R[运行MSConfig,
找到[服务]中Origin开头的服务,取消勾选后-应用-确定,重启可以解决,
下载完游戏记得再手动开回来。网上也有改防火墙的方案,比较麻烦些。
方法④:
现在Orgin很多游戏都上架Steam了,甚至是EA Play服务....Origin逐渐Uplay化,以后游戏的下载基本都不是问题,但Steam的价格,就没有橘子这么多骚操作野路子了。
方法都很简单,上网一搜都能查到,不过重复信息太多,筛选太费时间,所以统计在一起更方便省时些。问题和方案或有遗漏,欢迎大家补充。
EA游戏平台将推苹果macOS版客户端,取代原版Origin
IT之家 9 月 8 日消息,去年年底,美国艺电公司(EA)正式推出了其下一代游戏平台,即适用于 Windows 操作系统的 EA 应用,取代原有的 Origin 平台。
今日,EA 官网发布公告,将为苹果 Mac 用户带来全新的 EA 客户端,官方称“将很快取代 Origin 成为我们的主要 Mac 平台”。
EA 表示将邀请受支持的 macOS 版 Origin 玩家进行体验测试。为了尽可能简单地过渡到 EA 应用,当玩家收到邀请时,所有游戏和内容(包括之前安装的游戏)都将直接迁移到 EA 应用中,本地和云端数据也将保持一致。
EA 应用即将在支持的 macOS 设备上推出,IT之家暂未查询到具体上线时间。
声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌抄袭侵权/违法违规的内容, 请发送至邮件举报,一经查实,本站将立刻删除。转载务必注明出处:http://www.hixs.net/article/20230928/169494779519435.html