Quantstamp Labs
July 24, 2020


Quantstamp completed its informal code review of Yearn Finance. Yearn Finance provides yield-maximizing opportunities for liquidity providers, and is intended to be governed in a decentralized manner. Due to the large number of contracts involved in the yEarn system, we limited our review to the most prominent contracts—those that hold funds or can distribute funds. We performed this review as a service to the community. Findings are divided by contract below.

Acknowledgements: We want to especially thank Devops199fan, Klim K, Angel, Michael Egorov, Samczsun, vasiliy, Will Price, Cooper Turley, Damir Bandalo and others in the community for reaching out, walking through the code, and gathering needed documentation, as well as Andre Cronje for creating this grand experiment in decentralization. We are looking forward to seeing how this develops.

We hope this is useful documentation to community members that seek to understand and improve yearn. We understand yearn is a work in progress undergoing rapid iteration, and so is this review, we hope it is helpful.


The code base for this review was relatively challenging due to the following reasons:

YearnRewards (1st pool) - yCurve LP tokens

The yearn pool stakes yCRV tokens from the Y pool on Curve Finance. The Y pool performs automatic yield-hunting for liquidity providers. It switches liquidity between Aave, Compound, and DyDx to provide the best yield among these platforms. Users of the yearn pool receive YFI tokens. 


This contract is a copy of the Synthetix Unipool contract, which was reviewed by Sigma Prime in Feb 2020. Their report can be found here:

The only difference is that Uniswap V1 token was changed to the yCRV token ( yDAI/yUSDC/yUSDT/yTUSD (yDAI+yUSD...)) 0xdF5e0e81Dff6FAF3A7e52BA697820c5e32D806A8.

This contract does not have any governance logic in it.

YearnRewards (2nd pool) - Balancer BPT tokens

Pool #2 stakes pool tokens received when providing liquidity to a Balancer DAI-YFI pool. YFI is distributed to incentivize DAI-YFI liquidity. Users receive YFI tokens for providing this liquidity.


This contract is a copy of the Synthetix Unipool contract, which was reviewed by Sigma Prime in Feb 2020. Their report can be found here:

The only difference is that Uniswap V1 token was changed to the BPT token 0x60626db611a9957C1ae4Ac5b7eDE69e24A3B76c5.

This contract does not have any governance logic in it.

YearnGovernance (3rd pool) - Balancer BPT tokens

The Governance pool stakes pool tokens received when providing liquidity to a Balancer yCRV-YFI pool. Users receive YFI, and if they stake more than 1000 pool tokens, are eligible to vote on YFI governance proposals. 


Owner and Governance are not multisig-wallet

Severity: High

  • [owner] Set the reward distribution address
  • [governance]
    • Transfer any token besides YFI, BPT and the reward token (feesPaidIn) to their own address, by calling seize
    • Set the breaker that will block all fee claims when withdrawing, voting and staking, by calling setBreaker
    • Set the reward token address
    • Set the governance address
    • Set the minimum amount needed for proposals
    • Set the number of blocks (period) after a proposal when one can vote
    • Set the amount of time the vote is locked

The RewardDistribution address decides what reward will be given for the next 7 days, not the governance address

Severity: Medium

The rewardDistribution address is not public and therefore its value cannot be read directly from the smart contract. However, looking at the transaction history of the smart contract it is visible that the rewardDistribution address was set in the 3rd transaction ever to be executed on this contract: and it was set to the address controlled by Andre, namely 0x2d407ddb06311396fe14d4b49da5f0471447d45c. The rewardDistribution address is the only address that is allowed to call the notifyRewardAmount function, which can set the following state variables:

  1. rewardRate
  2. lastUpdateTime
  3. periodFinish, which is 7 days from the time when this function is called

This notifyRewardAmount function was called on July 19th at 03:38:14 PM +UTC in this transaction with the reward parameter set to 10,000 YFI . This means that the periodFinish will be on July 26 at 03:38 PM +UTC. At this point in time the:

  • lastTimeRewardApplicable function will always return the periodFinish date, which will lead the (see next bullet)
  • rewardPerToken function to stop increasing the reward.

