If you notice some outdated information please let us know!
PASS
The final review score is indicated as a percentage. The percentage is calculated as Achieved Points due to MAX Possible Points. For each element the answer can be either Yes/No or a percentage. For a detailed breakdown of the individual weights of each question, please consult this document.
Very simply, the audit looks for the following declarations from the developer's site. With these declarations, it is reasonable to trust the smart contracts.
This report is for informational purposes only and does not constitute investment advice of any kind, nor does it constitute an offer to provide investment advisory or other services. Nothing in this report shall be considered a solicitation or offer to buy or sell any security, token, future, option or other financial instrument or to offer or provide any investment advice or service to any person in any jurisdiction. Nothing contained in this report constitutes investment advice or offers any opinion with respect to the suitability of any security, and the views expressed in this report should not be taken as advice to buy, sell or hold any security. The information in this report should not be relied upon for the purpose of investing. In preparing the information contained in this report, we have not taken into account the investment needs, objectives and financial circumstances of any particular investor. This information has no regard to the specific investment objectives, financial situation and particular needs of any specific recipient of this information and investments discussed may not be suitable for all investors.
Any views expressed in this report by us were prepared based upon the information available to us at the time such views were written. The views expressed within this report are limited to DeFiSafety and the author and do not reflect those of any additional or third party and are strictly based upon DeFiSafety, its authors, interpretations and evaluation of relevant data. Changed or additional information could cause such views to change. All information is subject to possible correction. Information may quickly become unreliable for various reasons, including changes in market conditions or economic circumstances.
This completed report is copyright (c) DeFiSafety 2023. Permission is given to copy in whole, retaining this copyright label.
This section looks at the code deployed on the relevant chain that gets reviewed and its corresponding software repository. The document explaining these questions is here.
1. Are the smart contract addresses easy to find? (%)
They can be found at https://yearn.watch/network/ethereum, as indicated in the Appendix.
2. How active is the primary contract? (%)
Contract USDC Vault is used more than 10 times a day, as indicated in the Appendix.
3. Does the protocol have a public software repository? (Y/N)
Yes: https://github.com/iearn-finance
4. Is there a development history visible? (%)
At 347 commits on their vault repo alone, it's clear that this DeFi monster has well documented its rise.
5. Is the team public (not anonymous)?
Contributors are public and identify as Yearn employees on their LinkedIn employee page.
The difference between this and the old link is solely the link. This section looks at the software documentation. The document explaining these questions is here.
6. Is there a whitepaper? (Y/N)
Location: https://docs.yearn.finance/
7. Is the protocol's software architecture documented? (Y/N)
This protocol's software architecture is documented in their docs.
8. Does the software documentation fully cover the deployed contracts' source code? (%)
There is full coverage of deployed contracts by software function documentation.
9. Is it possible to trace the documented software to its implementation in the protocol's source code? (%)
There is clear but non-explicit traceability between software documentation and implemented code.
10. Has the protocol tested their deployed code? (%)
Code examples are in the Appendix at the end of this report. As per the SLOC, there is 211% testing to code (TtC). This score is guided by the Test to Code ratio (TtC). Generally a good test to code ratio is over 100%. However, the reviewer's best judgement is the final deciding factor.
11. How covered is the protocol's code? (%)
Code coverage test instructions are provided, and there is a report for users to view.
12. Does the protocol provide scripts and instructions to run their tests? (Y/N)
Scripts/Instructions location: https://github.com/yearn/yearn-vaults#tests
13. Is there a detailed report of the protocol's test results?(%)
A full report is documented.
14. Has the protocol undergone Formal Verification? (Y/N)
This protocol has not undergone formal verification. Yearn is exploring this, and this score will be updated as and when it goes live.
15. Were the smart contracts deployed to a testnet? (Y/N)
This protocol deploys to ganache.
This section looks at the 3rd party software audits done. It is explained in this document.
16. Is the protocol sufficiently audited? (%)
Yearn and its respective strapiucts have been audited countless times, both before and after launch. Yearn should be commended for this commitment to security - DeFiSafety has not seen this many audits conducted on a protocol before.
17. Is the bounty value acceptably high (%)
This protocol offers an active bug bounty of $200K.
This section covers the documentation of special access controls for a DeFi protocol. The admin access controls are the contracts that allow updating contracts or coefficients in the protocol. Since these contracts can allow the protocol admins to "change the rules", complete disclosure of capabilities is vital for user's transparency. It is explained in this document.
18. Is the protocol's admin control information easy to find?
Admin control information was documented at this location. This was quick to find.
19. Are relevant contracts clearly labelled as upgradeable or immutable? (%)
The relevant contracts are identified as upgradeable/callable/governable within their respective technical documentations, although this is certainly more implicit than explicit. However, this is more explicitly implied in their governance documentation.
20. Is the type of smart contract ownership clearly indicated? (%)
Ownership is clearly indicated in this location.
21. Are the protocol's smart contract change capabilities described? (%)
Smart contract change capabilities are identified.
22. Is the protocol's admin control information easy to understand? (%)
This information is in software specific language.
23. Is there sufficient Pause Control documentation? (%)
This protocol's pause control (setEmergencyShutdown) is documented here.
24. Is there sufficient Timelock documentation? (%)
This protocol has no timelock and this is explained.
25. Is the Timelock of an adequate length? (Y/N)
Timelock is not needed, as identified by the development team.
This section goes over the documentation that a protocol may or may not supply about their Oracle usage. Oracles are a fundamental part of DeFi as they are responsible for relaying tons of price data information to thousands of protocols using blockchain technology. Not only are they important for price feeds, but they are also an essential component of transaction verification and security. This is explained in this document.
26. Is the protocol's Oracle sufficiently documented? (%)
Yearn doesn't use oracles, as explained here.
27. Is front running mitigated by this protocol? (Y/N)
This protocol documents sandwich attack mitigation techniques.
28. Can flashloan attacks be applied to the protocol, and if so, are those flashloan attack risks mitigated? (Y/N)
This protocol documents flashloan countermeasures at this location.
1pragma solidity >=0.6.0 <0.7.0;
2pragma experimental ABIEncoderV2;
3
4import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol";
5import {SafeERC20} from "@openzeppelin/contracts/token/ERC20/SafeERC20.sol";
6import {SafeMath} from "@openzeppelin/contracts/math/SafeMath.sol";
7import {StrategyLib} from "./StrategyLib.sol";
8
9struct StrategyParams {
10 uint256 performanceFee;
11 uint256 activation;
12 uint256 debtRatio;
13 uint256 minDebtPerHarvest;
14 uint256 maxDebtPerHarvest;
15 uint256 lastReport;
16 uint256 totalDebt;
17 uint256 totalGain;
18 uint256 totalLoss;
19}
20
21interface VaultAPI is IERC20 {
22 function name() external view returns (string calldata);
23
24 function symbol() external view returns (string calldata);
25
26 function decimals() external view returns (uint256);
27
28 function apiVersion() external pure returns (string memory);
29
30 function permit(
31 address owner,
32 address spender,
33 uint256 amount,
34 uint256 expiry,
35 bytes calldata signature
36 ) external returns (bool);
37
38 // NOTE: Vyper strapiuces multiple signatures for a given function with "default" args
39 function deposit() external returns (uint256);
40
41 function deposit(uint256 amount) external returns (uint256);
42
43 function deposit(uint256 amount, address recipient) external returns (uint256);
44
45 // NOTE: Vyper strapiuces multiple signatures for a given function with "default" args
46 function withdraw() external returns (uint256);
47
48 function withdraw(uint256 maxShares) external returns (uint256);
49
50 function withdraw(uint256 maxShares, address recipient) external returns (uint256);
51
52 function token() external view returns (address);
53
54 function strategies(address _strategy) external view returns (StrategyParams memory);
55
56 function pricePerShare() external view returns (uint256);
57
58 function totalAssets() external view returns (uint256);
59
60 function depositLimit() external view returns (uint256);
61
62 function maxAvailableShares() external view returns (uint256);
63
64 /**
65 * View how much the Vault would increase this Strategy's borrow limit,
66 * based on its present performance (since its last report). Can be used to
67 * determine expectedReturn in your Strategy.
68 */
69 function creditAvailable() external view returns (uint256);
70
71 /**
72 * View how much the Vault would like to pull back from the Strategy,
73 * based on its present performance (since its last report). Can be used to
74 * determine expectedReturn in your Strategy.
75 */
76 function debtOutstanding() external view returns (uint256);
77
78 /**
79 * View how much the Vault expect this Strategy to return at the current
80 * block, based on its present performance (since its last report). Can be
81 * used to determine expectedReturn in your Strategy.
82 */
83 function expectedReturn() external view returns (uint256);
84
85 /**
86 * This is the main contact point where the Strategy interacts with the
87 * Vault. It is critical that this call is handled as intended by the
88 * Strategy. Therefore, this function will be called by BaseStrategy to
89 * make sure the integration is correct.
90 */
91 function report(
92 uint256 _gain,
93 uint256 _loss,
94 uint256 _debtPayment
95 ) external returns (uint256);
96
97 /**
98 * This function should only be used in the scenario where the Strategy is
99 * being retired but no migration of the positions are possible, or in the
100 * extreme scenario that the Strategy needs to be put into "Emergency Exit"
101 * mode in order for it to exit as quickly as possible. The latter scenario
102 * could be for any reason that is considered "critical" that the Strategy
103 * exits its position as fast as possible, such as a sudden change in
104 * market conditions leading to losses, or an imminent failure in an
105 * external dependency.
106 */
107 function revokeStrategy() external;
108
109 /**
110 * View the governance address of the Vault to assert privileged functions
111 * can only be called by governance. The Strategy serves the Vault, so it
112 * is subject to governance defined by the Vault.
113 */
114 function governance() external view returns (address);
115
116 /**
117 * View the management address of the Vault to assert privileged functions
118 * can only be called by management. The Strategy serves the Vault, so it
119 * is subject to management defined by the Vault.
120 */
121 function management() external view returns (address);
122
123 /**
124 * View the guardian address of the Vault to assert privileged functions
125 * can only be called by guardian. The Strategy serves the Vault, so it
126 * is subject to guardian defined by the Vault.
127 */
128 function guardian() external view returns (address);
129}
130
131/**
132 * This interface is here for the keeper bot to use.
133 */
134interface StrategyAPI {
135 function name() external view returns (string memory);
136
137 function vault() external view returns (address);
138
139 function want() external view returns (address);
140
141 function apiVersion() external pure returns (string memory);
142
143 function keeper() external view returns (address);
144
145 function isActive() external view returns (bool);
146
147 function delegatedAssets() external view returns (uint256);
148
149 function estimatedTotalAssets() external view returns (uint256);
150
151 function tendTrigger(uint256 callCost) external view returns (bool);
152
153 function tend() external;
154
155 function harvestTrigger(uint256 callCost) external view returns (bool);
156
157 function harvest() external;
158
159 event Harvested(uint256 profit, uint256 loss, uint256 debtPayment, uint256 debtOutstanding);
160}
161
162/**
163 * @title Yearn Base Strategy
164 * @author yearn.finance
165 * @notice
166 * BaseStrategy implements all of the required functionality to interoperate
167 * closely with the Vault contract. This contract should be inherited and the
168 * abstract methods implemented to adapt the Strategy to the particular needs
169 * it has to create a return.
170 *
171 * Of special interest is the relationship between `harvest()` and
172 * `vault.report()'. `harvest()` may be called simply because enough time has
173 * elapsed since the last report, and not because any funds need to be moved
174 * or positions adjusted. This is critical so that the Vault may maintain an
175 * accurate picture of the Strategy's performance. See `vault.report()`,
176 * `harvest()`, and `harvestTrigger()` for further details.
177 */
178
179abstract contract BaseStrategy {
180 using SafeMath for uint256;
181 string public metadataURI;
182
183 /**
184 * @notice
185 * Used to track which version of `StrategyAPI` this Strategy
186 * implements.
187 * @dev The Strategy's version must match the Vault's `API_VERSION`.
188 * @return A string which holds the current API version of this contract.
189 */
190 function apiVersion() public pure returns (string memory) {
191 return "0.4.3";
192 }
193
194 /**
195 * @notice This Strategy's name.
196 * @dev
197 * You can use this field to manage the "version" of this Strategy, e.g.
198 * `StrategySomethingOrOtherV1`. However, "API Version" is managed by
199 * `apiVersion()` function above.
200 * @return This Strategy's name.
201 */
202 function name() external view virtual returns (string memory);
203
204 /**
205 * @notice
206 * The amount (priced in want) of the total assets managed by this strategy should not count
207 * towards Yearn's TVL calculations.
208 * @dev
209 * You can override this field to set it to a non-zero value if some of the assets of this
210 * Strategy is somehow delegated inside another part of of Yearn's ecosystem e.g. another Vault.
211 * Note that this value must be strictly less than or equal to the amount provided by
212 * `estimatedTotalAssets()` below, as the TVL calc will be total assets minus delegated assets.
213 * Also note that this value is used to determine the total assets under management by this
214 * strategy, for the purposes of computing the management fee in `Vault`
215 * @return
216 * The amount of assets this strategy manages that should not be included in Yearn's Total Value
217 * Locked (TVL) calculation across it's ecosystem.
218 */
219 function delegatedAssets() external view virtual returns (uint256) {
220 return 0;
221 }
222
223 VaultAPI public vault;
224 address public strategist;
225 address public rewards;
226 address public keeper;
227
228 IERC20 public want;
229
230 // So indexers can keep track of this
231 event Harvested(uint256 profit, uint256 loss, uint256 debtPayment, uint256 debtOutstanding);
232
233 event UpdatedStrategist(address newStrategist);
234
235 event UpdatedKeeper(address newKeeper);
236
237 event UpdatedRewards(address rewards);
238
239 event UpdatedMinReportDelay(uint256 delay);
240
241 event UpdatedMaxReportDelay(uint256 delay);
242
243 event UpdatedProfitFactor(uint256 profitFactor);
244
245 event UpdatedDebtThreshold(uint256 debtThreshold);
246
247 event EmergencyExitEnabled();
248
249 event UpdatedMetadataURI(string metadataURI);
250
251 // The minimum number of seconds between harvest calls. See
252 // `setMinReportDelay()` for more details.
253 uint256 public minReportDelay;
254
255 // The maximum number of seconds between harvest calls. See
256 // `setMaxReportDelay()` for more details.
257 uint256 public maxReportDelay;
258
259 // The minimum multiple that `callCost` must be above the credit/profit to
260 // be "justifiable". See `setProfitFactor()` for more details.
261 uint256 public profitFactor;
262
263 // Use this to adjust the threshold at which running a debt causes a
264 // harvest trigger. See `setDebtThreshold()` for more details.
265 uint256 public debtThreshold;
266
267 // See note on `setEmergencyExit()`.
268 bool public emergencyExit;
269
270 // modifiers
271 modifier onlyAuthorized() {
272 _onlyAuthorized();
273 _;
274 }
275
276 modifier onlyEmergencyAuthorized() {
277 _onlyEmergencyAuthorized();
278 _;
279 }
280
281 modifier onlyStrategist() {
282 _onlyStrategist();
283 _;
284 }
285
286 modifier onlyGovernance() {
287 _onlyGovernance();
288 _;
289 }
290
291 modifier onlyRewarder() {
292 _onlyRewarder();
293 _;
294 }
295
296 modifier onlyKeepers() {
297 _onlyKeepers();
298 _;
299 }
300
301 function _onlyAuthorized() internal {
302 require(msg.sender == strategist || msg.sender == governance());
303 }
304
305 function _onlyEmergencyAuthorized() internal {
306 require(msg.sender == strategist || msg.sender == governance() || msg.sender == vault.guardian() || msg.sender == vault.management());
307 }
308
309 function _onlyStrategist() internal {
310 require(msg.sender == strategist);
311 }
312
313 function _onlyGovernance() internal {
314 require(msg.sender == governance());
315 }
316
317 function _onlyRewarder() internal {
318 require(msg.sender == governance() || msg.sender == strategist);
319 }
320
321 function _onlyKeepers() internal {
322 require(
323 msg.sender == keeper ||
324 msg.sender == strategist ||
325 msg.sender == governance() ||
326 msg.sender == vault.guardian() ||
327 msg.sender == vault.management()
328 );
329 }
330
331 constructor(address _vault) public {
332 _initialize(_vault, msg.sender, msg.sender, msg.sender);
333 }
334
335 /**
336 * @notice
337 * Initializes the Strategy, this is called only once, when the
338 * contract is deployed.
339 * @dev `_vault` should implement `VaultAPI`.
340 * @param _vault The address of the Vault responsible for this Strategy.
341 * @param _strategist The address to assign as `strategist`.
342 * The strategist is able to change the reward address
343 * @param _rewards The address to use for pulling rewards.
344 * @param _keeper The adddress of the _keeper. _keeper
345 * can harvest and tend a strategy.
346 */
347 function _initialize(
348 address _vault,
349 address _strategist,
350 address _rewards,
351 address _keeper
352 ) internal {
353 require(address(want) == address(0), "Strategy already initialized");
354
355 vault = VaultAPI(_vault);
356 want = IERC20(vault.token());
357 SafeERC20.safeApprove(want, _vault, type(uint256).max); // Give Vault unlimited access (might save gas)
358 strategist = _strategist;
359 rewards = _rewards;
360 keeper = _keeper;
361
362 // initialize variables
363 minReportDelay = 0;
364 maxReportDelay = 86400;
365 profitFactor = 100;
366 debtThreshold = 0;
367
368 vault.approve(rewards, type(uint256).max); // Allow rewards to be pulled
369 }
370
371 /**
372 * @notice
373 * Used to change `strategist`.
374 *
375 * This may only be called by governance or the existing strategist.
376 * @param _strategist The new address to assign as `strategist`.
377 */
378 function setStrategist(address _strategist) external onlyAuthorized {
379 require(_strategist != address(0));
380 strategist = _strategist;
381 emit UpdatedStrategist(_strategist);
382 }
383
384 /**
385 * @notice
386 * Used to change `keeper`.
387 *
388 * `keeper` is the only address that may call `tend()` or `harvest()`,
389 * other than `governance()` or `strategist`. However, unlike
390 * `governance()` or `strategist`, `keeper` may *only* call `tend()`
391 * and `harvest()`, and no other authorized functions, following the
392 * principle of least privilege.
393 *
394 * This may only be called by governance or the strategist.
395 * @param _keeper The new address to assign as `keeper`.
396 */
397 function setKeeper(address _keeper) external onlyAuthorized {
398 require(_keeper != address(0));
399 keeper = _keeper;
400 emit UpdatedKeeper(_keeper);
401 }
402
403 /**
404 * @notice
405 * Used to change `rewards`. EOA or smart contract which has the permission
406 * to pull rewards from the vault.
407 *
408 * This may only be called by the strategist.
409 * @param _rewards The address to use for pulling rewards.
410 */
411 function setRewards(address _rewards) external onlyRewarder {
412 address oldRewards = rewards;
413 rewards = _rewards;
414 StrategyLib.internalSetRewards(oldRewards, _rewards, address(vault));
415 emit UpdatedRewards(_rewards);
416 }
417
418 /**
419 * @notice
420 * Used to change `minReportDelay`. `minReportDelay` is the minimum number
421 * of blocks that should pass for `harvest()` to be called.
422 *
423 * For external keepers (such as the Keep3r network), this is the minimum
424 * time between jobs to wait. (see `harvestTrigger()`
425 * for more details.)
426 *
427 * This may only be called by governance or the strategist.
428 * @param _delay The minimum number of seconds to wait between harvests.
429 */
430 function setMinReportDelay(uint256 _delay) external onlyAuthorized {
431 minReportDelay = _delay;
432 emit UpdatedMinReportDelay(_delay);
433 }
434
435 /**
436 * @notice
437 * Used to change `maxReportDelay`. `maxReportDelay` is the maximum number
438 * of blocks that should pass for `harvest()` to be called.
439 *
440 * For external keepers (such as the Keep3r network), this is the maximum
441 * time between jobs to wait. (see `harvestTrigger()`
442 * for more details.)
443 *
444 * This may only be called by governance or the strategist.
445 * @param _delay The maximum number of seconds to wait between harvests.
446 */
447 function setMaxReportDelay(uint256 _delay) external onlyAuthorized {
448 maxReportDelay = _delay;
449 emit UpdatedMaxReportDelay(_delay);
450 }
451
452 /**
453 * @notice
454 * Used to change `profitFactor`. `profitFactor` is used to determine
455 * if it's worthwhile to harvest, given gas costs. (See `harvestTrigger()`
456 * for more details.)
457 *
458 * This may only be called by governance or the strategist.
459 * @param _profitFactor A ratio to multiply anticipated
460 * `harvest()` gas cost against.
461 */
462 function setProfitFactor(uint256 _profitFactor) external onlyAuthorized {
463 profitFactor = _profitFactor;
464 emit UpdatedProfitFactor(_profitFactor);
465 }
466
467 /**
468 * @notice
469 * Sets how far the Strategy can go into loss without a harvest and report
470 * being required.
471 *
472 * By default this is 0, meaning any losses would cause a harvest which
473 * will subsequently report the loss to the Vault for tracking. (See
474 * `harvestTrigger()` for more details.)
475 *
476 * This may only be called by governance or the strategist.
477 * @param _debtThreshold How big of a loss this Strategy may carry without
478 * being required to report to the Vault.
479 */
480 function setDebtThreshold(uint256 _debtThreshold) external onlyAuthorized {
481 debtThreshold = _debtThreshold;
482 emit UpdatedDebtThreshold(_debtThreshold);
483 }
484
485 /**
486 * @notice
487 * Used to change `metadataURI`. `metadataURI` is used to store the URI
488 * of the file describing the strategy.
489 *
490 * This may only be called by governance or the strategist.
491 * @param _metadataURI The URI that describe the strategy.
492 */
493 function setMetadataURI(string calldata _metadataURI) external onlyAuthorized {
494 metadataURI = _metadataURI;
495 emit UpdatedMetadataURI(_metadataURI);
496 }
497
Tests to Code: 1837 / 868 = 212 %