Voting 'period' and 'lock' values may differ

Severity: Medium

By default the period and lock state variables in the YearnGovernance contract have the same value of 17280 blocks (approximately 3 days), which is not a problem. However, the governance address can change these values independently such that they differ. If period > lock then stakers are allowed to withdraw their stake after lock blocks, then they can re-stake the same amount and double their votes for the same proposal. Here are the steps to execute this attack:

  1. Prerequisite: Governance address sets the period = 18000 and leaves lock = 17280.
  2. Alice creates a proposal (having a voting period = 18000)
  3. Bob, who already has a stake of 100, places his vote. His tokens are locked for 17280 blocks.
  4. After 17280 blocks, Bob’s tokens are unlocked and he withdraws them.
  5. Using a different address Bob places another stake using the same 100 tokens. Bob is now allowed to vote on Alice’s proposal again, which increases his vote count to 200 and locks his tokens for 17280 blocks.

Recommendation: Merge the lock variable with the period variable. It’s not clear why and if they will ever have different values.

A user can vote "For" and "Against" on the same proposal simultaneously

Severity: Medium

Community member Klim has brought up that a user can vote for and against simultaneously. We located the code segments in YearnGovernance, voteFor()and voteAgainst() functions and have confirmed the issue. While the functions check that if the user has voted for the same position before and accounts for the votes, they do not check if a user's vote has been accounted for the other stance and thus allows double voting. The issue is present in both functions.

Recommendation: We recommend adding a requirement statement and only allow the user to vote for one given stance.

YearnFeeRewards (4th pool) - stakes YFI for % of protocol fees 

The Fee Rewards pool allows users who have staked more than 1000 pool tokens in Pool #3 and also voted on a proposal to stake their YFI. By staking YFI, they receive rewards in the form of yCRV tokens. 


Owner and Governance are not multisig-wallet

Severity: Medium

Description: Similarly to pool 3 above, the current owner and governance addresses are set to 0x2d407ddb06311396fe14d4b49da5f0471447d45c. This gives the address power to transfer tokens (other than YFI and yCRV) to itself.

Users have to voteLock recently to claim rewards

Severity: Informational

The getReward() function requires the user to have a certain amount of yGov staked balance and have been active in governance, i.e. to have voted recently at the time they are claiming the reward. This requirement is identical as the ones in the stake() function, however, it is unclear whether this is intended as it is not documented or communicated openly.

Recommendation: confirm if this is the intended behavior of the function and document the rationale to inform the users.

YFI Contract


The governance address can add any address as a minter. That minter can mint tokens arbitrarily as there is no cap.

Recommendation: Cap value for minting should be decided by governance.



YFI Tokens can be seized by the governance (yearn:Deployer) address

Severity: Medium

A total of 10.85475338406697 YFI tokens were seized (transferred out the contract) by the yearn:Deployer address (`0x2d407ddb06311396fe14d4b49da5f0471447d45c`) on the 21st of July 2020, at 12:19 UTC. This happened in 2 consecutive calls to the seize function shown in the 2 transactions below:

  1. From RewardsTo yearn: Deployer For 1.085475338406696829 ($2,339.54)
  2. From RewardsTo yearn: Deployer For 9.769278045660271461 ($21,055.82)

The entire amount of YFI was then transferred to the DistributeYFI contract located here 

This contract has then distributed this amount non-uniformly in this transaction: to 17 addresses as indicated (hardcoded) in the `distribute()` function of the DistributeYFI contract:

  1. yfi.safeTransfer(0x28b88cfD875C883cDb61938C97B8d1baabf31c88,4084730714199977);
  2. yfi.safeTransfer(0x3F47A66aDA01491c3d364599e5bcBf80A1a67092,1405800000000000000);
  3. yfi.safeTransfer(0x5ade40e345817B739b91c6B4615eFCE87F9D7c57,1000000000000000);
  4. yfi.safeTransfer(0x6c6145d05Cd02B40403fd739f7B9B24fc8d8C410,2000000000000000);
  5. yfi.safeTransfer(0xc7D2fdE79bDAe639115607A5bddd1596058E9c29,100000000000000);
  6. yfi.safeTransfer(0xd6b806f51d41947B6C8465363De90941a81FD8Bd,85800000000000000);
  7. yfi.safeTransfer(0x4A915B337B38f4755b39f2801D4Df2901A1432CD,75500000000000000);
  8. yfi.safeTransfer(0xbA766A08f0a126Ed43B2699F722d2063e5fBb308,4282600000000000000);
  9. yfi.safeTransfer(0x5398850A9399Da87624874704FEAa8A9C6C4089B,10000000000000000);
  10. yfi.safeTransfer(0x93aa8C9A50EFa30Bb28cE0AD12516686c963472a,2416900000000000000);
  11. yfi.safeTransfer(0xf853184415AC2312844E77Cc7BaBda372e8F56aF,257000000000000000);
  12. yfi.safeTransfer(0xd53C9Fedcc95187307908D659846a443cb1e7350,122100000000000000);
  13. yfi.safeTransfer(0xCC1cc238f0D0de0bB82cD1F36Ea988D8EB4489AB,243200000000000000);
  14. yfi.safeTransfer(0xDda8901508211dfd3a2A912fEb0b913a6558c113,1006600000000000000);
  15. yfi.safeTransfer(0x1AE3A0366C8F540C9f31cfC18A23b7E4AC9D4d8D,289700000000000000);
  16. yfi.safeTransfer(0xdA581d5E0c21f59E681ADe7747F477D44e4E6FD4,634668653352768313);
  17. yfi.safeTransfer(0xbff3BdC458D94C9Ca36230bE24838e778305A954,17700000000000000);
These addresses burned these exact amounts of YFI tokens before by calling the claim function of the YearnRewards contract, for which they received aDAI (Aave interest bearing DAI) in exchange. It is not entirely clear why they were given back the YFI tokens later. Those addresses do not seem to have returned the aDAI in this transaction history:

We believe this is related to the fact that some manual operations still need to be performed by the yearn: Deployer admin key.


Severity: Informational


The updateTargetGovernance and updateThisGovernance functions are not access controlled in any way. Anyone can call these functions. The update period is hardcoded to 3 days (17280 blocks) and cannot be updated. The addMinter function cannot be called by TimelockGovernance.

APR oracle 


APR calculation sometimes takes 365 days, other times 366

Severity: Informational

Description: For Compound, the contract calculates APR based on a 365-day calendar; for DyDx, it does so using a 366 baseline. Both are approximations, but 365 seems more accurate.

Heavy use of magic numbers

Severity: Informational

Description: The contract makes heavy use of undocumented numerical constants (magic numbers), which requires readers and auditors alike to reverse engineer the underlying context. Examples:
  • In getCompoundAPR: 2102400. After a closer look, it stands for the blocks per year on compound:
  • 1 block per ~ 15sec -> 4 blocks per minute
  • 4 blocks per minute -> 240 blocks per hour
  • 5760 blocks per day
  • 5760 blocks per day -> 2102400 blocks per year (365 days)
  • The constant 2102400 is then used to calculate Compounds’ APR. Note that results may differ from Compound unless the latter keeps this constant hardcoded in their contracts (currently the case). See below.

  • In getDyDxAPR, 31622400 - stands for the number of seconds in a 366 day year.

No official documentation on DyDx APR calculation

Severity: Low

Description: DyDx does not provide any official documentation on how to calculate the supply APR for an underlying token. The developer of this APR Oracle even acknowledges that---see his Medium post “How we built on-chain APR for Ethereum DeFi”. Thus, it could be the case that the current implementation does not actually match DyDx’s true APR. Nonetheless, the implemented logic seems correct. 

Privileged Roles - Key Person Risk

Severity: Medium/High

Description: The contract contains many restricted functions (as they should be); thus, they can only be called by the address who deployed the contract (currently, the oracle address, which is the same as the contract owner: 0x284b672380a4b5e4d7ccfcc0541a1d0a78c37f8c). There is a risk that if its key gets compromised, the entire Oracle becomes unusable as it could be incorrectly configured by means of its set-like functions.

Unused Functions

Severity: Undetermined

Description: The Oracle contains some functions that do not seem to be used anywhere in the given contracts we had access to. However, we cannot claim that these functions are not indeed used (e.g., it could be the case that we did not get all contracts that participate in this platform). Among those functions that are not called, we found functions that change the Oracle’s price for a given token, as well as its liquidity.

No sanity check of input address parameters

Severity: Low

Description: All set-like functions that take a contract address as a parameter do not check if the given address is different from 0x0, nor that it points to a contract. Thus, 0x0 and EOA addresses can be accidentally set.


This informal security review is applicable to the current version of contracts as of July 24, 2020 10PM UTC. Quantstamp is aware of the announcement by on its plan to deploy v2 contracts in the next 3-4 days ( The v2 contracts are not yet all available and have not been included as part of this security review.

Quantstamp 实验室


Quantstamp 完成了对Yearn Finance的非正式代码审查。Yearn Finance为流动性提供者提供了收益最大化的机会,并打算以分散的方式进行管理。由于yEarn系统涉及大量的合约,我们的审查仅限于最主要的合约--那些持有资金或可以分配资金的合约。我们将此审查作为对社区的一种服务。以下是按合同划分的调查结果。

鸣谢。我们要特别感谢Devops199fan,Klim K,Angel,Michael Egorov,Samczsun,vasiliy,Will Price,Cooper Turley,Damir Bandalo和社区中的其他人,感谢他们的帮助,帮助我们整理代码,收集所需的文档。我们期待着看到它的发展。




YearnRewards(第一池)--yCurve LP代币。



该合同是Synthetix Unipool合同的副本,该合同于2020年2月由Sigma Prime审查。他们的报告可以在这里找到。

唯一不同的是Uniswap V1 token被改为yCRV token( yDAI/yUSDC/yUSDT/yTUSD(yDAI+yUSD...))0xdF5e0e81Dff6FAF3A7e52BA697820c5e32D806A8。





该合同是Synthetix Unipool合同的副本,该合同已于2020年2月由Sigma Prime审查。他们的报告可以在这里找到

唯一不同的是,Uniswap V1令牌被改为BPT令牌0x60626db611a9957C1ae4Ac5b7eDE69e24A3B76c5。



治理池押注在为Balancer yCRV-YFI池提供流动性时收到的池代币。用户收到YFI,如果他们押注超过1000个池子代币,就有资格对YFI治理提案进行投票。 




  • [所有者]设置奖励发放地址
  • [治理]
    • 除YFI、BPT和奖励令牌外,可转让任何令牌(已付费用)到自己的地址,可拨打 攫取
    • 设置断路器,在提款、投票和押注时阻止所有的费用申请,通过呼叫 设置断路器
    • 设置奖励令牌地址
    • 设置治理地址
    • 设定提案所需的最低金额
    • 设置块数 (期) 提案后
    • 设置投票的锁定时间



奖励分配 地址是不公开的,因此不能直接从智能合约中读取其价值。然而,从智能合约的交易历史中可以看出,该合约的 奖励分配 地址设置在本合同有史以来的第三笔交易中。 并将其设置为安德烈控制的地址,即 0x2d407ddb06311396fe14d4b49da5f0471447d45c.该 奖励分配 地址是唯一允许调用的地址。 通知奖励金额 函数,可以设置以下状态变量。

  1. 奖励率
  2. 最后更新时间
  3. 期限Finish,即从调用该函数的时间起7天内。

这个 通知奖励金额 函数于7月19日03:38:14 PM +UTC在此交易中被调用,奖励参数设置为10,000 YFI。 这意味着 期限Finish 将于7月26日下午03:38+UTC。 在这一点上,该。

  • lastTimeRewardApplicable 函数将始终返回 期限Finish 日子,这将会导致
  • 每块牌子的奖励 功能,停止增加奖励。



默认情况下 时期锁定 YearnGovernance合约中的状态变量具有相同的值17280块(约3天),这不是问题。但是,治理地址可以独立地改变这些值,使它们不同。 如果 时期 > 锁定 然后,赌徒可以在以下情况下撤出他们的股份 锁定 块,那么他们就可以重新获得相同数量的票数,并对同一提案进行双倍投票。以下是执行这种攻击的步骤。

  1. 前提是。治理地址设置 期限=18000 并留下 锁定=17280.
  2. 爱丽丝创建了一个提案(投票期=18000)。
  3. 鲍勃,已经有了100的股份,他投了一票。他的代币被锁定为17280块。
  4. 17280块之后,鲍勃的代币被解锁,他把代币提了出来。
  5. 使用不同的地址,Bob使用同样的100个代币放置另一个木桩。鲍勃现在被允许再次对爱丽丝的提案进行投票,这使他的投票数增加到200,并将他的代币锁定在17280个区块。

建议:合并 锁定 变量与 时期 变量。目前还不清楚为什么以及它们是否会有不同的价值。



社区成员Klim提出,一个用户可以同时投赞成票和反对票。我们将代码段定位在 YearnGovernance, 投票支持()反对票() 函数,并确认了这个问题。虽然这两个函数会检查用户是否曾经投票给同一个职位,并将票数入账,但它们不会检查用户的票数是否已经入账给其他立场,因此允许重复投票。该问题在两个功能中都存在。







说明:与上述第3号池类似,目前 主人治理 地址设置为 0x2d407ddb06311396fe14d4b49da5f0471447d45c.这就赋予了地址将令牌(除YFI和yCRV外)转移到自身的权力。



getReward() 功能要求用户有一定的yGov押注余额,并积极参与治理,即在领取奖励时最近投过票。这个要求和《》中的要求是一样的。 赌注() 职能,但不清楚这是否有意为之,因为没有记录或公开交流。




治理 地址可以添加任何地址作为矿工。该矿工可以任意铸造代币,因为没有上限。






2020年7月21日12:19UTC时,共有10.85475338406697个YFI代币被yearn:Deployer地址(`0x2d407ddb06311396fe14d4b49da5f0471447d45c`)扣押(转出合约)。这发生在连续2次对 攫取 以下2个事务中所示的功能。

  1. 来自Ygov.财经。奖励向往。Deployer For 1.085475338406696829 (2,339.54美元)
  2. 来自Ygov.财经。RewardsTo yearn:Deployer For 9.769278045660271461 ($21 055.82)



  1. yfi.safeTransfer(0x28b88cfD875C883cDb61938C97B8d1baabf31c88,4084730714199977)。
  2. yfi.safeTransfer(0x3F47A66aDA01491c3d364599e5bcBf80A1a67092,140580000000000)。
  3. yfi.safeTransfer(0x5ade40e345817B739b91c6B4615eFCE87F9D7c57,1000000000000000)。
  4. yfi.safeTransfer(0x6c6145d05Cd02B40403fd739f7B9B24fc8d8C410,2000000000000000)。
  5. yfi.safeTransfer(0xc7D2fdE79bDAe639115607A5bddd1596058E9c29,10000000000)。
  6. yfi.safeTransfer(0xd6b806f51d41947B6C8465363De90941a81FD8Bd,8580000000000)。
  7. yfi.safeTransfer(0x4A915B337B38f4755b39f2801D4Df2901A1432CD,75500000000000000)。
  8. yfi.safeTransfer(0xbA766A08f0a126Ed43B2699F722d2063e5fBb308,4282600000000000000)。
  9. yfi.safeTransfer(0x5398850A9399Da87624874704FEAa8A9C6C4089B,10000000000000000)。
  10. yfi.safeTransfer(0x93aa8C9A50EFa30Bb28cE0AD12516686c963472a,241690000000000)。
  11. yfi.safeTransfer(0xf853184415AC2312844E77Cc7BaBda372e8F56aF,257000000000000000)。
  12. yfi.safeTransfer(0xd53C9Fedcc95187307908D659846a443cb1e7350,12210000000000)。
  13. yfi.safeTransfer(0xCC1cc238f0D0de0bB82cD1F36Ea988D8EB4489AB,243200000000000000)。
  14. yfi.safeTransfer(0xDda8901508211dfd3a2A912fEb0b913a6558c113,1006600000000000000)。
  15. yfi.safeTransfer(0x1AE3A0366C8F540C9f31cfC18A23b7E4AC9D4d8D,289700000000)。
  16. yfi.safeTransfer(0xdA581d5E0c21f59E681ADe7747F477D44e4E6FD4,634668653352768313)。
  17. yfi.safeTransfer(0xbff3BdC458D94C9Ca36230bE24838e778305A954,1770000000000)。
这些地址之前通过调用YFI代币烧毁了这些确切数量的YFI代币。 声称 的作用 年终奖 合同,为此他们收到了DAI(Aave生息DAI)作为交换。目前还不完全清楚为什么后来又把YFI代币还给他们。在这个交易记录中,这些地址似乎并没有归还aDAI:。





updateTargetGovernance更新此治理 函数不受任何方式的访问控制,任何人都可以调用这些函数。任何人都可以调用这些函数。 更新周期被硬编码为3天(17280块),不能更新。 该 加注 函数不能被 TimelockGovernance.

APR oracle 







说明。合同中大量使用了未记录的数字常数(神奇数字),这就要求读者和审计人员对基本情况进行逆向工程。 例子:
  • getCompoundAPR: 2102400.仔细一看,它代表的是每年复合的块数。
  • 每15秒1块->每分钟4块。
  • 每分钟4块 -> 每小时240块
  • 每天5760块
  • 每天5760个区块 -> 每年2102400个区块(365天)
  • 然后用常数2102400来计算Compounds的年利率。请注意,结果可能会与Compound不同,除非后者将此常数硬编码在其合同中(目前的情况)。请看下文。

  • getDyDxAPR, 31622400 - stands for the number of seconds in a 366 day year.



说明:DyDx没有提供任何官方文件说明如何计算基础代币的供应年利率。DyDx没有提供任何关于如何计算基础代币的供应APR的官方文档。这个APR Oracle的开发者甚至承认这一点------请看他的Medium帖子"我们如何为Ethereum DeFi构建链上APR"。因此,可能是目前的实现实际上并不符合DyDx的真实APR。尽管如此,实现的逻辑似乎是正确的。 



说明。合同中包含了许多受限制的功能(它们应该是);因此,它们只能由部署合同的地址来调用(目前是 神谕 地址,与合同所有者相同:0x284b672380a4b5e4d7ccfcc0541a1d0a78c37f8c)。)有一种风险是,如果其密钥被泄露,整个甲骨文就无法使用,因为它可能通过其类似集的功能进行错误配置。








本次非正式安全审查适用于2020年7月24日10PM(世界协调时)当前版本的合同。Quantstamp 注意到yearn.finance宣布计划在未来3-4天内部署v2合同(。v2合约还未全部推出,未被纳入本次安全审查范围。

November 11, 2020

Quantstamp Community Update - October 2020

‍Audit of Ethereum 2.0 client Teku, blockchain insurance, Open DeFi, virtual events, and more media coverage... here’s what happened at Quantstamp in October.‍

November 5, 2020

Why Bitcoin is Capturing Enterprise Attention

MicroStrategy made headlines this summer as the first publicly-traded company to buy Bitcoin as part of its capital allocation strategy. Since then, other companies have followed suit. Learn how current economic conditions and the unique properties of Bitcoin have driven these decisions.

October 28, 2020

Formally Verifying Hedera Hashgraph's Stablecoin Framework

Quantstamp created and formally verified a specification for Hedera Hashgraph stablecoins. This simplifies the process of creating safe stablecoins and also makes easier for partners to safely integrate them.

October 27, 2020

Quantstamp Completes Audit of 2nd ETH 2.0 Implementation

Quantstamp has now completed its audit of Teku, the Ethereum 2.0 client developed by ConsenSys. Quantstamp also audited Prysm by Prysmatic Labs.