From b3898e751788f30445ebaabd06fab9c6d86258cf Mon Sep 17 00:00:00 2001 From: Shahul Hameed <10547529+shahthepro@users.noreply.github.com> Date: Wed, 15 Nov 2023 12:45:27 +0530 Subject: [PATCH 01/13] Fix failing fork tests --- contracts/test/_fixture.js | 16 ++++++++++++++++ contracts/test/oracle/oracle.fork-test.js | 2 +- .../test/strategies/oeth-metapool.fork-test.js | 1 + 3 files changed, 18 insertions(+), 1 deletion(-) diff --git a/contracts/test/_fixture.js b/contracts/test/_fixture.js index f692a29edf..788ffded20 100644 --- a/contracts/test/_fixture.js +++ b/contracts/test/_fixture.js @@ -1524,6 +1524,7 @@ async function convexOETHMetaVaultFixture( depositToStrategy: false, poolAddEthAmount: 0, poolAddOethAmount: 0, + balancePool: false, } ) { const fixture = await oethDefaultFixture(); @@ -1595,6 +1596,21 @@ async function convexOETHMetaVaultFixture( } } + if (config?.balancePool) { + const ethBalance = await fixture.oethMetaPool.balances(0); + const oethBalance = await fixture.oethMetaPool.balances(1); + + const diff = parseInt( + ethBalance.sub(oethBalance).div(oethUnits("1")).toString() + ); + + if (diff > 0) { + config.poolAddOethAmount = (config.poolAddOethAmount || 0) + diff; + } else if (diff < 0) { + config.poolAddEthAmount = (config.poolAddOethAmount || 0) - diff; + } + } + // Add ETH to the Metapool if (config?.poolAddEthAmount > 0) { // Fund Josh with ETH plus some extra for gas fees diff --git a/contracts/test/oracle/oracle.fork-test.js b/contracts/test/oracle/oracle.fork-test.js index 2df5e57ab5..52b75fe1ba 100644 --- a/contracts/test/oracle/oracle.fork-test.js +++ b/contracts/test/oracle/oracle.fork-test.js @@ -23,7 +23,7 @@ describe("ForkTest: Oracle Routers", function () { const price = await oethOracleRouter.price(reth.address); expect(price).to.gte(parseUnits("1083", 15)); - expect(price).to.lt(parseUnits("109", 16)); + expect(price).to.lt(parseUnits("111", 16)); }); it("should get frxETH price", async () => { const { frxETH } = fixture; diff --git a/contracts/test/strategies/oeth-metapool.fork-test.js b/contracts/test/strategies/oeth-metapool.fork-test.js index 8dc0a28327..bd25b770e8 100644 --- a/contracts/test/strategies/oeth-metapool.fork-test.js +++ b/contracts/test/strategies/oeth-metapool.fork-test.js @@ -555,6 +555,7 @@ describe("ForkTest: OETH AMO Curve Metapool Strategy", function () { wethMintAmount: 5000, depositToStrategy: false, poolAddOethAmount: 500, + balancePool: true, }); beforeEach(async () => { fixture = await loadFixture(); From 66fe62b65b3393493b3ab6048bc8589673306f75 Mon Sep 17 00:00:00 2001 From: Shahul Hameed <10547529+shahthepro@users.noreply.github.com> Date: Wed, 15 Nov 2023 13:17:52 +0530 Subject: [PATCH 02/13] Balance Pool before tests (#1934) * Balance Pool before tests * Fix slither issues * Fix bug --- contracts/contracts/strategies/AaveStrategy.sol | 5 +++-- .../contracts/strategies/balancer/BaseBalancerStrategy.sol | 1 + contracts/test/_fixture.js | 2 +- contracts/test/strategies/oeth-metapool.fork-test.js | 5 +++++ 4 files changed, 10 insertions(+), 3 deletions(-) diff --git a/contracts/contracts/strategies/AaveStrategy.sol b/contracts/contracts/strategies/AaveStrategy.sol index 72be317d52..629df13876 100644 --- a/contracts/contracts/strategies/AaveStrategy.sol +++ b/contracts/contracts/strategies/AaveStrategy.sol @@ -269,9 +269,10 @@ contract AaveStrategy is InitializableAbstractStrategy { // Collect available rewards and restart the cooldown timer, if either of // those should be run. if (block.timestamp > windowStart || cooldown == 0) { + uint256 assetsLen = assetsMapped.length; // aToken addresses for incentives controller - address[] memory aTokens = new address[](assetsMapped.length); - for (uint256 i = 0; i < assetsMapped.length; i++) { + address[] memory aTokens = new address[](assetsLen); + for (uint256 i = 0; i < assetsLen; ++i) { aTokens[i] = _getATokenFor(assetsMapped[i]); } diff --git a/contracts/contracts/strategies/balancer/BaseBalancerStrategy.sol b/contracts/contracts/strategies/balancer/BaseBalancerStrategy.sol index 8037b6939f..256dc2dfb7 100644 --- a/contracts/contracts/strategies/balancer/BaseBalancerStrategy.sol +++ b/contracts/contracts/strategies/balancer/BaseBalancerStrategy.sol @@ -297,6 +297,7 @@ abstract contract BaseBalancerStrategy is InitializableAbstractStrategy { * by numerical order. */ function _getPoolAssets() internal view returns (IERC20[] memory assets) { + // slither-disable-next-line unused-return (assets, , ) = balancerVault.getPoolTokens(balancerPoolId); } diff --git a/contracts/test/_fixture.js b/contracts/test/_fixture.js index 788ffded20..0fee88ac82 100644 --- a/contracts/test/_fixture.js +++ b/contracts/test/_fixture.js @@ -1607,7 +1607,7 @@ async function convexOETHMetaVaultFixture( if (diff > 0) { config.poolAddOethAmount = (config.poolAddOethAmount || 0) + diff; } else if (diff < 0) { - config.poolAddEthAmount = (config.poolAddOethAmount || 0) - diff; + config.poolAddEthAmount = (config.poolAddEthAmount || 0) - diff; } } diff --git a/contracts/test/strategies/oeth-metapool.fork-test.js b/contracts/test/strategies/oeth-metapool.fork-test.js index bd25b770e8..9f86589b9d 100644 --- a/contracts/test/strategies/oeth-metapool.fork-test.js +++ b/contracts/test/strategies/oeth-metapool.fork-test.js @@ -127,6 +127,7 @@ describe("ForkTest: OETH AMO Curve Metapool Strategy", function () { const loadFixture = createFixtureLoader(convexOETHMetaVaultFixture, { wethMintAmount: 5000, depositToStrategy: false, + balancePool: true, }); beforeEach(async () => { fixture = await loadFixture(); @@ -254,6 +255,7 @@ describe("ForkTest: OETH AMO Curve Metapool Strategy", function () { const loadFixture = createFixtureLoader(convexOETHMetaVaultFixture, { wethMintAmount: 5000, depositToStrategy: true, + balancePool: true, }); beforeEach(async () => { fixture = await loadFixture(); @@ -427,6 +429,7 @@ describe("ForkTest: OETH AMO Curve Metapool Strategy", function () { wethMintAmount: 5000, depositToStrategy: false, poolAddOethAmount: 4000, + balancePool: true, }); beforeEach(async () => { fixture = await loadFixture(); @@ -464,6 +467,7 @@ describe("ForkTest: OETH AMO Curve Metapool Strategy", function () { wethMintAmount: 5000, depositToStrategy: false, poolAddEthAmount: 200000, + balancePool: true, }); beforeEach(async () => { fixture = await loadFixture(); @@ -502,6 +506,7 @@ describe("ForkTest: OETH AMO Curve Metapool Strategy", function () { wethMintAmount: 5000, depositToStrategy: false, poolAddEthAmount: 8000, + balancePool: true, }); beforeEach(async () => { fixture = await loadFixture(); From ae870f34eead22f4197a48b73bd9a7d17c3287e8 Mon Sep 17 00:00:00 2001 From: Domen Grabec Date: Mon, 20 Nov 2023 00:31:11 +0100 Subject: [PATCH 03/13] add a pre-commit hook to check for presence of .only entries in the test files (#1935) --- .husky/pre-commit | 25 +++++++++++++++++++++++++ README.md | 16 ++++++++++++++++ 2 files changed, 41 insertions(+) create mode 100755 .husky/pre-commit diff --git a/.husky/pre-commit b/.husky/pre-commit new file mode 100755 index 0000000000..6e48ebdeda --- /dev/null +++ b/.husky/pre-commit @@ -0,0 +1,25 @@ +#!/bin/sh + +RED='\033[0;31m' +NC='\033[0m' +GREEN='\033[0;32m' +files="" +# find all file ending with test.js that are not in node_modules +for file in $(find . -type d -name node_modules -prune -o -name '*test.js' -print); do + # uncomment below to debug which files match + # echo "$file" + # search if any contain it.only or describe.only + match=$(grep -E "(describe|it)\.only[[:space:]]*\(" "$file") + + # add together the files containing .only + if [[ -n "${match}" ]]; then + files=${files}${file}'\n' + fi +done +if [[ -n "${files}" ]]; then + # fail pre-commit if + echo "${RED}Git pre-commit hook failed! Remove \".only\" from the following test file(s):${GREEN}" + echo ${files} + echo "${NC}" + exit 1 +fi diff --git a/README.md b/README.md index bf45433cdb..65a6476e25 100644 --- a/README.md +++ b/README.md @@ -221,3 +221,19 @@ Want to contribute to OUSD? Awesome! OUSD is an Open Source project and we welcome contributions of all sorts. There are many ways to help, from reporting issues, contributing to the code, and helping us improve our community. The best way to get involved is to join the Origin Protocol [discord server](https://discord.gg/jyxpUSe) and head over to the channel named ORIGIN DOLLAR & DEFI + +# Utils + +## Git pre-commit hooks (using Husky) + +### Setup +``` +# install Husky +npx install husky + +# from project root folder install Husky hooks +npx husky install + +``` + +If the script in .husky/pre-commit returns non 0 exit the pre-commit hook will fail. Currently the script prevents a commit if there is an ".only" in the test scripts. Use "git commit --no-verify" if you have the hook enabled and you'd like to skip pre-commit check. From aaa182c9b58e6e68d13ffc84a88340112b960358 Mon Sep 17 00:00:00 2001 From: Franck Date: Sun, 19 Nov 2023 21:55:52 -0800 Subject: [PATCH 04/13] Update README.md Mention both OETH and OUSD to avoid confusion. Remove outdated section about production deployment via GCP --- README.md | 45 ++++----------------------------------------- 1 file changed, 4 insertions(+), 41 deletions(-) diff --git a/README.md b/README.md index 65a6476e25..1e44d53600 100644 --- a/README.md +++ b/README.md @@ -1,7 +1,8 @@ -# Origin Dollar +# Origin DeFi's OTokens: Origin Dollar (OUSD) and Origin Ether (OETH) + +For more details about the product, checkout [our docs](https://docs.oeth.com). -OUSD is a new kind of stablecoin that passively accrues yield while you are holding it. -Checkout our [docs](https://docs.ousd.com) for more details about the product. +--- | Branch | CI/CD Status | | --------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | @@ -156,44 +157,6 @@ Go to root of the project and run `npx husky install` --- -## (Core Contributors) Running dapp in Production/Staging Mode Locally - -There may be a time that you will need to run the dapp in production/staging mode to test out a certain feature or do verification before a deploy. In this case there is no need for a local node as you will connect directly to the mainnet/testnet. - -### Requirements - -- `Google Cloud` CLI tool installed as explained [HERE](https://cloud.google.com/sdk/docs/quickstart) -- Permission to the Origin GCP Account to decrypt `*.secrets.enc` and deploy infrastructure - -#### Login to Google Cloud - -``` -# Login to GCP -gcloud auth login -``` - -#### Staging - -``` -# Decrypt staging secrets to local -yarn run decrypt-secrets:staging - -# Start local dapp in Staging mode -yarn run start:staging -``` - -#### Production - -``` -# Decrypt staging secrets to local -yarn run decrypt-secrets:production - -# Start local dapp in Production mode -yarn run start:production -``` - ---- - ## Running Smoke Tests Smoke tests can be run in 2 modes: From cc9ab4bc1daf33aa4f519ea06c32160053d82e2a Mon Sep 17 00:00:00 2001 From: Shahul Hameed <10547529+shahthepro@users.noreply.github.com> Date: Mon, 20 Nov 2023 11:52:32 +0530 Subject: [PATCH 05/13] Update runlog (#1937) --- brownie/runlogs/2023_11_strategist.py | 37 ++++++++++++++++++++++++++- 1 file changed, 36 insertions(+), 1 deletion(-) diff --git a/brownie/runlogs/2023_11_strategist.py b/brownie/runlogs/2023_11_strategist.py index 23e865bf36..5c0a4c7bef 100644 --- a/brownie/runlogs/2023_11_strategist.py +++ b/brownie/runlogs/2023_11_strategist.py @@ -41,4 +41,39 @@ def main(): ) ) - print(to_gnosis_json(txs)) \ No newline at end of file + print(to_gnosis_json(txs)) + +# ------------------------------------- +# Nov 17, 2023 - OUSD Reallocation +# ------------------------------------- +from world import * + +def main(): + with TemporaryForkForReallocations() as txs: + txs.append(vault_core.rebase({'from':STRATEGIST})) + txs.append(vault_value_checker.takeSnapshot({'from':STRATEGIST})) + + txs.append( + vault_admin.withdrawFromStrategy( + MORPHO_COMP_STRAT, + [USDC], + [morpho_comp_strat.checkBalance(USDC)], + {'from': STRATEGIST} + ) + ) + + txs.append( + vault_admin.depositToStrategy( + MORPHO_AAVE_STRAT, + [USDC], + [usdc.balanceOf(VAULT_PROXY_ADDRESS)], + {'from': STRATEGIST} + ) + ) + + vault_change = vault_core.totalValue() - vault_value_checker.snapshots(STRATEGIST)[0] + supply_change = ousd.totalSupply() - vault_value_checker.snapshots(STRATEGIST)[1] + profit = vault_change - supply_change + txs.append(vault_value_checker.checkDelta(profit, (500 * 10**18), vault_change, (500 * 10**18), {'from': STRATEGIST})) + + txs.append(vault_core.allocate({'from': STRATEGIST})) \ No newline at end of file From d4dcd4aba753c68ce4ae3ed03a38baf702469147 Mon Sep 17 00:00:00 2001 From: Shahul Hameed <10547529+shahthepro@users.noreply.github.com> Date: Mon, 20 Nov 2023 17:25:07 +0530 Subject: [PATCH 06/13] Fix fork tests (#1938) * Deposit more to Morpho Compound for tests * Increase tolerance * Adjust redeem amount * Prettify * Fix metapool tests * Clean up tests * Cleanup tests * Fix arrow function --- contracts/test/_metastrategies-fixtures.js | 13 +- .../test/strategies/morpho-comp.fork-test.js | 6 +- .../strategies/oeth-metapool.fork-test.js | 12 +- ...usd-metapool-3crv-tilted-pool.fork-test.js | 5 - .../ousd-metapool-balanced-pool.fork-test.js | 96 +- ...usd-metapool-ousd-tilted-pool.fork-test.js | 38 +- .../test/vault/collateral-swaps.fork-test.js | 864 +++++++++--------- contracts/test/vault/oeth-vault.fork-test.js | 259 +++--- 8 files changed, 613 insertions(+), 680 deletions(-) diff --git a/contracts/test/_metastrategies-fixtures.js b/contracts/test/_metastrategies-fixtures.js index f548610251..2f029044b9 100644 --- a/contracts/test/_metastrategies-fixtures.js +++ b/contracts/test/_metastrategies-fixtures.js @@ -2,7 +2,7 @@ const hre = require("hardhat"); const { ethers } = hre; const { formatUnits } = require("ethers/lib/utils"); -const { ousdUnits } = require("./helpers"); +const { ousdUnits, units } = require("./helpers"); const { convexMetaVaultFixture, resetAllowance } = require("./_fixture"); const addresses = require("../utils/addresses"); const erc20Abi = require("./abi/erc20.json"); @@ -18,7 +18,8 @@ const log = require("../utils/logger")("test:fixtures:strategies:meta"); async function withDefaultOUSDMetapoolStrategiesSet() { const fixture = await convexMetaVaultFixture(); - const { vault, timelock, dai, usdt, usdc, OUSDmetaStrategy } = fixture; + const { vault, timelock, dai, usdt, usdc, OUSDmetaStrategy, daniel } = + fixture; await vault .connect(timelock) @@ -37,6 +38,14 @@ async function withDefaultOUSDMetapoolStrategiesSet() { addresses.mainnet.CVXRewardsPool ); + // Also, mint some OUSD so that there's some in the pool + const amount = "20000"; + for (const asset of [usdt, usdc, dai]) { + await vault + .connect(daniel) + .mint(asset.address, await units(amount, asset), 0); + } + return fixture; } diff --git a/contracts/test/strategies/morpho-comp.fork-test.js b/contracts/test/strategies/morpho-comp.fork-test.js index 6962266060..11cfc122f5 100644 --- a/contracts/test/strategies/morpho-comp.fork-test.js +++ b/contracts/test/strategies/morpho-comp.fork-test.js @@ -48,7 +48,7 @@ describe("ForkTest: Morpho Compound Strategy", function () { const supplyBeforeMint = await ousd.totalSupply(); - const amount = "10010"; + const amount = "20020"; // Mint with all three assets for (const asset of [usdt, usdc, dai]) { @@ -59,7 +59,7 @@ describe("ForkTest: Morpho Compound Strategy", function () { const currentSupply = await ousd.totalSupply(); const supplyAdded = currentSupply.sub(supplyBeforeMint); - expect(supplyAdded).to.approxEqualTolerance(ousdUnits("30000"), 1); + expect(supplyAdded).to.approxEqualTolerance(ousdUnits("60000"), 1); const currentBalance = await ousd.connect(domen).balanceOf(domen.address); @@ -180,7 +180,7 @@ describe("ForkTest: Morpho Compound Strategy", function () { } ); - await expect(usdtBalanceDiff).to.be.gte(0); + expect(usdtBalanceDiff).to.be.gte(0); }); }); }); diff --git a/contracts/test/strategies/oeth-metapool.fork-test.js b/contracts/test/strategies/oeth-metapool.fork-test.js index 9f86589b9d..1c8bfa0294 100644 --- a/contracts/test/strategies/oeth-metapool.fork-test.js +++ b/contracts/test/strategies/oeth-metapool.fork-test.js @@ -309,18 +309,18 @@ describe("ForkTest: OETH AMO Curve Metapool Strategy", function () { const curveBalancesAfter = await oethMetaPool.get_balances(); expect(curveBalancesAfter[0]).to.approxEqualTolerance( curveBalancesBefore[0].sub(ethWithdrawAmount), - 0.01 // 0.01% or 1 basis point + 0.05 // 0.05% or 5 basis point ); expect(curveBalancesAfter[1]).to.approxEqualTolerance( curveBalancesBefore[1].sub(oethBurnAmount), - 0.01 // 0.01% + 0.05 // 0.05% ); // Check the OETH total supply decrease const oethSupplyAfter = await oeth.totalSupply(); expect(oethSupplyAfter).to.approxEqualTolerance( oethSupplyBefore.sub(oethBurnAmount), - 0.01 // 0.01% or 1 basis point + 0.05 // 0.01% or 5 basis point ); }); it("Vault should be able to withdraw some", async () => { @@ -372,18 +372,18 @@ describe("ForkTest: OETH AMO Curve Metapool Strategy", function () { const curveBalancesAfter = await oethMetaPool.get_balances(); expect(curveBalancesAfter[0]).to.approxEqualTolerance( curveBalancesBefore[0].sub(withdrawAmount), - 0.01 // 0.01% or 1 basis point + 0.05 // 0.05% or 5 basis point ); expect(curveBalancesAfter[1]).to.approxEqualTolerance( curveBalancesBefore[1].sub(oethBurnAmount), - 0.01 // 0.01% + 0.05 // 0.05% ); // Check the OETH total supply decrease const oethSupplyAfter = await oeth.totalSupply(); expect(oethSupplyAfter).to.approxEqualTolerance( oethSupplyBefore.sub(oethBurnAmount), - 0.01 // 0.01% or 1 basis point + 0.05 // 0.05% or 5 basis point ); // Check the WETH balance in the Vault diff --git a/contracts/test/strategies/ousd-metapool-3crv-tilted-pool.fork-test.js b/contracts/test/strategies/ousd-metapool-3crv-tilted-pool.fork-test.js index 6883dca06e..01c8de77e3 100644 --- a/contracts/test/strategies/ousd-metapool-3crv-tilted-pool.fork-test.js +++ b/contracts/test/strategies/ousd-metapool-3crv-tilted-pool.fork-test.js @@ -31,11 +31,6 @@ describe("ForkTest: Convex 3pool/OUSD Meta Strategy - Titled to 3CRV", function const { anna, dai } = fixture; await mintTest(fixture, anna, dai, "110000"); }); - - it("Should NOT stake DAI in Curve gauge via metapool", async function () { - const { anna, dai } = fixture; - await mintTest(fixture, anna, dai, "110000"); - }); }); describe("Redeem", function () { diff --git a/contracts/test/strategies/ousd-metapool-balanced-pool.fork-test.js b/contracts/test/strategies/ousd-metapool-balanced-pool.fork-test.js index 5195acbdfe..1cfac51a8f 100644 --- a/contracts/test/strategies/ousd-metapool-balanced-pool.fork-test.js +++ b/contracts/test/strategies/ousd-metapool-balanced-pool.fork-test.js @@ -25,83 +25,14 @@ describe("ForkTest: Convex 3pool/OUSD Meta Strategy - Balanced Metapool", functi await mintTest(fixture, josh, usdt, "100000"); }); - describe("Mint", function () { - it("Should stake USDT in Curve gauge via metapool", async function () { - const { josh, usdt } = fixture; - await mintTest(fixture, josh, usdt, "100000"); - }); - - it("Should stake USDC in Curve gauge via metapool", async function () { - const { matt, usdc } = fixture; - await mintTest(fixture, matt, usdc, "120000"); - }); - - it("Should stake DAI in Curve gauge via metapool", async function () { - const { anna, dai } = fixture; - await mintTest(fixture, anna, dai, "110000"); - }); + it("Should stake USDC in Curve gauge via metapool", async function () { + const { matt, usdc } = fixture; + await mintTest(fixture, matt, usdc, "120000"); }); - describe("Redeem", function () { - it("Should redeem", async () => { - const { vault, ousd, usdt, usdc, dai, anna, OUSDmetaStrategy } = - fixture; - - await vault.connect(anna).allocate(); - - const supplyBeforeMint = await ousd.totalSupply(); - - const amount = "10000"; - - const beforeMintBlock = await ethers.provider.getBlockNumber(); - - // Mint with all three assets - for (const asset of [usdt, usdc, dai]) { - await vault - .connect(anna) - .mint(asset.address, await units(amount, asset), 0); - } - - await vault.connect(anna).allocate(); - - log("After mints and allocate to strategy"); - await run("amoStrat", { - pool: "OUSD", - output: false, - fromBlock: beforeMintBlock, - }); - - // we multiply it by 3 because 1/3 of balance is represented by each of the assets - const strategyBalance = ( - await OUSDmetaStrategy.checkBalance(dai.address) - ).mul(3); - - // 3x 10k assets + 3x 10k OUSD = 60k - await expect(strategyBalance).to.be.gte(ousdUnits("59990")); - - // Total supply should be up by at least (10k x 2) + (10k x 2) + 10k = 50k - const currentSupply = await ousd.totalSupply(); - const supplyAdded = currentSupply.sub(supplyBeforeMint); - expect(supplyAdded).to.be.gte(ousdUnits("49995")); - - const currentBalance = await ousd.connect(anna).balanceOf(anna.address); - - // Now try to redeem the amount - const redeemAmount = ousdUnits("29990"); - await vault.connect(anna).redeem(redeemAmount, 0); - - // User balance should be down by 30k - const newBalance = await ousd.connect(anna).balanceOf(anna.address); - expect(newBalance).to.approxEqualTolerance( - currentBalance.sub(redeemAmount), - 1 - ); - - const newSupply = await ousd.totalSupply(); - const supplyDiff = currentSupply.sub(newSupply); - - expect(supplyDiff).to.be.gte(redeemAmount); - }); + it("Should stake DAI in Curve gauge via metapool", async function () { + const { anna, dai } = fixture; + await mintTest(fixture, anna, dai, "110000"); }); }); @@ -115,6 +46,8 @@ describe("ForkTest: Convex 3pool/OUSD Meta Strategy - Balanced Metapool", functi const amount = "10000"; + const beforeMintBlock = await ethers.provider.getBlockNumber(); + // Mint with all three assets for (const asset of [usdt, usdc, dai]) { await vault @@ -124,13 +57,20 @@ describe("ForkTest: Convex 3pool/OUSD Meta Strategy - Balanced Metapool", functi await vault.connect(anna).allocate(); + log("After mints and allocate to strategy"); + await run("amoStrat", { + pool: "OUSD", + output: false, + fromBlock: beforeMintBlock, + }); + // we multiply it by 3 because 1/3 of balance is represented by each of the assets const strategyBalance = ( await OUSDmetaStrategy.checkBalance(dai.address) ).mul(3); - // min 1x 3crv + 1x printed OUSD: (10k + 10k) * (usdt + usdc) = 40k - await expect(strategyBalance).to.be.gte(ousdUnits("40000")); + // 3x 10k assets + 3x 10k OUSD = 60k + await expect(strategyBalance).to.be.gte(ousdUnits("59990")); // Total supply should be up by at least (10k x 2) + (10k x 2) + 10k = 50k const currentSupply = await ousd.totalSupply(); @@ -140,7 +80,7 @@ describe("ForkTest: Convex 3pool/OUSD Meta Strategy - Balanced Metapool", functi const currentBalance = await ousd.connect(anna).balanceOf(anna.address); // Now try to redeem the amount - const redeemAmount = ousdUnits("22000"); + const redeemAmount = ousdUnits("29990"); await vault.connect(anna).redeem(redeemAmount, 0); // User balance should be down by 30k diff --git a/contracts/test/strategies/ousd-metapool-ousd-tilted-pool.fork-test.js b/contracts/test/strategies/ousd-metapool-ousd-tilted-pool.fork-test.js index 6e5ff02b43..ad51913ae8 100644 --- a/contracts/test/strategies/ousd-metapool-ousd-tilted-pool.fork-test.js +++ b/contracts/test/strategies/ousd-metapool-ousd-tilted-pool.fork-test.js @@ -22,24 +22,12 @@ describe("ForkTest: Convex 3pool/OUSD Meta Strategy - Titled to OUSD", function await mintTest(fixture, josh, usdt, "100000"); }); - describe("Mint", function () { - it("Should stake USDT in Curve gauge via metapool", async function () { - const { josh, usdt } = fixture; - await mintTest(fixture, josh, usdt, "100000"); - }); - - it("Should stake USDC in Curve gauge via metapool", async function () { - const { matt, usdc } = fixture; - await mintTest(fixture, matt, usdc, "120000"); - }); - - it("Should stake DAI in Curve gauge via metapool", async function () { - const { anna, dai } = fixture; - await mintTest(fixture, anna, dai, "110000"); - }); + it("Should stake USDC in Curve gauge via metapool", async function () { + const { matt, usdc } = fixture; + await mintTest(fixture, matt, usdc, "120000"); }); - it("Should NOT stake DAI in Curve gauge via metapool", async function () { + it("Should stake DAI in Curve gauge via metapool", async function () { const { anna, dai } = fixture; await mintTest(fixture, anna, dai, "110000"); }); @@ -52,6 +40,9 @@ describe("ForkTest: Convex 3pool/OUSD Meta Strategy - Titled to OUSD", function await vault.connect(anna).allocate(); const supplyBeforeMint = await ousd.totalSupply(); + const strategyBalanceBeforeMint = ( + await OUSDmetaStrategy.checkBalance(dai.address) + ).mul(3); const amount = "10000"; @@ -68,22 +59,25 @@ describe("ForkTest: Convex 3pool/OUSD Meta Strategy - Titled to OUSD", function const strategyBalance = ( await OUSDmetaStrategy.checkBalance(dai.address) ).mul(3); + const strategyBalanceChange = strategyBalance.sub( + strategyBalanceBeforeMint + ); - // min 1x 3crv + 1x printed OUSD: (10k + 10k) * (usdt + usdc) = 40k - await expect(strategyBalance).to.be.gte(ousdUnits("40000")); + // min 1x 3crv + 1x printed OUSD: (10k + 10k + 10k) * (usdt + usdc + dai) = 60k + expect(strategyBalanceChange).to.be.gte(ousdUnits("59500")); - // Total supply should be up by at least (10k x 2) + (10k x 2) + 10k = 50k + // Total supply should be up by at least (10k x 2) + (10k x 2) + (10k x 2) = 60k const currentSupply = await ousd.totalSupply(); const supplyAdded = currentSupply.sub(supplyBeforeMint); - expect(supplyAdded).to.be.gte(ousdUnits("49995")); + expect(supplyAdded).to.be.gte(ousdUnits("59500")); const currentBalance = await ousd.connect(anna).balanceOf(anna.address); // Now try to redeem the amount - const redeemAmount = ousdUnits("19000"); + const redeemAmount = ousdUnits("10000"); await vault.connect(anna).redeem(redeemAmount, 0); - // User balance should be down by 30k + // User balance should be down by 10k const newBalance = await ousd.connect(anna).balanceOf(anna.address); expect(newBalance).to.approxEqualTolerance( currentBalance.sub(redeemAmount), diff --git a/contracts/test/vault/collateral-swaps.fork-test.js b/contracts/test/vault/collateral-swaps.fork-test.js index b5fec56fda..3be93f3758 100644 --- a/contracts/test/vault/collateral-swaps.fork-test.js +++ b/contracts/test/vault/collateral-swaps.fork-test.js @@ -14,7 +14,7 @@ const { resolveAsset } = require("../../utils/assets"); const log = require("../../utils/logger")("test:fork:swaps"); -describe("ForkTest: Collateral swaps", function () { +describe("ForkTest: OETH Vault", function () { this.timeout(0); // Retry up to 3 times on CI @@ -22,461 +22,459 @@ describe("ForkTest: Collateral swaps", function () { let fixture; - describe("OETH Vault", () => { - describe("post swap deployment", () => { - const loadFixture = createFixtureLoader(oethDefaultFixture); - beforeEach(async () => { - fixture = await loadFixture(); - }); - it("should have swapper set", async () => { - const { oethVault, swapper } = fixture; - - expect(await oethVault.swapper()).to.equal(swapper.address); - }); - it("assets should have allowed slippage", async () => { - const { oethVault, weth, reth, stETH, frxETH } = fixture; - - const assets = [weth, stETH, reth, frxETH]; - const expectedConversions = [0, 0, 1, 0]; - const expectedSlippage = [20, 70, 200, 20]; - - for (let i = 0; i < assets.length; i++) { - const config = await oethVault.getAssetConfig(assets[i].address); - - expect(config.decimals, `decimals ${i}`).to.equal(18); - expect(config.isSupported, `isSupported ${i}`).to.be.true; - expect(config.unitConversion, `unitConversion ${i}`).to.be.equal( - expectedConversions[i] - ); - expect( - config.allowedOracleSlippageBps, - `allowedOracleSlippageBps ${i}` - ).to.equal(expectedSlippage[i]); - } - }); + describe("post swap deployment", () => { + const loadFixture = createFixtureLoader(oethDefaultFixture); + beforeEach(async () => { + fixture = await loadFixture(); }); + it("should have swapper set", async () => { + const { oethVault, swapper } = fixture; - describe("Collateral swaps", async () => { - const loadFixture = createFixtureLoader(oethCollateralSwapFixture); - beforeEach(async () => { - fixture = await loadFixture(); - }); - - const tests = [ - { - from: "WETH", - to: "rETH", - fromAmount: 100, - minToAssetAmount: 90, - }, - { - from: "WETH", - to: "stETH", - fromAmount: 100, - minToAssetAmount: 99.96, - }, - { - from: "WETH", - to: "frxETH", - fromAmount: 100, - minToAssetAmount: 100, - }, - { - from: "rETH", - to: "stETH", - fromAmount: 10, - minToAssetAmount: "10.73", - slippage: 0.1, - }, - { - from: "rETH", - to: "frxETH", - fromAmount: 10, - minToAssetAmount: 10.7, - slippage: 0.1, - }, - { - from: "rETH", - to: "WETH", - fromAmount: 10, - minToAssetAmount: "10.7", - slippage: 0.3, - }, - { - from: "stETH", - to: "rETH", - fromAmount: 400, - minToAssetAmount: 350, - approxFromBalance: true, - }, - { - from: "stETH", - to: "frxETH", - fromAmount: 400, - minToAssetAmount: 399.1, - approxFromBalance: true, - }, - { - from: "stETH", - to: "WETH", - fromAmount: 750, - minToAssetAmount: 749.1, - approxFromBalance: true, - }, - { - from: "frxETH", - to: "rETH", - fromAmount: 25, - minToAssetAmount: 21, - }, - { - from: "frxETH", - to: "stETH", - fromAmount: 25, - minToAssetAmount: 24.9, - }, - { - from: "frxETH", - to: "WETH", - fromAmount: 25, - minToAssetAmount: 24.9, - }, - { - from: "WETH", - to: "stETH", - fromAmount: 1, - minToAssetAmount: 0.9, - protocols: "UNISWAP_V2", - }, - { - from: "WETH", - to: "frxETH", - fromAmount: 10, - minToAssetAmount: 9.96, - protocols: "UNISWAP_V3", - }, - { - from: "WETH", - to: "frxETH", - fromAmount: 100, - minToAssetAmount: 100, - protocols: "CURVE,CURVE_V2", - }, - { - from: "WETH", - to: "stETH", - fromAmount: 100, - minToAssetAmount: 99.999, - protocols: "ST_ETH", - }, - { - from: "stETH", - to: "frxETH", - fromAmount: 750, - minToAssetAmount: 749.2, - protocols: "ST_ETH,CURVE,CURVE_V2,MAVERICK_V1", - approxFromBalance: true, - }, - { - from: "rETH", - to: "frxETH", - fromAmount: 100, - minToAssetAmount: 107.2, - protocols: - "BALANCER,BALANCER_V2,BALANCER_V2_WRAPPER,CURVE,CURVE_V2,MAVERICK_V1", - }, - ]; - for (const test of tests) { - it(`should be able to swap ${test.fromAmount} ${ - test.from - } for a min of ${test.minToAssetAmount} ${test.to} using ${ - test.protocols || "all" - } protocols`, async () => { - const fromAsset = await resolveAsset(test.from); - const toAsset = await resolveAsset(test.to); - await assertSwap( - { - ...test, - fromAsset, - toAsset, - vault: fixture.oethVault, - }, - fixture - ); - }); + expect(await oethVault.swapper()).to.equal(swapper.address); + }); + it("assets should have allowed slippage", async () => { + const { oethVault, weth, reth, stETH, frxETH } = fixture; + + const assets = [weth, stETH, reth, frxETH]; + const expectedConversions = [0, 0, 1, 0]; + const expectedSlippage = [20, 70, 200, 20]; + + for (let i = 0; i < assets.length; i++) { + const config = await oethVault.getAssetConfig(assets[i].address); + + expect(config.decimals, `decimals ${i}`).to.equal(18); + expect(config.isSupported, `isSupported ${i}`).to.be.true; + expect(config.unitConversion, `unitConversion ${i}`).to.be.equal( + expectedConversions[i] + ); + expect( + config.allowedOracleSlippageBps, + `allowedOracleSlippageBps ${i}` + ).to.equal(expectedSlippage[i]); } }); + }); - describe("Collateral swaps", async () => { - const loadFixture = createFixtureLoader(oethCollateralSwapFixture); - beforeEach(async () => { - fixture = await loadFixture(); + describe("Collateral swaps (Happy paths)", async () => { + const loadFixture = createFixtureLoader(oethCollateralSwapFixture); + beforeEach(async () => { + fixture = await loadFixture(); + }); + + const tests = [ + { + from: "WETH", + to: "rETH", + fromAmount: 100, + minToAssetAmount: 90, + }, + { + from: "WETH", + to: "stETH", + fromAmount: 100, + minToAssetAmount: 99.96, + }, + { + from: "WETH", + to: "frxETH", + fromAmount: 100, + minToAssetAmount: 100, + }, + { + from: "rETH", + to: "stETH", + fromAmount: 10, + minToAssetAmount: "10.73", + slippage: 0.1, + }, + { + from: "rETH", + to: "frxETH", + fromAmount: 10, + minToAssetAmount: 10.7, + slippage: 0.1, + }, + { + from: "rETH", + to: "WETH", + fromAmount: 10, + minToAssetAmount: "10.7", + slippage: 0.3, + }, + { + from: "stETH", + to: "rETH", + fromAmount: 400, + minToAssetAmount: 350, + approxFromBalance: true, + }, + { + from: "stETH", + to: "frxETH", + fromAmount: 400, + minToAssetAmount: 399.1, + approxFromBalance: true, + }, + { + from: "stETH", + to: "WETH", + fromAmount: 750, + minToAssetAmount: 749.1, + approxFromBalance: true, + }, + { + from: "frxETH", + to: "rETH", + fromAmount: 25, + minToAssetAmount: 21, + }, + { + from: "frxETH", + to: "stETH", + fromAmount: 25, + minToAssetAmount: 24.9, + }, + { + from: "frxETH", + to: "WETH", + fromAmount: 25, + minToAssetAmount: 24.9, + }, + { + from: "WETH", + to: "stETH", + fromAmount: 1, + minToAssetAmount: 0.9, + protocols: "UNISWAP_V2", + }, + { + from: "WETH", + to: "frxETH", + fromAmount: 10, + minToAssetAmount: 9.96, + protocols: "UNISWAP_V3", + }, + { + from: "WETH", + to: "frxETH", + fromAmount: 100, + minToAssetAmount: 100, + protocols: "CURVE,CURVE_V2", + }, + { + from: "WETH", + to: "stETH", + fromAmount: 100, + minToAssetAmount: 99.999, + protocols: "ST_ETH", + }, + { + from: "stETH", + to: "frxETH", + fromAmount: 750, + minToAssetAmount: 749.2, + protocols: "ST_ETH,CURVE,CURVE_V2,MAVERICK_V1", + approxFromBalance: true, + }, + { + from: "rETH", + to: "frxETH", + fromAmount: 100, + minToAssetAmount: 107.2, + protocols: + "BALANCER,BALANCER_V2,BALANCER_V2_WRAPPER,CURVE,CURVE_V2,MAVERICK_V1", + }, + ]; + for (const test of tests) { + it(`should be able to swap ${test.fromAmount} ${test.from} for a min of ${ + test.minToAssetAmount + } ${test.to} using ${test.protocols || "all"} protocols`, async () => { + const fromAsset = await resolveAsset(test.from); + const toAsset = await resolveAsset(test.to); + await assertSwap( + { + ...test, + fromAsset, + toAsset, + vault: fixture.oethVault, + }, + fixture + ); }); + } + }); - const tests = [ - { - error: "", - from: "WETH", - to: "frxETH", - fromAmount: 100, - minToAssetAmount: 105, - }, - { - error: "", - from: "WETH", - to: "stETH", - fromAmount: 100, - minToAssetAmount: 90, - protocols: "UNISWAP_V3", - }, - { - error: "Oracle slippage limit exceeded", - from: "WETH", - to: "stETH", - fromAmount: 100, - minToAssetAmount: 90, - protocols: "UNISWAP_V2", - }, - { - error: "To asset is not supported", - from: "WETH", - to: "USDT", - fromAmount: 20, - minToAssetAmount: 1, - }, - { - error: "ERC20: transfer amount exceeds balance", - from: "frxETH", - to: "WETH", - fromAmount: 50000, - minToAssetAmount: 49000, - }, - { - error: "SafeERC20: low-level call failed", - from: "WETH", - to: "frxETH", - fromAmount: 30000, - minToAssetAmount: 29900, - }, - { - error: "BALANCE_EXCEEDED", - from: "stETH", - to: "WETH", - fromAmount: 10000, - minToAssetAmount: 9900, - }, - { - error: "ERC20: transfer amount exceeds balance", - from: "rETH", - to: "WETH", - fromAmount: 10000, - minToAssetAmount: 9900, - }, - ]; - - for (const test of tests) { - it(`should fail to swap ${test.fromAmount} ${test.from} for ${ - test.to - } using ${test.protocols || "all"} protocols: error ${ - test.error - }`, async () => { - const fromAsset = await resolveAsset(test.from); - const toAsset = await resolveAsset(test.to); - await assertFailedSwap( - { - ...test, - fromAsset, - toAsset, - vault: fixture.oethVault, - }, - fixture - ); - }); - } + describe("Collateral swaps (Unhappy paths)", async () => { + const loadFixture = createFixtureLoader(oethCollateralSwapFixture); + beforeEach(async () => { + fixture = await loadFixture(); }); - }); - describe("OUSD Vault", () => { - describe("post deployment", () => { - const loadFixture = createFixtureLoader(defaultFixture); - beforeEach(async () => { - fixture = await loadFixture(); + const tests = [ + { + error: "", + from: "WETH", + to: "frxETH", + fromAmount: 100, + minToAssetAmount: 105, + }, + { + error: "", + from: "WETH", + to: "stETH", + fromAmount: 100, + minToAssetAmount: 90, + protocols: "UNISWAP_V3", + }, + { + error: "Oracle slippage limit exceeded", + from: "WETH", + to: "stETH", + fromAmount: 100, + minToAssetAmount: 90, + protocols: "UNISWAP_V2", + }, + { + error: "To asset is not supported", + from: "WETH", + to: "USDT", + fromAmount: 20, + minToAssetAmount: 1, + }, + { + error: "ERC20: transfer amount exceeds balance", + from: "frxETH", + to: "WETH", + fromAmount: 50000, + minToAssetAmount: 49000, + }, + { + error: "SafeERC20: low-level call failed", + from: "WETH", + to: "frxETH", + fromAmount: 30000, + minToAssetAmount: 29900, + }, + { + error: "BALANCE_EXCEEDED", + from: "stETH", + to: "WETH", + fromAmount: 10000, + minToAssetAmount: 9900, + }, + { + error: "ERC20: transfer amount exceeds balance", + from: "rETH", + to: "WETH", + fromAmount: 10000, + minToAssetAmount: 9900, + }, + ]; + + for (const test of tests) { + it(`should fail to swap ${test.fromAmount} ${test.from} for ${ + test.to + } using ${test.protocols || "all"} protocols: error ${ + test.error + }`, async () => { + const fromAsset = await resolveAsset(test.from); + const toAsset = await resolveAsset(test.to); + await assertFailedSwap( + { + ...test, + fromAsset, + toAsset, + vault: fixture.oethVault, + }, + fixture + ); }); + } + }); +}); - it("should have swapper set", async () => { - const { vault, swapper } = fixture; +describe("ForkTest: OUSD Vault", function () { + this.timeout(0); - expect(await vault.swapper()).to.equal(swapper.address); - }); - it("assets should have allowed slippage", async () => { - const { vault, dai, usdc, usdt } = fixture; - - const assets = [dai, usdc, usdt]; - const expectedDecimals = [18, 6, 6]; - const expectedConversions = [0, 0, 0]; - const expectedSlippage = [25, 25, 25]; - - for (let i = 0; i < assets.length; i++) { - const config = await vault.getAssetConfig(assets[i].address); - - expect(config.decimals, `decimals ${i}`).to.equal( - expectedDecimals[i] - ); - expect(config.isSupported, `isSupported ${i}`).to.be.true; - expect(config.unitConversion, `unitConversion ${i}`).to.be.equal( - expectedConversions[i] - ); - expect( - config.allowedOracleSlippageBps, - `allowedOracleSlippageBps ${i}` - ).to.equal(expectedSlippage[i]); - } - }); + // Retry up to 3 times on CI + this.retries(isCI ? 3 : 0); + + let fixture; + + describe("post deployment", () => { + const loadFixture = createFixtureLoader(defaultFixture); + beforeEach(async () => { + fixture = await loadFixture(); }); - describe("Collateral swaps", async () => { - const loadFixture = createFixtureLoader(ousdCollateralSwapFixture); - beforeEach(async () => { - fixture = await loadFixture(); - }); + it("should have swapper set", async () => { + const { vault, swapper } = fixture; - const tests = [ - { - from: "DAI", - to: "USDT", - fromAmount: 1000000, - minToAssetAmount: 990000, - }, - { - from: "DAI", - to: "USDC", - fromAmount: 1000000, - minToAssetAmount: 999900, - slippage: 0.1, // Max 1Inch slippage - }, - { - from: "USDT", - to: "DAI", - fromAmount: 1000000, - minToAssetAmount: 998000, - }, - { - from: "USDT", - to: "USDC", - fromAmount: 1000000, - minToAssetAmount: 998000, - }, - { - from: "USDC", - to: "DAI", - fromAmount: 1000000, - minToAssetAmount: 999900, - slippage: 0.05, // Max 1Inch slippage - }, - { - from: "USDC", - to: "USDT", - fromAmount: 1000000, - minToAssetAmount: "990000", - slippage: 0.02, - approxFromBalance: true, - }, - ]; - for (const test of tests) { - it(`should be able to swap ${test.fromAmount} ${ - test.from - } for a min of ${test.minToAssetAmount} ${test.to} using ${ - test.protocols || "all" - } protocols`, async () => { - const fromAsset = await resolveAsset(test.from); - const toAsset = await resolveAsset(test.to); - await assertSwap( - { - ...test, - fromAsset, - toAsset, - vault: fixture.vault, - }, - fixture - ); - }); + expect(await vault.swapper()).to.equal(swapper.address); + }); + it("assets should have allowed slippage", async () => { + const { vault, dai, usdc, usdt } = fixture; + + const assets = [dai, usdc, usdt]; + const expectedDecimals = [18, 6, 6]; + const expectedConversions = [0, 0, 0]; + const expectedSlippage = [25, 25, 25]; + + for (let i = 0; i < assets.length; i++) { + const config = await vault.getAssetConfig(assets[i].address); + + expect(config.decimals, `decimals ${i}`).to.equal(expectedDecimals[i]); + expect(config.isSupported, `isSupported ${i}`).to.be.true; + expect(config.unitConversion, `unitConversion ${i}`).to.be.equal( + expectedConversions[i] + ); + expect( + config.allowedOracleSlippageBps, + `allowedOracleSlippageBps ${i}` + ).to.equal(expectedSlippage[i]); } }); + }); + + describe("Collateral swaps (Happy paths)", async () => { + const loadFixture = createFixtureLoader(ousdCollateralSwapFixture); + beforeEach(async () => { + fixture = await loadFixture(); + }); - describe("Collateral swaps", async () => { - const loadFixture = createFixtureLoader(ousdCollateralSwapFixture); - beforeEach(async () => { - fixture = await loadFixture(); + const tests = [ + { + from: "DAI", + to: "USDT", + fromAmount: 1000000, + minToAssetAmount: 990000, + }, + { + from: "DAI", + to: "USDC", + fromAmount: 1000000, + minToAssetAmount: 999900, + slippage: 0.1, // Max 1Inch slippage + }, + { + from: "USDT", + to: "DAI", + fromAmount: 1000000, + minToAssetAmount: 998000, + }, + { + from: "USDT", + to: "USDC", + fromAmount: 1000000, + minToAssetAmount: 998000, + }, + { + from: "USDC", + to: "DAI", + fromAmount: 1000000, + minToAssetAmount: 999900, + slippage: 0.05, // Max 1Inch slippage + }, + { + from: "USDC", + to: "USDT", + fromAmount: 1000000, + minToAssetAmount: "990000", + slippage: 0.02, + approxFromBalance: true, + }, + ]; + for (const test of tests) { + it(`should be able to swap ${test.fromAmount} ${test.from} for a min of ${ + test.minToAssetAmount + } ${test.to} using ${test.protocols || "all"} protocols`, async () => { + const fromAsset = await resolveAsset(test.from); + const toAsset = await resolveAsset(test.to); + await assertSwap( + { + ...test, + fromAsset, + toAsset, + vault: fixture.vault, + }, + fixture + ); }); + } + }); - const tests = [ - { - error: "", - from: "DAI", - to: "USDC", - fromAmount: 100, - minToAssetAmount: 105, - }, - { - error: "From asset is not supported", - from: "WETH", - to: "USDT", - fromAmount: 20, - minToAssetAmount: 1, - }, - { - error: "To asset is not supported", - from: "DAI", - to: "WETH", - fromAmount: 20, - minToAssetAmount: 1, - }, - { - error: "Dai/insufficient-balance", - from: "DAI", - to: "USDC", - fromAmount: 30000000, - minToAssetAmount: 29000000, - }, - { - error: "SafeERC20: low-level call failed", - from: "USDT", - to: "USDC", - fromAmount: 50000000, - minToAssetAmount: 49900000, - }, - { - error: "ERC20: transfer amount exceeds balance", - from: "USDC", - to: "DAI", - fromAmount: 30000000, - minToAssetAmount: 29900000, - }, - ]; - - for (const test of tests) { - it(`should fail to swap ${test.fromAmount} ${test.from} for ${ - test.to - } using ${test.protocols || "all"} protocols: error ${ - test.error - }`, async () => { - const fromAsset = await resolveAsset(test.from); - const toAsset = await resolveAsset(test.to); - await assertFailedSwap( - { - ...test, - fromAsset, - toAsset, - vault: fixture.vault, - }, - fixture - ); - }); - } + describe("Collateral swaps (Unhappy paths)", async () => { + const loadFixture = createFixtureLoader(ousdCollateralSwapFixture); + beforeEach(async () => { + fixture = await loadFixture(); }); + + const tests = [ + { + error: "", + from: "DAI", + to: "USDC", + fromAmount: 100, + minToAssetAmount: 105, + }, + { + error: "From asset is not supported", + from: "WETH", + to: "USDT", + fromAmount: 20, + minToAssetAmount: 1, + }, + { + error: "To asset is not supported", + from: "DAI", + to: "WETH", + fromAmount: 20, + minToAssetAmount: 1, + }, + { + error: "Dai/insufficient-balance", + from: "DAI", + to: "USDC", + fromAmount: 30000000, + minToAssetAmount: 29000000, + }, + { + error: "SafeERC20: low-level call failed", + from: "USDT", + to: "USDC", + fromAmount: 50000000, + minToAssetAmount: 49900000, + }, + { + error: "ERC20: transfer amount exceeds balance", + from: "USDC", + to: "DAI", + fromAmount: 30000000, + minToAssetAmount: 29900000, + }, + ]; + + for (const test of tests) { + it(`should fail to swap ${test.fromAmount} ${test.from} for ${ + test.to + } using ${test.protocols || "all"} protocols: error ${ + test.error + }`, async () => { + const fromAsset = await resolveAsset(test.from); + const toAsset = await resolveAsset(test.to); + await assertFailedSwap( + { + ...test, + fromAsset, + toAsset, + vault: fixture.vault, + }, + fixture + ); + }); + } }); }); - const assertSwap = async ( { fromAsset, diff --git a/contracts/test/vault/oeth-vault.fork-test.js b/contracts/test/vault/oeth-vault.fork-test.js index 40d99c6a7c..7ea1b103d6 100644 --- a/contracts/test/vault/oeth-vault.fork-test.js +++ b/contracts/test/vault/oeth-vault.fork-test.js @@ -18,143 +18,140 @@ describe("ForkTest: OETH Vault", function () { let fixture; - describe("OETH Vault", () => { - describe("post deployment", () => { - const loadFixture = createFixtureLoader(oethDefaultFixture); - beforeEach(async () => { - fixture = await loadFixture(); - }); - - it("Should have the correct governor address set", async () => { - const { - oethVault, - oethDripper, - convexEthMetaStrategy, - fraxEthStrategy, - oeth, - woeth, - oethHarvester, - } = fixture; - - const oethContracts = [ - oethVault, - oethDripper, - convexEthMetaStrategy, - fraxEthStrategy, - oeth, - woeth, - oethHarvester, - ]; - - for (let i = 0; i < oethContracts.length; i++) { - expect(await oethContracts[i].governor()).to.equal( - addresses.mainnet.Timelock - ); - } - }); + describe("post deployment", () => { + const loadFixture = createFixtureLoader(oethDefaultFixture); + beforeEach(async () => { + fixture = await loadFixture(); + }); + + it("Should have the correct governor address set", async () => { + const { + oethVault, + oethDripper, + convexEthMetaStrategy, + fraxEthStrategy, + oeth, + woeth, + oethHarvester, + } = fixture; + + const oethContracts = [ + oethVault, + oethDripper, + convexEthMetaStrategy, + fraxEthStrategy, + oeth, + woeth, + oethHarvester, + ]; + + for (let i = 0; i < oethContracts.length; i++) { + expect(await oethContracts[i].governor()).to.equal( + addresses.mainnet.Timelock + ); + } }); - describe("user operations", () => { - let oethWhaleSigner; - const loadFixture = createFixtureLoader(oethDefaultFixture); - beforeEach(async () => { - fixture = await loadFixture(); - - oethWhaleSigner = await impersonateAndFund(oethWhaleAddress); - }); - - it("should mint using each asset", async () => { - const { oethVault, oethOracleRouter, weth, frxETH, stETH, reth, josh } = - fixture; - - const amount = parseUnits("1", 18); - const minOeth = parseUnits("0.8", 18); - - for (const asset of [weth, frxETH, stETH, reth]) { - await asset.connect(josh).approve(oethVault.address, amount); - - const price = await oethOracleRouter.price(asset.address); - if (price.gt(parseUnits("0.998"))) { - const tx = await oethVault - .connect(josh) - .mint(asset.address, amount, minOeth); - - if (asset === weth) { - await expect(tx) - .to.emit(oethVault, "Mint") - .withArgs(josh.address, amount); - } else { - // Oracle price means 1 asset != 1 OETH - await expect(tx) - .to.emit(oethVault, "Mint") - .withNamedArgs({ _addr: josh.address }); - } + }); + + describe("user operations", () => { + let oethWhaleSigner; + const loadFixture = createFixtureLoader(oethDefaultFixture); + beforeEach(async () => { + fixture = await loadFixture(); + + oethWhaleSigner = await impersonateAndFund(oethWhaleAddress); + }); + + it("should mint using each asset", async () => { + const { oethVault, oethOracleRouter, weth, frxETH, stETH, reth, josh } = + fixture; + + const amount = parseUnits("1", 18); + const minOeth = parseUnits("0.8", 18); + + for (const asset of [weth, frxETH, stETH, reth]) { + await asset.connect(josh).approve(oethVault.address, amount); + + const price = await oethOracleRouter.price(asset.address); + if (price.gt(parseUnits("0.998"))) { + const tx = await oethVault + .connect(josh) + .mint(asset.address, amount, minOeth); + + if (asset === weth) { + await expect(tx) + .to.emit(oethVault, "Mint") + .withArgs(josh.address, amount); } else { - const tx = oethVault - .connect(josh) - .mint(asset.address, amount, minOeth); - await expect(tx).to.revertedWith("Asset price below peg"); + // Oracle price means 1 asset != 1 OETH + await expect(tx) + .to.emit(oethVault, "Mint") + .withNamedArgs({ _addr: josh.address }); } + } else { + const tx = oethVault + .connect(josh) + .mint(asset.address, amount, minOeth); + await expect(tx).to.revertedWith("Asset price below peg"); } - }); - it("should partially redeem", async () => { - const { oeth, oethVault } = fixture; - - expect(await oeth.balanceOf(oethWhaleAddress)).to.gt(10); - - const amount = parseUnits("10", 18); - const minEth = parseUnits("9.94", 18); - - const tx = await oethVault - .connect(oethWhaleSigner) - .redeem(amount, minEth); - await expect(tx) - .to.emit(oethVault, "Redeem") - .withNamedArgs({ _addr: oethWhaleAddress }); - }); - it("OETH whale can not full redeem due to liquidity", async () => { - const { oeth, oethVault } = fixture; - - const oethWhaleBalance = await oeth.balanceOf(oethWhaleAddress); - expect(oethWhaleBalance, "no longer an OETH whale").to.gt( - parseUnits("100", 18) - ); + } + }); + it("should partially redeem", async () => { + const { oeth, oethVault } = fixture; - const tx = oethVault - .connect(oethWhaleSigner) - .redeem(oethWhaleBalance, 0); - await expect(tx).to.revertedWith("Liquidity error"); - }); - it("OETH whale can redeem after withdraw from all strategies", async () => { - const { oeth, oethVault, timelock } = fixture; - - const oethWhaleBalance = await oeth.balanceOf(oethWhaleAddress); - log(`OETH whale balance: ${formatUnits(oethWhaleBalance)}`); - expect(oethWhaleBalance, "no longer an OETH whale").to.gt( - parseUnits("1000", 18) - ); + expect(await oeth.balanceOf(oethWhaleAddress)).to.gt(10); + + const amount = parseUnits("10", 18); + const minEth = parseUnits("9.94", 18); - await oethVault.connect(timelock).withdrawAllFromStrategies(); - - const tx = await oethVault - .connect(oethWhaleSigner) - .redeem(oethWhaleBalance, 0); - await expect(tx) - .to.emit(oethVault, "Redeem") - .withNamedArgs({ _addr: oethWhaleAddress }); - }); - it("OETH whale redeem 100 OETH", async () => { - const { oethVault } = fixture; - - const amount = parseUnits("100", 18); - const minEth = parseUnits("99.4", 18); - - const tx = await oethVault - .connect(oethWhaleSigner) - .redeem(amount, minEth); - await expect(tx) - .to.emit(oethVault, "Redeem") - .withNamedArgs({ _addr: oethWhaleAddress }); - }); + const tx = await oethVault + .connect(oethWhaleSigner) + .redeem(amount, minEth); + await expect(tx) + .to.emit(oethVault, "Redeem") + .withNamedArgs({ _addr: oethWhaleAddress }); + }); + it("OETH whale can not full redeem due to liquidity", async () => { + const { oeth, oethVault } = fixture; + + const oethWhaleBalance = await oeth.balanceOf(oethWhaleAddress); + expect(oethWhaleBalance, "no longer an OETH whale").to.gt( + parseUnits("100", 18) + ); + + const tx = oethVault.connect(oethWhaleSigner).redeem(oethWhaleBalance, 0); + await expect(tx).to.revertedWith("Liquidity error"); + }); + it("OETH whale can redeem after withdraw from all strategies", async () => { + const { oeth, oethVault, timelock } = fixture; + + const oethWhaleBalance = await oeth.balanceOf(oethWhaleAddress); + log(`OETH whale balance: ${formatUnits(oethWhaleBalance)}`); + expect(oethWhaleBalance, "no longer an OETH whale").to.gt( + parseUnits("1000", 18) + ); + + await oethVault.connect(timelock).withdrawAllFromStrategies(); + + const tx = await oethVault + .connect(oethWhaleSigner) + .redeem(oethWhaleBalance, 0); + await expect(tx) + .to.emit(oethVault, "Redeem") + .withNamedArgs({ _addr: oethWhaleAddress }); + }); + it("OETH whale redeem 100 OETH", async () => { + const { oethVault } = fixture; + + const amount = parseUnits("100", 18); + const minEth = parseUnits("99.4", 18); + + const tx = await oethVault + .connect(oethWhaleSigner) + .redeem(amount, minEth); + await expect(tx) + .to.emit(oethVault, "Redeem") + .withNamedArgs({ _addr: oethWhaleAddress }); }); }); }); From 1e30d195aa5afbfcbca0f824d8c398afb910f439 Mon Sep 17 00:00:00 2001 From: Shahul Hameed <10547529+shahthepro@users.noreply.github.com> Date: Tue, 21 Nov 2023 18:16:52 +0530 Subject: [PATCH 07/13] Update runlog (#1939) --- brownie/runlogs/2023_11_strategist.py | 26 +++++++++++++++++++++++++- 1 file changed, 25 insertions(+), 1 deletion(-) diff --git a/brownie/runlogs/2023_11_strategist.py b/brownie/runlogs/2023_11_strategist.py index 5c0a4c7bef..8548ea0241 100644 --- a/brownie/runlogs/2023_11_strategist.py +++ b/brownie/runlogs/2023_11_strategist.py @@ -76,4 +76,28 @@ def main(): profit = vault_change - supply_change txs.append(vault_value_checker.checkDelta(profit, (500 * 10**18), vault_change, (500 * 10**18), {'from': STRATEGIST})) - txs.append(vault_core.allocate({'from': STRATEGIST})) \ No newline at end of file + txs.append(vault_core.allocate({'from': STRATEGIST})) + +# ------------------------------------- +# Nov 20, 2023 - OETH AMO Burn +# ------------------------------------- +from world import * + +def main(): + with TemporaryForkForReallocations() as txs: + # Before + txs.append(vault_oeth_core.rebase(std)) + txs.append(oeth_vault_value_checker.takeSnapshot(std)) + + # Remove 6522 LP Tokens (~6537 OETH) + txs.append(oeth_meta_strat.removeAndBurnOTokens(6522 * 1e18, std)) + + # After + vault_change = vault_oeth_core.totalValue() - oeth_vault_value_checker.snapshots(STRATEGIST)[0] + supply_change = oeth.totalSupply() - oeth_vault_value_checker.snapshots(STRATEGIST)[1] + profit = vault_change - supply_change + txs.append(oeth_vault_value_checker.checkDelta(profit, (0.1 * 10**18), vault_change, (0.1 * 10**18), std)) + print("-----") + print("Profit", "{:.6f}".format(profit / 10**18), profit) + print("Vault Change", "{:.6f}".format(vault_change / 10**18), vault_change) + print("-----") \ No newline at end of file From 84be238d577a37acd492f0962ec8e338c3746cb4 Mon Sep 17 00:00:00 2001 From: Domen Grabec Date: Wed, 22 Nov 2023 08:53:25 +0100 Subject: [PATCH 08/13] add strategist run for the oeth amo (#1940) * add strategist run for the oeth amo * Add buyback runlog --------- Co-authored-by: Shahul Hameed <10547529+shahthepro@users.noreply.github.com> --- brownie/brownie-config.yml | 1 + brownie/buyback.py | 4 +- brownie/collateralSwap.py | 60 +++++++++------------ brownie/oneinch.py | 63 +++++++++++++--------- brownie/prices.py | 2 +- brownie/runlogs/2023_11_strategist.py | 78 ++++++++++++++++++++++++++- 6 files changed, 144 insertions(+), 64 deletions(-) create mode 100644 brownie/brownie-config.yml diff --git a/brownie/brownie-config.yml b/brownie/brownie-config.yml new file mode 100644 index 0000000000..9fe320e424 --- /dev/null +++ b/brownie/brownie-config.yml @@ -0,0 +1 @@ +dotenv: .env diff --git a/brownie/buyback.py b/brownie/buyback.py index bd95a0cc78..e17de763bd 100644 --- a/brownie/buyback.py +++ b/brownie/buyback.py @@ -57,8 +57,8 @@ def get_token_quote(otoken, to_token, amount): price_before = parse_uniswap_x96_price(prices_before[len(prices_before) - 1]) no_slippage_quote = max([ - get_coingecko_quote(otoken, to_token, min_amount), - get_cmc_quote(otoken, to_token, min_amount), + # get_coingecko_quote(otoken, to_token, min_amount), + # get_cmc_quote(otoken, to_token, min_amount), uni_quote ]) no_slippage_quote = amount * no_slippage_quote / min_amount diff --git a/brownie/collateralSwap.py b/brownie/collateralSwap.py index 2c7f3afa50..60cd921fd6 100644 --- a/brownie/collateralSwap.py +++ b/brownie/collateralSwap.py @@ -38,29 +38,18 @@ def get_1inch_swap( UNISWAP_SELECTOR = "0xf78dc253" #unoswapTo(address,address,uint256,uint256,uint256[]) UNISWAPV3_SWAP_TO_SELECTOR = "0xbc80f1a8" #uniswapV3SwapTo(address,uint256,uint256,uint256[]) + result = get_1inch_swap_data( + from_token=from_token.lower(), + to_token=to_token.lower(), + swap_amount=from_amount, + slippage=slippage, + from_address=swapper_address.lower(), + to_address=vault_addr.lower(), + ) - req = requests.get('https://{}.1inch.io/v5.0/1/swap'.format(ONEINCH_SUBDOMAIN), params={ - 'fromTokenAddress': from_token.lower(), - 'fromAddress': swapper_address.lower(), - 'destReceiver': vault_addr.lower(), - 'toTokenAddress': to_token.lower(), - 'amount': str(from_amount), - 'allowPartialFill': allowPartialFill, - 'disableEstimate': 'true', - 'slippage': slippage - }, headers={ - 'accept': 'application/json' - }) - - if req.status_code != 200: - print(req.json()) - raise Exception("Error calling 1inch api") - - result = req.json() - - input_decoded = router_1inch.decode_input(result['tx']['data']) + input_decoded = router_1inch.decode_input(result.input) - selector = result['tx']['data'][:10] + selector = result.input[:10] data = '0x' # Swap selector if selector == SWAP_SELECTOR: @@ -72,9 +61,9 @@ def get_1inch_swap( raise Exception("Unrecognized 1Inch swap selector {}".format(selector)) swap_collateral_data = c_vault_core.swapCollateral.encode_input( - result['fromToken']['address'], - result['toToken']['address'], - result['fromTokenAmount'], + from_token.lower(), + to_token.lower(), + str(from_amount), min_expected_amount, data ) @@ -130,19 +119,20 @@ def build_swap_tx(from_token, to_token, from_amount, max_slippage, allow_partial if COINMARKETCAP_API_KEY is None: raise Exception("Set coinmarketcap api key by setting CMC_API_KEY variable. Free plan key will suffice: https://coinmarketcap.com/api/pricing/") + c_vault_core = vault_core if from_token in OUSD_ASSET_ADDRESSES else oeth_vault_core min_slippage_amount = scale_amount(WETH, from_token, 10**18) # 1 token of from_token (like 1WETH, 1DAI or 1USDT) quote_1inch = get_1inch_quote(from_token, to_token, from_amount) quote_1inch_min_swap_amount_price = get_1inch_quote(from_token, to_token, min_slippage_amount) quote_1inch_min_swap_amount = from_amount * quote_1inch_min_swap_amount_price / min_slippage_amount quote_oracles = get_oracle_router_quote(from_token, to_token, from_amount) - quote_coingecko = get_coingecko_quote(from_token, to_token, from_amount) - quote_cmc = get_cmc_quote(from_token, to_token, from_amount) + # quote_coingecko = get_coingecko_quote(from_token, to_token, from_amount) + # quote_cmc = get_cmc_quote(from_token, to_token, from_amount) # subtract the max slippage from minimum slippage query min_tokens_with_slippage = scale_amount(from_token, to_token, from_amount * quote_1inch_min_swap_amount_price * (100 - max_slippage) / 100 / min_slippage_amount) - coingecko_to_1inch_diff = (quote_1inch - quote_coingecko) / quote_1inch + # coingecko_to_1inch_diff = (quote_1inch - quote_coingecko) / quote_1inch oracle_to_1inch_diff = (quote_1inch - quote_oracles) / quote_1inch - cmc_to_1inch_diff = (quote_1inch - quote_cmc) / quote_1inch + # cmc_to_1inch_diff = (quote_1inch - quote_cmc) / quote_1inch actual_slippage = (quote_1inch_min_swap_amount - quote_1inch) / quote_1inch @@ -150,14 +140,14 @@ def build_swap_tx(from_token, to_token, from_amount, max_slippage, allow_partial print("1Inch expected tokens: {:.6f}".format(scale_amount(to_token, 'human', quote_1inch))) print("1Inch expected tokens (no slippage): {:.6f}".format(scale_amount(to_token, 'human', quote_1inch_min_swap_amount))) print("Oracle expected tokens: {:.6f}".format(scale_amount(to_token, 'human', quote_oracles))) - print("Coingecko expected tokens: {:.6f}".format(scale_amount(to_token, 'human', quote_coingecko))) - print("CoinmarketCap expected tokens: {:.6f}".format(scale_amount(to_token, 'human', quote_cmc))) + # print("Coingecko expected tokens: {:.6f}".format(scale_amount(to_token, 'human', quote_coingecko))) + # print("CoinmarketCap expected tokens: {:.6f}".format(scale_amount(to_token, 'human', quote_cmc))) print("Tokens expected (with {:.2f}% slippage) {:.6f}".format(max_slippage, scale_amount(to_token, 'human', min_tokens_with_slippage))) print("") print("------ Price Diffs -------") print("1Inch to Oracle Difference: {:.6f}%".format(oracle_to_1inch_diff * 100)) - print("1Inch to Coingecko Difference: {:.6f}%".format(coingecko_to_1inch_diff * 100)) - print("1Inch to CoinmarketCap Difference: {:.6f}%".format(cmc_to_1inch_diff * 100)) + # print("1Inch to Coingecko Difference: {:.6f}%".format(coingecko_to_1inch_diff * 100)) + # print("1Inch to CoinmarketCap Difference: {:.6f}%".format(cmc_to_1inch_diff * 100)) print("") print("-------- Slippage --------") print("Current market Slippage: {:.6f}%".format(actual_slippage * 100)) @@ -172,8 +162,8 @@ def build_swap_tx(from_token, to_token, from_amount, max_slippage, allow_partial for protocol, price_diff in { "Oracle": oracle_to_1inch_diff, - "Coingecko": coingecko_to_1inch_diff, - "CoinmarketCap": cmc_to_1inch_diff + # "Coingecko": coingecko_to_1inch_diff, + # "CoinmarketCap": cmc_to_1inch_diff }.items(): if (abs(price_diff * 100) > MAX_PRICE_DEVIATION): error = "1Inch and {} have too large price deviation: {:.6f}%".format(protocol, price_diff * 100) @@ -186,7 +176,7 @@ def build_swap_tx(from_token, to_token, from_amount, max_slippage, allow_partial return to, data decoded_input = vault_core.swapCollateral.decode_input(data) - return vault_core.swapCollateral(*decoded_input, {'from':STRATEGIST}) + return c_vault_core.swapCollateral(*decoded_input, {'from':STRATEGIST}) # from_token, to_token, from_token_amount, slippage, allow_partial_fill, dry_run diff --git a/brownie/oneinch.py b/brownie/oneinch.py index 79d70825f1..ca1c8b7e4f 100644 --- a/brownie/oneinch.py +++ b/brownie/oneinch.py @@ -2,44 +2,57 @@ import requests from types import SimpleNamespace import os +import json +import time + +ONEINCH_API_KEY = os.getenv('ONEINCH_API_KEY') + +def get_1inch_quote(from_token, to_token, from_amount, retry_on_ratelimit=True): + if len(ONEINCH_API_KEY) <= 0: + raise Exception("Missing API key") + res = requests.get('https://api.1inch.dev/swap/v5.2/1/quote', params={ + 'src': from_token, + 'dst': to_token, + 'amount': str(from_amount) + }, headers={ + 'accept': 'application/json', + 'Authorization': 'Bearer {}'.format(ONEINCH_API_KEY) + }) -ONEINCH_SUBDOMAIN = os.getenv('ONEINCH_SUBDOMAIN') -ONEINCH_SUBDOMAIN = ONEINCH_SUBDOMAIN if len(ONEINCH_SUBDOMAIN) > 0 else 'api' + if retry_on_ratelimit and res.status_code == 429: + time.sleep(2) # Wait for 2s and then try again + return get_1inch_quote(from_token, to_token, from_amount, False) + elif res.status_code != 200: + raise Exception("Error accessing 1inch api") -def get_1inch_quote(from_token, to_token, from_amount): - req = requests.get('https://{}.1inch.io/v5.0/1/quote'.format(ONEINCH_SUBDOMAIN), params={ - 'fromTokenAddress': from_token, - 'toTokenAddress': to_token, - 'amount': str(from_amount) - }, headers={ - 'accept': 'application/json' - }) + result = json.loads(res.text) - if req.status_code != 200: - print(req.json()) - raise Exception("Error accessing 1inch api") + return int(result['toAmount']) - result = req.json() - return int(result['toTokenAmount']) +def get_1inch_swap_data(from_token, to_token, swap_amount, slippage, from_address=STRATEGIST, to_address=STRATEGIST, retry_on_ratelimit=True): + if len(ONEINCH_API_KEY) <= 0: + raise Exception("Missing API key") -def get_1inch_swap_data(from_token, to_token, swap_amount, slippage, from_address=STRATEGIST, to_address=STRATEGIST): - req = requests.get('https://{}.1inch.io/v5.0/1/swap'.format(ONEINCH_SUBDOMAIN), params={ - 'fromTokenAddress': from_token, + res = requests.get('https://api.1inch.dev/swap/v5.2/1/swap', params={ + 'src': from_token, 'fromAddress': from_address, - 'destReceiver': to_address, - 'toTokenAddress': to_token, + 'receiver': to_address, + 'dst': to_token, 'amount': str(swap_amount), 'allowPartialFill': True, 'disableEstimate': 'true', 'slippage': slippage }, headers={ - 'accept': 'application/json' + 'accept': 'application/json', + 'Authorization': 'Bearer {}'.format(ONEINCH_API_KEY) }) - if req.status_code != 200: - print(req.json()) - raise Exception("Error calling 1inch api") + if retry_on_ratelimit and res.status_code == 429: + time.sleep(2) # Wait for 2s and then try again + return get_1inch_swap_data(from_token, to_token, swap_amount, slippage, from_address, to_address, False) + elif res.status_code != 200: + raise Exception("Error accessing 1inch api") - result = req.json() + result = json.loads(res.text) return SimpleNamespace(receiver = result['tx']['to'], input = result['tx']['data']) diff --git a/brownie/prices.py b/brownie/prices.py index dfb98f9953..780b9989f5 100644 --- a/brownie/prices.py +++ b/brownie/prices.py @@ -61,7 +61,7 @@ def get_cmc_quote(from_token, to_token, from_amount): if req.status_code != 200: print(req.json()) - raise Exception("Error accessing 1inch api") + raise Exception("Error accessing CMC api") result = req.json() diff --git a/brownie/runlogs/2023_11_strategist.py b/brownie/runlogs/2023_11_strategist.py index 8548ea0241..9fc74bee1f 100644 --- a/brownie/runlogs/2023_11_strategist.py +++ b/brownie/runlogs/2023_11_strategist.py @@ -100,4 +100,80 @@ def main(): print("-----") print("Profit", "{:.6f}".format(profit / 10**18), profit) print("Vault Change", "{:.6f}".format(vault_change / 10**18), vault_change) - print("-----") \ No newline at end of file + print("-----") + +# ------------------------------------- +# Nov 22, 2023 - OETH swap and allocation +# ------------------------------------- +from world import * +from collateralSwap import * + +# two thirds of the Vault's holding +two_thirds_steth = 3100603269179117666304 # 3100 stETH + +def main(): + txs = [] + with TemporaryFork(): + txs.append(oeth_vault_value_checker.takeSnapshot({'from':STRATEGIST})) + + txs.append( + build_swap_tx( + STETH, + WETH, + two_thirds_steth, + 0.3, + False, + dry_run=False + ) + ) + + txs.append( + vault_oeth_admin.depositToStrategy( + OETH_CONVEX_OETH_ETH_STRAT, + [weth], + [two_thirds_steth], + {'from': STRATEGIST} + ) + ) + + #txs.append(vault_value_checker.checkDelta(0, (1 * 10**18), 0, (1 * 10**18), {'from': STRATEGIST})) + vault_change = vault_oeth_core.totalValue() - oeth_vault_value_checker.snapshots(STRATEGIST)[0] + supply_change = oeth.totalSupply() - oeth_vault_value_checker.snapshots(STRATEGIST)[1] + profit = vault_change - supply_change + + txs.append(oeth_vault_value_checker.checkDelta(profit, (0.5 * 10**18), vault_change, (1.5 * 10**18), {'from': STRATEGIST})) + print("-----") + print("Profit", "{:.6f}".format(profit / 10**18), profit) + print("Vault Change", "{:.6f}".format(vault_change / 10**18), vault_change) + print("-----") + + print(to_gnosis_json(txs)) + +# ------------------------------------- +# Nov 22, 2023 - OETH & OUSD Buyback +# ------------------------------------- +from buyback import * + +def main(): + txs = [] + + with TemporaryFork(): + txs.append( + build_buyback_tx( + OETH, + oeth.balanceOf(OETH_BUYBACK), + max_ogv_slippage=1, + max_cvx_slippage=1 + ) + ) + + txs.append( + build_buyback_tx( + OUSD, + ousd.balanceOf(OUSD_BUYBACK), + max_ogv_slippage=1, + max_cvx_slippage=1 + ) + ) + + print(to_gnosis_json(txs)) From fccf68b09297584d1eef095504c78a85c3e5ee1f Mon Sep 17 00:00:00 2001 From: Domen Grabec Date: Wed, 22 Nov 2023 13:45:39 +0100 Subject: [PATCH 09/13] re-enable cmc and coingecko (#1941) --- brownie/buyback.py | 4 ++-- brownie/collateralSwap.py | 20 ++++++++++---------- 2 files changed, 12 insertions(+), 12 deletions(-) diff --git a/brownie/buyback.py b/brownie/buyback.py index e17de763bd..bd95a0cc78 100644 --- a/brownie/buyback.py +++ b/brownie/buyback.py @@ -57,8 +57,8 @@ def get_token_quote(otoken, to_token, amount): price_before = parse_uniswap_x96_price(prices_before[len(prices_before) - 1]) no_slippage_quote = max([ - # get_coingecko_quote(otoken, to_token, min_amount), - # get_cmc_quote(otoken, to_token, min_amount), + get_coingecko_quote(otoken, to_token, min_amount), + get_cmc_quote(otoken, to_token, min_amount), uni_quote ]) no_slippage_quote = amount * no_slippage_quote / min_amount diff --git a/brownie/collateralSwap.py b/brownie/collateralSwap.py index 60cd921fd6..8c68c94dac 100644 --- a/brownie/collateralSwap.py +++ b/brownie/collateralSwap.py @@ -125,14 +125,14 @@ def build_swap_tx(from_token, to_token, from_amount, max_slippage, allow_partial quote_1inch_min_swap_amount_price = get_1inch_quote(from_token, to_token, min_slippage_amount) quote_1inch_min_swap_amount = from_amount * quote_1inch_min_swap_amount_price / min_slippage_amount quote_oracles = get_oracle_router_quote(from_token, to_token, from_amount) - # quote_coingecko = get_coingecko_quote(from_token, to_token, from_amount) - # quote_cmc = get_cmc_quote(from_token, to_token, from_amount) + quote_coingecko = get_coingecko_quote(from_token, to_token, from_amount) + quote_cmc = get_cmc_quote(from_token, to_token, from_amount) # subtract the max slippage from minimum slippage query min_tokens_with_slippage = scale_amount(from_token, to_token, from_amount * quote_1inch_min_swap_amount_price * (100 - max_slippage) / 100 / min_slippage_amount) - # coingecko_to_1inch_diff = (quote_1inch - quote_coingecko) / quote_1inch + coingecko_to_1inch_diff = (quote_1inch - quote_coingecko) / quote_1inch oracle_to_1inch_diff = (quote_1inch - quote_oracles) / quote_1inch - # cmc_to_1inch_diff = (quote_1inch - quote_cmc) / quote_1inch + cmc_to_1inch_diff = (quote_1inch - quote_cmc) / quote_1inch actual_slippage = (quote_1inch_min_swap_amount - quote_1inch) / quote_1inch @@ -140,14 +140,14 @@ def build_swap_tx(from_token, to_token, from_amount, max_slippage, allow_partial print("1Inch expected tokens: {:.6f}".format(scale_amount(to_token, 'human', quote_1inch))) print("1Inch expected tokens (no slippage): {:.6f}".format(scale_amount(to_token, 'human', quote_1inch_min_swap_amount))) print("Oracle expected tokens: {:.6f}".format(scale_amount(to_token, 'human', quote_oracles))) - # print("Coingecko expected tokens: {:.6f}".format(scale_amount(to_token, 'human', quote_coingecko))) - # print("CoinmarketCap expected tokens: {:.6f}".format(scale_amount(to_token, 'human', quote_cmc))) + print("Coingecko expected tokens: {:.6f}".format(scale_amount(to_token, 'human', quote_coingecko))) + print("CoinmarketCap expected tokens: {:.6f}".format(scale_amount(to_token, 'human', quote_cmc))) print("Tokens expected (with {:.2f}% slippage) {:.6f}".format(max_slippage, scale_amount(to_token, 'human', min_tokens_with_slippage))) print("") print("------ Price Diffs -------") print("1Inch to Oracle Difference: {:.6f}%".format(oracle_to_1inch_diff * 100)) - # print("1Inch to Coingecko Difference: {:.6f}%".format(coingecko_to_1inch_diff * 100)) - # print("1Inch to CoinmarketCap Difference: {:.6f}%".format(cmc_to_1inch_diff * 100)) + print("1Inch to Coingecko Difference: {:.6f}%".format(coingecko_to_1inch_diff * 100)) + print("1Inch to CoinmarketCap Difference: {:.6f}%".format(cmc_to_1inch_diff * 100)) print("") print("-------- Slippage --------") print("Current market Slippage: {:.6f}%".format(actual_slippage * 100)) @@ -162,8 +162,8 @@ def build_swap_tx(from_token, to_token, from_amount, max_slippage, allow_partial for protocol, price_diff in { "Oracle": oracle_to_1inch_diff, - # "Coingecko": coingecko_to_1inch_diff, - # "CoinmarketCap": cmc_to_1inch_diff + "Coingecko": coingecko_to_1inch_diff, + "CoinmarketCap": cmc_to_1inch_diff }.items(): if (abs(price_diff * 100) > MAX_PRICE_DEVIATION): error = "1Inch and {} have too large price deviation: {:.6f}%".format(protocol, price_diff * 100) From c204ea29715fb0f42719c80857b8cdd1b07a5480 Mon Sep 17 00:00:00 2001 From: Shahul Hameed <10547529+shahthepro@users.noreply.github.com> Date: Thu, 23 Nov 2023 16:14:26 +0530 Subject: [PATCH 10/13] Update runlog (#1942) --- brownie/runlogs/2023_11_strategist.py | 30 +++++++++++++++++++++++++++ 1 file changed, 30 insertions(+) diff --git a/brownie/runlogs/2023_11_strategist.py b/brownie/runlogs/2023_11_strategist.py index 9fc74bee1f..ede9478da5 100644 --- a/brownie/runlogs/2023_11_strategist.py +++ b/brownie/runlogs/2023_11_strategist.py @@ -177,3 +177,33 @@ def main(): ) print(to_gnosis_json(txs)) + +# ------------------------------------- +# Nov 22, 2023 - Withdraw from Morpho +# ------------------------------------- +from world import * + +def main(): + with TemporaryForkForReallocations() as txs: + # Before + txs.append(vault_oeth_core.rebase(std)) + txs.append(oeth_vault_value_checker.takeSnapshot(std)) + + txs.append( + oeth_vault_admin.withdrawFromStrategy( + OETH_MORPHO_AAVE_STRAT, + [WETH], + [60 * 10**18], + std + ) + ) + + # After + vault_change = vault_oeth_core.totalValue() - oeth_vault_value_checker.snapshots(STRATEGIST)[0] + supply_change = oeth.totalSupply() - oeth_vault_value_checker.snapshots(STRATEGIST)[1] + profit = vault_change - supply_change + txs.append(oeth_vault_value_checker.checkDelta(profit, (0.1 * 10**18), vault_change, (0.1 * 10**18), std)) + print("-----") + print("Profit", "{:.6f}".format(profit / 10**18), profit) + print("Vault Change", "{:.6f}".format(vault_change / 10**18), vault_change) + print("-----") From 9c6855e843f7064714abbfe7282687092dbf02da Mon Sep 17 00:00:00 2001 From: Nick Addison Date: Mon, 27 Nov 2023 23:35:51 +1100 Subject: [PATCH 11/13] Added signer support for Open Zeppelin's Defender Relayer (#1900) * Added signer support for Defender Relayer * Made getSigner clear that the address param is optional * Logged which account is being used when using a Defender Relayer --- contracts/README.md | 57 ++++++++++++++- contracts/package.json | 1 + contracts/tasks/tokens.js | 12 +++ contracts/utils/signers.js | 31 +++++++- contracts/yarn.lock | 146 ++++++++++++++++++++++++++++++++++++- 5 files changed, 239 insertions(+), 8 deletions(-) diff --git a/contracts/README.md b/contracts/README.md index e794daded6..7d8351462c 100644 --- a/contracts/README.md +++ b/contracts/README.md @@ -44,9 +44,7 @@ yarn lint ### Install slither -If you use the slither documented "pip3 install slither-analyzer" there might be problems -with package collisions. Just use pipx that installs any package and all dependencies in -sandbox to circumvent the issue: `pipx install slither-analyzer` +If you use the slither documented "pip3 install slither-analyzer" there might be problems with package collisions. Just use pipx that installs any package and all dependencies in sandbox to circumvent the issue: `pipx install slither-analyzer` [Slither](https://github.com/crytic/slither#slither-the-solidity-source-analyzer) is used to for Solidity static analysis. @@ -228,6 +226,59 @@ If enabled, the gas usage will be output in a table after the tests have execute | MockOracleRouterNoStale · - · - · 743016 · 2.5 % · - │ ``` +## Signers + +When using Hardhat tasks, there are a few options for specifying the wallet to send transactions from. + +1. Primary key +2. Impersonate +3. Defender Relayer + +### Primary Key + +The primary key of the account to be used can be set with the `DEPLOYER_PK` or `GOVERNOR_PK` environment variables. These are traditionally used for contract deployments. + +> Add `export HISTCONTROL=ignorespace` to your shell config, eg `~/.profile` or `~/.zprofile`, so any command with a space at the start won’t go into your history file. + +When finished, you can unset the `DEPLOYER_PK` and `GOVERNOR_PK` environment variables so they aren't accidentally used. + +``` +unset DEPLOYER_PK +unset GOVERNOR_PK +``` + +### Impersonate + +If using a fork test or node, you can impersonate any externally owned account or contract. Export `IMPERSONATE` with the address of the account you want to impersonate. The account will be funded with some Ether. For example + +``` +export IMPERSONATE=0xF14BBdf064E3F67f51cd9BD646aE3716aD938FDC +``` + +When finished, you can stop impersonating by unsetting the `IMPERSONATE` environment variable. + +``` +unset IMPERSONATE +``` + +### Defender Relayer + +Open Zeppelin's [Defender](https://defender.openzeppelin.com/) product has a [Relayer](https://docs.openzeppelin.com/defender/v2/manage/relayers) service that is a managed wallet. It handles the nonce, gas, signing and sending of transactions. + +To use a [Relayer](https://defender.openzeppelin.com/v2/#/manage/relayers) account, first log into Defender and create an API key for the account you want to use. Use the generated API key and secret to set the `DEFENDER_API_KEY` and `DEFENDER_API_SECRET` environment variables. + +``` +export DEFENDER_API_KEY= +export DEFENDER_API_SECRET= +``` + +Once you have finished sending your transactions, the API key for hte Relayer account should be deleted in Defender and the environment variables unset. + +``` +unset DEFENDER_API_KEY +unset DEFENDER_API_SECRET +``` + ## Contract Verification The Hardhat plug-in [@nomiclabs/hardhat-etherscan](https://www.npmjs.com/package/@nomiclabs/hardhat-etherscan) is used to verify contracts on Etherscan. diff --git a/contracts/package.json b/contracts/package.json index 7798c3595d..10cb877c37 100644 --- a/contracts/package.json +++ b/contracts/package.json @@ -44,6 +44,7 @@ "@nomiclabs/hardhat-solhint": "^2.0.0", "@nomiclabs/hardhat-waffle": "^2.0.6", "@openzeppelin/contracts": "4.4.2", + "@openzeppelin/defender-sdk": "^1.3.0", "@openzeppelin/hardhat-upgrades": "^1.10.0", "@uniswap/v3-core": "^1.0.0", "@uniswap/v3-periphery": "^1.1.1", diff --git a/contracts/tasks/tokens.js b/contracts/tasks/tokens.js index a2a7742949..aa6d2f70b1 100644 --- a/contracts/tasks/tokens.js +++ b/contracts/tasks/tokens.js @@ -70,6 +70,18 @@ async function tokenTransfer(taskArguments) { throw new Error(`Invalid Ethereum address: ${to}`); } + // If ETH transfer + if (symbol === "ETH") { + log(`About to send ${amount} ${symbol} to ${to}`); + // send ETH in a transaction + const tx = await signer.sendTransaction({ + to, + value: parseUnits(amount.toString()), + }); + await logTxDetails(tx, "send"); + return; + } + const asset = await resolveAsset(symbol); const assetUnits = parseUnits(amount.toString(), await asset.decimals()); diff --git a/contracts/utils/signers.js b/contracts/utils/signers.js index 4ddbf6a3fa..dd16417d4b 100644 --- a/contracts/utils/signers.js +++ b/contracts/utils/signers.js @@ -1,4 +1,6 @@ const { Wallet } = require("ethers").utils; +const { Defender } = require("@openzeppelin/defender-sdk"); + const { ethereumAddress, privateKey } = require("./regex"); const { hardhatSetBalance } = require("../test/_fund"); const hhHelpers = require("@nomicfoundation/hardhat-network-helpers"); @@ -14,7 +16,7 @@ const log = require("./logger")("utils:signers"); * @param {*} address optional address of the signer * @returns */ -async function getSigner(address) { +async function getSigner(address = undefined) { if (address) { if (!address.match(ethereumAddress)) { throw Error(`Invalid format of address`); @@ -44,6 +46,11 @@ async function getSigner(address) { return await impersonateAndFund(address); } + // If using Defender Relayer + if (process.env.DEFENDER_API_KEY && process.env.DEFENDER_API_SECRET) { + return await getDefenderSigner(); + } + const signers = await hre.ethers.getSigners(); const signer = signers[0]; log(`Using signer ${await signer.getAddress()}`); @@ -51,6 +58,28 @@ async function getSigner(address) { return signer; } +const getDefenderSigner = async () => { + const speed = process.env.SPEED || "fast"; + if (!["safeLow", "average", "fast", "fastest"].includes(speed)) { + console.error( + `Defender Relay Speed param must be either 'safeLow', 'average', 'fast' or 'fastest'. Not "${speed}"` + ); + process.exit(2); + } + const credentials = { + relayerApiKey: process.env.DEFENDER_API_KEY, + relayerApiSecret: process.env.DEFENDER_API_SECRET, + }; + const client = new Defender(credentials); + const provider = client.relaySigner.getProvider(); + + const signer = client.relaySigner.getSigner(provider, { speed }); + log( + `Using Defender Relayer account ${await signer.getAddress()} from env vars DEFENDER_API_KEY and DEFENDER_API_SECRET` + ); + return signer; +}; + /** * Impersonate an account when connecting to a forked node. * @param {*} account the address of the contract or externally owned account to impersonate diff --git a/contracts/yarn.lock b/contracts/yarn.lock index d897222ba5..64a308629f 100644 --- a/contracts/yarn.lock +++ b/contracts/yarn.lock @@ -414,7 +414,7 @@ resolved "https://registry.yarnpkg.com/@ethersproject/logger/-/logger-5.7.0.tgz#6ce9ae168e74fecf287be17062b590852c311892" integrity sha512-0odtFdXu/XHtjQXJYA3u9G0G8btm0ND5Cu8M7i5vhEcE8/HmF4Lbdqanwyv4uQTr2tx6b7fQRmgLrsnpQlmnig== -"@ethersproject/networks@5.7.1", "@ethersproject/networks@^5.7.0": +"@ethersproject/networks@5.7.1", "@ethersproject/networks@^5.7.0", "@ethersproject/networks@^5.7.1": version "5.7.1" resolved "https://registry.yarnpkg.com/@ethersproject/networks/-/networks-5.7.1.tgz#118e1a981d757d45ccea6bb58d9fd3d9db14ead6" integrity sha512-n/MufjFYv3yFcUyfhnXotyDlNdFb7onmkSy8aQERi2PjNcnWQ66xXxa3XlS8nCcA8aJKJjIIMNJTC7tu80GwpQ== @@ -955,6 +955,117 @@ resolved "https://registry.yarnpkg.com/@openzeppelin/contracts/-/contracts-4.4.2.tgz#4e889c9c66e736f7de189a53f8ba5b8d789425c2" integrity sha512-NyJV7sJgoGYqbtNUWgzzOGW4T6rR19FmX1IJgXGdapGPWsuMelGJn9h03nos0iqfforCbCB0iYIR0MtIuIFLLw== +"@openzeppelin/defender-sdk-action-client@^1.3.0": + version "1.3.0" + resolved "https://registry.yarnpkg.com/@openzeppelin/defender-sdk-action-client/-/defender-sdk-action-client-1.3.0.tgz#6964523bc29da5c0104d364bb72123322c01954a" + integrity sha512-j1AzY4A+4ywHgI0SYJfh1LC5bNR430TCJmNfyn7D5ZM32sXPvTPu0SAQKGz6bYRFp+YAB6bOJZDyT1J3YbnxMw== + dependencies: + "@openzeppelin/defender-sdk-base-client" "^1.3.0" + axios "^1.4.0" + glob "^7.1.6" + jszip "^3.8.0" + lodash "^4.17.21" + +"@openzeppelin/defender-sdk-base-client@^1.3.0": + version "1.3.0" + resolved "https://registry.yarnpkg.com/@openzeppelin/defender-sdk-base-client/-/defender-sdk-base-client-1.3.0.tgz#9e4bc35e9c5d86578c5e509edcbda4aefffb2413" + integrity sha512-OMMt7NaAL8C95ralF9nMeKZpg96COLZT9FPpGpPsI7aB8fVZfCM8+6k99gTF44hMS6IsRdN2WthS3m7VzQeeoA== + dependencies: + amazon-cognito-identity-js "^6.0.1" + async-retry "^1.3.3" + +"@openzeppelin/defender-sdk-deploy-client@^1.3.0": + version "1.3.0" + resolved "https://registry.yarnpkg.com/@openzeppelin/defender-sdk-deploy-client/-/defender-sdk-deploy-client-1.3.0.tgz#d70f06e849080d6a0ad5b86f631567642204f1ba" + integrity sha512-RTYM3HnVvD2d5NoYfTug8UwT41e0Jjwb13lk9v0Jl8z7mcclUVvAnKD4DHJ4b8RhKpg4B15oLQK/Igzjg1HHRA== + dependencies: + "@ethersproject/abi" "^5.6.3" + "@openzeppelin/defender-sdk-base-client" "^1.3.0" + axios "^1.4.0" + lodash "^4.17.21" + +"@openzeppelin/defender-sdk-monitor-client@^1.3.0": + version "1.3.0" + resolved "https://registry.yarnpkg.com/@openzeppelin/defender-sdk-monitor-client/-/defender-sdk-monitor-client-1.3.0.tgz#c72f6fb517f3ae15d087de8d1837d49350b5dfd0" + integrity sha512-0drfifN4lk4Jpn5goU0imuuvQfxH8EDHUCEQSqxHbubsmNMrI+RqnHSuLZ26VceXbmcVbpfrYhVPnIVyyL4bVw== + dependencies: + "@ethersproject/abi" "^5.6.3" + "@openzeppelin/defender-sdk-base-client" "^1.3.0" + axios "^1.4.0" + lodash "^4.17.21" + +"@openzeppelin/defender-sdk-network-client@^1.3.0": + version "1.3.0" + resolved "https://registry.yarnpkg.com/@openzeppelin/defender-sdk-network-client/-/defender-sdk-network-client-1.3.0.tgz#6ec7b8180a43ce5d2f02f814dc178ab033769e44" + integrity sha512-GNDCH6b0KV0rNOVqxWDeKumEsvPGQAFih6XrOV2kU/AJWhtzLfRngvhaphB/Dv6wbCopo3e+8I8UN4PSC9+vEQ== + dependencies: + "@ethersproject/abi" "^5.6.3" + "@openzeppelin/defender-sdk-base-client" "^1.3.0" + axios "^1.4.0" + lodash "^4.17.21" + +"@openzeppelin/defender-sdk-notification-channel-client@^1.3.0": + version "1.3.0" + resolved "https://registry.yarnpkg.com/@openzeppelin/defender-sdk-notification-channel-client/-/defender-sdk-notification-channel-client-1.3.0.tgz#cbff26029c92d20275a079e2b1e81f8a694f86df" + integrity sha512-W5YrxB9nLXavtdVUpvgepXhBKLvJcJMcbWDs78/gpvLicKbpTXD0V1Vg9UjSstGUbOeU9yOqYSIzDqj6+cgq3Q== + dependencies: + "@ethersproject/abi" "^5.6.3" + "@openzeppelin/defender-sdk-base-client" "^1.3.0" + axios "^1.4.0" + lodash "^4.17.21" + +"@openzeppelin/defender-sdk-proposal-client@^1.3.0": + version "1.3.0" + resolved "https://registry.yarnpkg.com/@openzeppelin/defender-sdk-proposal-client/-/defender-sdk-proposal-client-1.3.0.tgz#cbcad0200b262d28234c6c45ef344050365f5704" + integrity sha512-IO2ZLgYshboBB0GCURM4APaePQIjOgTEgFchik612sk41VfGfIV7Ei/r0EllNVHS4385mMH2qWe/fK6isseBzQ== + dependencies: + "@openzeppelin/defender-sdk-base-client" "^1.3.0" + axios "^1.4.0" + ethers "^5.7.2" + lodash "^4.17.21" + +"@openzeppelin/defender-sdk-relay-client@^1.3.0": + version "1.3.0" + resolved "https://registry.yarnpkg.com/@openzeppelin/defender-sdk-relay-client/-/defender-sdk-relay-client-1.3.0.tgz#1eadb1f2ff2f8cb56a28c3ce82d9e86b0d73739d" + integrity sha512-PhvIcy1kRM+KHSILRPLAs3CKs44VsVkx966L//52jS8b38DW3XJTdmpCNGWUCg2BRIV4qFv13BXO4qXd3u6/1g== + dependencies: + "@openzeppelin/defender-sdk-base-client" "^1.3.0" + axios "^1.4.0" + lodash "^4.17.21" + +"@openzeppelin/defender-sdk-relay-signer-client@^1.3.0": + version "1.3.0" + resolved "https://registry.yarnpkg.com/@openzeppelin/defender-sdk-relay-signer-client/-/defender-sdk-relay-signer-client-1.3.0.tgz#2fc0448bebc60a08af4ca7a7e14b4ee6b4d681a4" + integrity sha512-aVBvZUy3TS1WcRykcFKd1sjO+LcDQP5kxKLovwb+JFuAqigsEoiJRZktnI5zFRzHs4wfuzOUPfbPcNW89mvNVw== + dependencies: + "@ethersproject/bignumber" "^5.7.0" + "@ethersproject/bytes" "^5.7.0" + "@ethersproject/contracts" "^5.7.0" + "@ethersproject/logger" "^5.7.0" + "@ethersproject/networks" "^5.7.1" + "@ethersproject/properties" "^5.7.0" + "@ethersproject/random" "^5.7.0" + "@ethersproject/strings" "^5.7.0" + "@openzeppelin/defender-sdk-base-client" "^1.3.0" + amazon-cognito-identity-js "^6.0.1" + axios "^1.4.0" + lodash "^4.17.21" + +"@openzeppelin/defender-sdk@^1.3.0": + version "1.3.0" + resolved "https://registry.yarnpkg.com/@openzeppelin/defender-sdk/-/defender-sdk-1.3.0.tgz#6b9f9d918081122504bf7c00a592226c96cc1772" + integrity sha512-bP3pInKR/JvAemGFaGB8z1cBJT88uQYID9X82jtphGLfnl8hjWjtLMej+F0X6JcJoY1F/8c0/7Q51vdPXaUE7Q== + dependencies: + "@openzeppelin/defender-sdk-action-client" "^1.3.0" + "@openzeppelin/defender-sdk-base-client" "^1.3.0" + "@openzeppelin/defender-sdk-deploy-client" "^1.3.0" + "@openzeppelin/defender-sdk-monitor-client" "^1.3.0" + "@openzeppelin/defender-sdk-network-client" "^1.3.0" + "@openzeppelin/defender-sdk-notification-channel-client" "^1.3.0" + "@openzeppelin/defender-sdk-proposal-client" "^1.3.0" + "@openzeppelin/defender-sdk-relay-client" "^1.3.0" + "@openzeppelin/defender-sdk-relay-signer-client" "^1.3.0" + "@openzeppelin/hardhat-upgrades@^1.10.0": version "1.27.0" resolved "https://registry.yarnpkg.com/@openzeppelin/hardhat-upgrades/-/hardhat-upgrades-1.27.0.tgz#0e304041d72c97979c76466137b48733120270fd" @@ -2957,7 +3068,7 @@ ethers@^4.0.40: uuid "2.0.1" xmlhttprequest "1.8.0" -ethers@^5.4.6, ethers@^5.5.3, ethers@^5.6.1, ethers@^5.7.1: +ethers@^5.4.6, ethers@^5.5.3, ethers@^5.6.1, ethers@^5.7.1, ethers@^5.7.2: version "5.7.2" resolved "https://registry.yarnpkg.com/ethers/-/ethers-5.7.2.tgz#3a7deeabbb8c030d4126b24f84e525466145872e" integrity sha512-wswUsmWo1aOK8rR7DIKiWSw9DbLWe6x98Jrn8wcTflTVvaXhAMaB5zGAXy0GYQEQp9iO1iSHWVyARQm11zUtyg== @@ -3457,7 +3568,7 @@ glob@^5.0.15: once "^1.3.0" path-is-absolute "^1.0.0" -glob@^7.0.0, glob@^7.1.2, glob@^7.1.3: +glob@^7.0.0, glob@^7.1.2, glob@^7.1.3, glob@^7.1.6: version "7.2.3" resolved "https://registry.yarnpkg.com/glob/-/glob-7.2.3.tgz#b8df0fb802bbfa8e89bd1d938b4e16578ed44f2b" integrity sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q== @@ -3865,6 +3976,11 @@ immediate@^3.2.3: resolved "https://registry.yarnpkg.com/immediate/-/immediate-3.3.0.tgz#1aef225517836bcdf7f2a2de2600c79ff0269266" integrity sha512-HR7EVodfFUdQCTIeySw+WDRFJlPcLOJbXfwwZ7Oom6tjsvZ3bOkCDJHehQC3nxJrv7+f9XecwazynjU8e4Vw3Q== +immediate@~3.0.5: + version "3.0.6" + resolved "https://registry.yarnpkg.com/immediate/-/immediate-3.0.6.tgz#9db1dbd0faf8de6fbe0f5dd5e56bb606280de69b" + integrity sha512-XXOFtyqDjNDAQxVfYxuF7g9Il/IbWmmlQg2MYKOH8ExIT1qg6xc4zyS3HaEEATgs1btfzxq15ciUiY7gjSXRGQ== + immediate@~3.2.3: version "3.2.3" resolved "https://registry.yarnpkg.com/immediate/-/immediate-3.2.3.tgz#d140fa8f614659bd6541233097ddaac25cdd991c" @@ -4301,6 +4417,16 @@ jsprim@^1.2.2: json-schema "0.4.0" verror "1.10.0" +jszip@^3.8.0: + version "3.10.1" + resolved "https://registry.yarnpkg.com/jszip/-/jszip-3.10.1.tgz#34aee70eb18ea1faec2f589208a157d1feb091c2" + integrity sha512-xXDvecyTpGLrqFrvkrUSoxxfJI5AH7U8zxxtVclpsUtMCq4JQ290LY8AW5c7Ggnr/Y/oK+bQMbqK2qmtk3pN4g== + dependencies: + lie "~3.3.0" + pako "~1.0.2" + readable-stream "~2.3.6" + setimmediate "^1.0.5" + keccak@3.0.1: version "3.0.1" resolved "https://registry.yarnpkg.com/keccak/-/keccak-3.0.1.tgz#ae30a0e94dbe43414f741375cff6d64c8bea0bff" @@ -4468,6 +4594,13 @@ levn@^0.4.1: prelude-ls "^1.2.1" type-check "~0.4.0" +lie@~3.3.0: + version "3.3.0" + resolved "https://registry.yarnpkg.com/lie/-/lie-3.3.0.tgz#dcf82dee545f46074daf200c7c1c5a08e0f40f6a" + integrity sha512-UaiMJzeWRlEujzAuw5LokY1L5ecNQYZKfmyZ9L7wDHb/p5etKaxXhohBcrw0EYby+G/NA52vRSN4N39dxHAIwQ== + dependencies: + immediate "~3.0.5" + lines-and-columns@^1.1.6: version "1.2.4" resolved "https://registry.yarnpkg.com/lines-and-columns/-/lines-and-columns-1.2.4.tgz#eca284f75d2965079309dc0ad9255abb2ebc1632" @@ -5112,6 +5245,11 @@ p-try@^2.0.0: resolved "https://registry.yarnpkg.com/p-try/-/p-try-2.2.0.tgz#cb2868540e313d61de58fafbe35ce9004d5540e6" integrity sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ== +pako@~1.0.2: + version "1.0.11" + resolved "https://registry.yarnpkg.com/pako/-/pako-1.0.11.tgz#6c9599d340d54dfd3946380252a35705a6b992bf" + integrity sha512-4hLB8Py4zZce5s4yd9XzopqwVv/yGNhV1Bl8NTmCq1763HeK2+EwVTv+leGeL13Dnh2wfbqowVPXCIO0z4taYw== + papaparse@^5.3.1: version "5.4.1" resolved "https://registry.yarnpkg.com/papaparse/-/papaparse-5.4.1.tgz#f45c0f871853578bd3a30f92d96fdcfb6ebea127" @@ -5356,7 +5494,7 @@ raw-body@^2.4.1: iconv-lite "0.4.24" unpipe "1.0.0" -readable-stream@^2.2.2: +readable-stream@^2.2.2, readable-stream@~2.3.6: version "2.3.8" resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-2.3.8.tgz#91125e8042bba1b9887f49345f6277027ce8be9b" integrity sha512-8p0AUk4XODgIewSi0l8Epjs+EVnWiK7NoDIEGU0HhE7+ZyY8D1IMY7odu5lRrFXGg71L15KG8QrPmum45RTtdA== From bad8924216cdc10647cdeea8c2d3183d16ddd3c8 Mon Sep 17 00:00:00 2001 From: Shahul Hameed <10547529+shahthepro@users.noreply.github.com> Date: Tue, 28 Nov 2023 14:38:49 +0530 Subject: [PATCH 12/13] Add support for Balancer, Curve and Uniswap V3 to Harvester (#1931) * checkpoint * checkpoint * Few code tweaks and add deployment file * Fix aura test * Fix aave config * Fix decimal caching * Add bunch of unit tests * Add bunch of unit tests * Prettify * Remove .only * Remove console statements * Few more test tweaks * Fix sig * Fix harvest tests * Rename behaviour test file * Price feed tests * Fix failing test * Slither * Rename test file * Checkpoint * Fix tests * Cleanup * Fix a bit of tests * Fix unit tests * Tweak fork test * Prettify * Adjust liquidation limits * Address CR comments * Fix unit tests * Fix aave unit tests * Remove commented out code * Fix bug * Add coverage * Fix error * Rename event args * Prettify * Gas opts * More readability * Change to struct * Fix CVX test * Address more CR comments * Add slippage checks * Farmer only receives fee from swap amount --- contracts/contracts/buyback/BaseBuyback.sol | 2 +- contracts/contracts/harvest/BaseHarvester.sol | 740 ++++++++--- contracts/contracts/harvest/Harvester.sol | 100 +- contracts/contracts/harvest/OETHHarvester.sol | 94 +- contracts/contracts/interfaces/IHarvester.sol | 64 - .../interfaces/balancer/IBalancerVault.sol | 38 + .../balancer/IOracleWeightedPool.sol | 44 + .../{ => uniswap}/IUniswapUniversalRouter.sol | 0 .../{ => uniswap}/IUniswapV3Router.sol | 0 contracts/contracts/mocks/MockAura.sol | 8 + .../contracts/mocks/MockBalancerVault.sol | 62 + .../contracts/mocks/MockOracleRouter.sol | 2 +- .../mocks/MockOracleRouterNoStale.sol | 7 +- .../mocks/MockOracleWeightedPool.sol | 25 + .../contracts/mocks/MockUniswapRouter.sol | 34 +- .../contracts/mocks/curve/MockCurvePool.sol | 21 + .../contracts/oracle/AuraWETHPriceFeed.sol | 167 +++ .../contracts/oracle/OETHOracleRouter.sol | 112 ++ contracts/contracts/oracle/OracleRouter.sol | 187 +-- .../contracts/oracle/OracleRouterBase.sol | 101 ++ contracts/contracts/strategies/ICurvePool.sol | 7 + contracts/deploy/000_mock.js | 19 + contracts/deploy/001_core.js | 21 +- .../deploy/076_upgrade_fraxeth_strategy.js | 1 + contracts/deploy/081_upgrade_harvester.js | 315 +++++ contracts/deploy/999_fork_test_setup.js | 4 +- contracts/test/_fixture.js | 68 +- contracts/test/_hot-deploy.js | 66 +- contracts/test/behaviour/harvest.fork.js | 93 ++ contracts/test/behaviour/harvester.js | 1079 ++++++++++++++--- .../test/behaviour/reward-tokens.fork.js | 127 ++ .../harvest/ousd-harvest-crv.fork-test.js | 46 +- contracts/test/helpers.js | 61 + contracts/test/oracle/aura-feed.fork-test.js | 67 + contracts/test/oracle/aura-feed.js | 207 ++++ contracts/test/oracle/oracle.fork-test.js | 89 +- contracts/test/strategies/3pool.js | 74 +- contracts/test/strategies/aave.js | 21 +- .../balancerMetaStablePool.fork-test.js | 59 +- contracts/test/strategies/compound.js | 52 - contracts/test/strategies/convex.js | 236 ---- contracts/test/strategies/generalized-meta.js | 47 - contracts/test/strategies/meta.js | 48 +- contracts/test/vault/compound.js | 224 +--- contracts/test/vault/harvester.fork-test.js | 287 ++++- contracts/test/vault/harvester.js | 769 +----------- contracts/test/vault/oeth-vault.fork-test.js | 62 +- contracts/test/vault/vault.fork-test.js | 50 + contracts/utils/addresses.js | 8 + contracts/utils/deploy.js | 7 +- 50 files changed, 3701 insertions(+), 2321 deletions(-) delete mode 100644 contracts/contracts/interfaces/IHarvester.sol create mode 100644 contracts/contracts/interfaces/balancer/IOracleWeightedPool.sol rename contracts/contracts/interfaces/{ => uniswap}/IUniswapUniversalRouter.sol (100%) rename contracts/contracts/interfaces/{ => uniswap}/IUniswapV3Router.sol (100%) create mode 100644 contracts/contracts/mocks/MockAura.sol create mode 100644 contracts/contracts/mocks/MockBalancerVault.sol create mode 100644 contracts/contracts/mocks/MockOracleWeightedPool.sol create mode 100644 contracts/contracts/oracle/AuraWETHPriceFeed.sol create mode 100644 contracts/contracts/oracle/OETHOracleRouter.sol create mode 100644 contracts/contracts/oracle/OracleRouterBase.sol create mode 100644 contracts/deploy/081_upgrade_harvester.js create mode 100644 contracts/test/behaviour/harvest.fork.js create mode 100644 contracts/test/behaviour/reward-tokens.fork.js create mode 100644 contracts/test/oracle/aura-feed.fork-test.js create mode 100644 contracts/test/oracle/aura-feed.js diff --git a/contracts/contracts/buyback/BaseBuyback.sol b/contracts/contracts/buyback/BaseBuyback.sol index 360419064f..6257d0759d 100644 --- a/contracts/contracts/buyback/BaseBuyback.sol +++ b/contracts/contracts/buyback/BaseBuyback.sol @@ -5,7 +5,7 @@ import { Strategizable } from "../governance/Strategizable.sol"; import "../interfaces/chainlink/AggregatorV3Interface.sol"; import { IERC20 } from "@openzeppelin/contracts/token/ERC20/IERC20.sol"; import { SafeERC20 } from "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol"; -import { IUniswapUniversalRouter } from "../interfaces/IUniswapUniversalRouter.sol"; +import { IUniswapUniversalRouter } from "../interfaces/uniswap/IUniswapUniversalRouter.sol"; import { ICVXLocker } from "../interfaces/ICVXLocker.sol"; import { Initializable } from "../utils/Initializable.sol"; diff --git a/contracts/contracts/harvest/BaseHarvester.sol b/contracts/contracts/harvest/BaseHarvester.sol index 3ec67f16db..c14ab2df0f 100644 --- a/contracts/contracts/harvest/BaseHarvester.sol +++ b/contracts/contracts/harvest/BaseHarvester.sol @@ -12,6 +12,9 @@ import { IVault } from "../interfaces/IVault.sol"; import { IOracle } from "../interfaces/IOracle.sol"; import { IStrategy } from "../interfaces/IStrategy.sol"; import { IUniswapV2Router } from "../interfaces/uniswap/IUniswapV2Router02.sol"; +import { IUniswapV3Router } from "../interfaces/uniswap/IUniswapV3Router.sol"; +import { IBalancerVault } from "../interfaces/balancer/IBalancerVault.sol"; +import { ICurvePool } from "../strategies/ICurvePool.sol"; import "../utils/Helpers.sol"; abstract contract BaseHarvester is Governable { @@ -19,16 +22,54 @@ abstract contract BaseHarvester is Governable { using SafeMath for uint256; using StableMath for uint256; - event UniswapUpdated(address _address); - event SupportedStrategyUpdate(address _address, bool _isSupported); + enum SwapPlatform { + UniswapV2Compatible, + UniswapV3, + Balancer, + Curve + } + + event SupportedStrategyUpdate(address strategyAddress, bool isSupported); event RewardTokenConfigUpdated( - address _tokenAddress, - uint16 _allowedSlippageBps, - uint16 _harvestRewardBps, - address _uniswapV2CompatibleAddr, - uint256 _liquidationLimit, - bool _doSwapRewardToken + address tokenAddress, + uint16 allowedSlippageBps, + uint16 harvestRewardBps, + SwapPlatform swapPlatform, + address swapPlatformAddr, + bytes swapData, + uint256 liquidationLimit, + bool doSwapRewardToken + ); + event RewardTokenSwapped( + address indexed rewardToken, + address indexed swappedInto, + SwapPlatform swapPlatform, + uint256 amountIn, + uint256 amountOut + ); + event RewardProceedsTransferred( + address indexed token, + address farmer, + uint256 protcolYield, + uint256 farmerFee ); + event RewardProceedsAddressChanged(address newProceedsAddress); + + error EmptyAddress(); + error InvalidSlippageBps(); + error InvalidHarvestRewardBps(); + + error InvalidSwapPlatform(SwapPlatform swapPlatform); + + error InvalidUniswapV2PathLength(); + error InvalidTokenInSwapPath(address token); + error EmptyBalancerPoolId(); + error InvalidCurvePoolAssetIndex(address token); + + error UnsupportedStrategy(address strategyAddress); + + error SlippageError(uint256 actualBalance, uint256 minExpected); + error BalanceMismatchAfterSwap(uint256 actualBalance, uint256 minExpected); // Configuration properties for harvesting logic of reward tokens struct RewardTokenConfig { @@ -36,13 +77,14 @@ abstract contract BaseHarvester is Governable { uint16 allowedSlippageBps; // Reward when calling a harvest function denominated in basis points. uint16 harvestRewardBps; - /* Address of Uniswap V2 compatible exchange (Uniswap V2, SushiSwap). - */ - address uniswapV2CompatibleAddr; + // Address of compatible exchange protocol (Uniswap V2/V3, SushiSwap, Balancer and Curve). + address swapPlatformAddr; /* When true the reward token is being swapped. In a need of (temporarily) disabling the swapping of * a reward token this needs to be set to false. */ bool doSwapRewardToken; + // Platform to use for Swapping + SwapPlatform swapPlatform; /* How much token can be sold per one harvest call. If the balance of rewards tokens * exceeds that limit multiple harvest calls are required to harvest all of the tokens. * Set it to MAX_INT to effectively disable the limit. @@ -58,131 +100,280 @@ abstract contract BaseHarvester is Governable { /** * Address receiving rewards proceeds. Initially the Vault contract later will possibly * be replaced by another contract that eases out rewards distribution. - */ + **/ address public rewardProceedsAddress; /** - * @dev Constructor to set up initial internal state - * @param _vaultAddress Address of the Vault - */ - constructor(address _vaultAddress) { - require(address(_vaultAddress) != address(0)); + * All tokens are swapped to this token before it gets transferred + * to the `rewardProceedsAddress`. USDT for OUSD and WETH for OETH. + **/ + address public immutable baseTokenAddress; + // Cached decimals for `baseTokenAddress` + uint256 public immutable baseTokenDecimals; + + // Uniswap V2 path for reward tokens using Uniswap V2 Router + mapping(address => address[]) public uniswapV2Path; + // Uniswap V3 path for reward tokens using Uniswap V3 Router + mapping(address => bytes) public uniswapV3Path; + // Pool ID to use for reward tokens on Balancer + mapping(address => bytes32) public balancerPoolId; + + struct CurvePoolIndices { + // Casted into uint128 and stored in a struct to save gas + uint128 rewardTokenIndex; + uint128 baseTokenIndex; + } + // Packed indices of assets on the Curve pool + mapping(address => CurvePoolIndices) public curvePoolIndices; + + constructor(address _vaultAddress, address _baseTokenAddress) { + require(_vaultAddress != address(0)); + require(_baseTokenAddress != address(0)); + vaultAddress = _vaultAddress; + baseTokenAddress = _baseTokenAddress; + + // Cache decimals as well + baseTokenDecimals = Helpers.getDecimals(_baseTokenAddress); } /*************************************** Configuration ****************************************/ - /** - * @dev Throws if called by any address other than the Vault. - */ - modifier onlyVaultOrGovernor() { - require( - msg.sender == vaultAddress || isGovernor(), - "Caller is not the Vault or Governor" - ); - _; - } - /** * Set the Address receiving rewards proceeds. * @param _rewardProceedsAddress Address of the reward token */ - function setRewardsProceedsAddress(address _rewardProceedsAddress) + function setRewardProceedsAddress(address _rewardProceedsAddress) external onlyGovernor { - require( - _rewardProceedsAddress != address(0), - "Rewards proceeds address should be a non zero address" - ); + if (_rewardProceedsAddress == address(0)) { + revert EmptyAddress(); + } rewardProceedsAddress = _rewardProceedsAddress; + emit RewardProceedsAddressChanged(_rewardProceedsAddress); } /** * @dev Add/update a reward token configuration that holds harvesting config variables * @param _tokenAddress Address of the reward token - * @param _allowedSlippageBps uint16 maximum allowed slippage denominated in basis points. - * Example: 300 == 3% slippage - * @param _harvestRewardBps uint16 amount of reward tokens the caller of the function is rewarded. - * Example: 100 == 1% - * @param _uniswapV2CompatibleAddr Address Address of a UniswapV2 compatible contract to perform - * the exchange from reward tokens to stablecoin (currently hard-coded to USDT) - * @param _liquidationLimit uint256 Maximum amount of token to be sold per one swap function call. - * When value is 0 there is no limit. - * @param _doSwapRewardToken bool When true the reward token is being swapped. In a need of (temporarily) - * disabling the swapping of a reward token this needs to be set to false. + * @param tokenConfig.allowedSlippageBps uint16 maximum allowed slippage denominated in basis points. + * Example: 300 == 3% slippage + * @param tokenConfig.harvestRewardBps uint16 amount of reward tokens the caller of the function is rewarded. + * Example: 100 == 1% + * @param tokenConfig.swapPlatformAddr Address Address of a UniswapV2 compatible contract to perform + * the exchange from reward tokens to stablecoin (currently hard-coded to USDT) + * @param tokenConfig.liquidationLimit uint256 Maximum amount of token to be sold per one swap function call. + * When value is 0 there is no limit. + * @param tokenConfig.doSwapRewardToken bool Disables swapping of the token when set to true, + * does not cause it to revert though. + * @param tokenConfig.swapPlatform SwapPlatform to use for Swapping + * @param swapData Additional data required for swapping */ function setRewardTokenConfig( address _tokenAddress, - uint16 _allowedSlippageBps, - uint16 _harvestRewardBps, - address _uniswapV2CompatibleAddr, - uint256 _liquidationLimit, - bool _doSwapRewardToken + RewardTokenConfig calldata tokenConfig, + bytes calldata swapData ) external onlyGovernor { - require( - _allowedSlippageBps <= 1000, - "Allowed slippage should not be over 10%" - ); - require( - _harvestRewardBps <= 1000, - "Harvest reward fee should not be over 10%" - ); - require( - _uniswapV2CompatibleAddr != address(0), - "Uniswap compatible address should be non zero address" - ); - - RewardTokenConfig memory tokenConfig = RewardTokenConfig({ - allowedSlippageBps: _allowedSlippageBps, - harvestRewardBps: _harvestRewardBps, - uniswapV2CompatibleAddr: _uniswapV2CompatibleAddr, - doSwapRewardToken: _doSwapRewardToken, - liquidationLimit: _liquidationLimit - }); + if (tokenConfig.allowedSlippageBps > 1000) { + revert InvalidSlippageBps(); + } - address oldUniswapAddress = rewardTokenConfigs[_tokenAddress] - .uniswapV2CompatibleAddr; - rewardTokenConfigs[_tokenAddress] = tokenConfig; + if (tokenConfig.harvestRewardBps > 1000) { + revert InvalidHarvestRewardBps(); + } - IERC20 token = IERC20(_tokenAddress); + address newRouterAddress = tokenConfig.swapPlatformAddr; + if (newRouterAddress == address(0)) { + // Swap router address should be non zero address + revert EmptyAddress(); + } - address priceProvider = IVault(vaultAddress).priceProvider(); + address oldRouterAddress = rewardTokenConfigs[_tokenAddress] + .swapPlatformAddr; + rewardTokenConfigs[_tokenAddress] = tokenConfig; // Revert if feed does not exist // slither-disable-next-line unused-return - IOracle(priceProvider).price(_tokenAddress); + IOracle(IVault(vaultAddress).priceProvider()).price(_tokenAddress); + IERC20 token = IERC20(_tokenAddress); // if changing token swap provider cancel existing allowance if ( - /* oldUniswapAddress == address(0) when there is no pre-existing + /* oldRouterAddress == address(0) when there is no pre-existing * configuration for said rewards token */ - oldUniswapAddress != address(0) && - oldUniswapAddress != _uniswapV2CompatibleAddr + oldRouterAddress != address(0) && + oldRouterAddress != newRouterAddress ) { - token.safeApprove(oldUniswapAddress, 0); + token.safeApprove(oldRouterAddress, 0); + } + + // Give SwapRouter infinite approval when needed + if (oldRouterAddress != newRouterAddress) { + token.safeApprove(newRouterAddress, 0); + token.safeApprove(newRouterAddress, type(uint256).max); } - // Give Uniswap infinite approval when needed - if (oldUniswapAddress != _uniswapV2CompatibleAddr) { - token.safeApprove(_uniswapV2CompatibleAddr, 0); - token.safeApprove(_uniswapV2CompatibleAddr, type(uint256).max); + SwapPlatform _platform = tokenConfig.swapPlatform; + if (_platform == SwapPlatform.UniswapV2Compatible) { + uniswapV2Path[_tokenAddress] = _decodeUniswapV2Path( + swapData, + _tokenAddress + ); + } else if (_platform == SwapPlatform.UniswapV3) { + uniswapV3Path[_tokenAddress] = _decodeUniswapV3Path( + swapData, + _tokenAddress + ); + } else if (_platform == SwapPlatform.Balancer) { + balancerPoolId[_tokenAddress] = _decodeBalancerPoolId( + swapData, + newRouterAddress, + _tokenAddress + ); + } else if (_platform == SwapPlatform.Curve) { + curvePoolIndices[_tokenAddress] = _decodeCurvePoolIndices( + swapData, + newRouterAddress, + _tokenAddress + ); + } else { + // Note: This code is unreachable since Solidity reverts when + // the value is outside the range of defined values of the enum + // (even if it's under the max length of the base type) + revert InvalidSwapPlatform(_platform); } emit RewardTokenConfigUpdated( _tokenAddress, - _allowedSlippageBps, - _harvestRewardBps, - _uniswapV2CompatibleAddr, - _liquidationLimit, - _doSwapRewardToken + tokenConfig.allowedSlippageBps, + tokenConfig.harvestRewardBps, + _platform, + newRouterAddress, + swapData, + tokenConfig.liquidationLimit, + tokenConfig.doSwapRewardToken ); } + /** + * @dev Decodes the data passed into Uniswap V2 path and validates + * it to make sure the path is for `token` to `baseToken` + * + * @param data Ecnoded data passed to the `setRewardTokenConfig` + * @param token The address of the reward token + * @return path The validated Uniswap V2 path + */ + function _decodeUniswapV2Path(bytes calldata data, address token) + internal + view + returns (address[] memory path) + { + (path) = abi.decode(data, (address[])); + uint256 len = path.length; + + if (len < 2) { + // Path should have at least two tokens + revert InvalidUniswapV2PathLength(); + } + + // Do some validation + if (path[0] != token) { + revert InvalidTokenInSwapPath(path[0]); + } + + if (path[len - 1] != baseTokenAddress) { + revert InvalidTokenInSwapPath(path[len - 1]); + } + } + + /** + * @dev Decodes the data passed into Uniswap V3 path and validates + * it to make sure the path is for `token` to `baseToken` + * + * @param data Ecnoded data passed to the `setRewardTokenConfig` + * @param token The address of the reward token + * @return path The validated Uniswap V3 path + */ + function _decodeUniswapV3Path(bytes calldata data, address token) + internal + view + returns (bytes calldata path) + { + path = data; + + address decodedAddress = address(uint160(bytes20(data[0:20]))); + + if (decodedAddress != token) { + // Invalid Reward Token in swap path + revert InvalidTokenInSwapPath(decodedAddress); + } + + decodedAddress = address(uint160(bytes20(data[path.length - 20:]))); + if (decodedAddress != baseTokenAddress) { + // Invalid Base Token in swap path + revert InvalidTokenInSwapPath(decodedAddress); + } + } + + /** + * @dev Decodes the data passed to Balancer Pool ID + * + * @param data Ecnoded data passed to the `setRewardTokenConfig` + * @return poolId The pool ID + */ + function _decodeBalancerPoolId( + bytes calldata data, + address balancerVault, + address token + ) internal view returns (bytes32 poolId) { + (poolId) = abi.decode(data, (bytes32)); + + if (poolId == bytes32(0)) { + revert EmptyBalancerPoolId(); + } + + IBalancerVault bVault = IBalancerVault(balancerVault); + + // Note: this reverts if token is not a pool asset + // slither-disable-next-line unused-return + bVault.getPoolTokenInfo(poolId, token); + + // slither-disable-next-line unused-return + bVault.getPoolTokenInfo(poolId, baseTokenAddress); + } + + /** + * @dev Decodes the data passed to get the pool indices and + * checks it against the Curve Pool to make sure it's + * not misconfigured. The indices are packed into a single + * uint256 for gas savings + * + * @param data Ecnoded data passed to the `setRewardTokenConfig` + * @param poolAddress Curve pool address + * @param token The address of the reward token + * @return indices Packed pool asset indices + */ + function _decodeCurvePoolIndices( + bytes calldata data, + address poolAddress, + address token + ) internal view returns (CurvePoolIndices memory indices) { + indices = abi.decode(data, (CurvePoolIndices)); + + ICurvePool pool = ICurvePool(poolAddress); + if (token != pool.coins(indices.rewardTokenIndex)) { + revert InvalidCurvePoolAssetIndex(token); + } + if (baseTokenAddress != pool.coins(indices.baseTokenIndex)) { + revert InvalidCurvePoolAssetIndex(baseTokenAddress); + } + } + /** * @dev Flags a strategy as supported or not supported one * @param _strategyAddress Address of the strategy @@ -190,7 +381,7 @@ abstract contract BaseHarvester is Governable { */ function setSupportedStrategy(address _strategyAddress, bool _isSupported) external - onlyVaultOrGovernor + onlyGovernor { supportedStrategies[_strategyAddress] = _isSupported; emit SupportedStrategyUpdate(_strategyAddress, _isSupported); @@ -214,40 +405,9 @@ abstract contract BaseHarvester is Governable { } /** - * @dev Collect reward tokens from all strategies - */ - function harvest() external onlyGovernor nonReentrant { - _harvest(); - } - - /** - * @dev Swap all supported swap tokens for stablecoins via Uniswap. - */ - function swap() external onlyGovernor nonReentrant { - _swap(rewardProceedsAddress); - } - - /* - * @dev Collect reward tokens from all strategies and swap for supported - * stablecoin via Uniswap - */ - function harvestAndSwap() external onlyGovernor nonReentrant { - _harvest(); - _swap(rewardProceedsAddress); - } - - /** - * @dev Collect reward tokens for a specific strategy. - * @param _strategyAddr Address of the strategy to collect rewards from - */ - function harvest(address _strategyAddr) external onlyGovernor nonReentrant { - _harvest(_strategyAddr); - } - - /** - * @dev Collect reward tokens for a specific strategy and swap for supported - * stablecoin via Uniswap. Can be called by anyone. Rewards incentivizing - * the caller are sent to the caller of this function. + * @dev Collect reward tokens from a specific strategy and swap them for + * base token on the configured swap platform. Can be called by anyone. + * Rewards incentivizing the caller are sent to the caller of this function. * @param _strategyAddr Address of the strategy to collect rewards from */ function harvestAndSwap(address _strategyAddr) external nonReentrant { @@ -256,8 +416,8 @@ abstract contract BaseHarvester is Governable { } /** - * @dev Collect reward tokens for a specific strategy and swap for supported - * stablecoin via Uniswap. Can be called by anyone. + * @dev Collect reward tokens from a specific strategy and swap them for + * base token on the configured swap platform. Can be called by anyone * @param _strategyAddr Address of the strategy to collect rewards from * @param _rewardTo Address where to send a share of harvest rewards to as an incentive * for executing this function @@ -271,32 +431,8 @@ abstract contract BaseHarvester is Governable { } /** - * @dev Governance convenience function to swap a specific _rewardToken and send - * rewards to the vault. - * @param _swapToken Address of the token to swap. - */ - function swapRewardToken(address _swapToken) - external - onlyGovernor - nonReentrant - { - _swap(_swapToken, rewardProceedsAddress); - } - - /** - * @dev Collect reward tokens from all strategies - */ - function _harvest() internal { - address[] memory allStrategies = IVault(vaultAddress) - .getAllStrategies(); - for (uint256 i = 0; i < allStrategies.length; i++) { - _harvest(allStrategies[i]); - } - } - - /** - * @dev Collect reward tokens for a specific strategy and swap for supported - * stablecoin via Uniswap. + * @dev Collect reward tokens from a specific strategy and swap them for + * base token on the configured swap platform * @param _strategyAddr Address of the strategy to collect rewards from * @param _rewardTo Address where to send a share of harvest rewards to as an incentive * for executing this function @@ -307,52 +443,298 @@ abstract contract BaseHarvester is Governable { _harvest(_strategyAddr); IStrategy strategy = IStrategy(_strategyAddr); address[] memory rewardTokens = strategy.getRewardTokenAddresses(); - for (uint256 i = 0; i < rewardTokens.length; i++) { - _swap(rewardTokens[i], _rewardTo); + IOracle priceProvider = IOracle(IVault(vaultAddress).priceProvider()); + uint256 len = rewardTokens.length; + for (uint256 i = 0; i < len; ++i) { + _swap(rewardTokens[i], _rewardTo, priceProvider); } } /** - * @dev Collect reward tokens from a single strategy and swap them for a - * supported stablecoin via Uniswap + * @dev Collect reward tokens from a specific strategy and swap them for + * base token on the configured swap platform * @param _strategyAddr Address of the strategy to collect rewards from. */ function _harvest(address _strategyAddr) internal { - require( - supportedStrategies[_strategyAddr], - "Not a valid strategy address" - ); + if (!supportedStrategies[_strategyAddr]) { + revert UnsupportedStrategy(_strategyAddr); + } IStrategy strategy = IStrategy(_strategyAddr); strategy.collectRewardTokens(); } /** - * @dev Swap all supported swap tokens for stablecoins via Uniswap. And send the incentive part - * of the rewards to _rewardTo address. - * @param _rewardTo Address where to send a share of harvest rewards to as an incentive - * for executing this function + * @dev Swap a reward token for the base token on the configured + * swap platform. The token must have a registered price feed + * with the price provider + * @param _swapToken Address of the token to swap + * @param _rewardTo Address where to send the share of harvest rewards to + * @param _priceProvider Oracle to get prices of the swap token */ - function _swap(address _rewardTo) internal { - address[] memory allStrategies = IVault(vaultAddress) - .getAllStrategies(); - - for (uint256 i = 0; i < allStrategies.length; i++) { - IStrategy strategy = IStrategy(allStrategies[i]); - address[] memory rewardTokenAddresses = strategy - .getRewardTokenAddresses(); - - for (uint256 j = 0; j < rewardTokenAddresses.length; j++) { - _swap(rewardTokenAddresses[j], _rewardTo); - } + function _swap( + address _swapToken, + address _rewardTo, + IOracle _priceProvider + ) internal virtual { + RewardTokenConfig memory tokenConfig = rewardTokenConfigs[_swapToken]; + + /* This will trigger a return when reward token configuration has not yet been set + * or we have temporarily disabled swapping of specific reward token via setting + * doSwapRewardToken to false. + */ + if (!tokenConfig.doSwapRewardToken) { + return; + } + + uint256 balance = IERC20(_swapToken).balanceOf(address(this)); + + if (balance == 0) { + return; + } + + if (tokenConfig.liquidationLimit > 0) { + balance = Math.min(balance, tokenConfig.liquidationLimit); + } + + // This'll revert if there is no price feed + uint256 oraclePrice = _priceProvider.price(_swapToken); + + // Oracle price is 1e18 + uint256 minExpected = (balance * + (1e4 - tokenConfig.allowedSlippageBps) * // max allowed slippage + oraclePrice).scaleBy( + baseTokenDecimals, + Helpers.getDecimals(_swapToken) + ) / + 1e4 / // fix the max slippage decimal position + 1e18; // and oracle price decimals position + + // Do the swap + uint256 amountReceived = _doSwap( + tokenConfig.swapPlatform, + tokenConfig.swapPlatformAddr, + _swapToken, + balance, + minExpected + ); + + if (amountReceived < minExpected) { + revert SlippageError(amountReceived, minExpected); + } + + emit RewardTokenSwapped( + _swapToken, + baseTokenAddress, + tokenConfig.swapPlatform, + balance, + amountReceived + ); + + IERC20 baseToken = IERC20(baseTokenAddress); + uint256 baseTokenBalance = baseToken.balanceOf(address(this)); + if (baseTokenBalance < amountReceived) { + // Note: It's possible to bypass this check by transfering `baseToken` + // directly to Harvester before calling the `harvestAndSwap`. However, + // there's no incentive for an attacker to do that. Doing a balance diff + // will increase the gas cost significantly + revert BalanceMismatchAfterSwap(baseTokenBalance, amountReceived); + } + + // Farmer only gets fee from the base amount they helped farm, + // They do not get anything from anything that already was there + // on the Harvester + uint256 farmerFee = amountReceived.mulTruncateScale( + tokenConfig.harvestRewardBps, + 1e4 + ); + uint256 protcolYield = baseTokenBalance - farmerFee; + + baseToken.safeTransfer(rewardProceedsAddress, protcolYield); + baseToken.safeTransfer(_rewardTo, farmerFee); + emit RewardProceedsTransferred( + baseTokenAddress, + _rewardTo, + protcolYield, + farmerFee + ); + } + + function _doSwap( + SwapPlatform swapPlatform, + address routerAddress, + address rewardTokenAddress, + uint256 amountIn, + uint256 minAmountOut + ) internal returns (uint256 amountOut) { + if (swapPlatform == SwapPlatform.UniswapV2Compatible) { + return + _swapWithUniswapV2( + routerAddress, + rewardTokenAddress, + amountIn, + minAmountOut + ); + } else if (swapPlatform == SwapPlatform.UniswapV3) { + return + _swapWithUniswapV3( + routerAddress, + rewardTokenAddress, + amountIn, + minAmountOut + ); + } else if (swapPlatform == SwapPlatform.Balancer) { + return + _swapWithBalancer( + routerAddress, + rewardTokenAddress, + amountIn, + minAmountOut + ); + } else if (swapPlatform == SwapPlatform.Curve) { + return + _swapWithCurve( + routerAddress, + rewardTokenAddress, + amountIn, + minAmountOut + ); + } else { + // Should never be invoked since we catch invalid values + // in the `setRewardTokenConfig` function before it's set + revert InvalidSwapPlatform(swapPlatform); } } /** - * @dev Swap a reward token for stablecoins on Uniswap. The token must have - * a registered price feed with the price provider. - * @param _swapToken Address of the token to swap. - * @param _rewardTo Address where to send the share of harvest rewards to + * @dev Swaps the token to `baseToken` with Uniswap V2 + * + * @param routerAddress Uniswap V2 Router address + * @param swapToken Address of the tokenIn + * @param amountIn Amount of `swapToken` to swap + * @param minAmountOut Minimum expected amount of `baseToken` + * + * @return amountOut Amount of `baseToken` received after the swap + */ + function _swapWithUniswapV2( + address routerAddress, + address swapToken, + uint256 amountIn, + uint256 minAmountOut + ) internal returns (uint256 amountOut) { + address[] memory path = uniswapV2Path[swapToken]; + + uint256[] memory amounts = IUniswapV2Router(routerAddress) + .swapExactTokensForTokens( + amountIn, + minAmountOut, + path, + address(this), + block.timestamp + ); + + amountOut = amounts[amounts.length - 1]; + } + + /** + * @dev Swaps the token to `baseToken` with Uniswap V3 + * + * @param routerAddress Uniswap V3 Router address + * @param swapToken Address of the tokenIn + * @param amountIn Amount of `swapToken` to swap + * @param minAmountOut Minimum expected amount of `baseToken` + * + * @return amountOut Amount of `baseToken` received after the swap + */ + function _swapWithUniswapV3( + address routerAddress, + address swapToken, + uint256 amountIn, + uint256 minAmountOut + ) internal returns (uint256 amountOut) { + bytes memory path = uniswapV3Path[swapToken]; + + IUniswapV3Router.ExactInputParams memory params = IUniswapV3Router + .ExactInputParams({ + path: path, + recipient: address(this), + deadline: block.timestamp, + amountIn: amountIn, + amountOutMinimum: minAmountOut + }); + amountOut = IUniswapV3Router(routerAddress).exactInput(params); + } + + /** + * @dev Swaps the token to `baseToken` on Balancer + * + * @param balancerVaultAddress BalancerVaultAddress + * @param swapToken Address of the tokenIn + * @param amountIn Amount of `swapToken` to swap + * @param minAmountOut Minimum expected amount of `baseToken` + * + * @return amountOut Amount of `baseToken` received after the swap */ - function _swap(address _swapToken, address _rewardTo) internal virtual; + function _swapWithBalancer( + address balancerVaultAddress, + address swapToken, + uint256 amountIn, + uint256 minAmountOut + ) internal returns (uint256 amountOut) { + bytes32 poolId = balancerPoolId[swapToken]; + + IBalancerVault.SingleSwap memory singleSwap = IBalancerVault + .SingleSwap({ + poolId: poolId, + kind: IBalancerVault.SwapKind.GIVEN_IN, + assetIn: swapToken, + assetOut: baseTokenAddress, + amount: amountIn, + userData: hex"" + }); + + IBalancerVault.FundManagement memory fundMgmt = IBalancerVault + .FundManagement({ + sender: address(this), + fromInternalBalance: false, + recipient: payable(address(this)), + toInternalBalance: false + }); + + amountOut = IBalancerVault(balancerVaultAddress).swap( + singleSwap, + fundMgmt, + minAmountOut, + block.timestamp + ); + } + + /** + * @dev Swaps the token to `baseToken` on Curve + * + * @param poolAddress Curve Pool Address + * @param swapToken Address of the tokenIn + * @param amountIn Amount of `swapToken` to swap + * @param minAmountOut Minimum expected amount of `baseToken` + * + * @return amountOut Amount of `baseToken` received after the swap + */ + function _swapWithCurve( + address poolAddress, + address swapToken, + uint256 amountIn, + uint256 minAmountOut + ) internal returns (uint256 amountOut) { + CurvePoolIndices memory indices = curvePoolIndices[swapToken]; + + // Note: Not all CurvePools return the `amountOut`, make sure + // to use only pool that do. Otherwise the swap would revert + // always + amountOut = ICurvePool(poolAddress).exchange( + uint256(indices.rewardTokenIndex), + uint256(indices.baseTokenIndex), + amountIn, + minAmountOut + ); + } } diff --git a/contracts/contracts/harvest/Harvester.sol b/contracts/contracts/harvest/Harvester.sol index 4a78cd2c03..3c8839e3bd 100644 --- a/contracts/contracts/harvest/Harvester.sol +++ b/contracts/contracts/harvest/Harvester.sol @@ -1,104 +1,10 @@ // SPDX-License-Identifier: MIT pragma solidity ^0.8.0; -import { IERC20 } from "@openzeppelin/contracts/token/ERC20/IERC20.sol"; -import { SafeERC20 } from "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol"; -import { SafeMath } from "@openzeppelin/contracts/utils/math/SafeMath.sol"; -import "@openzeppelin/contracts/utils/math/Math.sol"; - -import { StableMath } from "../utils/StableMath.sol"; -import { IVault } from "../interfaces/IVault.sol"; -import { IOracle } from "../interfaces/IOracle.sol"; import { BaseHarvester } from "./BaseHarvester.sol"; -import { IUniswapV2Router } from "../interfaces/uniswap/IUniswapV2Router02.sol"; -import "../utils/Helpers.sol"; contract Harvester is BaseHarvester { - using SafeERC20 for IERC20; - using SafeMath for uint256; - using StableMath for uint256; - - address public immutable usdtAddress; - - /** - * @dev Constructor to set up initial internal state - * @param _vault Address of the Vault - * @param _usdtAddress Address of Tether - */ - constructor(address _vault, address _usdtAddress) BaseHarvester(_vault) { - require(address(_usdtAddress) != address(0)); - usdtAddress = _usdtAddress; - } - - /** - * @dev Swap a reward token for stablecoins on Uniswap. The token must have - * a registered price feed with the price provider. - * @param _swapToken Address of the token to swap. - * @param _rewardTo Address where to send the share of harvest rewards to - */ - function _swap(address _swapToken, address _rewardTo) internal override { - RewardTokenConfig memory tokenConfig = rewardTokenConfigs[_swapToken]; - - /* This will trigger a return when reward token configuration has not yet been set - * or we have temporarily disabled swapping of specific reward token via setting - * doSwapRewardToken to false. - */ - if (!tokenConfig.doSwapRewardToken) { - return; - } - - address priceProvider = IVault(vaultAddress).priceProvider(); - - IERC20 swapToken = IERC20(_swapToken); - uint256 balance = swapToken.balanceOf(address(this)); - - if (balance == 0) { - return; - } - - uint256 balanceToSwap = Math.min(balance, tokenConfig.liquidationLimit); - - // This'll revert if there is no price feed - uint256 oraclePrice = IOracle(priceProvider).price(_swapToken); - - // Oracle price is 1e18, USDT output is 1e6 - uint256 minExpected = (balanceToSwap * - (1e4 - tokenConfig.allowedSlippageBps) * // max allowed slippage - oraclePrice).scaleBy(6, Helpers.getDecimals(_swapToken)) / - 1e4 / // fix the max slippage decimal position - 1e18; // and oracle price decimals position - - // Uniswap redemption path - address[] memory path = new address[](3); - path[0] = _swapToken; - path[1] = IUniswapV2Router(tokenConfig.uniswapV2CompatibleAddr).WETH(); - path[2] = usdtAddress; - - // slither-disable-next-line unused-return - IUniswapV2Router(tokenConfig.uniswapV2CompatibleAddr) - .swapExactTokensForTokens( - balanceToSwap, - minExpected, - path, - address(this), - block.timestamp - ); - - IERC20 usdt = IERC20(usdtAddress); - uint256 usdtBalance = usdt.balanceOf(address(this)); - - uint256 vaultBps = 1e4 - tokenConfig.harvestRewardBps; - uint256 rewardsProceedsShare = (usdtBalance * vaultBps) / 1e4; - - require( - vaultBps > tokenConfig.harvestRewardBps, - "Address receiving harvest incentive is receiving more rewards than the rewards proceeds address" - ); - - usdt.safeTransfer(rewardProceedsAddress, rewardsProceedsShare); - usdt.safeTransfer( - _rewardTo, - usdtBalance - rewardsProceedsShare // remaining share of the rewards - ); - } + constructor(address _vault, address _usdtAddress) + BaseHarvester(_vault, _usdtAddress) + {} } diff --git a/contracts/contracts/harvest/OETHHarvester.sol b/contracts/contracts/harvest/OETHHarvester.sol index d767268f56..609a5fc038 100644 --- a/contracts/contracts/harvest/OETHHarvester.sol +++ b/contracts/contracts/harvest/OETHHarvester.sol @@ -1,98 +1,10 @@ // SPDX-License-Identifier: MIT pragma solidity ^0.8.0; -import { IERC20 } from "@openzeppelin/contracts/token/ERC20/IERC20.sol"; -import { SafeERC20 } from "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol"; -import { SafeMath } from "@openzeppelin/contracts/utils/math/SafeMath.sol"; -import "@openzeppelin/contracts/utils/math/Math.sol"; - -import { StableMath } from "../utils/StableMath.sol"; -import { IVault } from "../interfaces/IVault.sol"; -import { IOracle } from "../interfaces/IOracle.sol"; import { BaseHarvester } from "./BaseHarvester.sol"; -import { IUniswapV2Router } from "../interfaces/uniswap/IUniswapV2Router02.sol"; -import "../utils/Helpers.sol"; contract OETHHarvester is BaseHarvester { - using SafeERC20 for IERC20; - using SafeMath for uint256; - using StableMath for uint256; - - // "_usdtAddress" is set to Vault's address, but is really not used - constructor(address _vault) BaseHarvester(_vault) {} - - /** - * @dev Swap a reward token for stablecoins on Uniswap. The token must have - * a registered price feed with the price provider. - * @param _swapToken Address of the token to swap. - * @param _rewardTo Address where to send the share of harvest rewards to - */ - function _swap(address _swapToken, address _rewardTo) internal override { - RewardTokenConfig memory tokenConfig = rewardTokenConfigs[_swapToken]; - - /* This will trigger a return when reward token configuration has not yet been set - * or we have temporarily disabled swapping of specific reward token via setting - * doSwapRewardToken to false. - */ - if (!tokenConfig.doSwapRewardToken) { - return; - } - - address priceProvider = IVault(vaultAddress).priceProvider(); - - IERC20 swapToken = IERC20(_swapToken); - uint256 balance = swapToken.balanceOf(address(this)); - - if (balance == 0) { - return; - } - - uint256 balanceToSwap = Math.min(balance, tokenConfig.liquidationLimit); - - // Find reward token price feed paired with (W)ETH - uint256 oraclePrice = IOracle(priceProvider).price(_swapToken); - - // Oracle price is in 18 digits, WETH decimals are in 1e18 - uint256 minExpected = (balanceToSwap * - (1e4 - tokenConfig.allowedSlippageBps) * // max allowed slippage - oraclePrice).scaleBy(18, Helpers.getDecimals(_swapToken)) / - 1e4 / // fix the max slippage decimal position - 1e18; // and oracle price decimals position - - address wethAddress = IUniswapV2Router( - tokenConfig.uniswapV2CompatibleAddr - ).WETH(); - - // Uniswap redemption path - address[] memory path = new address[](2); - path[0] = _swapToken; - path[1] = wethAddress; - - // slither-disable-next-line unused-return - IUniswapV2Router(tokenConfig.uniswapV2CompatibleAddr) - .swapExactTokensForTokens( - balanceToSwap, - minExpected, - path, - address(this), - block.timestamp - ); - - IERC20 wethErc20 = IERC20(wethAddress); - uint256 wethBalance = wethErc20.balanceOf(address(this)); - - uint256 vaultBps = 1e4 - tokenConfig.harvestRewardBps; - uint256 rewardsProceedsShare = (wethBalance * vaultBps) / 1e4; - - require( - vaultBps > tokenConfig.harvestRewardBps, - "Address receiving harvest incentive is receiving more rewards than the rewards proceeds address" - ); - - wethErc20.safeTransfer(rewardProceedsAddress, rewardsProceedsShare); - wethErc20.safeTransfer( - _rewardTo, - wethBalance - rewardsProceedsShare // remaining share of the rewards - ); - } + constructor(address _vault, address _wethAddress) + BaseHarvester(_vault, _wethAddress) + {} } diff --git a/contracts/contracts/interfaces/IHarvester.sol b/contracts/contracts/interfaces/IHarvester.sol deleted file mode 100644 index eeb77907c8..0000000000 --- a/contracts/contracts/interfaces/IHarvester.sol +++ /dev/null @@ -1,64 +0,0 @@ -// SPDX-License-Identifier: MIT -pragma solidity ^0.8.0; - -interface IHarvester { - event RewardTokenConfigUpdated( - address _tokenAddress, - uint16 _allowedSlippageBps, - uint16 _harvestRewardBps, - address _uniswapV2CompatibleAddr, - uint256 _liquidationLimit, - bool _doSwapRewardToken - ); - - struct RewardTokenConfig { - uint16 allowedSlippageBps; - uint16 harvestRewardBps; - address uniswapV2CompatibleAddr; - bool doSwapRewardToken; - uint256 liquidationLimit; - } - - // Governable.sol - function transferGovernance(address _newGovernor) external; - - function claimGovernance() external; - - function governor() external view returns (address); - - // Harvester.sol - function addSwapToken(address _addr) external; - - function removeSwapToken(address _addr) external; - - function setRewardsProceedsAddress(address _rewardProceedsAddress) external; - - function harvest() external; - - function harvest(address _strategyAddr) external; - - function swap() external; - - function harvestAndSwap() external; - - function harvestAndSwap(address _strategyAddr) external; - - function harvestAndSwap(address _strategyAddr, address _rewardTo) external; - - function setSupportedStrategy(address _strategyAddress, bool _isSupported) - external; - - function rewardTokenConfigs(address _tokenAddress) - external - view - returns (RewardTokenConfig calldata); - - function setRewardTokenConfig( - address _tokenAddress, - uint16 _allowedSlippageBps, - uint16 _harvestRewardBps, - address _uniswapV2CompatibleAddr, - uint256 _liquidationLimit, - bool _doSwapRewardToken - ) external; -} diff --git a/contracts/contracts/interfaces/balancer/IBalancerVault.sol b/contracts/contracts/interfaces/balancer/IBalancerVault.sol index 61e3a0dd8e..c0736aab9f 100644 --- a/contracts/contracts/interfaces/balancer/IBalancerVault.sol +++ b/contracts/contracts/interfaces/balancer/IBalancerVault.sol @@ -160,4 +160,42 @@ interface IBalancerVault { TRANSFER_INTERNAL, TRANSFER_EXTERNAL } + + enum SwapKind { + GIVEN_IN, + GIVEN_OUT + } + + struct SingleSwap { + bytes32 poolId; + SwapKind kind; + address assetIn; + address assetOut; + uint256 amount; + bytes userData; + } + + struct FundManagement { + address sender; + bool fromInternalBalance; + address payable recipient; + bool toInternalBalance; + } + + function swap( + SingleSwap calldata singleSwap, + FundManagement calldata funds, + uint256 limit, + uint256 deadline + ) external returns (uint256 amountCalculated); + + function getPoolTokenInfo(bytes32 poolId, address token) + external + view + returns ( + uint256 cash, + uint256 managed, + uint256 lastChangeBlock, + address assetManager + ); } diff --git a/contracts/contracts/interfaces/balancer/IOracleWeightedPool.sol b/contracts/contracts/interfaces/balancer/IOracleWeightedPool.sol new file mode 100644 index 0000000000..d58d6b89d4 --- /dev/null +++ b/contracts/contracts/interfaces/balancer/IOracleWeightedPool.sol @@ -0,0 +1,44 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.0; + +// The three values that can be queried: +// +// - PAIR_PRICE: the price of the tokens in the Pool, expressed as the price of the second token in units of the +// first token. For example, if token A is worth $2, and token B is worth $4, the pair price will be 2.0. +// Note that the price is computed *including* the tokens decimals. This means that the pair price of a Pool with +// DAI and USDC will be close to 1.0, despite DAI having 18 decimals and USDC 6. +// +// - BPT_PRICE: the price of the Pool share token (BPT), in units of the first token. +// Note that the price is computed *including* the tokens decimals. This means that the BPT price of a Pool with +// USDC in which BPT is worth $5 will be 5.0, despite the BPT having 18 decimals and USDC 6. +// +// - INVARIANT: the value of the Pool's invariant, which serves as a measure of its liquidity. +enum Variable { + PAIR_PRICE, + BPT_PRICE, + INVARIANT +} + +/** + * @dev Information for a Time Weighted Average query. + * + * Each query computes the average over a window of duration `secs` seconds that ended `ago` seconds ago. For + * example, the average over the past 30 minutes is computed by settings secs to 1800 and ago to 0. If secs is 1800 + * and ago is 1800 as well, the average between 60 and 30 minutes ago is computed instead. + */ +struct OracleAverageQuery { + Variable variable; + uint256 secs; + uint256 ago; +} + +interface IOracleWeightedPool { + /** + * @dev Returns the time average weighted price corresponding to each of `queries`. Prices are represented as 18 + * decimal fixed point values. + */ + function getTimeWeightedAverage(OracleAverageQuery[] memory queries) + external + view + returns (uint256[] memory results); +} diff --git a/contracts/contracts/interfaces/IUniswapUniversalRouter.sol b/contracts/contracts/interfaces/uniswap/IUniswapUniversalRouter.sol similarity index 100% rename from contracts/contracts/interfaces/IUniswapUniversalRouter.sol rename to contracts/contracts/interfaces/uniswap/IUniswapUniversalRouter.sol diff --git a/contracts/contracts/interfaces/IUniswapV3Router.sol b/contracts/contracts/interfaces/uniswap/IUniswapV3Router.sol similarity index 100% rename from contracts/contracts/interfaces/IUniswapV3Router.sol rename to contracts/contracts/interfaces/uniswap/IUniswapV3Router.sol diff --git a/contracts/contracts/mocks/MockAura.sol b/contracts/contracts/mocks/MockAura.sol new file mode 100644 index 0000000000..d0cb19acd6 --- /dev/null +++ b/contracts/contracts/mocks/MockAura.sol @@ -0,0 +1,8 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.0; + +import "./MintableERC20.sol"; + +contract MockAura is MintableERC20 { + constructor() ERC20("Aura", "AURA") {} +} diff --git a/contracts/contracts/mocks/MockBalancerVault.sol b/contracts/contracts/mocks/MockBalancerVault.sol new file mode 100644 index 0000000000..eb140d01e8 --- /dev/null +++ b/contracts/contracts/mocks/MockBalancerVault.sol @@ -0,0 +1,62 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.0; + +import { IERC20 } from "@openzeppelin/contracts/token/ERC20/IERC20.sol"; +import { IBalancerVault } from "../interfaces/balancer/IBalancerVault.sol"; +import { MintableERC20 } from "./MintableERC20.sol"; +import { StableMath } from "../utils/StableMath.sol"; + +// import "hardhat/console.sol"; + +contract MockBalancerVault { + using StableMath for uint256; + uint256 public slippage = 1 ether; + bool public transferDisabled = false; + bool public slippageErrorDisabled = false; + + function swap( + IBalancerVault.SingleSwap calldata singleSwap, + IBalancerVault.FundManagement calldata funds, + uint256 minAmountOut, + uint256 + ) external returns (uint256 amountCalculated) { + amountCalculated = (minAmountOut * slippage) / 1 ether; + if (!slippageErrorDisabled) { + require(amountCalculated >= minAmountOut, "Slippage error"); + } + IERC20(singleSwap.assetIn).transferFrom( + funds.sender, + address(this), + singleSwap.amount + ); + if (!transferDisabled) { + MintableERC20(singleSwap.assetOut).mintTo( + funds.recipient, + amountCalculated + ); + } + } + + function setSlippage(uint256 _slippage) external { + slippage = _slippage; + } + + function getPoolTokenInfo(bytes32 poolId, address token) + external + view + returns ( + uint256, + uint256, + uint256, + address + ) + {} + + function disableTransfer() external { + transferDisabled = true; + } + + function disableSlippageError() external { + slippageErrorDisabled = true; + } +} diff --git a/contracts/contracts/mocks/MockOracleRouter.sol b/contracts/contracts/mocks/MockOracleRouter.sol index 1aecd3df8c..c06dfe84c8 100644 --- a/contracts/contracts/mocks/MockOracleRouter.sol +++ b/contracts/contracts/mocks/MockOracleRouter.sol @@ -6,7 +6,7 @@ import { IOracle } from "../interfaces/IOracle.sol"; import { Helpers } from "../utils/Helpers.sol"; import { StableMath } from "../utils/StableMath.sol"; import { SafeCast } from "@openzeppelin/contracts/utils/math/SafeCast.sol"; -import { OracleRouterBase } from "../oracle/OracleRouter.sol"; +import { OracleRouterBase } from "../oracle/OracleRouterBase.sol"; // @notice Oracle Router required for testing environment contract MockOracleRouter is OracleRouterBase { diff --git a/contracts/contracts/mocks/MockOracleRouterNoStale.sol b/contracts/contracts/mocks/MockOracleRouterNoStale.sol index 005e8348a0..e487aa7b28 100644 --- a/contracts/contracts/mocks/MockOracleRouterNoStale.sol +++ b/contracts/contracts/mocks/MockOracleRouterNoStale.sol @@ -6,7 +6,8 @@ import { IOracle } from "../interfaces/IOracle.sol"; import { Helpers } from "../utils/Helpers.sol"; import { StableMath } from "../utils/StableMath.sol"; import { SafeCast } from "@openzeppelin/contracts/utils/math/SafeCast.sol"; -import { OracleRouter, OETHOracleRouter } from "../oracle/OracleRouter.sol"; +import { OracleRouter } from "../oracle/OracleRouter.sol"; +import { OETHOracleRouter } from "../oracle/OETHOracleRouter.sol"; // @notice Oracle Router used to bypass staleness contract MockOracleRouterNoStale is OracleRouter { @@ -24,9 +25,11 @@ contract MockOracleRouterNoStale is OracleRouter { // @notice Oracle Router used to bypass staleness contract MockOETHOracleRouterNoStale is OETHOracleRouter { + constructor(address auraPriceFeed) OETHOracleRouter(auraPriceFeed) {} + function feedMetadata(address asset) internal - pure + view virtual override returns (address feedAddress, uint256 maxStaleness) diff --git a/contracts/contracts/mocks/MockOracleWeightedPool.sol b/contracts/contracts/mocks/MockOracleWeightedPool.sol new file mode 100644 index 0000000000..9e83c3ed9e --- /dev/null +++ b/contracts/contracts/mocks/MockOracleWeightedPool.sol @@ -0,0 +1,25 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.0; + +import { Variable, OracleAverageQuery, IOracleWeightedPool } from "../interfaces/balancer/IOracleWeightedPool.sol"; + +contract MockOracleWeightedPool is IOracleWeightedPool { + uint256[] public nextResults; + + constructor() { + nextResults = [1 ether, 1 ether]; + } + + function getTimeWeightedAverage(OracleAverageQuery[] memory) + external + view + override + returns (uint256[] memory results) + { + return nextResults; + } + + function setNextResults(uint256[] calldata results) external { + nextResults = results; + } +} diff --git a/contracts/contracts/mocks/MockUniswapRouter.sol b/contracts/contracts/mocks/MockUniswapRouter.sol index 365084ed27..5106a67924 100644 --- a/contracts/contracts/mocks/MockUniswapRouter.sol +++ b/contracts/contracts/mocks/MockUniswapRouter.sol @@ -2,17 +2,16 @@ pragma solidity ^0.8.0; import { IERC20 } from "@openzeppelin/contracts/token/ERC20/IERC20.sol"; - +import { MintableERC20 } from "./MintableERC20.sol"; import { IUniswapV2Router } from "../interfaces/uniswap/IUniswapV2Router02.sol"; import { Helpers } from "../utils/Helpers.sol"; import { StableMath } from "../utils/StableMath.sol"; -// import "hardhat/console.sol"; - contract MockUniswapRouter is IUniswapV2Router { using StableMath for uint256; mapping(address => address) public pairMaps; + uint256 public slippage = 1 ether; function initialize( address[] calldata _0tokens, @@ -27,6 +26,10 @@ contract MockUniswapRouter is IUniswapV2Router { } } + function setSlippage(uint256 _slippage) external { + slippage = _slippage; + } + function swapExactTokensForTokens( uint256 amountIn, uint256 amountOutMin, @@ -34,18 +37,18 @@ contract MockUniswapRouter is IUniswapV2Router { address to, // solhint-disable-next-line no-unused-vars uint256 - ) external override returns (uint256[] memory) { + ) external override returns (uint256[] memory amountsOut) { address tok0 = path[0]; - address tok1 = pairMaps[tok0]; - // Give 1:1 - uint256 amountOut = amountIn.scaleBy( - Helpers.getDecimals(tok1), - Helpers.getDecimals(tok0) - ); + address tok1 = path[path.length - 1]; + + uint256 amountOut = (amountOutMin * slippage) / 1 ether; require(amountOut >= amountOutMin, "Slippage error"); IERC20(tok0).transferFrom(msg.sender, address(this), amountIn); - IERC20(tok1).transfer(to, amountOut); + MintableERC20(tok1).mintTo(to, amountOut); + + amountsOut = new uint256[](path.length); + amountsOut[path.length - 1] = amountOut; } struct ExactInputParams { @@ -63,13 +66,10 @@ contract MockUniswapRouter is IUniswapV2Router { { (address tok0, address tok1) = _getFirstAndLastToken(params.path); - amountOut = params.amountIn.scaleBy( - Helpers.getDecimals(tok1), - Helpers.getDecimals(tok0) - ); + amountOut = (params.amountOutMinimum * slippage) / 1 ether; IERC20(tok0).transferFrom(msg.sender, address(this), params.amountIn); - IERC20(tok1).transfer(params.recipient, amountOut); + MintableERC20(tok1).mintTo(params.recipient, amountOut); require( amountOut >= params.amountOutMinimum, @@ -126,7 +126,7 @@ contract MockUniswapRouter is IUniswapV2Router { Helpers.getDecimals(token1) ); - IERC20(token1).transfer(recipient, amountOutMinimum); + MintableERC20(token1).mintTo(recipient, amountOutMinimum); } } diff --git a/contracts/contracts/mocks/curve/MockCurvePool.sol b/contracts/contracts/mocks/curve/MockCurvePool.sol index fb9f981ee2..578279e79a 100644 --- a/contracts/contracts/mocks/curve/MockCurvePool.sol +++ b/contracts/contracts/mocks/curve/MockCurvePool.sol @@ -15,12 +15,17 @@ contract MockCurvePool { address[] public coins; uint256[3] public balances; address lpToken; + uint256 public slippage = 1 ether; constructor(address[3] memory _coins, address _lpToken) { coins = _coins; lpToken = _lpToken; } + function setCoins(address[] memory _coins) external { + coins = _coins; + } + // Returns the same amount of LP tokens in 1e18 decimals function add_liquidity(uint256[3] calldata _amounts, uint256 _minAmount) external @@ -124,4 +129,20 @@ contract MockCurvePool { function fee() external pure returns (uint256) { return 1000000; } + + function exchange( + uint256 coin0, + uint256 coin1, + uint256 amountIn, + uint256 minAmountOut + ) external returns (uint256 amountOut) { + IERC20(coins[coin0]).transferFrom(msg.sender, address(this), amountIn); + amountOut = (minAmountOut * slippage) / 1 ether; + require(amountOut >= minAmountOut, "Slippage error"); + IMintableERC20(coins[coin1]).mintTo(msg.sender, amountOut); + } + + function setSlippage(uint256 _slippage) external { + slippage = _slippage; + } } diff --git a/contracts/contracts/oracle/AuraWETHPriceFeed.sol b/contracts/contracts/oracle/AuraWETHPriceFeed.sol new file mode 100644 index 0000000000..89f1b2e78a --- /dev/null +++ b/contracts/contracts/oracle/AuraWETHPriceFeed.sol @@ -0,0 +1,167 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.0; + +import { Variable, OracleAverageQuery, IOracleWeightedPool } from "../interfaces/balancer/IOracleWeightedPool.sol"; +import { Strategizable } from "../governance/Strategizable.sol"; +import { AggregatorV3Interface } from "../interfaces/chainlink/AggregatorV3Interface.sol"; +import { SafeCast } from "@openzeppelin/contracts/utils/math/SafeCast.sol"; + +contract AuraWETHPriceFeed is AggregatorV3Interface, Strategizable { + using SafeCast for uint256; + using SafeCast for int256; + + event PriceFeedPaused(); + event PriceFeedUnpaused(); + event ToleranceChanged(uint256 oldTolerance, uint256 newTolerance); + + error PriceFeedPausedError(); + error PriceFeedUnpausedError(); + error InvalidToleranceBps(); + error HighPriceVolatility(uint256 deviation); + + bool public paused; + uint256 public tolerance = 0.02 ether; // 2% by default + + // Fields to make it compatible with `AggregatorV3Interface` + uint8 public constant override decimals = 18; + string public constant override description = ""; + uint256 public constant override version = 1; + + IOracleWeightedPool public immutable auraOracleWeightedPool; + + constructor(address _auraOracleWeightedPool, address _governor) { + _setGovernor(_governor); + auraOracleWeightedPool = IOracleWeightedPool(_auraOracleWeightedPool); + } + + /** + * @dev Queries the OracleWeightedPool for TWAP of two intervals + * (1h data from 5m ago and the recent 5m data) and ensures that + * the price hasn't deviated too much and returns the most recent + * TWAP price. + * + * @return price The price scaled to 18 decimals + **/ + function price() external view returns (int256) { + return _price(); + } + + function _price() internal view returns (int256) { + if (paused) { + revert PriceFeedPausedError(); + } + OracleAverageQuery[] memory queries = new OracleAverageQuery[](2); + + queries[0] = OracleAverageQuery({ + variable: Variable.PAIR_PRICE, + secs: 3600, // Get 1h data + ago: 300 // From 5min ago + }); + queries[1] = OracleAverageQuery({ + variable: Variable.PAIR_PRICE, + secs: 300, // Get 5min data + ago: 0 // From now + }); + + uint256[] memory prices = auraOracleWeightedPool.getTimeWeightedAverage( + queries + ); + int256 price_1h = prices[0].toInt256(); + int256 price_5m = prices[1].toInt256(); + + int256 diff = (1e18 * (price_1h - price_5m)) / + ((price_1h + price_5m) / 2); + uint256 absDiff = diff >= 0 ? diff.toUint256() : (-diff).toUint256(); + + // Ensure the price hasn't moved too much (2% tolerance) + // between now and the past hour + if (absDiff > tolerance) { + revert HighPriceVolatility(absDiff); + } + + // Return the recent price + return price_5m; + } + + /** + * Pauses the price feed. Callable by Strategist as well. + **/ + function pause() external onlyGovernorOrStrategist { + if (paused) { + revert PriceFeedPausedError(); + } + paused = true; + emit PriceFeedPaused(); + } + + /** + * Unpauses the price feed. Only Governor can call it + **/ + function unpause() external onlyGovernor { + if (!paused) { + revert PriceFeedUnpausedError(); + } + paused = false; + emit PriceFeedUnpaused(); + } + + /** + * Set the max amount of tolerance acceptable between + * two different price points. + * + * @param _tolerance New tolerance value + **/ + function setTolerance(uint256 _tolerance) external onlyGovernor { + if (_tolerance > 0.1 ether) { + revert InvalidToleranceBps(); + } + emit ToleranceChanged(tolerance, _tolerance); + tolerance = _tolerance; + } + + /** + * @dev This function exists to make the contract compatible + * with AggregatorV3Interface (which OETHOracleRouter uses to + * get the price). + * + * The `answer` returned by this is same as what `price()` would return. + * + * It doesn't return any data about rounds (since those doesn't exist). + **/ + function latestRoundData() + external + view + override + returns ( + uint80, + int256 answer, + uint256, + uint256 updatedAt, + uint80 + ) + { + answer = _price(); + updatedAt = block.timestamp; + } + + /** + * @dev This function exists to make the contract compatible + * with AggregatorV3Interface. + * + * Always reverts since there're no round data in this contract. + **/ + function getRoundData(uint80) + external + pure + override + returns ( + uint80, + int256, + uint256, + uint256, + uint80 + ) + { + revert("No data present"); + } +} diff --git a/contracts/contracts/oracle/OETHOracleRouter.sol b/contracts/contracts/oracle/OETHOracleRouter.sol new file mode 100644 index 0000000000..4f3a853398 --- /dev/null +++ b/contracts/contracts/oracle/OETHOracleRouter.sol @@ -0,0 +1,112 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.0; + +import "../interfaces/chainlink/AggregatorV3Interface.sol"; +import { OracleRouterBase } from "./OracleRouterBase.sol"; +import { StableMath } from "../utils/StableMath.sol"; + +// @notice Oracle Router that denominates all prices in ETH +contract OETHOracleRouter is OracleRouterBase { + using StableMath for uint256; + + address public immutable auraPriceFeed; + + constructor(address _auraPriceFeed) { + auraPriceFeed = _auraPriceFeed; + } + + /** + * @notice Returns the total price in 18 digit units for a given asset. + * This implementation does not (!) do range checks as the + * parent OracleRouter does. + * @param asset address of the asset + * @return uint256 unit price for 1 asset unit, in 18 decimal fixed + */ + function price(address asset) + external + view + virtual + override + returns (uint256) + { + (address _feed, uint256 maxStaleness) = feedMetadata(asset); + if (_feed == FIXED_PRICE) { + return 1e18; + } + require(_feed != address(0), "Asset not available"); + + // slither-disable-next-line unused-return + (, int256 _iprice, , uint256 updatedAt, ) = AggregatorV3Interface(_feed) + .latestRoundData(); + + require( + updatedAt + maxStaleness >= block.timestamp, + "Oracle price too old" + ); + + uint8 decimals = getDecimals(_feed); + uint256 _price = uint256(_iprice).scaleBy(18, decimals); + return _price; + } + + /** + * @dev The price feed contract to use for a particular asset along with + * maximum data staleness + * @param asset address of the asset + * @return feedAddress address of the price feed for the asset + * @return maxStaleness maximum acceptable data staleness duration + */ + function feedMetadata(address asset) + internal + view + virtual + override + returns (address feedAddress, uint256 maxStaleness) + { + if (asset == 0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2) { + // FIXED_PRICE: WETH/ETH + feedAddress = FIXED_PRICE; + maxStaleness = 0; + } else if (asset == 0x5E8422345238F34275888049021821E8E08CAa1f) { + // frxETH/ETH + feedAddress = 0xC58F3385FBc1C8AD2c0C9a061D7c13b141D7A5Df; + maxStaleness = 18 hours + STALENESS_BUFFER; + } else if (asset == 0xae7ab96520DE3A18E5e111B5EaAb095312D7fE84) { + // https://data.chain.link/ethereum/mainnet/crypto-eth/steth-eth + // Chainlink: stETH/ETH + feedAddress = 0x86392dC19c0b719886221c78AB11eb8Cf5c52812; + maxStaleness = 1 days + STALENESS_BUFFER; + } else if (asset == 0xae78736Cd615f374D3085123A210448E74Fc6393) { + // https://data.chain.link/ethereum/mainnet/crypto-eth/reth-eth + // Chainlink: rETH/ETH + feedAddress = 0x536218f9E9Eb48863970252233c8F271f554C2d0; + maxStaleness = 1 days + STALENESS_BUFFER; + } else if (asset == 0xD533a949740bb3306d119CC777fa900bA034cd52) { + // https://data.chain.link/ethereum/mainnet/crypto-eth/crv-eth + // Chainlink: CRV/ETH + feedAddress = 0x8a12Be339B0cD1829b91Adc01977caa5E9ac121e; + maxStaleness = 1 days + STALENESS_BUFFER; + } else if (asset == 0x4e3FBD56CD56c3e72c1403e103b45Db9da5B9D2B) { + // https://data.chain.link/ethereum/mainnet/crypto-eth/cvx-eth + // Chainlink: CVX/ETH + feedAddress = 0xC9CbF687f43176B302F03f5e58470b77D07c61c6; + maxStaleness = 1 days + STALENESS_BUFFER; + } else if (asset == 0xBe9895146f7AF43049ca1c1AE358B0541Ea49704) { + // https://data.chain.link/ethereum/mainnet/crypto-eth/cbeth-eth + // Chainlink: cbETH/ETH + feedAddress = 0xF017fcB346A1885194689bA23Eff2fE6fA5C483b; + maxStaleness = 1 days + STALENESS_BUFFER; + } else if (asset == 0xba100000625a3754423978a60c9317c58a424e3D) { + // https://data.chain.link/ethereum/mainnet/crypto-eth/bal-eth + // Chainlink: BAL/ETH + feedAddress = 0xC1438AA3823A6Ba0C159CfA8D98dF5A994bA120b; + maxStaleness = 1 days + STALENESS_BUFFER; + } else if (asset == 0xC0c293ce456fF0ED870ADd98a0828Dd4d2903DBF) { + // AURA/ETH + feedAddress = auraPriceFeed; + maxStaleness = 0; + } else { + revert("Asset not available"); + } + } +} diff --git a/contracts/contracts/oracle/OracleRouter.sol b/contracts/contracts/oracle/OracleRouter.sol index 35c4745924..52a0077b7a 100644 --- a/contracts/contracts/oracle/OracleRouter.sol +++ b/contracts/contracts/oracle/OracleRouter.sol @@ -2,102 +2,7 @@ pragma solidity ^0.8.0; import "../interfaces/chainlink/AggregatorV3Interface.sol"; -import { IOracle } from "../interfaces/IOracle.sol"; -import { Helpers } from "../utils/Helpers.sol"; -import { StableMath } from "../utils/StableMath.sol"; -import { SafeCast } from "@openzeppelin/contracts/utils/math/SafeCast.sol"; - -// @notice Abstract functionality that is shared between various Oracle Routers -abstract contract OracleRouterBase is IOracle { - using StableMath for uint256; - using SafeCast for int256; - - uint256 internal constant MIN_DRIFT = 0.7e18; - uint256 internal constant MAX_DRIFT = 1.3e18; - address internal constant FIXED_PRICE = - 0x0000000000000000000000000000000000000001; - // Maximum allowed staleness buffer above normal Oracle maximum staleness - uint256 internal constant STALENESS_BUFFER = 1 days; - mapping(address => uint8) internal decimalsCache; - - /** - * @dev The price feed contract to use for a particular asset along with - * maximum data staleness - * @param asset address of the asset - * @return feedAddress address of the price feed for the asset - * @return maxStaleness maximum acceptable data staleness duration - */ - function feedMetadata(address asset) - internal - view - virtual - returns (address feedAddress, uint256 maxStaleness); - - /** - * @notice Returns the total price in 18 digit unit for a given asset. - * @param asset address of the asset - * @return uint256 unit price for 1 asset unit, in 18 decimal fixed - */ - function price(address asset) - external - view - virtual - override - returns (uint256) - { - (address _feed, uint256 maxStaleness) = feedMetadata(asset); - require(_feed != address(0), "Asset not available"); - require(_feed != FIXED_PRICE, "Fixed price feeds not supported"); - - (, int256 _iprice, , uint256 updatedAt, ) = AggregatorV3Interface(_feed) - .latestRoundData(); - - require( - updatedAt + maxStaleness >= block.timestamp, - "Oracle price too old" - ); - - uint8 decimals = getDecimals(_feed); - - uint256 _price = _iprice.toUint256().scaleBy(18, decimals); - if (shouldBePegged(asset)) { - require(_price <= MAX_DRIFT, "Oracle: Price exceeds max"); - require(_price >= MIN_DRIFT, "Oracle: Price under min"); - } - return _price; - } - - function getDecimals(address _feed) internal view virtual returns (uint8) { - uint8 decimals = decimalsCache[_feed]; - require(decimals > 0, "Oracle: Decimals not cached"); - return decimals; - } - - /** - * @notice Before an asset/feed price is fetches for the first time the - * decimals need to be cached. This is a gas optimization - * @param asset address of the asset - * @return uint8 corresponding asset decimals - */ - function cacheDecimals(address asset) external returns (uint8) { - (address _feed, ) = feedMetadata(asset); - require(_feed != address(0), "Asset not available"); - require(_feed != FIXED_PRICE, "Fixed price feeds not supported"); - - uint8 decimals = AggregatorV3Interface(_feed).decimals(); - decimalsCache[_feed] = decimals; - return decimals; - } - - function shouldBePegged(address _asset) internal view returns (bool) { - string memory symbol = Helpers.getSymbol(_asset); - bytes32 symbolHash = keccak256(abi.encodePacked(symbol)); - return - symbolHash == keccak256(abi.encodePacked("DAI")) || - symbolHash == keccak256(abi.encodePacked("USDC")) || - symbolHash == keccak256(abi.encodePacked("USDT")); - } -} +import { OracleRouterBase } from "./OracleRouterBase.sol"; // @notice Oracle Router that denominates all prices in USD contract OracleRouter is OracleRouterBase { @@ -157,93 +62,3 @@ contract OracleRouter is OracleRouterBase { } } } - -// @notice Oracle Router that denominates all prices in ETH -contract OETHOracleRouter is OracleRouter { - using StableMath for uint256; - - /** - * @notice Returns the total price in 18 digit units for a given asset. - * This implementation does not (!) do range checks as the - * parent OracleRouter does. - * @param asset address of the asset - * @return uint256 unit price for 1 asset unit, in 18 decimal fixed - */ - function price(address asset) - external - view - virtual - override - returns (uint256) - { - (address _feed, uint256 maxStaleness) = feedMetadata(asset); - if (_feed == FIXED_PRICE) { - return 1e18; - } - require(_feed != address(0), "Asset not available"); - - (, int256 _iprice, , uint256 updatedAt, ) = AggregatorV3Interface(_feed) - .latestRoundData(); - - require( - updatedAt + maxStaleness >= block.timestamp, - "Oracle price too old" - ); - - uint8 decimals = getDecimals(_feed); - uint256 _price = uint256(_iprice).scaleBy(18, decimals); - return _price; - } - - /** - * @dev The price feed contract to use for a particular asset along with - * maximum data staleness - * @param asset address of the asset - * @return feedAddress address of the price feed for the asset - * @return maxStaleness maximum acceptable data staleness duration - */ - function feedMetadata(address asset) - internal - pure - virtual - override - returns (address feedAddress, uint256 maxStaleness) - { - if (asset == 0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2) { - // FIXED_PRICE: WETH/ETH - feedAddress = FIXED_PRICE; - maxStaleness = 0; - } else if (asset == 0x5E8422345238F34275888049021821E8E08CAa1f) { - // frxETH/ETH - feedAddress = 0xC58F3385FBc1C8AD2c0C9a061D7c13b141D7A5Df; - maxStaleness = 18 hours + STALENESS_BUFFER; - } else if (asset == 0xae7ab96520DE3A18E5e111B5EaAb095312D7fE84) { - // https://data.chain.link/ethereum/mainnet/crypto-eth/steth-eth - // Chainlink: stETH/ETH - feedAddress = 0x86392dC19c0b719886221c78AB11eb8Cf5c52812; - maxStaleness = 1 days + STALENESS_BUFFER; - } else if (asset == 0xae78736Cd615f374D3085123A210448E74Fc6393) { - // https://data.chain.link/ethereum/mainnet/crypto-eth/reth-eth - // Chainlink: rETH/ETH - feedAddress = 0x536218f9E9Eb48863970252233c8F271f554C2d0; - maxStaleness = 1 days + STALENESS_BUFFER; - } else if (asset == 0xD533a949740bb3306d119CC777fa900bA034cd52) { - // https://data.chain.link/ethereum/mainnet/crypto-eth/crv-eth - // Chainlink: CRV/ETH - feedAddress = 0x8a12Be339B0cD1829b91Adc01977caa5E9ac121e; - maxStaleness = 1 days + STALENESS_BUFFER; - } else if (asset == 0x4e3FBD56CD56c3e72c1403e103b45Db9da5B9D2B) { - // https://data.chain.link/ethereum/mainnet/crypto-eth/cvx-eth - // Chainlink: CVX/ETH - feedAddress = 0xC9CbF687f43176B302F03f5e58470b77D07c61c6; - maxStaleness = 1 days + STALENESS_BUFFER; - } else if (asset == 0xBe9895146f7AF43049ca1c1AE358B0541Ea49704) { - // https://data.chain.link/ethereum/mainnet/crypto-eth/cbeth-eth - // Chainlink: cbETH/ETH - feedAddress = 0xF017fcB346A1885194689bA23Eff2fE6fA5C483b; - maxStaleness = 1 days + STALENESS_BUFFER; - } else { - revert("Asset not available"); - } - } -} diff --git a/contracts/contracts/oracle/OracleRouterBase.sol b/contracts/contracts/oracle/OracleRouterBase.sol new file mode 100644 index 0000000000..51ca17f097 --- /dev/null +++ b/contracts/contracts/oracle/OracleRouterBase.sol @@ -0,0 +1,101 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.0; + +import "../interfaces/chainlink/AggregatorV3Interface.sol"; +import { IOracle } from "../interfaces/IOracle.sol"; +import { Helpers } from "../utils/Helpers.sol"; +import { StableMath } from "../utils/StableMath.sol"; +import { SafeCast } from "@openzeppelin/contracts/utils/math/SafeCast.sol"; + +// @notice Abstract functionality that is shared between various Oracle Routers +abstract contract OracleRouterBase is IOracle { + using StableMath for uint256; + using SafeCast for int256; + + uint256 internal constant MIN_DRIFT = 0.7e18; + uint256 internal constant MAX_DRIFT = 1.3e18; + address internal constant FIXED_PRICE = + 0x0000000000000000000000000000000000000001; + // Maximum allowed staleness buffer above normal Oracle maximum staleness + uint256 internal constant STALENESS_BUFFER = 1 days; + mapping(address => uint8) internal decimalsCache; + + /** + * @dev The price feed contract to use for a particular asset along with + * maximum data staleness + * @param asset address of the asset + * @return feedAddress address of the price feed for the asset + * @return maxStaleness maximum acceptable data staleness duration + */ + function feedMetadata(address asset) + internal + view + virtual + returns (address feedAddress, uint256 maxStaleness); + + /** + * @notice Returns the total price in 18 digit unit for a given asset. + * @param asset address of the asset + * @return uint256 unit price for 1 asset unit, in 18 decimal fixed + */ + function price(address asset) + external + view + virtual + override + returns (uint256) + { + (address _feed, uint256 maxStaleness) = feedMetadata(asset); + require(_feed != address(0), "Asset not available"); + require(_feed != FIXED_PRICE, "Fixed price feeds not supported"); + + // slither-disable-next-line unused-return + (, int256 _iprice, , uint256 updatedAt, ) = AggregatorV3Interface(_feed) + .latestRoundData(); + + require( + updatedAt + maxStaleness >= block.timestamp, + "Oracle price too old" + ); + + uint8 decimals = getDecimals(_feed); + + uint256 _price = _iprice.toUint256().scaleBy(18, decimals); + if (shouldBePegged(asset)) { + require(_price <= MAX_DRIFT, "Oracle: Price exceeds max"); + require(_price >= MIN_DRIFT, "Oracle: Price under min"); + } + return _price; + } + + function getDecimals(address _feed) internal view virtual returns (uint8) { + uint8 decimals = decimalsCache[_feed]; + require(decimals > 0, "Oracle: Decimals not cached"); + return decimals; + } + + /** + * @notice Before an asset/feed price is fetches for the first time the + * decimals need to be cached. This is a gas optimization + * @param asset address of the asset + * @return uint8 corresponding asset decimals + */ + function cacheDecimals(address asset) external returns (uint8) { + (address _feed, ) = feedMetadata(asset); + require(_feed != address(0), "Asset not available"); + require(_feed != FIXED_PRICE, "Fixed price feeds not supported"); + + uint8 decimals = AggregatorV3Interface(_feed).decimals(); + decimalsCache[_feed] = decimals; + return decimals; + } + + function shouldBePegged(address _asset) internal view returns (bool) { + string memory symbol = Helpers.getSymbol(_asset); + bytes32 symbolHash = keccak256(abi.encodePacked(symbol)); + return + symbolHash == keccak256(abi.encodePacked("DAI")) || + symbolHash == keccak256(abi.encodePacked("USDC")) || + symbolHash == keccak256(abi.encodePacked("USDT")); + } +} diff --git a/contracts/contracts/strategies/ICurvePool.sol b/contracts/contracts/strategies/ICurvePool.sol index 9f2b5c9a28..50641881ad 100644 --- a/contracts/contracts/strategies/ICurvePool.sol +++ b/contracts/contracts/strategies/ICurvePool.sol @@ -30,6 +30,13 @@ interface ICurvePool { view returns (uint256); + function exchange( + uint256 i, + uint256 j, + uint256 dx, + uint256 min_dy + ) external returns (uint256); + function coins(uint256 _index) external view returns (address); function remove_liquidity_imbalance( diff --git a/contracts/deploy/000_mock.js b/contracts/deploy/000_mock.js index 259bfc003e..824a69f8a5 100644 --- a/contracts/deploy/000_mock.js +++ b/contracts/deploy/000_mock.js @@ -110,6 +110,11 @@ const deployMocks = async ({ getNamedAccounts, deployments }) => { from: deployerAddr, }); + // Mock AURA token + await deploy("MockAura", { + from: deployerAddr, + }); + // Deploy a mock Vault with additional functions for tests await deploy("MockVault", { from: governorAddr, @@ -233,12 +238,22 @@ const deployMocks = async ({ getNamedAccounts, deployments }) => { contract: "MockChainlinkOracleFeed", args: [parseUnits("1", 18).toString(), 18], // 1 frxETH = 1 ETH , 18 digits decimal. }); + await deploy("MockChainlinkOracleFeedBALETH", { + from: deployerAddr, + contract: "MockChainlinkOracleFeed", + args: [parseUnits("0.002", 18).toString(), 18], // 500 BAL = 1 ETH , 18 digits decimal. + }); // Deploy mock Uniswap router await deploy("MockUniswapRouter", { from: deployerAddr, }); + // Deploy mock Uniswap router + await deploy("MockBalancerVault", { + from: deployerAddr, + }); + // Deploy 3pool mocks await deploy("Mock3CRV", { from: deployerAddr, @@ -390,6 +405,10 @@ const deployMocks = async ({ getNamedAccounts, deployments }) => { from: deployerAddr, }); + await deploy("MockOracleWeightedPool", { + from: deployerAddr, + }); + console.log("000_mock deploy done."); return true; diff --git a/contracts/deploy/001_core.js b/contracts/deploy/001_core.js index 49a38b9a5a..59a7704df0 100644 --- a/contracts/deploy/001_core.js +++ b/contracts/deploy/001_core.js @@ -518,6 +518,7 @@ const deployHarvesters = async () => { ]); const dOETHHarvester = await deployWithConfirmation("OETHHarvester", [ cOETHVaultProxy.address, + assetAddresses.WETH, ]); const cHarvester = await ethers.getContractAt( "Harvester", @@ -548,13 +549,13 @@ const deployHarvesters = async () => { await withConfirmation( cHarvester .connect(sGovernor) - .setRewardsProceedsAddress(cVaultProxy.address) + .setRewardProceedsAddress(cVaultProxy.address) ); await withConfirmation( cOETHHarvester .connect(sGovernor) - .setRewardsProceedsAddress(cOETHVaultProxy.address) + .setRewardProceedsAddress(cOETHVaultProxy.address) ); } @@ -708,7 +709,7 @@ const deployFraxEthStrategy = async () => { * Deploy the OracleRouter and initialise it with Chainlink sources. */ const deployOracles = async () => { - const { deployerAddr } = await getNamedAccounts(); + const { deployerAddr, governorAddr } = await getNamedAccounts(); // Signers const sDeployer = await ethers.provider.getSigner(deployerAddr); @@ -716,11 +717,19 @@ const deployOracles = async () => { const oracleContract = isMainnet ? "OracleRouter" : "MockOracleRouter"; await deployWithConfirmation("OracleRouter", [], oracleContract); const oracleRouter = await ethers.getContract("OracleRouter"); + log("Deployed OracleRouter"); + + const assetAddresses = await getAssetAddresses(deployments); + await deployWithConfirmation("AuraWETHPriceFeed", [ + assetAddresses.auraWeightedOraclePool, + governorAddr, + ]); + const auraWethPriceFeed = await ethers.getContract("AuraWETHPriceFeed"); + log("Deployed AuraWETHPriceFeed"); // Register feeds // Not needed in production const oracleAddresses = await getOracleAddresses(deployments); - const assetAddresses = await getAssetAddresses(deployments); /* Mock oracle feeds report 0 for updatedAt data point. Set * maxStaleness to 100 years from epoch to make the Oracle * feeds valid @@ -734,6 +743,7 @@ const deployOracles = async () => { [assetAddresses.TUSD, oracleAddresses.chainlink.TUSD_USD], [assetAddresses.COMP, oracleAddresses.chainlink.COMP_USD], [assetAddresses.AAVE, oracleAddresses.chainlink.AAVE_USD], + [assetAddresses.AAVE_TOKEN, oracleAddresses.chainlink.AAVE_USD], [assetAddresses.CRV, oracleAddresses.chainlink.CRV_USD], [assetAddresses.CVX, oracleAddresses.chainlink.CVX_USD], [assetAddresses.RETH, oracleAddresses.chainlink.RETH_ETH], @@ -745,6 +755,8 @@ const deployOracles = async () => { assetAddresses.NonStandardToken, oracleAddresses.chainlink.NonStandardToken_USD, ], + [assetAddresses.AURA, auraWethPriceFeed.address], + [assetAddresses.BAL, oracleAddresses.chainlink.BAL_ETH], ]; for (const [asset, oracle] of oracleFeeds) { @@ -752,6 +764,7 @@ const deployOracles = async () => { oracleRouter.connect(sDeployer).setFeed(asset, oracle, maxStaleness) ); } + log("Initialized AuraWETHPriceFeed"); }; /** diff --git a/contracts/deploy/076_upgrade_fraxeth_strategy.js b/contracts/deploy/076_upgrade_fraxeth_strategy.js index 5a1af5a530..5c70c656ac 100644 --- a/contracts/deploy/076_upgrade_fraxeth_strategy.js +++ b/contracts/deploy/076_upgrade_fraxeth_strategy.js @@ -5,6 +5,7 @@ module.exports = deploymentWithGovernanceProposal( { deployName: "076_upgrade_fraxeth_strategy", forceDeploy: false, + forceSkip: true, reduceQueueTime: true, deployerIsProposer: true, // proposalId: "", diff --git a/contracts/deploy/081_upgrade_harvester.js b/contracts/deploy/081_upgrade_harvester.js new file mode 100644 index 0000000000..05497afa18 --- /dev/null +++ b/contracts/deploy/081_upgrade_harvester.js @@ -0,0 +1,315 @@ +const { + deploymentWithGovernanceProposal, + withConfirmation, +} = require("../utils/deploy"); +const addresses = require("../utils/addresses"); +const { oethUnits } = require("../test/helpers"); +const { utils } = require("ethers"); + +module.exports = deploymentWithGovernanceProposal( + { + deployName: "081_upgrade_harvester", + forceDeploy: false, + // forceSkip: true, + reduceQueueTime: true, + deployerIsProposer: true, + executeGasLimit: 30000000, + }, + async ({ deployWithConfirmation, ethers, getTxOpts }) => { + const { timelockAddr } = await getNamedAccounts(); + + // Current contracts + const cOUSDVaultProxy = await ethers.getContract("VaultProxy"); + const cOETHVaultProxy = await ethers.getContract("OETHVaultProxy"); + const cOUSDVault = await ethers.getContractAt( + "IVault", + cOUSDVaultProxy.address + ); + const cOETHVault = await ethers.getContractAt( + "IVault", + cOETHVaultProxy.address + ); + const cOUSDHarvesterProxy = await ethers.getContract("HarvesterProxy"); + const cOETHHarvesterProxy = await ethers.getContract("OETHHarvesterProxy"); + const cOUSDHarvester = await ethers.getContractAt( + "Harvester", + cOUSDHarvesterProxy.address + ); + const cOETHHarvester = await ethers.getContractAt( + "OETHHarvester", + cOETHHarvesterProxy.address + ); + + // Deployer Actions + // ---------------- + + // 1. Deploy Aura Price feed + await deployWithConfirmation("AuraWETHPriceFeed", [ + addresses.mainnet.AuraWeightedOraclePool, + timelockAddr, + ]); + const auraPriceFeed = await ethers.getContract("AuraWETHPriceFeed"); + console.log("AuraWETHPriceFeed address: ", auraPriceFeed.address); + + // 2. Deploy OETHOracleRouter + await deployWithConfirmation("OETHOracleRouter", [auraPriceFeed.address]); + const dOETHRouter = await ethers.getContract("OETHOracleRouter"); + console.log("new OETHOracleRouter address: ", dOETHRouter.address); + + // 2.1. Cache decimals on OETHOracleRouter + for (const asset of [ + addresses.mainnet.CRV, + addresses.mainnet.CVX, + addresses.mainnet.AURA, + addresses.mainnet.BAL, + addresses.mainnet.stETH, + addresses.mainnet.rETH, + addresses.mainnet.frxETH, + ]) { + await withConfirmation( + dOETHRouter.cacheDecimals(asset, await getTxOpts()) + ); + } + const dOUSDRouter = await ethers.getContract("OracleRouter"); + await withConfirmation( + dOUSDRouter.cacheDecimals(addresses.mainnet.Aave, await getTxOpts()) + ); + + // 3. Deploy Harvester + await deployWithConfirmation( + "Harvester", + [cOUSDVault.address, addresses.mainnet.USDT], + undefined, + true + ); + const dHarvesterImpl = await ethers.getContract("Harvester"); + console.log( + "New Harvester implementation address: ", + dHarvesterImpl.address + ); + + // 4. Deploy OETHHarvester + await deployWithConfirmation( + "OETHHarvester", + [cOETHVault.address, addresses.mainnet.WETH], + undefined, + true + ); + const dOETHHarvesterImpl = await ethers.getContract("OETHHarvester"); + console.log( + "New OETHHarvester implementation address: ", + dOETHHarvesterImpl.address + ); + + const setRewardTokenConfigSig = + "setRewardTokenConfig(address,(uint16,uint16,address,bool,uint8,uint256),bytes)"; + + // Governance Actions + // ---------------- + return { + name: "Upgrade Harvester contracts", + actions: [ + // 1. Upgrade OETH Harvester + { + contract: cOETHHarvesterProxy, + signature: "upgradeTo(address)", + args: [dOETHHarvesterImpl.address], + }, + // 2. Upgrade OUSD Harvester + { + contract: cOUSDHarvesterProxy, + signature: "upgradeTo(address)", + args: [dHarvesterImpl.address], + }, + // 3. Set OETH Oracle Router on Vault + { + contract: cOETHVault, + signature: "setPriceProvider(address)", + args: [dOETHRouter.address], + }, + // 4. Configure OETH Harvester to swap CRV with Curve + { + contract: cOETHHarvester, + signature: setRewardTokenConfigSig, + args: [ + addresses.mainnet.CRV, + { + allowedSlippageBps: 300, + harvestRewardBps: 200, + swapPlatform: 3, // Curve + swapPlatformAddr: addresses.mainnet.CurveTriPool, + liquidationLimit: oethUnits("4000"), + doSwapRewardToken: true, + }, + utils.defaultAbiCoder.encode(["uint128", "uint128"], ["2", "1"]), + ], + }, + // 5. Configure OETH Harvester to swap BAL with Balancer + { + contract: cOETHHarvester, + signature: setRewardTokenConfigSig, + args: [ + addresses.mainnet.BAL, + { + allowedSlippageBps: 300, + harvestRewardBps: 200, + swapPlatform: 2, // Balancer + swapPlatformAddr: addresses.mainnet.balancerVault, + liquidationLimit: oethUnits("1000"), + doSwapRewardToken: true, + }, + utils.defaultAbiCoder.encode( + ["bytes32"], + [ + "0x5c6ee304399dbdb9c8ef030ab642b10820db8f56000200000000000000000014", + ] + ), + ], + }, + // 6. Configure OETH Harvester to swap AURA with Balancer + { + contract: cOETHHarvester, + signature: setRewardTokenConfigSig, + args: [ + addresses.mainnet.AURA, + { + allowedSlippageBps: 300, + harvestRewardBps: 200, + swapPlatform: 2, // Balancer + swapPlatformAddr: addresses.mainnet.balancerVault, + liquidationLimit: oethUnits("4000"), + doSwapRewardToken: true, + }, + utils.defaultAbiCoder.encode( + ["bytes32"], + [ + "0xcfca23ca9ca720b6e98e3eb9b6aa0ffc4a5c08b9000200000000000000000274", + ] + ), + ], + }, + // 7. Configure OETH Harvester to swap CVX with Curve + { + contract: cOETHHarvester, + signature: setRewardTokenConfigSig, + args: [ + addresses.mainnet.CVX, + { + allowedSlippageBps: 300, + harvestRewardBps: 200, + swapPlatform: 3, // Curve + swapPlatformAddr: addresses.mainnet.CurveCVXPool, + liquidationLimit: oethUnits("2500"), + doSwapRewardToken: true, + }, + utils.defaultAbiCoder.encode(["uint128", "uint128"], ["1", "0"]), + ], + }, + // 8. Configure OUSD Harvester to swap CRV with Uniswap V3 + { + contract: cOUSDHarvester, + signature: setRewardTokenConfigSig, + args: [ + addresses.mainnet.CRV, + { + allowedSlippageBps: 300, + harvestRewardBps: 200, + swapPlatform: 1, // Uniswap V3 + swapPlatformAddr: addresses.mainnet.uniswapV3Router, + liquidationLimit: oethUnits("4000"), + doSwapRewardToken: true, + }, + utils.solidityPack( + ["address", "uint24", "address", "uint24", "address"], + [ + addresses.mainnet.CRV, + 3000, + addresses.mainnet.WETH, + 500, + addresses.mainnet.USDT, + ] + ), + ], + }, + // 9. Configure OUSD Harvester to swap CVX with Uniswap V3 + { + contract: cOUSDHarvester, + signature: setRewardTokenConfigSig, + args: [ + addresses.mainnet.CVX, + { + allowedSlippageBps: 300, + harvestRewardBps: 100, + swapPlatform: 1, // Uniswap V3 + swapPlatformAddr: addresses.mainnet.uniswapV3Router, + liquidationLimit: oethUnits("2500"), + doSwapRewardToken: true, + }, + utils.solidityPack( + ["address", "uint24", "address", "uint24", "address"], + [ + addresses.mainnet.CVX, + 10000, + addresses.mainnet.WETH, + 500, + addresses.mainnet.USDT, + ] + ), + ], + }, + // 10. Configure OUSD Harvester to swap COMP with Uniswap V3 + { + contract: cOUSDHarvester, + signature: setRewardTokenConfigSig, + args: [ + addresses.mainnet.COMP, + { + allowedSlippageBps: 300, + harvestRewardBps: 100, + swapPlatform: 1, // Uniswap V3 + swapPlatformAddr: addresses.mainnet.uniswapV3Router, + liquidationLimit: 0, + doSwapRewardToken: true, + }, + utils.solidityPack( + ["address", "uint24", "address", "uint24", "address"], + [ + addresses.mainnet.COMP, + 3000, + addresses.mainnet.WETH, + 500, + addresses.mainnet.USDT, + ] + ), + ], + }, + // 11. Configure OUSD Harvester to swap AAVE with Uniswap V3 + { + contract: cOUSDHarvester, + signature: setRewardTokenConfigSig, + args: [ + addresses.mainnet.Aave, + { + allowedSlippageBps: 300, + harvestRewardBps: 100, + swapPlatform: 1, // Uniswap V3 + swapPlatformAddr: addresses.mainnet.uniswapV3Router, + liquidationLimit: 0, + doSwapRewardToken: true, + }, + utils.solidityPack( + ["address", "uint24", "address", "uint24", "address"], + [ + addresses.mainnet.Aave, + 10000, + addresses.mainnet.WETH, + 500, + addresses.mainnet.USDT, + ] + ), + ], + }, + ], + }; + } +); diff --git a/contracts/deploy/999_fork_test_setup.js b/contracts/deploy/999_fork_test_setup.js index 0239b25491..4b752ab62e 100644 --- a/contracts/deploy/999_fork_test_setup.js +++ b/contracts/deploy/999_fork_test_setup.js @@ -37,8 +37,10 @@ const main = async (hre) => { const dMockOracleRouterNoStale = await deployWithConfirmation( "MockOracleRouterNoStale" ); + const dAuraPriceFeed = await ethers.getContract("AuraWETHPriceFeed"); const dMockOETHOracleRouterNoStale = await deployWithConfirmation( - "MockOETHOracleRouterNoStale" + "MockOETHOracleRouterNoStale", + [dAuraPriceFeed.address] ); log("Deployed MockOracleRouterNoStale and MockOETHOracleRouterNoStale"); await replaceContractAt(oracleRouter.address, dMockOracleRouterNoStale); diff --git a/contracts/test/_fixture.js b/contracts/test/_fixture.js index 0fee88ac82..4e16a5ddc8 100644 --- a/contracts/test/_fixture.js +++ b/contracts/test/_fixture.js @@ -3,6 +3,7 @@ const { ethers } = hre; const { BigNumber } = ethers; const { expect } = require("chai"); const { formatUnits } = require("ethers/lib/utils"); +const mocha = require("mocha"); require("./_global-hooks"); @@ -51,7 +52,13 @@ const { impersonateAndFund } = require("../utils/signers"); const log = require("../utils/logger")("test:fixtures"); +let snapshotId; + const defaultFixture = deployments.createFixture(async () => { + if (!snapshotId && !isFork) { + snapshotId = await nodeSnapshot(); + } + log(`Forked from block: ${await hre.ethers.provider.getBlockNumber()}`); log(`Before deployments with param "${isFork ? undefined : ["unit_tests"]}"`); @@ -367,6 +374,7 @@ const defaultFixture = deployments.createFixture(async () => { cusdc = await ethers.getContract("MockCUSDC"); comp = await ethers.getContract("MockCOMP"); bal = await ethers.getContract("MockBAL"); + aura = await ethers.getContract("MockAura"); crv = await ethers.getContract("MockCRV"); cvx = await ethers.getContract("MockCVX"); @@ -726,12 +734,12 @@ async function oeth1InchSwapperFixture() { const fixture = await oethDefaultFixture(); const { mock1InchSwapRouter } = fixture; - const swapRouterAddr = "0x1111111254EEB25477B68fb85Ed929f73A960582"; - await replaceContractAt(swapRouterAddr, mock1InchSwapRouter); + const swapPlatformAddr = "0x1111111254EEB25477B68fb85Ed929f73A960582"; + await replaceContractAt(swapPlatformAddr, mock1InchSwapRouter); const stubbedRouterContract = await hre.ethers.getContractAt( "Mock1InchSwapRouter", - swapRouterAddr + swapPlatformAddr ); fixture.mock1InchSwapRouter = stubbedRouterContract; @@ -2000,6 +2008,53 @@ async function buybackFixture() { return fixture; } +async function harvesterFixture() { + let fixture; + + if (isFork) { + fixture = await defaultFixture(); + } else { + fixture = await compoundVaultFixture(); + + const { + vault, + governor, + harvester, + dai, + aaveStrategy, + comp, + aaveToken, + strategist, + compoundStrategy, + } = fixture; + + // Add Aave which only supports DAI + await vault.connect(governor).approveStrategy(aaveStrategy.address); + + await harvester + .connect(governor) + .setSupportedStrategy(aaveStrategy.address, true); + + // Add direct allocation of DAI to Aave + await vault + .connect(governor) + .setAssetDefaultStrategy(dai.address, aaveStrategy.address); + + // Let strategies hold some reward tokens + await comp + .connect(strategist) + .mintTo(compoundStrategy.address, ousdUnits("120")); + await aaveToken + .connect(strategist) + .mintTo(aaveStrategy.address, ousdUnits("150")); + + fixture.uniswapRouter = await ethers.getContract("MockUniswapRouter"); + fixture.balancerVault = await ethers.getContract("MockBalancerVault"); + } + + return fixture; +} + /** * A fixture is a setup function that is run only the first time it's invoked. On subsequent invocations, * Hardhat will reset the state of the network to what it was at the point after the fixture was initially executed. @@ -2039,6 +2094,12 @@ async function loadDefaultFixture() { return await defaultFixture(); } +mocha.after(async () => { + if (snapshotId) { + await nodeRevert(snapshotId); + } +}); + module.exports = { createFixtureLoader, loadDefaultFixture, @@ -2073,6 +2134,7 @@ module.exports = { ousdCollateralSwapFixture, fluxStrategyFixture, buybackFixture, + harvesterFixture, nodeSnapshot, nodeRevert, }; diff --git a/contracts/test/_hot-deploy.js b/contracts/test/_hot-deploy.js index 5e770b6bd1..e803d5b71f 100644 --- a/contracts/test/_hot-deploy.js +++ b/contracts/test/_hot-deploy.js @@ -4,7 +4,7 @@ */ const { ethers } = hre; -const { isFork } = require("./helpers"); +const { isFork, isCI } = require("./helpers"); const addresses = require("../utils/addresses"); const { balancer_rETH_WETH_PID, @@ -102,16 +102,21 @@ async function hotDeployOption( fixtureName, config = { isOethFixture: false } ) { - if (!isFork) return; + // Disable Hot Deploy on CI and for unit tests + if (!isFork || isCI) return; const hotDeployOptions = (process.env.HOT_DEPLOY || "") .split(",") .map((item) => item.trim()); + + if (!hotDeployOptions.length) return; + const { isOethFixture } = config; const deployStrat = hotDeployOptions.includes("strategy"); const deployVaultCore = hotDeployOptions.includes("vaultCore"); const deployVaultAdmin = hotDeployOptions.includes("vaultAdmin"); const deployHarvester = hotDeployOptions.includes("harvester"); + const deployOracleRouter = hotDeployOptions.includes("oracleRouter"); log(`Running fixture hot deployment w/ config; isOethFixture:${isOethFixture} strategy:${!!deployStrat} vaultCore:${!!deployVaultCore} vaultAdmin:${!!deployVaultAdmin} harvester:${!!deployHarvester}`); @@ -153,7 +158,10 @@ async function hotDeployOption( ); } if (deployHarvester) { - // TODO: update harvester + await hotDeployHarvester(fixture, isOethFixture); + } + if (deployOracleRouter) { + await hotDeployOracleRouter(fixture, isOethFixture); } } @@ -217,6 +225,58 @@ async function hotDeployVaultAdmin( } } +async function hotDeployHarvester(fixture, forOETH) { + const { deploy } = deployments; + const harvesterName = `${forOETH ? "OETH" : ""}Harvester`; + const harvesterProxyName = `${forOETH ? "OETH" : ""}HarvesterProxy`; + const vault = forOETH ? fixture.oethVault : fixture.vault; + const baseToken = forOETH ? fixture.weth : fixture.usdt; + + const cHarvesterProxy = await ethers.getContract(harvesterProxyName); + + log(`Deploying new ${harvesterName} implementation`); + await deploy(harvesterName, { + from: addresses.mainnet.Timelock, + contract: harvesterName, + args: [vault.address, baseToken.address], + }); + const implementation = await ethers.getContract(harvesterName); + const liveImplContractAddress = await cHarvesterProxy.implementation(); + log( + `Replacing implementation at ${liveImplContractAddress} with the fresh bytecode` + ); + await replaceContractAt(liveImplContractAddress, implementation); +} + +async function hotDeployOracleRouter(fixture, forOETH) { + const { deploy } = deployments; + const routerName = `${forOETH ? "OETH" : ""}OracleRouter`; + + const cRouter = await ethers.getContract(routerName); + + if (forOETH) { + await deploy("AuraWETHPriceFeed", { + from: await fixture.strategist.getAddress(), + args: [addresses.mainnet.AuraWeightedOraclePool], + }); + const auraPriceFeed = await ethers.getContract("AuraWETHPriceFeed"); + + await deploy(routerName, { + from: await fixture.strategist.getAddress(), + args: [auraPriceFeed.address], + }); + } else { + await deploy(routerName, { + from: await fixture.strategist.getAddress(), + args: [], + }); + } + + const implementation = await ethers.getContract(routerName); + log(`Replacing implementation at ${cRouter.address} with the fresh bytecode`); + await replaceContractAt(cRouter.address, implementation); +} + /* Run the fixture and replace the main strategy contract(s) of the fixture * with the freshly compiled implementation. */ diff --git a/contracts/test/behaviour/harvest.fork.js b/contracts/test/behaviour/harvest.fork.js new file mode 100644 index 0000000000..2497398d50 --- /dev/null +++ b/contracts/test/behaviour/harvest.fork.js @@ -0,0 +1,93 @@ +const { expect } = require("chai"); + +const { isFork, units } = require("../helpers"); +const { setERC20TokenBalance } = require("../_fund"); +const { impersonateAndFund } = require("../../utils/signers"); + +/** + * + * @param {*} context a function that returns: + * - strategy: Strategy to test against + * - harvester: OUSD Harvester or OETH Harvester + * - dripper: Dripper + * - fixture: The actual fixture + * @example + * shouldHarvestRewardTokens(() => ({ + * fixture, + * harvester, + * dripper, + * strategy + * })); + */ +const shouldHarvestRewardTokens = (context) => { + if (!isFork) { + // Only meant to be used on fork + return; + } + + describe("Reward Tokens", () => { + it("Should collect and send reward tokens to harvester", async () => { + const { strategy, harvester } = context(); + + const harvesterSigner = await impersonateAndFund(harvester.address); + + const rewardTokens = await strategy.getRewardTokenAddresses(); + const harvesterBalBefore = []; + + for (let i = 0; i < rewardTokens.length; i++) { + const token = await ethers.getContractAt("IERC20", rewardTokens[i]); + // Send some rewards to the strategy + await setERC20TokenBalance(strategy.address, token, "1000"); + harvesterBalBefore[i] = await token.balanceOf(harvester.address); + rewardTokens[i] = token; + } + + const tx = await strategy.connect(harvesterSigner).collectRewardTokens(); + await expect(tx).to.emit(strategy, "RewardTokenCollected"); + + // Should've transferred everything to Harvester + for (let i = 0; i < rewardTokens.length; i++) { + const token = rewardTokens[i]; + expect(await token.balanceOf(strategy.address)).to.eq("0"); + expect(await token.balanceOf(harvester.address)).to.be.gte( + harvesterBalBefore[i].add(await units("1000", token)) + ); + } + }); + + it("Should swap reward tokens", async () => { + const { strategy, harvester, fixture } = context(); + const { strategist } = fixture; + + const rewardTokens = await strategy.getRewardTokenAddresses(); + + let i = 0; + for (const tokenAddr of rewardTokens) { + const token = await ethers.getContractAt("IERC20", tokenAddr); + // Send some rewards to the strategy + await setERC20TokenBalance(strategy.address, token); + + rewardTokens[i++] = token; + } + + // Trigger the swap + // prettier-ignore + const tx = await harvester + .connect(strategist)["harvestAndSwap(address)"](strategy.address); + + // Make sure the events have been emitted + await expect(tx).to.emit(strategy, "RewardTokenCollected"); + await expect(tx).to.emit(harvester, "RewardTokenSwapped"); + await expect(tx).to.emit(harvester, "RewardProceedsTransferred"); + + // Should've transferred everything to Harvester + for (const token of rewardTokens) { + expect(await token.balanceOf(strategy.address)).to.eq("0"); + } + }); + }); +}; + +module.exports = { + shouldHarvestRewardTokens, +}; diff --git a/contracts/test/behaviour/harvester.js b/contracts/test/behaviour/harvester.js index 4e9c2fd713..9ae8668b2d 100644 --- a/contracts/test/behaviour/harvester.js +++ b/contracts/test/behaviour/harvester.js @@ -1,8 +1,17 @@ const { expect } = require("chai"); -const { parseUnits } = require("ethers").utils; -const { usdtUnits } = require("../helpers"); +const { + changeInMultipleBalances, + setOracleTokenPriceUsd, + daiUnits, + ousdUnits, + usdtUnits, + oethUnits, +} = require("../helpers"); const { impersonateAndFund } = require("../../utils/signers"); +const addresses = require("../../utils/addresses"); +const { utils } = require("ethers"); +const { MAX_UINT256 } = require("../../utils/constants"); /** * @@ -11,24 +20,60 @@ const { impersonateAndFund } = require("../../utils/signers"); * - rewards: array of objects with asset and expected properties * @example shouldBehaveLikeHarvester(() => ({ - ...fixture, - strategy: fixture.convexStrategy, - rewards: [ - { asset: fixture.crv, expected: parseUnits("2") }, - { asset: fixture.cvx, expected: parseUnits("3") }, - ], + fixture, + harvester: fixture.harvester, + strategies: [{ strategy, rewardTokens }], + rewardProceedsAddress: fixture.vault.address, })); */ const shouldBehaveLikeHarvester = (context) => { - describe("Harvester behaviour", () => { + describe("Harvest", () => { + async function _checkBalancesPostHarvesting(harvestFn, strategies) { + const { harvester } = context(); + + if (!Array.isArray(strategies)) { + strategies = [strategies]; + } + + const rewardTokens = strategies.reduce( + (all, s) => [...all, ...s.rewardTokens], + [] + ); + + const balanceDiff = await changeInMultipleBalances( + async () => { + await harvestFn(); + }, + rewardTokens, + [...strategies.map((s) => s.strategy.address), harvester.address] + ); + + for (const { strategy, rewardTokens } of strategies) { + for (const token of rewardTokens) { + expect(balanceDiff[harvester.address][token.address]).to.equal( + -1 * balanceDiff[strategy.address][token.address], + `Balance mismatch for rewardToken: ${await token.symbol()}` + ); + } + } + } + it("Should allow rewards to be collect from the strategy by the harvester", async () => { - const { harvester, strategy } = context(); + const { harvester, strategies } = context(); + const { strategy } = strategies[0]; const harvesterSigner = await impersonateAndFund(harvester.address); - await strategy.connect(harvesterSigner).collectRewardTokens(); + + await _checkBalancesPostHarvesting( + () => strategy.connect(harvesterSigner).collectRewardTokens(), + strategies[0] + ); }); + it("Should NOT allow rewards to be collected by non-harvester", async () => { - const { anna, governor, strategist, strategy } = context(); + const { fixture, strategies } = context(); + const { anna, governor, strategist } = fixture; + const { strategy } = strategies[0]; for (const signer of [anna, governor, strategist]) { await expect( @@ -36,220 +81,894 @@ const shouldBehaveLikeHarvester = (context) => { ).to.be.revertedWith("Caller is not the Harvester"); } }); - it("Should allow the governor to call harvest for a specific strategy", async () => { - const { harvester, governor, strategy } = context(); - // prettier-ignore + }); + + describe("RewardTokenConfig", () => { + it("Should only allow valid Uniswap V2 path", async () => { + const { harvester, fixture } = context(); + const { crv, usdt, governor, uniswapRouter } = fixture; + + const config = { + allowedSlippageBps: 200, + harvestRewardBps: 500, + swapPlatformAddr: uniswapRouter.address, + doSwapRewardToken: true, + swapPlatform: 0, + liquidationLimit: 0, + }; + + let uniV2Path = utils.defaultAbiCoder.encode( + ["address[]"], + [[usdt.address, crv.address]] + ); + + await expect( + harvester + .connect(governor) + .setRewardTokenConfig(crv.address, config, uniV2Path) + ).to.be.revertedWith("InvalidTokenInSwapPath"); + + uniV2Path = utils.defaultAbiCoder.encode( + ["address[]"], + [[crv.address, crv.address]] + ); + + await expect( + harvester + .connect(governor) + .setRewardTokenConfig(crv.address, config, uniV2Path) + ).to.be.revertedWith("InvalidTokenInSwapPath"); + + uniV2Path = utils.defaultAbiCoder.encode(["address[]"], [[usdt.address]]); + await expect( + harvester + .connect(governor) + .setRewardTokenConfig(crv.address, config, uniV2Path) + ).to.be.revertedWith("InvalidUniswapV2PathLength"); + + uniV2Path = utils.defaultAbiCoder.encode( + ["address[]"], + [[crv.address, usdt.address]] + ); + + await expect( + harvester + .connect(governor) + .setRewardTokenConfig(crv.address, config, uniV2Path) + ).to.not.be.reverted; + }); + + it("Should only allow valid Uniswap V3 path", async () => { + const { harvester, fixture } = context(); + const { crv, usdt, governor, uniswapRouter } = fixture; + + const config = { + allowedSlippageBps: 200, + harvestRewardBps: 500, + swapPlatformAddr: uniswapRouter.address, + doSwapRewardToken: true, + swapPlatform: 1, + liquidationLimit: 0, + }; + + let uniV3Path = utils.solidityPack( + ["address", "uint24", "address"], + [usdt.address, 24, crv.address] + ); + await expect( + harvester + .connect(governor) + .setRewardTokenConfig(crv.address, config, uniV3Path) + ).to.be.revertedWith("InvalidTokenInSwapPath"); + + uniV3Path = utils.solidityPack( + ["address", "uint24", "address"], + [crv.address, 24, crv.address] + ); + await expect( + harvester + .connect(governor) + .setRewardTokenConfig(crv.address, config, uniV3Path) + ).to.be.revertedWith("InvalidTokenInSwapPath"); + + uniV3Path = utils.solidityPack( + ["address", "uint24", "address"], + [crv.address, 24, usdt.address] + ); + await expect( + harvester + .connect(governor) + .setRewardTokenConfig(crv.address, config, uniV3Path) + ).to.not.be.reverted; + }); + + it("Should only allow valid balancer config", async () => { + const { harvester, fixture } = context(); + const { crv, governor, balancerVault } = fixture; + + const config = { + allowedSlippageBps: 200, + harvestRewardBps: 500, + swapPlatformAddr: balancerVault.address, + doSwapRewardToken: true, + swapPlatform: 2, + liquidationLimit: 0, + }; + + await expect( + harvester + .connect(governor) + .setRewardTokenConfig( + crv.address, + config, + utils.defaultAbiCoder.encode( + ["bytes32"], + [ + "0x0000000000000000000000000000000000000000000000000000000000000000", + ] + ) + ) + ).to.be.revertedWith("EmptyBalancerPoolId"); + + await expect( + harvester + .connect(governor) + .setRewardTokenConfig( + crv.address, + config, + utils.defaultAbiCoder.encode( + ["bytes32"], + [ + "0x000000000000000000000000000000000000000000000000000000000000dead", + ] + ) + ) + ).to.not.be.reverted; + }); + + it("Should only allow valid Curve config", async () => { + const { harvester, fixture } = context(); + const { crv, usdt, governor, threePool } = fixture; + + const config = { + allowedSlippageBps: 200, + harvestRewardBps: 500, + swapPlatformAddr: threePool.address, + doSwapRewardToken: true, + swapPlatform: 3, + liquidationLimit: 0, + }; + + await threePool + .connect(governor) + .setCoins([crv.address, crv.address, usdt.address]); + + await expect( + harvester + .connect(governor) + .setRewardTokenConfig( + crv.address, + config, + utils.defaultAbiCoder.encode(["uint256", "uint256"], ["1", "0"]) + ) + ).to.be.revertedWith("InvalidCurvePoolAssetIndex"); + + await expect( + harvester + .connect(governor) + .setRewardTokenConfig( + crv.address, + config, + utils.defaultAbiCoder.encode(["uint256", "uint256"], ["2", "2"]) + ) + ).to.be.revertedWith("InvalidCurvePoolAssetIndex"); + + await expect( + harvester + .connect(governor) + .setRewardTokenConfig( + crv.address, + config, + utils.defaultAbiCoder.encode(["uint256", "uint256"], ["0", "2"]) + ) + ).to.not.be.reverted; + }); + + it("Should revert on unsupported platform", async () => { + const { harvester, fixture } = context(); + const { crv, governor, uniswapRouter } = fixture; + + const config = { + allowedSlippageBps: 200, + harvestRewardBps: 500, + swapPlatformAddr: uniswapRouter.address, + doSwapRewardToken: true, + swapPlatform: 4, + liquidationLimit: 0, + }; + + await expect( + harvester + .connect(governor) + .setRewardTokenConfig( + crv.address, + config, + utils.defaultAbiCoder.encode(["uint256"], ["0"]) + ) + ).to.be.reverted; + }); + + it("Should reset allowance on older router", async () => { + const { harvester, fixture } = context(); + const { crv, usdt, governor, vault, uniswapRouter } = fixture; + + const oldRouter = vault; // Pretend vault is a router + + const config = { + allowedSlippageBps: 200, + harvestRewardBps: 500, + swapPlatformAddr: oldRouter.address, + doSwapRewardToken: true, + swapPlatform: 0, + liquidationLimit: 0, + }; + + const uniV2Path = utils.defaultAbiCoder.encode( + ["address[]"], + [[crv.address, usdt.address]] + ); + await harvester - .connect(governor)["harvest(address)"](strategy.address); + .connect(governor) + .setRewardTokenConfig(crv.address, config, uniV2Path); + + // Check allowance on old router + expect(await crv.allowance(harvester.address, oldRouter.address)).to.eq( + MAX_UINT256 + ); + + // Change router + await harvester.connect(governor).setRewardTokenConfig( + crv.address, + { + ...config, + swapPlatformAddr: uniswapRouter.address, + }, + uniV2Path + ); + + // Check allowance on old & new router + expect( + await crv.allowance(harvester.address, uniswapRouter.address) + ).to.eq(MAX_UINT256); + expect(await crv.allowance(harvester.address, oldRouter.address)).to.eq( + 0 + ); }); - it("Should collect reward tokens using collect rewards on all strategies", async () => { - const { harvester, governor, rewards } = context(); + it("Should not allow setting a valid router address", async () => { + const { harvester, fixture } = context(); + const { crv, governor } = fixture; - // No rewards in the harvester before harvest - for (const reward of rewards) { - await expect(harvester).to.have.balanceOf(0, reward.asset); - } + const config = { + allowedSlippageBps: 200, + harvestRewardBps: 500, + swapPlatformAddr: addresses.zero, + doSwapRewardToken: true, + swapPlatform: 0, + liquidationLimit: 0, + }; + + await expect( + harvester + .connect(governor) + .setRewardTokenConfig(crv.address, config, []) + ).to.be.revertedWith("EmptyAddress"); + }); - // Harvest rewards from all strategies - await harvester.connect(governor)["harvest()"](); + it("Should not allow higher slippage", async () => { + const { harvester, fixture } = context(); + const { crv, governor, uniswapRouter } = fixture; - // Check all the rewards were harvested - for (const reward of rewards) { - await expect( - await reward.asset.balanceOf(harvester.address) - ).to.be.equal(reward.expected); - } + const config = { + allowedSlippageBps: 1001, + harvestRewardBps: 500, + swapPlatformAddr: uniswapRouter.address, + doSwapRewardToken: true, + swapPlatform: 0, + liquidationLimit: 0, + }; + + await expect( + harvester + .connect(governor) + .setRewardTokenConfig(crv.address, config, []) + ).to.be.revertedWith("InvalidSlippageBps"); }); - it("Should collect all reward tokens even though the swap limits are set", async () => { - const { harvester, governor, rewards } = context(); - const mockUniswapRouter = await ethers.getContract("MockUniswapRouter"); + it("Should not allow higher reward fee", async () => { + const { harvester, fixture } = context(); + const { crv, governor, uniswapRouter } = fixture; - for (const reward of rewards) { - await expect( - harvester - .connect(governor) - .setRewardTokenConfig( - reward.asset.address, - 300, - 100, - mockUniswapRouter.address, - parseUnits("1", 18), - true - ) + const config = { + allowedSlippageBps: 133, + harvestRewardBps: 1001, + swapPlatformAddr: uniswapRouter.address, + doSwapRewardToken: true, + swapPlatform: 0, + liquidationLimit: 0, + }; + + await expect( + harvester + .connect(governor) + .setRewardTokenConfig(crv.address, config, []) + ).to.be.revertedWith("InvalidHarvestRewardBps"); + }); + + it("Should revert for unsupported tokens", async () => { + const { harvester, fixture } = context(); + const { ousd, governor, uniswapRouter } = fixture; + + const config = { + allowedSlippageBps: 133, + harvestRewardBps: 344, + swapPlatformAddr: uniswapRouter.address, + doSwapRewardToken: true, + swapPlatform: 0, + liquidationLimit: 0, + }; + + await expect( + harvester + .connect(governor) + .setRewardTokenConfig(ousd.address, config, []) + ).to.be.revertedWith("Asset not available"); + }); + + it("Should revert when it's not Governor", async () => { + const { harvester, fixture } = context(); + const { ousd, strategist, uniswapRouter } = fixture; + + const config = { + allowedSlippageBps: 133, + harvestRewardBps: 344, + swapPlatformAddr: uniswapRouter.address, + doSwapRewardToken: true, + swapPlatform: 0, + liquidationLimit: 0, + }; + + await expect( + harvester + .connect(strategist) + .setRewardTokenConfig(ousd.address, config, []) + ).to.be.revertedWith("Caller is not the Governor"); + }); + }); + + describe("Swap", () => { + async function _swapWithRouter(swapRouterConfig, swapData) { + const { harvester, strategies, rewardProceedsAddress, fixture } = + context(); + const { governor, uniswapRouter, domen } = fixture; + + const { strategy, rewardTokens } = strategies[0]; + + const baseToken = await ethers.getContractAt( + "MockUSDT", + await harvester.baseTokenAddress() + ); + const swapToken = rewardTokens[0]; + + // Configure to use Uniswap V3 + const config = { + allowedSlippageBps: 200, + harvestRewardBps: 500, + swapPlatformAddr: uniswapRouter.address, + doSwapRewardToken: true, + swapPlatform: 1, + liquidationLimit: 0, + ...swapRouterConfig, + }; + + await harvester + .connect(governor) + .setRewardTokenConfig(swapToken.address, config, swapData); + + await setOracleTokenPriceUsd(await swapToken.symbol(), "1"); + + let swapTx; + const balanceDiff = await changeInMultipleBalances( + async () => { + // Do the swap + // prettier-ignore + swapTx = await harvester.connect(domen)["harvestAndSwap(address)"]( + strategy.address + ) + }, + [baseToken, swapToken], + [ + harvester.address, + domen.address, + strategy.address, + rewardProceedsAddress, + ] + ); + + const balanceSwapped = + -1 * + (balanceDiff[strategy.address][swapToken.address] + + balanceDiff[harvester.address][swapToken.address]); + const tokensReceived = + balanceDiff[rewardProceedsAddress][baseToken.address] + + balanceDiff[domen.address][baseToken.address]; + + const protocolYield = (tokensReceived * 9500) / 10000; + const farmerFee = (tokensReceived * 500) / 10000; + + await expect(swapTx) + .to.emit(harvester, "RewardTokenSwapped") + .withArgs( + swapToken.address, + baseToken.address, + config.swapPlatform, + balanceSwapped.toString(), + tokensReceived.toString() + ); + + await expect(swapTx) + .to.emit(harvester, "RewardProceedsTransferred") + .withArgs(baseToken.address, domen.address, protocolYield, farmerFee); + + expect(balanceDiff[domen.address][baseToken.address]).to.equal(farmerFee); + expect(balanceDiff[rewardProceedsAddress][baseToken.address]).to.equal( + protocolYield + ); + } + + it("Should harvest and swap with Uniswap V2", async () => { + const { fixture, harvester, strategies } = context(); + const { uniswapRouter } = fixture; + const { rewardTokens } = strategies[0]; + + const baseToken = await ethers.getContractAt( + "MockUSDT", + await harvester.baseTokenAddress() + ); + const swapToken = rewardTokens[0]; + + await _swapWithRouter( + { + swapPlatform: 0, + swapPlatformAddr: uniswapRouter.address, + }, + utils.defaultAbiCoder.encode( + ["address[]"], + [[swapToken.address, baseToken.address]] ) - .to.emit(harvester, "RewardTokenConfigUpdated") - .withArgs( - reward.asset.address, - 300, - 100, - mockUniswapRouter.address, - parseUnits("1", 18), - true - ); - } + ); + }); - // Mint of MockCRVMinter mints a fixed 2e18 - await harvester.connect(governor)["harvest()"](); + it("Should harvest and swap with Uniswap V3", async () => { + const { fixture, harvester, strategies } = context(); + const { uniswapRouter, domen, daniel, governor } = fixture; + const { strategy, rewardTokens } = strategies[0]; - for (const reward of rewards) { - await expect( - await reward.asset.balanceOf(harvester.address) - ).to.be.equal(reward.expected); - } + const baseToken = await ethers.getContractAt( + "MockUSDT", + await harvester.baseTokenAddress() + ); + const swapToken = rewardTokens[0]; + + // Configure to use Uniswap V3 + const config = { + allowedSlippageBps: 200, + harvestRewardBps: 500, + swapPlatformAddr: uniswapRouter.address, + doSwapRewardToken: true, + swapPlatform: 0, + liquidationLimit: 0, + }; + + await harvester + .connect(governor) + .setRewardTokenConfig( + swapToken.address, + config, + utils.defaultAbiCoder.encode( + ["address[]"], + [[swapToken.address, baseToken.address]] + ) + ); + + await setOracleTokenPriceUsd(await swapToken.symbol(), "1"); + + const balBefore = await baseToken.balanceOf(daniel.address); + + // prettier-ignore + await harvester.connect(domen)["harvestAndSwap(address,address)"]( + strategy.address, + daniel.address + ) + + expect(await baseToken.balanceOf(daniel.address)).to.be.gt(balBefore); }); - it("Should collect reward tokens using collect rewards on a specific strategy", async () => { - const { harvester, governor, rewards, strategy } = context(); + it("Should harvest and swap with Balancer", async () => { + const { fixture } = context(); + const { balancerVault } = fixture; + await _swapWithRouter( + { + swapPlatform: 2, + swapPlatformAddr: balancerVault.address, + }, + utils.defaultAbiCoder.encode( + ["bytes32"], + ["0x000000000000000000000000000000000000000000000000000000000000dead"] + ) + ); + }); - await harvester.connect(governor)[ - // eslint-disable-next-line - "harvest(address)" - ](strategy.address); + it("Should harvest and swap with Curve", async () => { + const { fixture, harvester, strategies } = context(); + const { threePool, governor } = fixture; + const { rewardTokens } = strategies[0]; - for (const reward of rewards) { - await expect( - await reward.asset.balanceOf(harvester.address) - ).to.be.equal(reward.expected); - } + const baseToken = await ethers.getContractAt( + "MockUSDT", + await harvester.baseTokenAddress() + ); + const swapToken = rewardTokens[0]; + + await threePool + .connect(governor) + .setCoins([swapToken.address, baseToken.address, baseToken.address]); + + await _swapWithRouter( + { + swapPlatform: 3, + swapPlatformAddr: threePool.address, + }, + utils.defaultAbiCoder.encode(["uint256", "uint256"], ["0", "2"]) + ); }); - describe("harvest using Uniswap considering liquidation limits", () => { - it("Should collect and swap using harvestAndSwap() by governor", async () => { - await harvestAndSwapTokens(true); - }); + it("Should not swap when disabled", async () => { + const { harvester, strategies, fixture } = context(); + const { governor, balancerVault, domen } = fixture; + + const { strategy, rewardTokens } = strategies[0]; + + const swapToken = rewardTokens[0]; + + // Configure to use Uniswap V3 + const config = { + allowedSlippageBps: 200, + harvestRewardBps: 500, + swapPlatformAddr: balancerVault.address, + doSwapRewardToken: false, + swapPlatform: 2, + liquidationLimit: 0, + }; + + await harvester + .connect(governor) + .setRewardTokenConfig( + swapToken.address, + config, + utils.defaultAbiCoder.encode( + ["bytes32"], + [ + "0x000000000000000000000000000000000000000000000000000000000000dead", + ] + ) + ); - it("Should collect and swap using harvestAndSwap(strategy_address) by anyone", async () => { - await harvestAndSwapTokens(false); - }); + await setOracleTokenPriceUsd(await swapToken.symbol(), "1"); + + // prettier-ignore + const swapTx = await harvester + .connect(domen)["harvestAndSwap(address)"](strategy.address); + + await expect(swapTx).to.not.emit(harvester, "RewardTokenSwapped"); + await expect(swapTx).to.not.emit(harvester, "RewardProceedsTransferred"); }); - const harvestAndSwapTokens = async (callAsGovernor) => { - const { - anna, - harvester, - governor, - rewards, - crv, - cvx, - usdt, - vault, - strategy, - } = context(); - - const mockUniswapRouter = await ethers.getContract("MockUniswapRouter"); - await mockUniswapRouter.initialize( - [crv.address, cvx.address], - [usdt.address, usdt.address] + it("Should harvest and swap and send rewards to external address", async () => { + const { fixture, harvester, strategies } = context(); + const { uniswapRouter } = fixture; + const { rewardTokens } = strategies[0]; + + const baseToken = await ethers.getContractAt( + "MockUSDT", + await harvester.baseTokenAddress() ); + const swapToken = rewardTokens[0]; + + await _swapWithRouter( + { + swapPlatform: 0, + swapPlatformAddr: uniswapRouter.address, + }, + utils.defaultAbiCoder.encode( + ["address[]"], + [[swapToken.address, baseToken.address]] + ) + ); + }); + + it("Should not swap when balance is zero", async () => { + const { harvester, strategies, fixture } = context(); + const { governor, balancerVault } = fixture; - // Make sure Vault has 0 USDT balance - await expect(vault).has.a.balanceOf("0", usdt); - await expect(vault).has.a.balanceOf("0", crv); - await expect(vault).has.a.balanceOf("0", cvx); + const { rewardTokens, strategy } = strategies[0]; - // Give Uniswap mock some USDT so it can give it back in CRV liquidation - await usdt - .connect(anna) - .transfer(mockUniswapRouter.address, usdtUnits("1000")); + const swapToken = rewardTokens[0]; + + // Configure to use Uniswap V3 + const config = { + allowedSlippageBps: 200, + harvestRewardBps: 500, + swapPlatformAddr: balancerVault.address, + doSwapRewardToken: true, + swapPlatform: 2, + liquidationLimit: 0, + }; await harvester .connect(governor) .setRewardTokenConfig( - rewards[0].asset.address, - 300, - 100, - mockUniswapRouter.address, - parseUnits("0.8", 18), - true + swapToken.address, + config, + utils.defaultAbiCoder.encode( + ["bytes32"], + [ + "0x000000000000000000000000000000000000000000000000000000000000dead", + ] + ) ); - if (rewards.length > 1) { - await harvester - .connect(governor) - .setRewardTokenConfig( - rewards[1].asset.address, - 300, - 100, - mockUniswapRouter.address, - parseUnits("1.5", 18), - true - ); - } + await setOracleTokenPriceUsd(await swapToken.symbol(), "1"); + + // Remove everything from strategy + await swapToken + .connect(await impersonateAndFund(strategy.address)) + .transfer( + governor.address, + await swapToken.balanceOf(strategy.address) + ); + + // prettier-ignore + const swapTx = await harvester + .connect(governor)["harvestAndSwap(address)"](strategy.address); + + await expect(swapTx).to.not.emit(harvester, "RewardTokenSwapped"); + await expect(swapTx).to.not.emit(harvester, "RewardProceedsTransferred"); + }); + + it("Should revert when swap platform doesn't return enough tokens", async () => { + const { harvester, strategies, fixture } = context(); + const { governor, balancerVault } = fixture; + + const { rewardTokens, strategy } = strategies[0]; + + const swapToken = rewardTokens[0]; + + // Configure to use Uniswap V3 + const config = { + allowedSlippageBps: 200, + harvestRewardBps: 500, + swapPlatformAddr: balancerVault.address, + doSwapRewardToken: true, + swapPlatform: 2, + liquidationLimit: 0, + }; + + await harvester + .connect(governor) + .setRewardTokenConfig( + swapToken.address, + config, + utils.defaultAbiCoder.encode( + ["bytes32"], + [ + "0x000000000000000000000000000000000000000000000000000000000000dead", + ] + ) + ); + + await setOracleTokenPriceUsd(await swapToken.symbol(), "1"); - const reward1Config = await harvester.rewardTokenConfigs( - rewards[0].asset.address + // Disable transfer on mock balancerVault + await balancerVault.connect(governor).disableTransfer(); + + // prettier-ignore + const swapTx = harvester + .connect(governor)["harvestAndSwap(address)"](strategy.address); + + await expect(swapTx).to.be.revertedWith("BalanceMismatchAfterSwap"); + }); + + it("Should revert when slippage is high", async () => { + const { harvester, strategies, fixture } = context(); + const { governor, balancerVault } = fixture; + + const { rewardTokens, strategy } = strategies[0]; + + const swapToken = rewardTokens[0]; + + // Configure to use Uniswap V3 + const config = { + allowedSlippageBps: 200, + harvestRewardBps: 500, + swapPlatformAddr: balancerVault.address, + doSwapRewardToken: true, + swapPlatform: 2, + liquidationLimit: 0, + }; + + await harvester + .connect(governor) + .setRewardTokenConfig( + swapToken.address, + config, + utils.defaultAbiCoder.encode( + ["bytes32"], + [ + "0x000000000000000000000000000000000000000000000000000000000000dead", + ] + ) + ); + + await setOracleTokenPriceUsd(await swapToken.symbol(), "1"); + + // Disable transfer on mock balancerVault + await balancerVault.connect(governor).disableSlippageError(); + await balancerVault.connect(governor).setSlippage(oethUnits("0.1")); + + // prettier-ignore + const swapTx = harvester + .connect(governor)["harvestAndSwap(address)"](strategy.address); + + await expect(swapTx).to.be.revertedWith("SlippageError"); + }); + + it("Should use liquidation limit", async () => { + const { harvester, strategies, fixture } = context(); + const { governor, balancerVault, domen } = fixture; + + const { strategy, rewardTokens } = strategies[0]; + + const swapToken = rewardTokens[0]; + const baseToken = await ethers.getContractAt( + "MockUSDT", + await harvester.baseTokenAddress() ); - expect(reward1Config.liquidationLimit).to.equal(parseUnits("0.8", 18)); - if (rewards.length > 1) { - const reward2Config = await harvester.rewardTokenConfigs( - rewards[1].asset.address + // Configure to use Balancer + const config = { + allowedSlippageBps: 0, + harvestRewardBps: 0, + swapPlatformAddr: balancerVault.address, + doSwapRewardToken: true, + swapPlatform: 2, + liquidationLimit: ousdUnits("100"), + }; + + await harvester + .connect(governor) + .setRewardTokenConfig( + swapToken.address, + config, + utils.defaultAbiCoder.encode( + ["bytes32"], + [ + "0x000000000000000000000000000000000000000000000000000000000000dead", + ] + ) ); - expect(reward2Config.liquidationLimit).to.equal(parseUnits("1.5", 18)); - } - const balanceBeforeAnna = await usdt.balanceOf(anna.address); + await setOracleTokenPriceUsd(await swapToken.symbol(), "1"); - if (callAsGovernor) { - const usdtInVaultExpected = - rewards.length > 1 - ? "2.3" // 0.8 + 1.5 - : "0.8"; - // prettier-ignore - await harvester - .connect(governor)["harvestAndSwap()"](); - await expect(vault).has.a.balanceOf(usdtInVaultExpected, usdt); + await swapToken + .connect(domen) + .mintTo(harvester.address, ousdUnits("1000")); - await expect( - harvester.connect(anna)["harvestAndSwap()"]() - ).to.be.revertedWith("Caller is not the Governor"); - } else { - // prettier-ignore - await harvester - .connect(anna)["harvestAndSwap(address)"](strategy.address); - - const usdtInVaultExpected = - rewards.length > 1 - ? "2.277" // (0.8 + 1.5) - 1% - : "0.792"; - await expect(vault).has.a.balanceOf(usdtInVaultExpected, usdt); - - const usdtToHarvesterExpected = - rewards.length > 1 - ? "0.023" // 2.3* 1% = 0.023 - : "0.008"; // 0.8 * 1% = 0.08 - const balanceAfterAnna = await usdt.balanceOf(anna.address); - await expect(balanceAfterAnna - balanceBeforeAnna).to.be.equal( - parseUnits(usdtToHarvesterExpected, 6) + // prettier-ignore + const swapTx = await harvester + .connect(domen)["harvestAndSwap(address)"](strategy.address); + + await expect(swapTx) + .to.emit(harvester, "RewardTokenSwapped") + .withArgs( + swapToken.address, + baseToken.address, + 2, + ousdUnits("100"), + usdtUnits("100") ); - } + }); + + it("Should not harvest from unsupported strategies", async () => { + const { harvester, fixture } = context(); + const { domen } = fixture; - await expect(harvester).has.a.balanceOf("1.2", rewards[0].asset); + // prettier-ignore await expect( - await rewards[0].asset.balanceOf(strategy.address) - ).to.be.equal("0"); + harvester + .connect(domen)["harvestAndSwap(address)"](domen.address) + ).to.be.revertedWith("UnsupportedStrategy"); + }); + }); + + describe("Admin function", () => { + it("Should only allow governor to change RewardProceedsAddress", async () => { + const { harvester, fixture } = context(); + const { governor, daniel, strategist } = fixture; - if (rewards.length > 1) { - await expect(harvester).has.a.balanceOf("1.5", rewards[1].asset); + const tx = await harvester + .connect(governor) + .setRewardProceedsAddress(strategist.address); + + await expect(tx) + .to.emit(harvester, "RewardProceedsAddressChanged") + .withArgs(strategist.address); + + expect(await harvester.rewardProceedsAddress()).to.equal( + strategist.address + ); + + for (const signer of [daniel, strategist]) { await expect( - await rewards[1].asset.balanceOf(strategy.address) - ).to.be.equal("0"); + harvester.connect(signer).setRewardProceedsAddress(governor.address) + ).to.be.revertedWith("Caller is not the Governor"); } - }; + }); - it("Should revert when zero address attempts to be set as reward token address", async () => { - const { strategy, governor, rewards } = context(); + it("Should not allow to set invalid rewardProceedsAddress", async () => { + const { harvester, fixture } = context(); + const { governor } = fixture; await expect( - strategy - .connect(governor) - .setRewardTokenAddresses([ - rewards[0].asset.address, - "0x0000000000000000000000000000000000000000", - ]) - ).to.be.revertedWith("Can not set an empty address as a reward token"); + harvester.connect(governor).setRewardProceedsAddress(addresses.zero) + ).to.be.revertedWith("EmptyAddress"); + }); + + it("Should allow governor to set supported strategies", async () => { + const { harvester, fixture } = context(); + const { governor, strategist } = fixture; + + expect(await harvester.supportedStrategies(strategist.address)).to.be + .false; + await harvester + .connect(governor) + .setSupportedStrategy(strategist.address, true); + expect(await harvester.supportedStrategies(strategist.address)).to.be + .true; + + await expect( + harvester + .connect(strategist) + .setSupportedStrategy(strategist.address, false) + ).to.be.revertedWith("Caller is not the Governor"); + }); + + it("Should allow governor to transfer any token", async () => { + const { harvester, fixture } = context(); + const { governor, strategist, dai } = fixture; + + await dai.connect(governor).mintTo(harvester.address, daiUnits("1000")); + + await expect( + harvester.connect(strategist).transferToken(dai.address, "1") + ).to.be.revertedWith("Caller is not the Governor"); + + await harvester + .connect(governor) + .transferToken(dai.address, daiUnits("1000")); + + expect(await dai.balanceOf(harvester.address)).to.eq("0"); }); }); }; diff --git a/contracts/test/behaviour/reward-tokens.fork.js b/contracts/test/behaviour/reward-tokens.fork.js new file mode 100644 index 0000000000..038d0771ad --- /dev/null +++ b/contracts/test/behaviour/reward-tokens.fork.js @@ -0,0 +1,127 @@ +const { expect } = require("chai"); + +const { isFork } = require("../helpers"); +const addresses = require("../../utils/addresses"); +const { BigNumber } = require("ethers"); + +/** + * + * @param {*} context a function that returns: + * - vault: OUSD Vault or OETH Vault + * - harvester: OUSD Harvester or OETH Harvester + * - expectedConfigs: Expected Reward Token Config + * @example + shouldBehaveLikeHarvester(() => ({ + ...fixture + })); + */ +const shouldHaveRewardTokensConfigured = (context) => { + if (!isFork) { + // Only meant to be used on fork + return; + } + + describe("Reward Tokens", () => { + it("Should have swap config for all reward tokens from strategies", async () => { + let { vault, harvester, expectedConfigs } = context(); + const strategies = await vault.getAllStrategies(); + + expectedConfigs = Object.keys(expectedConfigs).reduce( + (o, k) => ({ + ...o, + [k.toLowerCase()]: expectedConfigs[k], + }), + {} + ); + + const checkedConfigs = []; + + for (const strategyAddr of strategies) { + const strategy = await ethers.getContractAt("IStrategy", strategyAddr); + const rewardTokens = await strategy.getRewardTokenAddresses(); + + // If the strategy has tokens, + if (rewardTokens.length) { + // It should be whitelisted on Harvester + expect(await harvester.supportedStrategies(strategy.address)).to.be + .true; + + for (let token of rewardTokens) { + token = token.toLowerCase(); + if (checkedConfigs.includes(token)) { + return; + } + checkedConfigs.push(token); + + const config = await harvester.rewardTokenConfigs(token); + const expectedConfig = expectedConfigs[token.toLowerCase()]; + + // Each reward token should have a swap route configured + expect(config.swapPlatformAddr).to.not.eq( + addresses.zero, + `Harvester not configured for token: ${token}` + ); + expect(config.doSwapRewardToken).to.be.eq( + true, + `Swap disabled for token: ${token}` + ); + + expect(config.swapPlatform).to.eq(expectedConfig.swapPlatform); + expect(config.swapPlatformAddr).to.eq( + expectedConfig.swapPlatformAddr + ); + expect(config.harvestRewardBps).to.eq( + expectedConfig.harvestRewardBps + ); + expect(config.allowedSlippageBps).to.eq( + expectedConfig.allowedSlippageBps + ); + expect(config.liquidationLimit).to.eq( + expectedConfig.liquidationLimit + ); + + if (config.swapPlatform == 0) { + // Uniswap V2 + expect(await harvester.uniswapV2Path(token)).to.eq( + expectedConfig.uniswapV2Path + ); + } else if (config.swapPlatform == 1) { + // Uniswap V3 + expect(await harvester.uniswapV3Path(token)).to.eq( + expectedConfig.uniswapV3Path + ); + } else if (config.swapPlatform == 2) { + // Balancer + expect(await harvester.balancerPoolId(token)).to.eq( + expectedConfig.balancerPoolId + ); + } else if (config.swapPlatform == 3) { + const [rewardTokenIndex, baseTokenIndex] = + expectedConfig.curvePoolIndices; + const actualIndices = await harvester.curvePoolIndices(token); + // Curve + expect(actualIndices[0].toString()).to.eq( + BigNumber.from(rewardTokenIndex).toString() + ); + expect(actualIndices[1].toString()).to.eq( + BigNumber.from(baseTokenIndex).toString() + ); + } + } + } + } + + const missingTokenConfigs = Object.keys(expectedConfigs).filter( + (k) => !checkedConfigs.includes(k) + ); + expect(missingTokenConfigs.length).to.eq( + 0, + `Missing config for reward tokens: ${missingTokenConfigs}` + ); + }); + }); +}; + +module.exports = { + shouldHaveRewardTokensConfigured, +}; diff --git a/contracts/test/harvest/ousd-harvest-crv.fork-test.js b/contracts/test/harvest/ousd-harvest-crv.fork-test.js index 696ff1230c..f79dbd1f72 100644 --- a/contracts/test/harvest/ousd-harvest-crv.fork-test.js +++ b/contracts/test/harvest/ousd-harvest-crv.fork-test.js @@ -47,21 +47,20 @@ describe("ForkTest: Harvest OUSD", function () { await expect(usdtSwapped).to.be.gt(usdtUnits("3100")); }); }); - describe("no CRV liquidation limit", function () { + describe.skip("no CRV liquidation limit", function () { beforeEach(async () => { const { crv, harvester, timelock } = fixture; const oldCrvTokenConfig = await harvester.rewardTokenConfigs(crv.address); - await harvester - .connect(timelock) - .setRewardTokenConfig( - crv.address, - oldCrvTokenConfig.allowedSlippageBps, - oldCrvTokenConfig.harvestRewardBps, - oldCrvTokenConfig.uniswapV2CompatibleAddr, - MAX_UINT256, - oldCrvTokenConfig.doSwapRewardToken - ); + await harvester.connect(timelock).setRewardTokenConfig( + crv.address, + oldCrvTokenConfig.allowedSlippageBps, + oldCrvTokenConfig.harvestRewardBps, + 0, // Uniswap V2 compatible + oldCrvTokenConfig.swapPlatformAddr, + MAX_UINT256, + oldCrvTokenConfig.doSwapRewardToken + ); }); /* * Skipping this test as it should only fail on a specific block number, where @@ -71,7 +70,7 @@ describe("ForkTest: Harvest OUSD", function () { * - depth of the SushiSwap pool is not deep enough to handle the swap without * hitting the slippage limit. */ - it.skip("should not harvest and swap", async function () { + it("should not harvest and swap", async function () { const { anna, OUSDmetaStrategy, harvester } = fixture; // prettier-ignore @@ -82,30 +81,29 @@ describe("ForkTest: Harvest OUSD", function () { ); }); }); - describe("CRV liquidation limit", function () { + describe.skip("CRV liquidation limit", function () { const crvLimit = 4000; beforeEach(async () => { const { crv, harvester, timelock } = fixture; const oldCrvTokenConfig = await harvester.rewardTokenConfigs(crv.address); - await harvester - .connect(timelock) - .setRewardTokenConfig( - crv.address, - oldCrvTokenConfig.allowedSlippageBps, - oldCrvTokenConfig.harvestRewardBps, - oldCrvTokenConfig.uniswapV2CompatibleAddr, - parseUnits(crvLimit.toString(), 18), - oldCrvTokenConfig.doSwapRewardToken - ); + await harvester.connect(timelock).setRewardTokenConfig( + crv.address, + oldCrvTokenConfig.allowedSlippageBps, + oldCrvTokenConfig.harvestRewardBps, + 0, // Uniswap V2 compatible + oldCrvTokenConfig.swapPlatformAddr, + parseUnits(crvLimit.toString(), 18), + oldCrvTokenConfig.doSwapRewardToken + ); }); /* * Skipping this test as it will only succeed again on a specific block number. * If strategy doesn't have enough CRV not nearly enough rewards are going to be * harvested for the test to pass. */ - it.skip("should harvest and swap", async function () { + it("should harvest and swap", async function () { const { crv, OUSDmetaStrategy, dripper, harvester, timelock, usdt } = fixture; diff --git a/contracts/test/helpers.js b/contracts/test/helpers.js index 5c7e82989f..42002f50ce 100644 --- a/contracts/test/helpers.js +++ b/contracts/test/helpers.js @@ -335,6 +335,9 @@ const getOracleAddresses = async (deployments) => { OGN_ETH: addresses.mainnet.chainlinkOGN_ETH, RETH_ETH: addresses.mainnet.chainlinkRETH_ETH, stETH_ETH: addresses.mainnet.chainlinkstETH_ETH, + BAL_ETH: addresses.mainnet.chainlinkBAL_ETH, + // TODO: Update with deployed address + // AURA_ETH: addresses.mainnet.chainlinkAURA_ETH, }, openOracle: addresses.mainnet.openOracle, // Deprecated }; @@ -366,6 +369,8 @@ const getOracleAddresses = async (deployments) => { .address, WETH_ETH: (await deployments.get("MockChainlinkOracleFeedWETHETH")) .address, + BAL_ETH: (await deployments.get("MockChainlinkOracleFeedBALETH")) + .address, NonStandardToken_USD: ( await deployments.get("MockChainlinkOracleFeedNonStandardToken") ).address, @@ -414,6 +419,9 @@ const getAssetAddresses = async (deployments) => { uniswapV3Router: addresses.mainnet.uniswapV3Router, uniswapUniversalRouter: addresses.mainnet.uniswapUniversalRouter, sushiswapRouter: addresses.mainnet.sushiswapRouter, + auraWeightedOraclePool: addresses.mainnet.AuraWeightedOraclePool, + AURA: addresses.mainnet.AURA, + BAL: addresses.mainnet.BAL, }; } else { const addressMap = { @@ -455,6 +463,10 @@ const getAssetAddresses = async (deployments) => { uniswapUniversalRouter: (await deployments.get("MockUniswapRouter")) .address, sushiswapRouter: (await deployments.get("MockUniswapRouter")).address, + auraWeightedOraclePool: (await deployments.get("MockOracleWeightedPool")) + .address, + AURA: (await deployments.get("MockAura")).address, + BAL: (await deployments.get("MockBAL")).address, }; try { @@ -521,6 +533,54 @@ async function changeInBalance( return balanceAfter - balanceBefore; } +/** + * Calculates the change in balance after a function has been executed on a contract + * @param {Function} functionChangingBalance - The function that changes the balance + * @param {[Object]} tokens - The token contract + * @param {[string]} accounts - An array of addresses + **/ +async function changeInMultipleBalances( + functionChangingBalance, + tokens, + accounts +) { + const _getBalances = async () => { + const out = {}; + + for (const account of accounts) { + out[account] = {}; + + const balances = await Promise.all( + tokens.map((t) => t.balanceOf(account)) + ); + + for (let i = 0; i < balances.length; i++) { + out[account][tokens[i].address] = balances[i]; + } + } + + return out; + }; + + const balanceBefore = await _getBalances(); + + await functionChangingBalance(); + + const balanceAfter = await _getBalances(); + + const balanceDiff = {}; + for (const account of accounts) { + balanceDiff[account] = {}; + for (const token of tokens) { + const tokenAddr = token.address; + balanceDiff[account][tokenAddr] = + balanceAfter[account][tokenAddr] - balanceBefore[account][tokenAddr]; + } + } + + return balanceDiff; +} + /** * Is first parameter's BigNumber value inside expected tolerance * @param {BigNumber} bigNumber: The BigNumber whose value is being inspected @@ -719,6 +779,7 @@ module.exports = { advanceBlocks, isWithinTolerance, changeInBalance, + changeInMultipleBalances, differenceInErc20TokenBalance, differenceInErc20TokenBalances, differenceInStrategyBalance, diff --git a/contracts/test/oracle/aura-feed.fork-test.js b/contracts/test/oracle/aura-feed.fork-test.js new file mode 100644 index 0000000000..af0141ca76 --- /dev/null +++ b/contracts/test/oracle/aura-feed.fork-test.js @@ -0,0 +1,67 @@ +const { expect } = require("chai"); +const { loadDefaultFixture } = require("../_fixture"); +const { oethUnits } = require("../helpers"); +const addresses = require("../../utils/addresses"); +const { hotDeployOption } = require("../_hot-deploy"); + +describe("ForkTest: Aura/WETH Price Feed", function () { + this.timeout(0); + + let fixture; + + beforeEach(async () => { + fixture = await loadDefaultFixture(); + + hotDeployOption(fixture, null, { + isOethFixture: true, + }); + + fixture.auraWETHPriceFeed = await ethers.getContract("AuraWETHPriceFeed"); + + fixture.auraWETHWeightedPool = await ethers.getContractAt( + "IOracleWeightedPool", + addresses.mainnet.AuraWeightedOraclePool + ); + }); + + it("should get Aura price", async () => { + const { auraWETHPriceFeed, auraWETHWeightedPool } = fixture; + + const [price_1h, price_5m] = + await auraWETHWeightedPool.getTimeWeightedAverage([ + [0, 3600, 300], + [0, 300, 0], + ]); + + let shouldRevert = false; + let diff = oethUnits("0"); + + if (price_1h > price_5m) { + diff = oethUnits("1") - (oethUnits("1") * price_1h) / price_5m; + } else if (price_5m > price_1h) { + diff = oethUnits("1") - (oethUnits("1") * price_5m) / price_1h; + } + + shouldRevert = diff > oethUnits("0.02"); + + if (shouldRevert) { + await expect(auraWETHPriceFeed.price).to.be.revertedWith( + "HighPriceVolatility" + ); + } else { + expect(await auraWETHPriceFeed.price()).to.equal(price_5m); + + const [, answer] = await auraWETHPriceFeed.latestRoundData(); + expect(answer).to.equal(price_5m); + } + + await expect(auraWETHPriceFeed.getRoundData(1)).to.be.revertedWith( + "No data present" + ); + }); + + it("should get price from oracle", async () => { + const { oethOracleRouter, aura } = fixture; + await oethOracleRouter.price(aura.address); + }); +}); diff --git a/contracts/test/oracle/aura-feed.js b/contracts/test/oracle/aura-feed.js new file mode 100644 index 0000000000..8ab2d0949a --- /dev/null +++ b/contracts/test/oracle/aura-feed.js @@ -0,0 +1,207 @@ +const { expect } = require("chai"); +const { loadDefaultFixture } = require("../_fixture"); +const { oethUnits } = require("../helpers"); + +describe("ForkTest: Aura/WETH Price Feed", function () { + this.timeout(0); + + let fixture; + + beforeEach(async () => { + fixture = await loadDefaultFixture(); + + fixture.auraWETHPriceFeed = await ethers.getContract("AuraWETHPriceFeed"); + fixture.auraWETHWeightedPool = await ethers.getContract( + "MockOracleWeightedPool" + ); + + await fixture.auraWETHPriceFeed + .connect(fixture.governor) + .setStrategistAddr(fixture.strategist.address); + }); + + it("should get Aura price from weighted pool", async () => { + const { auraWETHPriceFeed, auraWETHWeightedPool, strategist } = fixture; + + // Price with less than 2% deviation + await auraWETHWeightedPool + .connect(strategist) + .setNextResults([oethUnits("1"), oethUnits("1.01")]); + + expect(await auraWETHPriceFeed.price()).to.eq(oethUnits("1.01")); + + // Price with less than 2% deviation + await auraWETHWeightedPool + .connect(strategist) + .setNextResults([oethUnits("1.01"), oethUnits("1")]); + + expect(await auraWETHPriceFeed.price()).to.eq(oethUnits("1")); + }); + + it("should revert if price volatility is high", async () => { + const { auraWETHPriceFeed, auraWETHWeightedPool, strategist } = fixture; + + // Price with > 2% deviation + await auraWETHWeightedPool + .connect(strategist) + .setNextResults([oethUnits("1"), oethUnits("1.03")]); + + await expect(auraWETHPriceFeed.price()).to.be.revertedWith( + "HighPriceVolatility" + ); + + // Price with > 2% deviation + await auraWETHWeightedPool + .connect(strategist) + .setNextResults([oethUnits("1.03"), oethUnits("1")]); + + await expect(auraWETHPriceFeed.price()).to.be.revertedWith( + "HighPriceVolatility" + ); + }); + + it("should revert if paused", async () => { + const { auraWETHPriceFeed, auraWETHWeightedPool, strategist } = fixture; + + // Price with > 2% deviation + await auraWETHWeightedPool + .connect(strategist) + .setNextResults([oethUnits("1"), oethUnits("1")]); + + await auraWETHPriceFeed.connect(strategist).pause(); + + await expect(auraWETHPriceFeed.price()).to.be.revertedWith( + "PriceFeedPausedError" + ); + + // Price with > 2% deviation + await auraWETHWeightedPool + .connect(strategist) + .setNextResults([oethUnits("1.03"), oethUnits("1")]); + + await expect(auraWETHPriceFeed.price()).to.be.revertedWith( + "PriceFeedPausedError" + ); + }); + + it("Should allow strategist to pause price feeds", async () => { + const { auraWETHPriceFeed, strategist } = fixture; + + await expect(await auraWETHPriceFeed.connect(strategist).pause()).to.emit( + auraWETHPriceFeed, + "PriceFeedPaused" + ); + + expect(await auraWETHPriceFeed.paused()).to.be.true; + }); + + it("Should allow governor to pause price feeds", async () => { + const { auraWETHPriceFeed, governor } = fixture; + + await expect(await auraWETHPriceFeed.connect(governor).pause()).to.emit( + auraWETHPriceFeed, + "PriceFeedPaused" + ); + + expect(await auraWETHPriceFeed.paused()).to.be.true; + }); + + it("Should allow governor to unpause price feeds", async () => { + const { auraWETHPriceFeed, governor, strategist } = fixture; + + // Pause it + await auraWETHPriceFeed.connect(governor).pause(); + + await expect( + auraWETHPriceFeed.connect(strategist).unpause() + ).to.be.revertedWith("Caller is not the Governor"); + + await expect(await auraWETHPriceFeed.connect(governor).unpause()).to.emit( + auraWETHPriceFeed, + "PriceFeedUnpaused" + ); + expect(await auraWETHPriceFeed.paused()).to.be.false; + }); + + it("Should not allow pause/unpause if already in the state", async () => { + const { auraWETHPriceFeed, governor } = fixture; + + await expect( + auraWETHPriceFeed.connect(governor).unpause() + ).to.be.revertedWith("PriceFeedUnpausedError"); + + await auraWETHPriceFeed.connect(governor).pause(); + + await expect( + auraWETHPriceFeed.connect(governor).pause() + ).to.be.revertedWith("PriceFeedPausedError"); + }); + + it("Should allow governor to set tolerance value", async () => { + const { auraWETHWeightedPool, auraWETHPriceFeed, governor, strategist } = + fixture; + + const tx = await auraWETHPriceFeed + .connect(governor) + .setTolerance(oethUnits("0.09")); + + await expect(tx) + .to.emit(auraWETHPriceFeed, "ToleranceChanged") + .withArgs(oethUnits("0.02").toString(), oethUnits("0.09").toString()); + + expect(await auraWETHPriceFeed.tolerance()).to.eq(oethUnits("0.09")); + + // Price with > 9% deviation + await auraWETHWeightedPool + .connect(strategist) + .setNextResults([oethUnits("1"), oethUnits("1.1")]); + + await expect(auraWETHPriceFeed.price()).to.be.revertedWith( + "HighPriceVolatility" + ); + + // Price with < 9% deviation + await auraWETHWeightedPool + .connect(strategist) + .setNextResults([oethUnits("1"), oethUnits("1.05")]); + + await expect(auraWETHPriceFeed.price()).to.not.be.reverted; + }); + + it("Should not allow strategist to set tolerance value", async () => { + const { auraWETHPriceFeed, strategist } = fixture; + + await expect( + auraWETHPriceFeed.connect(strategist).setTolerance(oethUnits("0.09")) + ).to.be.revertedWith("Caller is not the Governor"); + }); + + it("Should not allow higher tolerance", async () => { + const { auraWETHPriceFeed, governor } = fixture; + + await expect( + auraWETHPriceFeed.connect(governor).setTolerance(oethUnits("0.11")) + ).to.be.revertedWith("InvalidToleranceBps"); + }); + + it("should get price from oracle", async () => { + const { oethOracleRouter, aura } = fixture; + await oethOracleRouter.price(aura.address); + }); + + it("should be compatible with AggregatorV3Interface", async () => { + const { auraWETHPriceFeed } = fixture; + + const vals = await auraWETHPriceFeed.latestRoundData(); + + expect(vals[0]).to.eq("0"); + expect(vals[1]).to.eq(oethUnits("1")); + expect(vals[2]).to.eq("0"); + expect(vals[3]).to.be.gt("0"); + expect(vals[4]).to.eq("0"); + + await expect(auraWETHPriceFeed.getRoundData(1)).to.be.revertedWith( + "No data present" + ); + }); +}); diff --git a/contracts/test/oracle/oracle.fork-test.js b/contracts/test/oracle/oracle.fork-test.js index 52b75fe1ba..1da7bb1741 100644 --- a/contracts/test/oracle/oracle.fork-test.js +++ b/contracts/test/oracle/oracle.fork-test.js @@ -4,54 +4,55 @@ const { parseUnits } = require("ethers/lib/utils"); const { loadDefaultFixture } = require("../_fixture"); const { isCI } = require("../helpers"); -describe("ForkTest: Oracle Routers", function () { +describe("ForkTest: OETH Oracle Routers", function () { this.timeout(0); // Retry up to 3 times on CI this.retries(isCI ? 3 : 0); - let fixture; - - describe("OETH Oracle Router", () => { - let oethOracleRouter; - beforeEach(async () => { - fixture = await loadDefaultFixture(); - oethOracleRouter = await ethers.getContract("OETHOracleRouter"); - }); - it("should get rETH price", async () => { - const { reth } = fixture; - - const price = await oethOracleRouter.price(reth.address); - expect(price).to.gte(parseUnits("1083", 15)); - expect(price).to.lt(parseUnits("111", 16)); - }); - it("should get frxETH price", async () => { - const { frxETH } = fixture; - - const price = await oethOracleRouter.price(frxETH.address); - expect(price).to.lt(parseUnits("1", 18)); - }); - it("should get WETH price", async () => { - const { weth } = fixture; - - const price = await oethOracleRouter.price(weth.address); - expect(price).to.eq(parseUnits("1", 18)); - }); - it("should get stETH price", async () => { - const { stETH } = fixture; - - const price = await oethOracleRouter.price(stETH.address); - expect(price).to.approxEqualTolerance(parseUnits("1", 18), 1); - }); - it("should get gas costs of assets", async () => { - const { reth, frxETH, stETH, weth, josh } = fixture; - - for (const asset of [frxETH, reth, stETH, weth]) { - const tx = await oethOracleRouter - .connect(josh) - .populateTransaction.price(asset.address); - await josh.sendTransaction(tx); - } - }); + let fixture, oethOracleRouter; + beforeEach(async () => { + fixture = await loadDefaultFixture(); + oethOracleRouter = await ethers.getContract("OETHOracleRouter"); + }); + + it("should get rETH price", async () => { + const { reth } = fixture; + + const price = await oethOracleRouter.price(reth.address); + expect(price).to.gte(parseUnits("1083", 15)); + expect(price).to.lt(parseUnits("111", 16)); + }); + + it("should get frxETH price", async () => { + const { frxETH } = fixture; + + const price = await oethOracleRouter.price(frxETH.address); + expect(price).to.lt(parseUnits("1", 18)); + }); + + it("should get WETH price", async () => { + const { weth } = fixture; + + const price = await oethOracleRouter.price(weth.address); + expect(price).to.eq(parseUnits("1", 18)); + }); + + it("should get stETH price", async () => { + const { stETH } = fixture; + + const price = await oethOracleRouter.price(stETH.address); + expect(price).to.approxEqualTolerance(parseUnits("1", 18), 1); + }); + + it("should get gas costs of assets", async () => { + const { reth, frxETH, stETH, weth, josh } = fixture; + + for (const asset of [frxETH, reth, stETH, weth]) { + const tx = await oethOracleRouter + .connect(josh) + .populateTransaction.price(asset.address); + await josh.sendTransaction(tx); + } }); }); diff --git a/contracts/test/strategies/3pool.js b/contracts/test/strategies/3pool.js index 6be3fc5942..30421bbcbb 100644 --- a/contracts/test/strategies/3pool.js +++ b/contracts/test/strategies/3pool.js @@ -1,11 +1,9 @@ const { expect } = require("chai"); const { utils } = require("ethers"); -const { MAX_UINT256 } = require("../../utils/constants"); const { createFixtureLoader, threepoolVaultFixture } = require("../_fixture"); const { daiUnits, - usdtUnits, ousdUnits, units, expectApproxSupply, @@ -137,49 +135,23 @@ describe("3Pool Strategy", function () { ).to.be.revertedWith("Caller is not the Governor"); }); - it("Should allow the governor to call harvest for a specific strategy", async () => { - // Mint of MockCRVMinter mints a fixed 2e18 - // prettier-ignore - await harvester - .connect(governor)["harvest(address)"](threePoolStrategy.address); - }); - - it("Should collect reward tokens using collect rewards on all strategies", async () => { - // Mint of MockCRVMinter mints a fixed 2e18 - await crvMinter.connect(governor).mint(threePoolStrategy.address); - await harvester.connect(governor)["harvest()"](); - await expect(await crv.balanceOf(harvester.address)).to.be.equal( - utils.parseUnits("2", 18) - ); - }); - - it("Should collect reward tokens using collect rewards on a specific strategy", async () => { - // Mint of MockCRVMinter mints a fixed 2e18 - await crvMinter.connect(governor).mint(threePoolStrategy.address); - await harvester.connect(governor)[ - // eslint-disable-next-line - "harvest(address)" - ](threePoolStrategy.address); - await expect(await crv.balanceOf(harvester.address)).to.be.equal( - utils.parseUnits("2", 18) - ); - await crvMinter.connect(governor).mint(threePoolStrategy.address); - await expect(await crv.balanceOf(harvester.address)).to.be.equal( - utils.parseUnits("2", 18) - ); - }); - it("Should collect reward tokens and swap via Uniswap", async () => { const mockUniswapRouter = await ethers.getContract("MockUniswapRouter"); - await mockUniswapRouter.initialize([crv.address], [usdt.address]); await harvester.connect(governor).setRewardTokenConfig( crv.address, // reward token - 300, // max slippage bps - 100, // harvest reward bps - mockUniswapRouter.address, - MAX_UINT256, - true + { + allowedSlippageBps: 0, + harvestRewardBps: 200, + swapPlatformAddr: mockUniswapRouter.address, + doSwapRewardToken: true, + swapPlatform: 0, + liquidationLimit: 0, + }, + utils.defaultAbiCoder.encode( + ["address[]"], + [[crv.address, usdt.address]] + ) ); // Make sure Vault has 0 USDT balance @@ -187,16 +159,10 @@ describe("3Pool Strategy", function () { // Make sure the Strategy has CRV balance await crvMinter.connect(governor).mint(threePoolStrategy.address); - await expect( - await crv.balanceOf(await governor.getAddress()) - ).to.be.equal("0"); - await expect(await crv.balanceOf(threePoolStrategy.address)).to.be.equal( + expect(await crv.balanceOf(await governor.getAddress())).to.be.equal("0"); + expect(await crv.balanceOf(threePoolStrategy.address)).to.be.equal( utils.parseUnits("2", 18) ); - // Give Uniswap mock some USDT so it can give it back in CRV liquidation - await usdt - .connect(anna) - .transfer(mockUniswapRouter.address, usdtUnits("100")); const balanceBeforeAnna = await usdt.balanceOf(anna.address); // prettier-ignore await harvester @@ -204,17 +170,15 @@ describe("3Pool Strategy", function () { const balanceAfterAnna = await usdt.balanceOf(anna.address); - // Make sure Vault has 100 USDT balance (the Uniswap mock converts at 1:1) - await expect(vault).has.a.balanceOf("1.98", usdt); - await expect(balanceAfterAnna - balanceBeforeAnna).to.be.equal( - utils.parseUnits("0.02", 6) + // Make sure Vault has 100 USDT balance + await expect(vault).has.a.balanceOf("1.96", usdt); + expect(balanceAfterAnna - balanceBeforeAnna).to.be.equal( + utils.parseUnits("0.04", 6) ); // No CRV in Vault or Compound strategy await expect(harvester).has.a.balanceOf("0", crv); - await expect(await crv.balanceOf(threePoolStrategy.address)).to.be.equal( - "0" - ); + expect(await crv.balanceOf(threePoolStrategy.address)).to.be.equal("0"); }); }); }); diff --git a/contracts/test/strategies/aave.js b/contracts/test/strategies/aave.js index 86af39121c..266822ec6b 100644 --- a/contracts/test/strategies/aave.js +++ b/contracts/test/strategies/aave.js @@ -188,9 +188,28 @@ describe("Aave Strategy", function () { ); } + // Disable swap + await harvester.connect(governor).setRewardTokenConfig( + aave.address, + { + allowedSlippageBps: 200, + harvestRewardBps: 0, + swapPlatformAddr: aave.address, + doSwapRewardToken: false, + swapPlatform: 0, + liquidationLimit: 0, + }, + utils.defaultAbiCoder.encode( + ["address[]"], + [[aave.address, fixture.usdt.address]] + ) + ); + // Run // ---- - await harvester.connect(governor)["harvest()"](); + // prettier-ignore + await harvester + .connect(governor)["harvestAndSwap(address)"](aaveStrategy.address); currentTimestamp = await getBlockTimestamp(); // Verification diff --git a/contracts/test/strategies/balancerMetaStablePool.fork-test.js b/contracts/test/strategies/balancerMetaStablePool.fork-test.js index c91e5bcc37..8ff9f14c5d 100644 --- a/contracts/test/strategies/balancerMetaStablePool.fork-test.js +++ b/contracts/test/strategies/balancerMetaStablePool.fork-test.js @@ -595,22 +595,37 @@ describe("ForkTest: Balancer MetaStablePool rETH/WETH Strategy", function () { oethHarvester, rEthBPT, oethDripper, + bal, + aura, } = fixture; + // Deposite some LP to the pool so that we can harvest some tokens await depositTest(fixture, [5, 5], [weth, reth], rEthBPT); await mine(1000); + // Let the strategy have some tokens it can send to Harvester + await setERC20TokenBalance( + balancerREthStrategy.address, + bal, + oethUnits("50") + ); + await setERC20TokenBalance( + balancerREthStrategy.address, + aura, + oethUnits("50") + ); + const wethBalanceBefore = await weth.balanceOf(oethDripper.address); await oethHarvester.connect(josh)[ // eslint-disable-next-line "harvestAndSwap(address)" ](balancerREthStrategy.address); - const wethBalanceDiff = wethBalanceBefore.sub( - await weth.balanceOf(oethDripper.address) + const wethBalanceDiff = (await weth.balanceOf(oethDripper.address)).sub( + wethBalanceBefore ); - expect(wethBalanceDiff).to.be.gte(oethUnits("0")); + expect(wethBalanceDiff).to.be.gt(oethUnits("0")); }); }); }); @@ -748,13 +763,47 @@ describe("ForkTest: Balancer MetaStablePool wstETH/WETH Strategy", function () { describe("Harvest rewards", function () { it("Should be able to collect reward tokens", async function () { - const { josh, balancerWstEthStrategy, oethHarvester } = - await loadBalancerWstEthFixture(); + const fixture = await loadBalancerWstEthFixture(); + const { + josh, + balancerWstEthStrategy, + oethHarvester, + oethDripper, + stETH, + stEthBPT, + weth, + bal, + aura, + } = fixture; + + // Deposite some LP to the pool so that we can harvest some tokens + await wstETHDepositTest(fixture, [5, 5], [weth, stETH], stEthBPT); + await mine(1000); + + // Let the strategy have some tokens it can send to Harvester + await setERC20TokenBalance( + balancerWstEthStrategy.address, + bal, + oethUnits("50") + ); + await setERC20TokenBalance( + balancerWstEthStrategy.address, + aura, + oethUnits("50") + ); + + const wethBalanceBefore = await weth.balanceOf(oethDripper.address); await oethHarvester.connect(josh)[ // eslint-disable-next-line "harvestAndSwap(address)" ](balancerWstEthStrategy.address); + + const wethBalanceDiff = (await weth.balanceOf(oethDripper.address)).sub( + wethBalanceBefore + ); + + expect(wethBalanceDiff).to.be.gt(oethUnits("0")); }); }); diff --git a/contracts/test/strategies/compound.js b/contracts/test/strategies/compound.js index 58f020db95..5fb2d1f0b2 100644 --- a/contracts/test/strategies/compound.js +++ b/contracts/test/strategies/compound.js @@ -1,5 +1,4 @@ const { expect } = require("chai"); -const { utils } = require("ethers"); const { impersonateAndFund } = require("../../utils/signers"); const { createFixtureLoader, compoundFixture } = require("../_fixture"); @@ -70,57 +69,6 @@ describe("Compound strategy", function () { ); }); - it("Should collect rewards", async () => { - const { - cStandalone, - strategySigner, - vault, - vaultSigner, - governor, - harvester, - usdc, - comp, - } = fixture; - - await expect(await cStandalone.checkBalance(usdc.address)).to.be.equal("0"); - - // Fund the strategy - usdc.connect(strategySigner).mint(usdcUnits("1000")); - expect(await usdc.balanceOf(cStandalone.address)).to.be.equal( - usdcUnits("1000") - ); - - // Approve compound on vault - await vault.connect(governor).approveStrategy(cStandalone.address); - - await harvester - .connect(governor) - .setSupportedStrategy(cStandalone.address, true); - - // Run deposit() - await cStandalone - .connect(vaultSigner) - .deposit(usdc.address, usdcUnits("1000")); - - const compAmount = utils.parseUnits("100", 18); - await comp.connect(strategySigner).mint(compAmount); - - // Make sure the Strategy has COMP balance - await expect(await comp.balanceOf(vault.address)).to.be.equal("0"); - await expect(await comp.balanceOf(cStandalone.address)).to.be.equal( - compAmount - ); - - await harvester.connect(governor)["harvest(address)"](cStandalone.address); - - // Vault address on Compound Strategy is set to governor so they Should - // receive the reward - await expect(await comp.balanceOf(harvester.address)).to.be.equal( - compAmount - ); - await expect(await comp.balanceOf(cStandalone.address)).to.be.equal("0"); - }); - it("Should allow Governor to set reward token address", async () => { const { cStandalone, governor, comp } = fixture; diff --git a/contracts/test/strategies/convex.js b/contracts/test/strategies/convex.js index e14b43c27b..0407f9abe1 100644 --- a/contracts/test/strategies/convex.js +++ b/contracts/test/strategies/convex.js @@ -1,11 +1,8 @@ const { expect } = require("chai"); -const { utils } = require("ethers"); -const { MAX_UINT256 } = require("../../utils/constants"); const { createFixtureLoader, convexVaultFixture } = require("../_fixture"); const { daiUnits, - usdtUnits, ousdUnits, units, expectApproxSupply, @@ -20,10 +17,8 @@ describe("Convex Strategy", function () { let anna, ousd, vault, - harvester, governor, crv, - cvx, threePoolToken, convexStrategy, cvxBooster, @@ -46,11 +41,9 @@ describe("Convex Strategy", function () { const fixture = await loadFixture(); anna = fixture.anna; vault = fixture.vault; - harvester = fixture.harvester; ousd = fixture.ousd; governor = fixture.governor; crv = fixture.crv; - cvx = fixture.cvx; threePoolToken = fixture.threePoolToken; convexStrategy = fixture.convexStrategy; cvxBooster = fixture.cvxBooster; @@ -124,235 +117,6 @@ describe("Convex Strategy", function () { ).to.be.revertedWith("Caller is not the Governor"); }); - it("Should allow the strategist to call harvest for a specific strategy", async () => { - // Mint of MockCRVMinter mints a fixed 2e18 - // prettier-ignore - await harvester - .connect(governor)["harvest(address)"](convexStrategy.address); - }); - - it("Should collect reward tokens using collect rewards on all strategies", async () => { - // Mint of MockCRVMinter mints a fixed 2e18 - await harvester.connect(governor)["harvest()"](); - await expect(await crv.balanceOf(harvester.address)).to.be.equal( - utils.parseUnits("2", 18) - ); - await expect(await cvx.balanceOf(harvester.address)).to.be.equal( - utils.parseUnits("3", 18) - ); - }); - - it("Should collect all reward tokens even though the swap limits are set", async () => { - const mockUniswapRouter = await ethers.getContract("MockUniswapRouter"); - - await expect( - harvester - .connect(governor) - .setRewardTokenConfig( - crv.address, - 300, - 100, - mockUniswapRouter.address, - utils.parseUnits("1", 18), - true - ) - ) - .to.emit(harvester, "RewardTokenConfigUpdated") - .withArgs( - crv.address, - 300, - 100, - mockUniswapRouter.address, - utils.parseUnits("1", 18), - true - ); - - await expect( - harvester - .connect(governor) - .setRewardTokenConfig( - cvx.address, - 300, - 100, - mockUniswapRouter.address, - utils.parseUnits("1.5", 18), - true - ) - ) - .to.emit(harvester, "RewardTokenConfigUpdated") - .withArgs( - cvx.address, - 300, - 100, - mockUniswapRouter.address, - utils.parseUnits("1.5", 18), - true - ); - - // Mint of MockCRVMinter mints a fixed 2e18 - await harvester.connect(governor)["harvest()"](); - await expect(await crv.balanceOf(harvester.address)).to.be.equal( - utils.parseUnits("2", 18) - ); - await expect(await cvx.balanceOf(harvester.address)).to.be.equal( - utils.parseUnits("3", 18) - ); - }); - - it("Should collect reward tokens using collect rewards on a specific strategy", async () => { - await harvester.connect(governor)[ - // eslint-disable-next-line - "harvest(address)" - ](convexStrategy.address); - - await expect(await crv.balanceOf(harvester.address)).to.be.equal( - utils.parseUnits("2", 18) - ); - await expect(await cvx.balanceOf(harvester.address)).to.be.equal( - utils.parseUnits("3", 18) - ); - }); - - it("Should collect reward tokens and swap via Uniswap", async () => { - const mockUniswapRouter = await ethers.getContract("MockUniswapRouter"); - - await mockUniswapRouter.initialize( - [crv.address, cvx.address], - [usdt.address, usdt.address] - ); - - await harvester - .connect(governor) - .setRewardTokenConfig( - crv.address, - 300, - 200, - mockUniswapRouter.address, - MAX_UINT256, - true - ); - - await harvester - .connect(governor) - .setRewardTokenConfig( - cvx.address, - 300, - 200, - mockUniswapRouter.address, - MAX_UINT256, - true - ); - - // Make sure Vault has 0 USDT balance - await expect(vault).has.a.balanceOf("0", usdt); - await expect(vault).has.a.balanceOf("0", crv); - await expect(vault).has.a.balanceOf("0", cvx); - - // Give Uniswap mock some USDT so it can give it back in CRV liquidation - await usdt - .connect(anna) - .transfer(mockUniswapRouter.address, usdtUnits("100")); - - // prettier-ignore - await harvester - .connect(governor)["harvestAndSwap()"](); - - // Make sure Vault has 100 USDT balance (the Uniswap mock converts at 1:1) - await expect(vault).has.a.balanceOf("5", usdt); - - // No CRV in Vault or Compound strategy - await expect(harvester).has.a.balanceOf("0", crv); - await expect(harvester).has.a.balanceOf("0", cvx); - await expect(await crv.balanceOf(convexStrategy.address)).to.be.equal( - "0" - ); - await expect(await cvx.balanceOf(convexStrategy.address)).to.be.equal( - "0" - ); - }); - - it("Should collect reward tokens and swap via Uniswap considering liquidation limits using harvestAndSwap()", async () => { - await harvestAndSwapTokens(false); - }); - - it("Should collect reward tokens and swap via Uniswap considering liquidation limits using harvestAndSwap(strategy_address)", async () => { - await harvestAndSwapTokens(true); - }); - - const harvestAndSwapTokens = async (callAsGovernor) => { - const mockUniswapRouter = await ethers.getContract("MockUniswapRouter"); - await mockUniswapRouter.initialize( - [crv.address, cvx.address], - [usdt.address, usdt.address] - ); - - // Make sure Vault has 0 USDT balance - await expect(vault).has.a.balanceOf("0", usdt); - await expect(vault).has.a.balanceOf("0", crv); - await expect(vault).has.a.balanceOf("0", cvx); - - // Give Uniswap mock some USDT so it can give it back in CRV liquidation - await usdt - .connect(anna) - .transfer(mockUniswapRouter.address, usdtUnits("1000")); - - await harvester - .connect(governor) - .setRewardTokenConfig( - crv.address, - 300, - 100, - mockUniswapRouter.address, - utils.parseUnits("0.8", 18), - true - ); - - await harvester - .connect(governor) - .setRewardTokenConfig( - cvx.address, - 300, - 100, - mockUniswapRouter.address, - utils.parseUnits("1.5", 18), - true - ); - - const crvConfig = await harvester.rewardTokenConfigs(crv.address); - const cvxConfig = await harvester.rewardTokenConfigs(cvx.address); - - expect(crvConfig.liquidationLimit).to.equal(utils.parseUnits("0.8", 18)); - expect(cvxConfig.liquidationLimit).to.equal(utils.parseUnits("1.5", 18)); - - const balanceBeforeAnna = await usdt.balanceOf(anna.address); - - if (callAsGovernor) { - // prettier-ignore - await harvester - .connect(anna)["harvestAndSwap(address)"](convexStrategy.address); - - await expect(vault).has.a.balanceOf("2.277", usdt); // (0.8 + 1.5) - 1% - const balanceAfterAnna = await usdt.balanceOf(anna.address); - await expect(balanceAfterAnna - balanceBeforeAnna).to.be.equal( - utils.parseUnits("0.023", 6) - ); - } else { - // prettier-ignore - await harvester - .connect(governor)["harvestAndSwap()"](); - await expect(vault).has.a.balanceOf("2.3", usdt); // (0.8 + 1.5) - } - - await expect(harvester).has.a.balanceOf("1.2", crv); - await expect(harvester).has.a.balanceOf("1.5", cvx); - await expect(await crv.balanceOf(convexStrategy.address)).to.be.equal( - "0" - ); - await expect(await cvx.balanceOf(convexStrategy.address)).to.be.equal( - "0" - ); - }; - it("Should revert when zero address attempts to be set as reward token address", async () => { await expect( convexStrategy diff --git a/contracts/test/strategies/generalized-meta.js b/contracts/test/strategies/generalized-meta.js index 901a4f4281..bff8a376a1 100644 --- a/contracts/test/strategies/generalized-meta.js +++ b/contracts/test/strategies/generalized-meta.js @@ -1,5 +1,4 @@ const { expect } = require("chai"); -const { utils } = require("ethers"); const { createFixtureLoader, @@ -21,10 +20,7 @@ describe.skip("Convex 3pool/Generalized (LUSD) Meta Strategy", function () { let anna, ousd, vault, - harvester, governor, - crv, - cvx, LUSDMetaStrategy, LUSDMetapoolToken, cvxBooster, @@ -47,11 +43,8 @@ describe.skip("Convex 3pool/Generalized (LUSD) Meta Strategy", function () { const fixture = await loadFixture(); anna = fixture.anna; vault = fixture.vault; - harvester = fixture.harvester; ousd = fixture.ousd; governor = fixture.governor; - crv = fixture.crv; - cvx = fixture.cvx; LUSDMetaStrategy = fixture.LUSDMetaStrategy; LUSDMetapoolToken = fixture.LUSDMetapoolToken; cvxBooster = fixture.cvxBooster; @@ -133,44 +126,4 @@ describe.skip("Convex 3pool/Generalized (LUSD) Meta Strategy", function () { ).to.be.revertedWith("Caller is not the Governor"); }); }); - - describe("Harvest", function () { - it("Should allow the strategist to call harvest for a specific strategy", async () => { - // prettier-ignore - await harvester - .connect(governor)["harvest(address)"](LUSDMetaStrategy.address); - }); - - it("Should collect reward tokens using collect rewards on all strategies", async () => { - // Mint of MockCRVMinter mints a fixed 2e18 - await harvester.connect(governor)["harvest()"](); - await expect(await crv.balanceOf(harvester.address)).to.be.equal( - utils.parseUnits("2", 18) - ); - await expect(await cvx.balanceOf(harvester.address)).to.be.equal( - utils.parseUnits("3", 18) - ); - }); - - it("Should collect reward tokens using collect rewards on a specific strategy", async () => { - await expect(await crv.balanceOf(harvester.address)).to.be.equal( - utils.parseUnits("0", 18) - ); - await expect(await cvx.balanceOf(harvester.address)).to.be.equal( - utils.parseUnits("0", 18) - ); - - await harvester.connect(governor)[ - // eslint-disable-next-line - "harvest(address)" - ](LUSDMetaStrategy.address); - - await expect(await crv.balanceOf(harvester.address)).to.be.equal( - utils.parseUnits("2", 18) - ); - await expect(await cvx.balanceOf(harvester.address)).to.be.equal( - utils.parseUnits("3", 18) - ); - }); - }); }); diff --git a/contracts/test/strategies/meta.js b/contracts/test/strategies/meta.js index 29fbef09dc..c83362773b 100644 --- a/contracts/test/strategies/meta.js +++ b/contracts/test/strategies/meta.js @@ -1,5 +1,5 @@ const { expect } = require("chai"); -const { utils, BigNumber } = require("ethers"); +const { BigNumber } = require("ethers"); const { convexMetaVaultFixture, createFixtureLoader } = require("../_fixture"); const { @@ -18,10 +18,7 @@ describe.skip("Convex 3pool/OUSD Meta Strategy", function () { let anna, ousd, vault, - harvester, governor, - crv, - cvx, OUSDmetaStrategy, metapoolToken, cvxBooster, @@ -44,11 +41,8 @@ describe.skip("Convex 3pool/OUSD Meta Strategy", function () { const fixture = await loadFixture(); anna = fixture.anna; vault = fixture.vault; - harvester = fixture.harvester; ousd = fixture.ousd; governor = fixture.governor; - crv = fixture.crv; - cvx = fixture.cvx; OUSDmetaStrategy = fixture.OUSDmetaStrategy; metapoolToken = fixture.metapoolToken; cvxBooster = fixture.cvxBooster; @@ -160,44 +154,4 @@ describe.skip("Convex 3pool/OUSD Meta Strategy", function () { ).to.be.revertedWith("Attempting to burn too much OUSD."); }); }); - - describe("Harvest", function () { - it("Should allow the strategist to call harvest for a specific strategy", async () => { - // prettier-ignore - await harvester - .connect(governor)["harvest(address)"](OUSDmetaStrategy.address); - }); - - it("Should collect reward tokens using collect rewards on all strategies", async () => { - // Mint of MockCRVMinter mints a fixed 2e18 - await harvester.connect(governor)["harvest()"](); - await expect(await crv.balanceOf(harvester.address)).to.be.equal( - utils.parseUnits("2", 18) - ); - await expect(await cvx.balanceOf(harvester.address)).to.be.equal( - utils.parseUnits("3", 18) - ); - }); - - it("Should collect reward tokens using collect rewards on a specific strategy", async () => { - await expect(await crv.balanceOf(harvester.address)).to.be.equal( - utils.parseUnits("0", 18) - ); - await expect(await cvx.balanceOf(harvester.address)).to.be.equal( - utils.parseUnits("0", 18) - ); - - await harvester.connect(governor)[ - // eslint-disable-next-line - "harvest(address)" - ](OUSDmetaStrategy.address); - - await expect(await crv.balanceOf(harvester.address)).to.be.equal( - utils.parseUnits("2", 18) - ); - await expect(await cvx.balanceOf(harvester.address)).to.be.equal( - utils.parseUnits("3", 18) - ); - }); - }); }); diff --git a/contracts/test/vault/compound.js b/contracts/test/vault/compound.js index dc56d982a0..898edf3f38 100644 --- a/contracts/test/vault/compound.js +++ b/contracts/test/vault/compound.js @@ -2,7 +2,6 @@ const { expect } = require("chai"); const { utils } = require("ethers"); const { createFixtureLoader, compoundVaultFixture } = require("../_fixture"); -const { MAX_UINT256 } = require("../../utils/constants"); const { advanceTime, @@ -329,16 +328,22 @@ describe("Vault with Compound strategy", function () { expect(await vault.totalValue()).to.approxEqual( utils.parseUnits("200", 18) ); - const mockUniswapRouter = await ethers.getContract("MockUniswapRouter"); await harvester.connect(governor).setRewardTokenConfig( comp.address, // reward token - 300, // max slippage bps - 0, // harvest reward bps - mockUniswapRouter.address, - MAX_UINT256, - true + { + allowedSlippageBps: 300, + harvestRewardBps: 0, + swapPlatformAddr: mockUniswapRouter.address, + doSwapRewardToken: true, + swapPlatform: 0, + liquidationLimit: 0, + }, + utils.defaultAbiCoder.encode( + ["address[]"], + [[comp.address, usdt.address]] + ) ); // Matt deposits USDC, 6 decimals @@ -617,102 +622,41 @@ describe("Vault with Compound strategy", function () { } }); - it("Should collect reward tokens using collect rewards on all strategies", async () => { - const { governor, harvester, compoundStrategy, comp } = fixture; - - const compAmount = utils.parseUnits("100", 18); - await comp.connect(governor).mint(compAmount); - await comp.connect(governor).transfer(compoundStrategy.address, compAmount); - - // Make sure the Strategy has COMP balance - await expect(await comp.balanceOf(await governor.getAddress())).to.be.equal( - "0" - ); - await expect(await comp.balanceOf(compoundStrategy.address)).to.be.equal( - compAmount - ); - - await harvester.connect(governor)["harvest()"](); - - // Note if Uniswap address was configured, it would withdrawAll the COMP for - // a stablecoin to increase the value of Harvester. No Uniswap configured here - // so the COMP just sits in Harvester - await expect(await comp.balanceOf(harvester.address)).to.be.equal( - compAmount - ); - }); - - it("Should collect reward tokens using collect rewards on a specific strategy", async () => { - const { harvester, governor, compoundStrategy, comp } = fixture; - - const compAmount = utils.parseUnits("100", 18); - await comp.connect(governor).mint(compAmount); - await comp.connect(governor).transfer(compoundStrategy.address, compAmount); - - // Make sure the Strategy has COMP balance - await expect(await comp.balanceOf(await governor.getAddress())).to.be.equal( - "0" - ); - await expect(await comp.balanceOf(compoundStrategy.address)).to.be.equal( - compAmount - ); - - // prettier-ignore - await harvester - .connect(governor)["harvest(address)"](compoundStrategy.address); - - await expect(await comp.balanceOf(harvester.address)).to.be.equal( - compAmount - ); - }); - it("Should collect reward tokens and swap via Uniswap", async () => { - const { - josh, - anna, - vault, - harvester, - governor, - compoundStrategy, - comp, - usdt, - } = fixture; + const { anna, vault, harvester, governor, compoundStrategy, comp, usdt } = + fixture; const mockUniswapRouter = await ethers.getContract("MockUniswapRouter"); - await mockUniswapRouter.initialize([comp.address], [usdt.address]); - const compAmount = utils.parseUnits("100", 18); await comp.connect(governor).mint(compAmount); await comp.connect(governor).transfer(compoundStrategy.address, compAmount); - await harvester - .connect(governor) - .setRewardTokenConfig( - comp.address, - 300, - 100, - mockUniswapRouter.address, - MAX_UINT256, - true - ); + await harvester.connect(governor).setRewardTokenConfig( + comp.address, // reward token + { + allowedSlippageBps: 0, + harvestRewardBps: 100, + swapPlatformAddr: mockUniswapRouter.address, + doSwapRewardToken: true, + swapPlatform: 0, + liquidationLimit: 0, + }, + utils.defaultAbiCoder.encode( + ["address[]"], + [[comp.address, usdt.address]] + ) + ); // Make sure Vault has 0 USDT balance await expect(vault).has.a.balanceOf("0", usdt); // Make sure the Strategy has COMP balance - await expect(await comp.balanceOf(await governor.getAddress())).to.be.equal( - "0" - ); - await expect(await comp.balanceOf(compoundStrategy.address)).to.be.equal( + expect(await comp.balanceOf(await governor.getAddress())).to.be.equal("0"); + expect(await comp.balanceOf(compoundStrategy.address)).to.be.equal( compAmount ); - // Give Uniswap mock some USDT so it can give it back in COMP liquidation - await usdt - .connect(josh) - .transfer(mockUniswapRouter.address, usdtUnits("100")); - const balanceBeforeAnna = await usdt.balanceOf(anna.address); // prettier-ignore @@ -726,10 +670,8 @@ describe("Vault with Compound strategy", function () { // No COMP in Harvester or Compound strategy await expect(harvester).has.a.balanceOf("0", comp); - await expect(await comp.balanceOf(compoundStrategy.address)).to.be.equal( - "0" - ); - await expect(balanceAfterAnna - balanceBeforeAnna).to.be.equal( + expect(await comp.balanceOf(compoundStrategy.address)).to.be.equal("0"); + expect(balanceAfterAnna - balanceBeforeAnna).to.be.equal( utils.parseUnits("1", 6) ); }); @@ -740,8 +682,6 @@ describe("Vault with Compound strategy", function () { const mockUniswapRouter = await ethers.getContract("MockUniswapRouter"); - await mockUniswapRouter.initialize([comp.address], [usdt.address]); - // Mock router gives 1:1, if we set this to something high there will be // too much slippage await setOracleTokenPriceUsd("COMP", "1.3"); @@ -749,99 +689,37 @@ describe("Vault with Compound strategy", function () { const compAmount = utils.parseUnits("100", 18); await comp.connect(governor).mint(compAmount); await comp.connect(governor).transfer(compoundStrategy.address, compAmount); + await mockUniswapRouter.setSlippage(utils.parseEther("0.75")); - await harvester - .connect(governor) - .setRewardTokenConfig( - comp.address, - 300, - 100, - mockUniswapRouter.address, - MAX_UINT256, - true - ); - + await harvester.connect(governor).setRewardTokenConfig( + comp.address, // reward token + { + allowedSlippageBps: 0, + harvestRewardBps: 100, + swapPlatformAddr: mockUniswapRouter.address, + doSwapRewardToken: true, + swapPlatform: 0, + liquidationLimit: 0, + }, + utils.defaultAbiCoder.encode( + ["address[]"], + [[comp.address, usdt.address]] + ) + ); // Make sure Vault has 0 USDT balance await expect(vault).has.a.balanceOf("0", usdt); // Make sure the Strategy has COMP balance - await expect(await comp.balanceOf(await governor.getAddress())).to.be.equal( - "0" - ); - await expect(await comp.balanceOf(compoundStrategy.address)).to.be.equal( + expect(await comp.balanceOf(await governor.getAddress())).to.be.equal("0"); + expect(await comp.balanceOf(compoundStrategy.address)).to.be.equal( compAmount ); - // Give Uniswap mock some USDT so it can give it back in COMP liquidation - await usdt - .connect(josh) - .transfer(mockUniswapRouter.address, usdtUnits("100")); - // prettier-ignore await expect(harvester .connect(josh)["harvestAndSwap(address)"](compoundStrategy.address)).to.be.revertedWith("Slippage error"); }); - it("Should collect reward tokens and swap as separate calls", async () => { - const { josh, vault, harvester, governor, compoundStrategy, comp, usdt } = - fixture; - - const mockUniswapRouter = await ethers.getContract("MockUniswapRouter"); - - await mockUniswapRouter.initialize([comp.address], [usdt.address]); - - const compAmount = utils.parseUnits("100", 18); - await comp.connect(governor).mint(compAmount); - await comp.connect(governor).transfer(compoundStrategy.address, compAmount); - - await harvester - .connect(governor) - .setRewardTokenConfig( - comp.address, - 300, - 100, - mockUniswapRouter.address, - MAX_UINT256, - true - ); - - // Make sure Vault has 0 USDT balance - await expect(vault).has.a.balanceOf("0", usdt); - - // Make sure the Strategy has COMP balance - await expect(await comp.balanceOf(await governor.getAddress())).to.be.equal( - "0" - ); - await expect(await comp.balanceOf(compoundStrategy.address)).to.be.equal( - compAmount - ); - - // Give Uniswap mock some USDT so it can give it back in COMP liquidation - await usdt - .connect(josh) - .transfer(mockUniswapRouter.address, usdtUnits("100")); - - // prettier-ignore - await harvester.connect(governor)["harvest()"](); - - // COMP should be sitting in Harvester - await expect(await comp.balanceOf(harvester.address)).to.be.equal( - compAmount - ); - - // Call the swap - await harvester.connect(governor)["swap()"](); - - // Make sure Vault has 100 USDT balance (the Uniswap mock converts at 1:1) - await expect(vault).has.a.balanceOf("100", usdt); - - // No COMP in Vault or Compound strategy - await expect(harvester).has.a.balanceOf("0", comp); - await expect(await comp.balanceOf(compoundStrategy.address)).to.be.equal( - "0" - ); - }); - const mintDoesAllocate = async (amount) => { const { anna, vault, usdc, governor } = fixture; diff --git a/contracts/test/vault/harvester.fork-test.js b/contracts/test/vault/harvester.fork-test.js index 6bfac63097..bdf28ca0ff 100644 --- a/contracts/test/vault/harvester.fork-test.js +++ b/contracts/test/vault/harvester.fork-test.js @@ -1,11 +1,15 @@ const { expect } = require("chai"); -const { utils } = require("ethers"); +const { utils, BigNumber } = require("ethers"); -const { loadDefaultFixture } = require("./../_fixture"); -const { isCI } = require("./../helpers"); -const { MAX_UINT256 } = require("../../utils/constants"); +const { createFixtureLoader, harvesterFixture } = require("./../_fixture"); +const { isCI, oethUnits } = require("./../helpers"); +const { hotDeployOption } = require("../_hot-deploy"); +const addresses = require("../../utils/addresses"); +const { setERC20TokenBalance } = require("../_fund"); const { parseUnits } = require("ethers").utils; +const loadFixture = createFixtureLoader(harvesterFixture); + describe("ForkTest: Harvester", function () { this.timeout(0); @@ -14,10 +18,99 @@ describe("ForkTest: Harvester", function () { let fixture; beforeEach(async () => { - fixture = await loadDefaultFixture(); + fixture = await loadFixture(); + await hotDeployOption(fixture, null, { + isOethFixture: true, + }); + await hotDeployOption(fixture, null, { + isOethFixture: false, + }); + }); + + describe("with Curve", () => { + it("Should swap CRV for WETH", async () => { + const { + oethHarvester, + strategist, + convexEthMetaStrategy, + oethDripper, + crv, + weth, + } = fixture; + const wethBefore = await weth.balanceOf(oethDripper.address); + + // Send some rewards to the strategy + await setERC20TokenBalance(convexEthMetaStrategy.address, crv, "1000"); + + // prettier-ignore + const tx = await oethHarvester + .connect(strategist)["harvestAndSwap(address)"](convexEthMetaStrategy.address); + + await expect(tx).to.emit(convexEthMetaStrategy, "RewardTokenCollected"); + await expect(tx).to.emit(oethHarvester, "RewardTokenSwapped"); + await expect(tx).to.emit(oethHarvester, "RewardProceedsTransferred"); + + // Should've transferred swapped WETH to Dripper + expect(await weth.balanceOf(oethDripper.address)).to.be.gt( + wethBefore.add(oethUnits("0.1")) + ); + }); }); - describe("Rewards Config", () => { + // Commenting this out since none of the strategies have anything + // that we want to harvest on Uniswap V2 or V3 right now. + + // describe("with Uniswap V3", () => { + // it("Should swap CRV for USDT", async () => { + // const { harvester, timelock, crv } = fixture; + // const crvBefore = await crv.balanceOf(harvester.address); + + // await harvester.connect(timelock).swapRewardToken(crv.address); + + // expect(await crv.balanceOf(harvester.address)).to.equal( + // crvBefore.sub(oethUnits("4000")) + // ); + // }); + // }); + + describe("with Balancer", () => { + it("Should swap BAL and AURA for WETH", async () => { + const { + oethHarvester, + strategist, + bal, + aura, + weth, + oethDripper, + balancerREthStrategy, + } = fixture; + + const wethBefore = await weth.balanceOf(oethDripper.address); + + // Send some rewards to the strategy + await setERC20TokenBalance(balancerREthStrategy.address, bal, "1000"); + await setERC20TokenBalance(balancerREthStrategy.address, aura, "1000"); + + // prettier-ignore + const tx = await oethHarvester + .connect(strategist)["harvestAndSwap(address)"](balancerREthStrategy.address); + + await expect(tx).to.emit(balancerREthStrategy, "RewardTokenCollected"); + await expect(tx).to.emit(oethHarvester, "RewardTokenSwapped"); + await expect(tx).to.emit(oethHarvester, "RewardProceedsTransferred"); + + // Should've transferred everything to Harvester + expect(await bal.balanceOf(balancerREthStrategy.address)).to.equal("0"); + expect(await aura.balanceOf(balancerREthStrategy.address)).to.equal("0"); + + // Should've transferred swapped WETH to Dripper + expect(await weth.balanceOf(oethDripper.address)).to.be.gt( + wethBefore.add(oethUnits("0.1")) + ); + }); + }); + + describe("OUSD Rewards Config", () => { it("Should have correct reward token config for CRV", async () => { const { harvester, crv } = fixture; @@ -25,11 +118,26 @@ describe("ForkTest: Harvester", function () { expect(config.allowedSlippageBps).to.equal(300); expect(config.harvestRewardBps).to.equal(200); - expect(config.uniswapV2CompatibleAddr).to.equal( - "0xd9e1cE17f2641f24aE83637ab66a2cca9C378B9F" + expect(config.swapPlatform).to.equal( + 1 // Uniswap V3 + ); + expect(config.swapPlatformAddr).to.equal( + "0xE592427A0AEce92De3Edee1F18E0157C05861564" ); expect(config.doSwapRewardToken).to.be.true; expect(config.liquidationLimit).to.equal(parseUnits("4000", 18)); + expect(await harvester.uniswapV3Path(crv.address)).to.eq( + utils.solidityPack( + ["address", "uint24", "address", "uint24", "address"], + [ + addresses.mainnet.CRV, + 3000, + addresses.mainnet.WETH, + 500, + addresses.mainnet.USDT, + ] + ) + ); }); it("Should have correct reward token config for CVX", async () => { @@ -39,11 +147,26 @@ describe("ForkTest: Harvester", function () { expect(config.allowedSlippageBps).to.equal(300); expect(config.harvestRewardBps).to.equal(100); - expect(config.uniswapV2CompatibleAddr).to.equal( - "0xd9e1cE17f2641f24aE83637ab66a2cca9C378B9F" + expect(config.swapPlatform).to.equal( + 1 // Uniswap V3 + ); + expect(config.swapPlatformAddr).to.equal( + "0xE592427A0AEce92De3Edee1F18E0157C05861564" ); expect(config.doSwapRewardToken).to.be.true; expect(config.liquidationLimit).to.equal(utils.parseEther("2500")); + expect(await harvester.uniswapV3Path(cvx.address)).to.eq( + utils.solidityPack( + ["address", "uint24", "address", "uint24", "address"], + [ + addresses.mainnet.CVX, + 10000, + addresses.mainnet.WETH, + 500, + addresses.mainnet.USDT, + ] + ) + ); }); it("Should have correct reward token config for COMP", async () => { @@ -53,11 +176,26 @@ describe("ForkTest: Harvester", function () { expect(config.allowedSlippageBps).to.equal(300); expect(config.harvestRewardBps).to.equal(100); - expect(config.uniswapV2CompatibleAddr).to.equal( - "0xd9e1cE17f2641f24aE83637ab66a2cca9C378B9F" + expect(config.swapPlatform).to.equal( + 1 // Uniswap V3 + ); + expect(config.swapPlatformAddr).to.equal( + "0xE592427A0AEce92De3Edee1F18E0157C05861564" ); expect(config.doSwapRewardToken).to.be.true; - expect(config.liquidationLimit).to.equal(MAX_UINT256); + expect(config.liquidationLimit).to.equal(0); + expect((await harvester.uniswapV3Path(comp.address)).toLowerCase()).to.eq( + utils.solidityPack( + ["address", "uint24", "address", "uint24", "address"], + [ + addresses.mainnet.COMP, + 3000, + addresses.mainnet.WETH, + 500, + addresses.mainnet.USDT, + ] + ) + ); }); it("Should have correct reward token config for AAVE", async () => { @@ -67,54 +205,109 @@ describe("ForkTest: Harvester", function () { expect(config.allowedSlippageBps).to.equal(300); expect(config.harvestRewardBps).to.equal(100); - expect(config.uniswapV2CompatibleAddr).to.equal( - "0xd9e1cE17f2641f24aE83637ab66a2cca9C378B9F" + expect(config.swapPlatform).to.equal( + 1 // Uniswap V3 + ); + expect(config.swapPlatformAddr).to.equal( + "0xE592427A0AEce92De3Edee1F18E0157C05861564" ); expect(config.doSwapRewardToken).to.be.true; - expect(config.liquidationLimit).to.equal(MAX_UINT256); + expect(config.liquidationLimit).to.equal(0); + + expect((await harvester.uniswapV3Path(aave.address)).toLowerCase()).to.eq( + utils.solidityPack( + ["address", "uint24", "address", "uint24", "address"], + [ + addresses.mainnet.Aave, + 10000, + addresses.mainnet.WETH, + 500, + addresses.mainnet.USDT, + ] + ) + ); }); }); - describe("Harvest", () => { - it("Should harvest from all strategies", async () => { - const { harvester, timelock } = fixture; - await harvester.connect(timelock)["harvest()"](); - }); + describe("OETH Rewards Config", () => { + it("Should have correct reward token config for CRV", async () => { + const { oethHarvester, crv } = fixture; - it("Should swap all coins", async () => { - const { harvester, timelock } = fixture; - await harvester.connect(timelock).swap(); - }); + const config = await oethHarvester.rewardTokenConfigs(crv.address); - it.skip("Should harvest and swap from all strategies", async () => { - // Skip this test because we don't call or use this method anywhere. - // Also, because this test is flaky at times due to slippage and the - // individual `harvest` and `swap` methods for each strategies are - // covered in the tests above this. - const { harvester, timelock } = fixture; - await harvester.connect(timelock)["harvestAndSwap()"](); + expect(config.allowedSlippageBps).to.equal(300); + expect(config.harvestRewardBps).to.equal(200); + expect(config.swapPlatform).to.equal( + 3 // Curve + ); + expect(config.swapPlatformAddr).to.equal( + "0x4eBdF703948ddCEA3B11f675B4D1Fba9d2414A14" + ); + expect(config.doSwapRewardToken).to.be.true; + expect(config.liquidationLimit).to.equal(parseUnits("4000", 18)); + const indices = await oethHarvester.curvePoolIndices(crv.address); + expect(indices[0]).to.eq(BigNumber.from("2")); + expect(indices[1]).to.eq(BigNumber.from("1")); }); - it("Should swap CRV", async () => { - const { harvester, timelock, crv } = fixture; - await harvester.connect(timelock).swapRewardToken(crv.address); - }); + it("Should have correct reward token config for CVX", async () => { + const { oethHarvester, cvx } = fixture; - it("Should swap CVX", async () => { - const { harvester, timelock, cvx } = fixture; - await harvester.connect(timelock).swapRewardToken(cvx.address); - }); + const config = await oethHarvester.rewardTokenConfigs(cvx.address); - it("Should swap COMP", async () => { - const { harvester, timelock, comp } = fixture; - await harvester.connect(timelock).swapRewardToken(comp.address); + expect(config.allowedSlippageBps).to.equal(300); + expect(config.harvestRewardBps).to.equal(200); + expect(config.swapPlatform).to.equal( + 3 // Curve + ); + expect(config.swapPlatformAddr).to.equal( + "0xB576491F1E6e5E62f1d8F26062Ee822B40B0E0d4" + ); + expect(config.doSwapRewardToken).to.be.true; + expect(config.liquidationLimit).to.equal(parseUnits("2500", 18)); + const indices = await oethHarvester.curvePoolIndices(cvx.address); + expect(indices[0]).to.eq(BigNumber.from("1")); + expect(indices[1]).to.eq(BigNumber.from("0")); }); - it("Should swap AAVE", async () => { - const { harvester, timelock, aave } = fixture; - await harvester.connect(timelock).swapRewardToken(aave.address); + it("Should have correct reward token config for BAL", async () => { + const { oethHarvester, bal } = fixture; + + const config = await oethHarvester.rewardTokenConfigs(bal.address); + + expect(config.allowedSlippageBps).to.equal(300); + expect(config.harvestRewardBps).to.equal(200); + expect(config.swapPlatform).to.equal( + 2 // Balancer + ); + expect(config.swapPlatformAddr).to.equal( + "0xBA12222222228d8Ba445958a75a0704d566BF2C8" + ); + expect(config.doSwapRewardToken).to.be.true; + expect(config.liquidationLimit).to.equal(parseUnits("1000", 18)); + expect(await oethHarvester.balancerPoolId(bal.address)).to.equal( + "0x5c6ee304399dbdb9c8ef030ab642b10820db8f56000200000000000000000014" + ); }); - // TODO: Tests for `harvest(address)` for each strategy + it("Should have correct reward token config for AURA", async () => { + const { oethHarvester, aura } = fixture; + + const config = await oethHarvester.rewardTokenConfigs(aura.address); + + expect(config.allowedSlippageBps).to.equal(300); + expect(config.harvestRewardBps).to.equal(200); + expect(config.swapPlatform).to.equal( + 2 // Balancer + ); + expect(config.swapPlatformAddr).to.equal( + "0xBA12222222228d8Ba445958a75a0704d566BF2C8" + ); + expect(config.doSwapRewardToken).to.be.true; + expect(config.liquidationLimit).to.equal(parseUnits("4000", 18)); + expect(await oethHarvester.balancerPoolId(aura.address)).to.equal( + "0xcfca23ca9ca720b6e98e3eb9b6aa0ffc4a5c08b9000200000000000000000274" + ); + }); }); }); diff --git a/contracts/test/vault/harvester.js b/contracts/test/vault/harvester.js index 434df15d29..323955d963 100644 --- a/contracts/test/vault/harvester.js +++ b/contracts/test/vault/harvester.js @@ -1,761 +1,28 @@ -const { expect } = require("chai"); -const { utils, constants } = require("ethers"); +const { createFixtureLoader, harvesterFixture } = require("./../_fixture"); +const { shouldBehaveLikeHarvester } = require("../behaviour/harvester"); -const { createFixtureLoader, compoundVaultFixture } = require("./../_fixture"); -const { - isFork, - setOracleTokenPriceUsd, - changeInBalance, - usdtUnits, -} = require("./../helpers"); -const addresses = require("./../../utils/addresses"); -const { MAX_UINT256 } = require("../../utils/constants"); +const loadFixture = createFixtureLoader(harvesterFixture); describe("Harvester", function () { - if (isFork) { - this.timeout(0); - } - - const sendRewardsToCompStrategy = async ( - amount, - governor, - compoundStrategy, - comp - ) => { - const compAmount = utils.parseUnits(amount, 18); - await comp.connect(governor).mint(compAmount); - await comp.connect(governor).transfer(compoundStrategy.address, compAmount); - }; - let fixture; - const loadFixture = createFixtureLoader(compoundVaultFixture); - beforeEach(async function () { - fixture = await loadFixture(); - - /* Ethereum Waffle caches fixtures and uses evm snapshot and evm revert: - * https://github.com/TrueFiEng/Waffle/blob/f0d78cd5529684f2f377aaa0025c33aed52e268e/waffle-provider/src/fixtures.ts#L18-L32 - * - * to optimize the speed of test execution. Somewhere in the caching - * there is a bug where Harvester tests fail if they are ran within the whole - * unit test suite and succeed if they are ran by themselves. This is a bit - * of a nasty workaround. - */ - const { governorAddr } = await getNamedAccounts(); - const sGovernor = await ethers.provider.getSigner(governorAddr); - - try { - await fixture.vault - .connect(sGovernor) - .approveStrategy(fixture.compoundStrategy.address); - } catch (e) { - // ignore the strategy already approved exception - } - - await fixture.harvester - .connect(sGovernor) - .setSupportedStrategy(fixture.compoundStrategy.address, true); - }); - - it("Should correctly set reward token config and have correct allowances set for Uniswap like routers", async () => { - const { harvester, governor, comp } = fixture; - const mockUniswapRouter = await ethers.getContract("MockUniswapRouter"); - - await harvester - .connect(governor) - .setRewardTokenConfig( - comp.address, - 300, - 100, - mockUniswapRouter.address, - utils.parseUnits("1.44", 18), - true - ); - - let compConfig = await harvester.rewardTokenConfigs(comp.address); - - expect(compConfig.liquidationLimit).to.equal(utils.parseUnits("1.44", 18)); - expect(compConfig.allowedSlippageBps).to.equal(300); - expect(compConfig.harvestRewardBps).to.equal(100); - expect(compConfig.uniswapV2CompatibleAddr).to.equal( - mockUniswapRouter.address - ); - - expect( - await comp.allowance(harvester.address, mockUniswapRouter.address) - ).to.equal(constants.MaxUint256); - expect( - await comp.allowance(harvester.address, addresses.mainnet.uniswapV3Router) - ).to.equal(0); - - await harvester - .connect(governor) - .setRewardTokenConfig( - comp.address, - 350, - 120, - addresses.mainnet.uniswapV3Router, - utils.parseUnits("1.22", 18), - true - ); - - compConfig = await harvester.rewardTokenConfigs(comp.address); - - expect( - await comp.allowance(harvester.address, mockUniswapRouter.address) - ).to.equal(0); - expect( - await comp.allowance(harvester.address, addresses.mainnet.uniswapV3Router) - ).to.equal(constants.MaxUint256); - - expect(compConfig.liquidationLimit).to.equal(utils.parseUnits("1.22", 18)); - expect(compConfig.allowedSlippageBps).to.equal(350); - expect(compConfig.harvestRewardBps).to.equal(120); - }); - - it("Should fail when calling harvest or harvestAndSwap with the non valid strategy address", async () => { - const { harvester, governor, anna } = fixture; - const mockUniswapRouter = await ethers.getContract("MockUniswapRouter"); - - // prettier-ignore - await expect( - harvester - .connect(anna)["harvestAndSwap(address)"](mockUniswapRouter.address) - ).to.be.revertedWith("Not a valid strategy address"); - - await expect( - harvester.connect(governor)["harvest(address)"](mockUniswapRouter.address) - ).to.be.revertedWith("Not a valid strategy address"); - - // prettier-ignore - await expect( - harvester - .connect(anna)["harvestAndSwap(address,address)"]( - mockUniswapRouter.address, - anna.address - ) - ).to.be.revertedWith("Not a valid strategy address"); - }); - - it("Should not allow adding reward token config without price feed", async () => { - const { harvester, governor } = fixture; - - await expect( - harvester - .connect(governor) - .setRewardTokenConfig( - harvester.address, - 350, - 120, - addresses.mainnet.uniswapV3Router, - utils.parseUnits("11", 18), - true - ) - ).to.be.revertedWith("Asset not available"); - }); - - it("Should not allow non-Governor to set reward token config", async () => { - const { harvester, anna, comp } = fixture; - - await expect( - // Use the vault address for an address that definitely won't have a price - // feed - harvester - .connect(anna) - .setRewardTokenConfig( - comp.address, - 350, - 120, - addresses.mainnet.uniswapV3Router, - utils.parseUnits("11", 18), - true - ) - ).to.be.revertedWith("Caller is not the Governor"); - }); - - it("Should allow Governor to set reward token config", async () => { - const { harvester, governor, comp } = fixture; - - await harvester - .connect(governor) - .setRewardTokenConfig( - comp.address, - 350, - 120, - addresses.mainnet.uniswapV3Router, - utils.parseUnits("11", 18), - true - ); - }); - - it("Should skip swapping when token configuration is missing and leave harvested funds on harvester", async () => { - const { harvester, governor, comp, compoundStrategy, anna, usdt, vault } = - fixture; - - await sendRewardsToCompStrategy("100", governor, compoundStrategy, comp); - - const balanceBeforeAnna = await usdt.balanceOf(anna.address); - // prettier-ignore - await harvester - .connect(anna)["harvestAndSwap(address)"](compoundStrategy.address); - const balanceAfterAnna = await usdt.balanceOf(anna.address); - - expect(await comp.balanceOf(compoundStrategy.address)).to.be.equal("0"); - expect(balanceAfterAnna - balanceBeforeAnna).to.be.equal( - utils.parseUnits("0", 6) - ); - expect(await usdt.balanceOf(vault.address)).to.be.equal("0"); - expect(await comp.balanceOf(harvester.address)).to.be.equal( - utils.parseUnits("100", 18) - ); - expect(await usdt.balanceOf(harvester.address)).to.be.equal("0"); - }); - - it("Should swap when slippage is just under threshold", async () => { - const { - harvester, - governor, - comp, - compoundStrategy, - anna, - josh, - usdt, - vault, - } = fixture; - - await sendRewardsToCompStrategy("10", governor, compoundStrategy, comp); - - const mockUniswapRouter = await ethers.getContract("MockUniswapRouter"); - await mockUniswapRouter.initialize([comp.address], [usdt.address]); - await usdt - .connect(josh) - .transfer(mockUniswapRouter.address, usdtUnits("100")); - - await setOracleTokenPriceUsd("COMP", "1.0404"); // 1/1.0404 = 0,9611687812 - - await harvester - .connect(governor) - .setRewardTokenConfig( - comp.address, - 400, - 100, - mockUniswapRouter.address, - MAX_UINT256, - true - ); - - // prettier-ignore - const annaBalanceChange = await changeInBalance( - async () => { - await harvester - .connect(anna)["harvestAndSwap(address)"](compoundStrategy.address); - }, - usdt, - anna.address - ); - - expect(await comp.balanceOf(compoundStrategy.address)).to.be.equal("0"); - expect(annaBalanceChange).to.be.equal(utils.parseUnits("0.1", 6)); - expect(await usdt.balanceOf(vault.address)).to.be.equal( - utils.parseUnits("9.9", 6) - ); - expect(await comp.balanceOf(harvester.address)).to.be.equal("0"); - expect(await usdt.balanceOf(harvester.address)).to.be.equal("0"); - }); - - it("Should fail when slippage is just over threshold", async () => { - const { harvester, governor, comp, compoundStrategy, anna, josh, usdt } = - fixture; - - await sendRewardsToCompStrategy("10", governor, compoundStrategy, comp); - - const mockUniswapRouter = await ethers.getContract("MockUniswapRouter"); - await mockUniswapRouter.initialize([comp.address], [usdt.address]); - await usdt - .connect(josh) - .transfer(mockUniswapRouter.address, usdtUnits("100")); - - await setOracleTokenPriceUsd("COMP", "1.042"); // 1/1.042 = 0,95969 - - await harvester - .connect(governor) - .setRewardTokenConfig( - comp.address, - 400, - 100, - mockUniswapRouter.address, - MAX_UINT256, - true - ); - - // prettier-ignore - await expect( - harvester - .connect(anna)["harvestAndSwap(address)"](compoundStrategy.address) - ).to.be.revertedWith("Slippage error"); - }); - - it("Should correctly distribute rewards when reward share is 1%", async () => { - const { - harvester, - governor, - comp, - compoundStrategy, - anna, - josh, - usdt, - vault, - } = fixture; - - await sendRewardsToCompStrategy("10", governor, compoundStrategy, comp); - - const mockUniswapRouter = await ethers.getContract("MockUniswapRouter"); - await mockUniswapRouter.initialize([comp.address], [usdt.address]); - await usdt - .connect(josh) - .transfer(mockUniswapRouter.address, usdtUnits("100")); - - await harvester - .connect(governor) - .setRewardTokenConfig( - comp.address, - 300, - 100, - mockUniswapRouter.address, - MAX_UINT256, - true - ); - - // prettier-ignore - const annaBalanceChange = await changeInBalance( - async () => { - await harvester - .connect(anna)["harvestAndSwap(address)"](compoundStrategy.address); - }, - usdt, - anna.address - ); - - expect(await comp.balanceOf(compoundStrategy.address)).to.be.equal("0"); - expect(annaBalanceChange).to.be.equal(utils.parseUnits("0.1", 6)); - expect(await usdt.balanceOf(vault.address)).to.be.equal( - utils.parseUnits("9.9", 6) - ); - expect(await comp.balanceOf(harvester.address)).to.be.equal("0"); - expect(await usdt.balanceOf(harvester.address)).to.be.equal("0"); - }); - - it("Should fail setting rewards percentage to 11%", async () => { - const { harvester, governor, comp } = fixture; - - const mockUniswapRouter = await ethers.getContract("MockUniswapRouter"); - await expect( - harvester - .connect(governor) - .setRewardTokenConfig( - comp.address, - 300, - 1100, - mockUniswapRouter.address, - MAX_UINT256, - true - ) - ).to.be.revertedWith("Harvest reward fee should not be over 10%"); - }); - - it("Should fail setting rewards percentage to a negative value", async () => { - const { harvester, governor, comp } = fixture; - const mockUniswapRouter = await ethers.getContract("MockUniswapRouter"); - - try { - await harvester - .connect(governor) - .setRewardTokenConfig( - comp.address, - 300, - -100, - mockUniswapRouter.address, - MAX_UINT256, - true - ); - - // if no exception fail - expect.fail( - "setRewardTokenConfig should fail when setting a negative value as reward" - ); - } catch (e) { - expect(e.message).to.include("value out-of-bounds"); - } - }); - - it("Should correctly distribute rewards when reward share is 9%", async () => { - const { - harvester, - governor, - comp, - compoundStrategy, - anna, - josh, - usdt, - vault, - } = fixture; - - await sendRewardsToCompStrategy("10", governor, compoundStrategy, comp); - - const mockUniswapRouter = await ethers.getContract("MockUniswapRouter"); - await mockUniswapRouter.initialize([comp.address], [usdt.address]); - await usdt - .connect(josh) - .transfer(mockUniswapRouter.address, usdtUnits("100")); - - await harvester - .connect(governor) - .setRewardTokenConfig( - comp.address, - 300, - 900, - mockUniswapRouter.address, - MAX_UINT256, - true - ); - - // prettier-ignore - const annaBalanceChange = await changeInBalance( - async () => { - await harvester - .connect(anna)["harvestAndSwap(address)"](compoundStrategy.address); - }, - usdt, - anna.address - ); - - expect(await comp.balanceOf(compoundStrategy.address)).to.be.equal("0"); - expect(annaBalanceChange).to.be.equal(utils.parseUnits("0.9", 6)); - expect(await usdt.balanceOf(vault.address)).to.be.equal( - utils.parseUnits("9.1", 6) - ); - expect(await comp.balanceOf(harvester.address)).to.be.equal("0"); - expect(await usdt.balanceOf(harvester.address)).to.be.equal("0"); - }); - - it("Should fail when setting setSupportedStrategy from a non vault/governor address", async () => { - const { harvester, anna, compoundStrategy } = fixture; - - // prettier-ignore - await expect( - harvester - .connect(anna)["setSupportedStrategy(address,bool)"](compoundStrategy.address, true) - ).to.be.revertedWith("Caller is not the Vault or Governor"); - }); - - it("Should succeed when governor sets a supported strategy address", async () => { - const { harvester, governor, compoundStrategy } = fixture; - - // prettier-ignore - await harvester - .connect(governor)["setSupportedStrategy(address,bool)"](compoundStrategy.address, true) - }); - - it("Harvest should work even when the vault removed the strategy", async () => { - const { - harvester, - governor, - comp, - compoundStrategy, - anna, - josh, - usdt, - usdc, - vault, - dai, - threePoolStrategy, - } = fixture; - // load another strategy to override default asset strategies to lift restriction of removing compound strategy - await vault.connect(governor).approveStrategy(threePoolStrategy.address); - - await vault - .connect(governor) - .setAssetDefaultStrategy(dai.address, threePoolStrategy.address); - await vault - .connect(governor) - .setAssetDefaultStrategy(usdc.address, threePoolStrategy.address); - await vault - .connect(governor) - .setAssetDefaultStrategy(usdt.address, threePoolStrategy.address); - - await sendRewardsToCompStrategy("10", governor, compoundStrategy, comp); - const mockUniswapRouter = await ethers.getContract("MockUniswapRouter"); - await mockUniswapRouter.initialize([comp.address], [usdt.address]); - await usdt - .connect(josh) - .transfer(mockUniswapRouter.address, usdtUnits("100")); - - await harvester - .connect(governor) - .setRewardTokenConfig( - comp.address, - 300, - 100, - mockUniswapRouter.address, - MAX_UINT256, - true - ); - - await vault.connect(governor).removeStrategy(compoundStrategy.address); - - // prettier-ignore - const annaBalanceChange = await changeInBalance( - async () => { - await harvester - .connect(anna)["harvestAndSwap(address)"](compoundStrategy.address); - }, - usdt, - anna.address - ); - - expect(await comp.balanceOf(compoundStrategy.address)).to.be.equal("0"); - expect(annaBalanceChange).to.be.equal(utils.parseUnits("0.1", 6)); - expect(await usdt.balanceOf(vault.address)).to.be.equal( - utils.parseUnits("9.9", 6) - ); - expect(await comp.balanceOf(harvester.address)).to.be.equal("0"); - expect(await usdt.balanceOf(harvester.address)).to.be.equal("0"); - }); - - it("Should fail harvestAndSwap when governor sets a strategy as not supported one", async () => { - const { harvester, governor, anna, compoundStrategy } = fixture; - - // prettier-ignore - await harvester - .connect(governor)["setSupportedStrategy(address,bool)"](compoundStrategy.address, false) - - // prettier-ignore - await expect( - harvester - .connect(anna)["harvestAndSwap(address)"](compoundStrategy.address) - ).to.be.revertedWith("Not a valid strategy address"); - }); - - it("Should not swap any coins when liquidation limit is 0", async () => { - const { - harvester, - governor, - comp, - compoundStrategy, - anna, - josh, - usdt, - vault, - } = fixture; - - await sendRewardsToCompStrategy("10", governor, compoundStrategy, comp); - - const mockUniswapRouter = await ethers.getContract("MockUniswapRouter"); - await mockUniswapRouter.initialize([comp.address], [usdt.address]); - await usdt - .connect(josh) - .transfer(mockUniswapRouter.address, usdtUnits("100")); - - await harvester - .connect(governor) - .setRewardTokenConfig( - comp.address, - 300, - 900, - mockUniswapRouter.address, - 0, - true - ); - - let annaBalanceChange; - - // prettier-ignore - const vaultBalanceChange = await changeInBalance( - async () => { - annaBalanceChange = await changeInBalance( - async () => { - await harvester - .connect(anna)["harvestAndSwap(address)"](compoundStrategy.address); - }, - usdt, - anna.address - ); - }, - usdt, - vault.address - ); - - expect(await comp.balanceOf(compoundStrategy.address)).to.be.equal("0"); - expect(annaBalanceChange).to.be.equal(0); - expect(vaultBalanceChange).to.be.equal(0); - expect(await comp.balanceOf(harvester.address)).to.be.equal( - utils.parseUnits("10", 18) - ); - expect(await usdt.balanceOf(harvester.address)).to.be.equal("0"); + beforeEach(async () => { + fixture = await loadFixture(); }); - it("Should correctly swap coins when liquidation limit is set", async () => { - const { - harvester, - governor, - comp, - compoundStrategy, - anna, - josh, - usdt, - vault, - } = fixture; - - await sendRewardsToCompStrategy("10", governor, compoundStrategy, comp); - - const mockUniswapRouter = await ethers.getContract("MockUniswapRouter"); - await mockUniswapRouter.initialize([comp.address], [usdt.address]); - await usdt - .connect(josh) - .transfer(mockUniswapRouter.address, usdtUnits("100")); - - await harvester - .connect(governor) - .setRewardTokenConfig( - comp.address, - 300, - 100, - mockUniswapRouter.address, - utils.parseUnits("3", 18), - true - ); - - let annaBalanceChange; - - // prettier-ignore - const vaultBalanceChange = await changeInBalance( - async () => { - annaBalanceChange = await changeInBalance( - async () => { - await harvester - .connect(anna)["harvestAndSwap(address,address)"](compoundStrategy.address, anna.address); - }, - usdt, - anna.address - ); + shouldBehaveLikeHarvester(() => ({ + fixture, + harvester: fixture.harvester, + strategies: [ + { + strategy: fixture.compoundStrategy, + rewardTokens: [fixture.comp], }, - usdt, - vault.address - ); - - expect(await comp.balanceOf(compoundStrategy.address)).to.be.equal("0"); - expect(annaBalanceChange).to.be.equal(utils.parseUnits("0.03", 6)); - expect(vaultBalanceChange).to.be.equal(utils.parseUnits("2.97", 6)); - expect(await comp.balanceOf(harvester.address)).to.be.equal( - utils.parseUnits("7", 18) - ); - expect(await usdt.balanceOf(harvester.address)).to.be.equal("0"); - }); - - it("Should correctly swap coins and set rewards when rewardTo is non caller address", async () => { - const { - harvester, - governor, - comp, - compoundStrategy, - anna, - josh, - usdt, - vault, - } = fixture; - - await sendRewardsToCompStrategy("10", governor, compoundStrategy, comp); - - const mockUniswapRouter = await ethers.getContract("MockUniswapRouter"); - await mockUniswapRouter.initialize([comp.address], [usdt.address]); - await usdt - .connect(josh) - .transfer(mockUniswapRouter.address, usdtUnits("100")); - - await harvester - .connect(governor) - .setRewardTokenConfig( - comp.address, - 300, - 100, - mockUniswapRouter.address, - utils.parseUnits("3", 18), - true - ); - - let joshBalanceChange; - - // prettier-ignore - const vaultBalanceChange = await changeInBalance( - async () => { - joshBalanceChange = await changeInBalance( - async () => { - await harvester - .connect(anna)["harvestAndSwap(address,address)"](compoundStrategy.address, josh.address); - }, - usdt, - josh.address - ); + { + strategy: fixture.aaveStrategy, + rewardTokens: [fixture.aaveToken], }, - usdt, - vault.address - ); - - expect(await comp.balanceOf(compoundStrategy.address)).to.be.equal("0"); - expect(joshBalanceChange).to.be.equal(utils.parseUnits("0.03", 6)); - expect(vaultBalanceChange).to.be.equal(utils.parseUnits("2.97", 6)); - expect(await comp.balanceOf(harvester.address)).to.be.equal( - utils.parseUnits("7", 18) - ); - expect(await usdt.balanceOf(harvester.address)).to.be.equal("0"); - }); - - it("Should correctly distribute rewards to a changed proceeds address", async () => { - const { harvester, governor, comp, compoundStrategy, anna, josh, usdt } = - fixture; - - await sendRewardsToCompStrategy("10", governor, compoundStrategy, comp); - - const mockUniswapRouter = await ethers.getContract("MockUniswapRouter"); - await mockUniswapRouter.initialize([comp.address], [usdt.address]); - await usdt - .connect(josh) - .transfer(mockUniswapRouter.address, usdtUnits("100")); - - await harvester - .connect(governor) - .setRewardTokenConfig( - comp.address, - 300, - 100, - mockUniswapRouter.address, - MAX_UINT256, - true - ); - - await harvester.connect(governor).setRewardsProceedsAddress(josh.address); - - let annaBalanceChange; - // prettier-ignore - const joshBalanceChange = await changeInBalance( - async () => { - annaBalanceChange = await changeInBalance( - async () => { - await harvester - .connect(anna)["harvestAndSwap(address)"](compoundStrategy.address); - }, - usdt, - anna.address - ) - }, - usdt, - josh.address - ); - - expect(await comp.balanceOf(compoundStrategy.address)).to.be.equal("0"); - expect(annaBalanceChange).to.be.equal(utils.parseUnits("0.1", 6)); - expect(joshBalanceChange).to.be.equal(utils.parseUnits("9.9", 6)); - expect(await comp.balanceOf(harvester.address)).to.be.equal("0"); - expect(await usdt.balanceOf(harvester.address)).to.be.equal("0"); - }); + ], + rewardProceedsAddress: fixture.vault.address, + })); }); diff --git a/contracts/test/vault/oeth-vault.fork-test.js b/contracts/test/vault/oeth-vault.fork-test.js index 7ea1b103d6..6f7571dd90 100644 --- a/contracts/test/vault/oeth-vault.fork-test.js +++ b/contracts/test/vault/oeth-vault.fork-test.js @@ -3,13 +3,17 @@ const { formatUnits, parseUnits } = require("ethers/lib/utils"); const addresses = require("../../utils/addresses"); const { createFixtureLoader, oethDefaultFixture } = require("../_fixture"); -const { isCI } = require("../helpers"); +const { isCI, oethUnits } = require("../helpers"); const { impersonateAndFund } = require("../../utils/signers"); +const { + shouldHaveRewardTokensConfigured, +} = require("../behaviour/reward-tokens.fork"); const log = require("../../utils/logger")("test:fork:oeth:vault"); const { oethWhaleAddress } = addresses.mainnet; +const loadFixture = createFixtureLoader(oethDefaultFixture); describe("ForkTest: OETH Vault", function () { this.timeout(0); @@ -17,13 +21,11 @@ describe("ForkTest: OETH Vault", function () { this.retries(isCI ? 3 : 0); let fixture; + beforeEach(async () => { + fixture = await loadFixture(); + }); describe("post deployment", () => { - const loadFixture = createFixtureLoader(oethDefaultFixture); - beforeEach(async () => { - fixture = await loadFixture(); - }); - it("Should have the correct governor address set", async () => { const { oethVault, @@ -55,10 +57,7 @@ describe("ForkTest: OETH Vault", function () { describe("user operations", () => { let oethWhaleSigner; - const loadFixture = createFixtureLoader(oethDefaultFixture); beforeEach(async () => { - fixture = await loadFixture(); - oethWhaleSigner = await impersonateAndFund(oethWhaleAddress); }); @@ -154,4 +153,49 @@ describe("ForkTest: OETH Vault", function () { .withNamedArgs({ _addr: oethWhaleAddress }); }); }); + + shouldHaveRewardTokensConfigured(() => ({ + vault: fixture.oethVault, + harvester: fixture.oethHarvester, + expectedConfigs: { + [fixture.cvx.address]: { + allowedSlippageBps: 300, + harvestRewardBps: 200, + swapPlatformAddr: "0xB576491F1E6e5E62f1d8F26062Ee822B40B0E0d4", + doSwapRewardToken: true, + swapPlatform: 3, + liquidationLimit: oethUnits("2500"), + curvePoolIndices: [1, 0], + }, + [fixture.crv.address]: { + allowedSlippageBps: 300, + harvestRewardBps: 200, + swapPlatformAddr: "0x4eBdF703948ddCEA3B11f675B4D1Fba9d2414A14", + doSwapRewardToken: true, + swapPlatform: 3, + liquidationLimit: oethUnits("4000"), + curvePoolIndices: [2, 1], + }, + [fixture.bal.address]: { + allowedSlippageBps: 300, + harvestRewardBps: 200, + swapPlatformAddr: "0xBA12222222228d8Ba445958a75a0704d566BF2C8", + doSwapRewardToken: true, + swapPlatform: 2, + liquidationLimit: oethUnits("1000"), + balancerPoolId: + "0x5c6ee304399dbdb9c8ef030ab642b10820db8f56000200000000000000000014", + }, + [fixture.aura.address]: { + allowedSlippageBps: 300, + harvestRewardBps: 200, + swapPlatformAddr: "0xBA12222222228d8Ba445958a75a0704d566BF2C8", + doSwapRewardToken: true, + swapPlatform: 2, + liquidationLimit: oethUnits("4000"), + balancerPoolId: + "0xcfca23ca9ca720b6e98e3eb9b6aa0ffc4a5c08b9000200000000000000000274", + }, + }, + })); }); diff --git a/contracts/test/vault/vault.fork-test.js b/contracts/test/vault/vault.fork-test.js index 64aacd7058..581df18ca1 100644 --- a/contracts/test/vault/vault.fork-test.js +++ b/contracts/test/vault/vault.fork-test.js @@ -13,6 +13,9 @@ const { isCI, } = require("./../helpers"); const { impersonateAndFund } = require("../../utils/signers"); +const { + shouldHaveRewardTokensConfigured, +} = require("../behaviour/reward-tokens.fork"); const log = require("../../utils/logger")("test:fork:ousd:vault"); @@ -409,4 +412,51 @@ describe("ForkTest: Vault", function () { await vault.connect(timelock).withdrawAllFromStrategies(); }); }); + + shouldHaveRewardTokensConfigured(() => ({ + vault: fixture.vault, + harvester: fixture.harvester, + expectedConfigs: { + [fixture.aave.address]: { + allowedSlippageBps: 300, + harvestRewardBps: 100, + swapPlatformAddr: "0xE592427A0AEce92De3Edee1F18E0157C05861564", + doSwapRewardToken: true, + swapPlatform: 1, + liquidationLimit: 0, + uniswapV3Path: + "0x7fc66500c84a76ad7e9c93437bfc5ac33e2ddae9002710c02aaa39b223fe8d0a0e5c4f27ead9083c756cc20001f4dac17f958d2ee523a2206206994597c13d831ec7", + }, + [fixture.cvx.address]: { + allowedSlippageBps: 300, + harvestRewardBps: 100, + swapPlatformAddr: "0xE592427A0AEce92De3Edee1F18E0157C05861564", + doSwapRewardToken: true, + swapPlatform: 1, + liquidationLimit: ousdUnits("2500"), + uniswapV3Path: + "0x4e3fbd56cd56c3e72c1403e103b45db9da5b9d2b002710c02aaa39b223fe8d0a0e5c4f27ead9083c756cc20001f4dac17f958d2ee523a2206206994597c13d831ec7", + }, + [fixture.crv.address]: { + allowedSlippageBps: 300, + harvestRewardBps: 200, + swapPlatformAddr: "0xE592427A0AEce92De3Edee1F18E0157C05861564", + doSwapRewardToken: true, + swapPlatform: 1, + liquidationLimit: ousdUnits("4000"), + uniswapV3Path: + "0xd533a949740bb3306d119cc777fa900ba034cd52000bb8c02aaa39b223fe8d0a0e5c4f27ead9083c756cc20001f4dac17f958d2ee523a2206206994597c13d831ec7", + }, + [fixture.comp.address]: { + allowedSlippageBps: 300, + harvestRewardBps: 100, + swapPlatformAddr: "0xE592427A0AEce92De3Edee1F18E0157C05861564", + doSwapRewardToken: true, + swapPlatform: 1, + liquidationLimit: 0, + uniswapV3Path: + "0xc00e94cb662c3520282e6f5717214004a7f26888000bb8c02aaa39b223fe8d0a0e5c4f27ead9083c756cc20001f4dac17f958d2ee523a2206206994597c13d831ec7", + }, + }, + })); }); diff --git a/contracts/utils/addresses.js b/contracts/utils/addresses.js index 8aaaf0ec8a..ec00faf856 100644 --- a/contracts/utils/addresses.js +++ b/contracts/utils/addresses.js @@ -119,6 +119,8 @@ addresses.mainnet.chainlinkstETH_ETH = "0x86392dC19c0b719886221c78AB11eb8Cf5c52812"; addresses.mainnet.chainlinkcbETH_ETH = "0xF017fcB346A1885194689bA23Eff2fE6fA5C483b"; +addresses.mainnet.chainlinkBAL_ETH = + "0xC1438AA3823A6Ba0C159CfA8D98dF5A994bA120b"; // WETH Token addresses.mainnet.WETH = "0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2"; @@ -220,6 +222,8 @@ addresses.mainnet.wstETH_sfrxETH_rETH_AuraRewards = // Aura addresses.mainnet.AURA = "0xC0c293ce456fF0ED870ADd98a0828Dd4d2903DBF"; +addresses.mainnet.AuraWeightedOraclePool = + "0xc29562b045D80fD77c69Bec09541F5c16fe20d9d"; // Flux Strategy addresses.mainnet.fDAI = "0xe2bA8693cE7474900A045757fe0efCa900F6530b"; @@ -233,4 +237,8 @@ addresses.mainnet.FrxEthFraxOracle = addresses.mainnet.FrxEthEthDualOracle = "0xb12c19C838499E3447AFd9e59274B1BE56b1546A"; +// Curve Pools +addresses.mainnet.CurveTriPool = "0x4ebdf703948ddcea3b11f675b4d1fba9d2414a14"; +addresses.mainnet.CurveCVXPool = "0xB576491F1E6e5E62f1d8F26062Ee822B40B0E0d4"; + module.exports = addresses; diff --git a/contracts/utils/deploy.js b/contracts/utils/deploy.js index bd29fdd666..716563735b 100644 --- a/contracts/utils/deploy.js +++ b/contracts/utils/deploy.js @@ -388,7 +388,9 @@ const executeGovernanceProposalOnFork = async ({ await advanceTime(queuePeriod + 1); } - await governorFive.connect(sMultisig5of8)["execute(uint256)"](proposalIdBn); + await governorFive.connect(sMultisig5of8)["execute(uint256)"](proposalIdBn, { + gasLimit: executeGasLimit, + }); const newProposalState = await getProposalState(proposalIdBn); if (newProposalState === "Executed") { @@ -697,6 +699,7 @@ const handlePossiblyActiveGovernanceProposal = async ( proposalIdBn, proposalState, reduceQueueTime, + executeGasLimit, }); // proposal executed skip deployment @@ -831,6 +834,7 @@ function deploymentWithGovernanceProposal(opts, fn) { proposalId, deployerIsProposer = false, // The deployer issues the propose to OGV Governor reduceQueueTime = false, // reduce governance queue times + executeGasLimit = null, } = opts; const runDeployment = async (hre) => { const oracleAddresses = await getOracleAddresses(hre.deployments); @@ -897,6 +901,7 @@ function deploymentWithGovernanceProposal(opts, fn) { proposalIdBn, proposalState, reduceQueueTime, + executeGasLimit, }); log("Proposal executed."); } else { From 3f8c91cba06ac7d30e325801340a25fe8c247f70 Mon Sep 17 00:00:00 2001 From: Shahul Hameed <10547529+shahthepro@users.noreply.github.com> Date: Tue, 28 Nov 2023 14:39:02 +0530 Subject: [PATCH 13/13] Add diagrams for Harvester and Buyback contracts (#1944) * checkpoint * checkpoint * Few code tweaks and add deployment file * Fix aura test * Fix aave config * Fix decimal caching * Add bunch of unit tests * Add bunch of unit tests * Prettify * Remove .only * Remove console statements * Few more test tweaks * Fix sig * Fix harvest tests * Rename behaviour test file * Price feed tests * Fix failing test * Slither * Rename test file * Checkpoint * Fix tests * Cleanup * Fix a bit of tests * Fix unit tests * Tweak fork test * Prettify * Adjust liquidation limits * Address CR comments * Fix unit tests * Fix aave unit tests * Remove commented out code * Fix bug * Add coverage * Fix error * Rename event args * Prettify * Gas opts * More readability * Change to struct * Fix CVX test * Address more CR comments * Add slippage checks * Farmer only receives fee from swap amount * Update docs --- contracts/docs/AaveStrategyHierarchy.svg | 28 +- contracts/docs/AaveStrategySquashed.svg | 52 +-- contracts/docs/AuraWETHPriceFeedHierarchy.svg | 74 +++++ contracts/docs/AuraWETHPriceFeedSquashed.svg | 72 +++++ contracts/docs/AuraWETHPriceFeedStorage.svg | 79 +++++ .../BalancerMetaPoolStrategyHierarchy.svg | 44 +-- .../docs/BalancerMetaPoolStrategySquashed.svg | 68 ++-- .../docs/ConvexEthMetaStrategyHierarchy.svg | 28 +- .../docs/ConvexEthMetaStrategySquashed.svg | 64 ++-- .../docs/ConvexOUSDMetaStrategyHierarchy.svg | 44 +-- .../docs/ConvexOUSDMetaStrategySquashed.svg | 58 ++-- contracts/docs/DripperHierarchy.svg | 150 ++++++--- contracts/docs/DripperSquashed.svg | 4 +- contracts/docs/FlipperHierarchy.svg | 80 ++--- contracts/docs/FlipperSquashed.svg | 4 +- contracts/docs/FluxStrategyHierarchy.svg | 44 +-- contracts/docs/FluxStrategySquashed.svg | 54 ++-- contracts/docs/FraxETHStrategyHierarchy.svg | 36 +-- contracts/docs/FraxETHStrategySquashed.svg | 52 +-- .../docs/Generalized4626StrategyHierarchy.svg | 28 +- .../docs/Generalized4626StrategySquashed.svg | 52 +-- contracts/docs/GovernorHierarchy.svg | 20 +- contracts/docs/GovernorSquashed.svg | 4 +- contracts/docs/HarvesterHierarchy.svg | 306 ++++++++++++------ contracts/docs/HarvesterSquashed.svg | 123 +++---- contracts/docs/HarvesterStorage.svg | 144 ++++++--- ...InitializableAbstractStrategyHierarchy.svg | 150 +++++---- .../InitializableAbstractStrategySquashed.svg | 123 ++++--- .../InitializableERC20DetailedHierarchy.svg | 12 +- .../InitializableERC20DetailedSquashed.svg | 4 +- .../docs/MorphoAaveStrategyHierarchy.svg | 28 +- contracts/docs/MorphoAaveStrategySquashed.svg | 54 ++-- .../docs/MorphoCompStrategyHierarchy.svg | 36 +-- contracts/docs/MorphoCompStrategySquashed.svg | 54 ++-- contracts/docs/OETHBuybackHierarchy.svg | 116 +++++++ contracts/docs/OETHBuybackSquashed.svg | 89 +++++ contracts/docs/OETHBuybackStorage.svg | 163 ++++++++++ contracts/docs/OETHDripperHierarchy.svg | 158 ++++++--- contracts/docs/OETHDripperSquashed.svg | 4 +- contracts/docs/OETHHarvesterHierarchy.svg | 306 ++++++++++++------ contracts/docs/OETHHarvesterSquashed.svg | 115 ++++--- contracts/docs/OETHHarvesterStorage.svg | 144 ++++++--- contracts/docs/OETHHierarchy.svg | 44 +-- contracts/docs/OETHOracleRouterHierarchy.svg | 103 +++--- contracts/docs/OETHOracleRouterSquashed.svg | 55 ++-- contracts/docs/OETHSquashed.svg | 4 +- contracts/docs/OETHVaultAdminSquashed.svg | 4 +- contracts/docs/OETHVaultCoreSquashed.svg | 4 +- contracts/docs/OETHVaultHierarchy.svg | 156 ++++----- contracts/docs/OUSDBuybackHierarchy.svg | 116 +++++++ contracts/docs/OUSDBuybackSquashed.svg | 89 +++++ contracts/docs/OUSDBuybackStorage.svg | 163 ++++++++++ contracts/docs/OUSDHierarchy.svg | 36 +-- contracts/docs/OUSDProxyHierarchy.svg | 20 +- contracts/docs/OUSDProxySquashed.svg | 4 +- contracts/docs/OUSDSquashed.svg | 4 +- contracts/docs/OracleRouterHierarchy.svg | 62 ++++ contracts/docs/OracleRouterSquashed.svg | 35 ++ contracts/docs/OracleRouterStorage.svg | 27 ++ contracts/docs/StableMathHierarchy.svg | 12 +- contracts/docs/StableMathSquashed.svg | 4 +- contracts/docs/Swapper1InchV5Hierarchy.svg | 44 +-- contracts/docs/Swapper1InchV5Squashed.svg | 4 +- contracts/docs/TimelockHierarchy.svg | 12 +- contracts/docs/TimelockSquashed.svg | 4 +- contracts/docs/VaultAdminSquashed.svg | 4 +- contracts/docs/VaultCoreSquashed.svg | 4 +- contracts/docs/VaultHierarchy.svg | 140 ++++---- contracts/docs/WOETHHierarchy.svg | 128 ++++---- contracts/docs/WOETHSquashed.svg | 124 +++---- contracts/docs/WOUSDHierarchy.svg | 120 +++---- contracts/docs/WOUSDSquashed.svg | 124 +++---- contracts/docs/generate.sh | 15 + 73 files changed, 3245 insertions(+), 1688 deletions(-) create mode 100644 contracts/docs/AuraWETHPriceFeedHierarchy.svg create mode 100644 contracts/docs/AuraWETHPriceFeedSquashed.svg create mode 100644 contracts/docs/AuraWETHPriceFeedStorage.svg create mode 100644 contracts/docs/OETHBuybackHierarchy.svg create mode 100644 contracts/docs/OETHBuybackSquashed.svg create mode 100644 contracts/docs/OETHBuybackStorage.svg create mode 100644 contracts/docs/OUSDBuybackHierarchy.svg create mode 100644 contracts/docs/OUSDBuybackSquashed.svg create mode 100644 contracts/docs/OUSDBuybackStorage.svg create mode 100644 contracts/docs/OracleRouterHierarchy.svg create mode 100644 contracts/docs/OracleRouterSquashed.svg create mode 100644 contracts/docs/OracleRouterStorage.svg diff --git a/contracts/docs/AaveStrategyHierarchy.svg b/contracts/docs/AaveStrategyHierarchy.svg index a82f93c07f..74f116b8ab 100644 --- a/contracts/docs/AaveStrategyHierarchy.svg +++ b/contracts/docs/AaveStrategyHierarchy.svg @@ -9,51 +9,51 @@ UmlClassDiagram - + -18 +20 Governable ../contracts/governance/Governable.sol - + -140 +150 AaveStrategy ../contracts/strategies/AaveStrategy.sol - + -185 +195 <<Abstract>> InitializableAbstractStrategy ../contracts/utils/InitializableAbstractStrategy.sol - + -140->185 +150->195 - + -184 +194 <<Abstract>> Initializable ../contracts/utils/Initializable.sol - + -185->18 +195->20 - + -185->184 +195->194 diff --git a/contracts/docs/AaveStrategySquashed.svg b/contracts/docs/AaveStrategySquashed.svg index 803f742d2e..21d2e01374 100644 --- a/contracts/docs/AaveStrategySquashed.svg +++ b/contracts/docs/AaveStrategySquashed.svg @@ -9,9 +9,9 @@ UmlClassDiagram - + -140 +150 AaveStrategy ../contracts/strategies/AaveStrategy.sol @@ -70,30 +70,30 @@    withdraw(_recipient: address, _asset: address, _amount: uint256) <<onlyVault, nonReentrant>> <<AaveStrategy>>    withdrawAll() <<onlyVaultOrGovernor, nonReentrant>> <<AaveStrategy>>    checkBalance(_asset: address): (balance: uint256) <<AaveStrategy>> -    supportsAsset(_asset: address): bool <<AaveStrategy>> -    initialize(_rewardTokenAddresses: address[], _assets: address[], _pTokens: address[], _incentivesAddress: address, _stkAaveAddress: address) <<onlyGovernor, initializer>> <<AaveStrategy>> -Public: -    <<event>> PendingGovernorshipTransfer(previousGovernor: address, newGovernor: address) <<Governable>> -    <<event>> GovernorshipTransferred(previousGovernor: address, newGovernor: address) <<Governable>> -    <<event>> PTokenAdded(_asset: address, _pToken: address) <<InitializableAbstractStrategy>> -    <<event>> PTokenRemoved(_asset: address, _pToken: address) <<InitializableAbstractStrategy>> -    <<event>> Deposit(_asset: address, _pToken: address, _amount: uint256) <<InitializableAbstractStrategy>> -    <<event>> Withdrawal(_asset: address, _pToken: address, _amount: uint256) <<InitializableAbstractStrategy>> -    <<event>> RewardTokenCollected(recipient: address, rewardToken: address, amount: uint256) <<InitializableAbstractStrategy>> -    <<event>> RewardTokenAddressesUpdated(_oldAddresses: address[], _newAddresses: address[]) <<InitializableAbstractStrategy>> -    <<event>> HarvesterAddressesUpdated(_oldHarvesterAddress: address, _newHarvesterAddress: address) <<InitializableAbstractStrategy>> -    <<modifier>> initializer() <<Initializable>> -    <<modifier>> onlyGovernor() <<Governable>> -    <<modifier>> nonReentrant() <<Governable>> -    <<modifier>> onlyVault() <<InitializableAbstractStrategy>> -    <<modifier>> onlyHarvester() <<InitializableAbstractStrategy>> -    <<modifier>> onlyVaultOrGovernor() <<InitializableAbstractStrategy>> -    <<modifier>> onlyVaultOrGovernorOrStrategist() <<InitializableAbstractStrategy>> -    constructor() <<Governable>> -    governor(): address <<Governable>> -    isGovernor(): bool <<Governable>> -    constructor(_stratConfig: BaseStrategyConfig) <<AaveStrategy>> -    transferToken(_asset: address, _amount: uint256) <<onlyGovernor>> <<InitializableAbstractStrategy>> +    initialize(_rewardTokenAddresses: address[], _assets: address[], _pTokens: address[], _incentivesAddress: address, _stkAaveAddress: address) <<onlyGovernor, initializer>> <<AaveStrategy>> +Public: +    <<event>> PendingGovernorshipTransfer(previousGovernor: address, newGovernor: address) <<Governable>> +    <<event>> GovernorshipTransferred(previousGovernor: address, newGovernor: address) <<Governable>> +    <<event>> PTokenAdded(_asset: address, _pToken: address) <<InitializableAbstractStrategy>> +    <<event>> PTokenRemoved(_asset: address, _pToken: address) <<InitializableAbstractStrategy>> +    <<event>> Deposit(_asset: address, _pToken: address, _amount: uint256) <<InitializableAbstractStrategy>> +    <<event>> Withdrawal(_asset: address, _pToken: address, _amount: uint256) <<InitializableAbstractStrategy>> +    <<event>> RewardTokenCollected(recipient: address, rewardToken: address, amount: uint256) <<InitializableAbstractStrategy>> +    <<event>> RewardTokenAddressesUpdated(_oldAddresses: address[], _newAddresses: address[]) <<InitializableAbstractStrategy>> +    <<event>> HarvesterAddressesUpdated(_oldHarvesterAddress: address, _newHarvesterAddress: address) <<InitializableAbstractStrategy>> +    <<modifier>> initializer() <<Initializable>> +    <<modifier>> onlyGovernor() <<Governable>> +    <<modifier>> nonReentrant() <<Governable>> +    <<modifier>> onlyVault() <<InitializableAbstractStrategy>> +    <<modifier>> onlyHarvester() <<InitializableAbstractStrategy>> +    <<modifier>> onlyVaultOrGovernor() <<InitializableAbstractStrategy>> +    <<modifier>> onlyVaultOrGovernorOrStrategist() <<InitializableAbstractStrategy>> +    constructor() <<Governable>> +    governor(): address <<Governable>> +    isGovernor(): bool <<Governable>> +    constructor(_stratConfig: BaseStrategyConfig) <<AaveStrategy>> +    transferToken(_asset: address, _amount: uint256) <<onlyGovernor>> <<InitializableAbstractStrategy>> +    supportsAsset(_asset: address): bool <<AaveStrategy>> diff --git a/contracts/docs/AuraWETHPriceFeedHierarchy.svg b/contracts/docs/AuraWETHPriceFeedHierarchy.svg new file mode 100644 index 0000000000..5e6482f5c0 --- /dev/null +++ b/contracts/docs/AuraWETHPriceFeedHierarchy.svg @@ -0,0 +1,74 @@ + + + + + + +UmlClassDiagram + + + +20 + +Governable +../contracts/governance/Governable.sol + + + +25 + +Strategizable +../contracts/governance/Strategizable.sol + + + +25->20 + + + + + +227 + +<<Interface>> +IOracleWeightedPool +../contracts/interfaces/balancer/IOracleWeightedPool.sol + + + +229 + +<<Interface>> +AggregatorV3Interface +../contracts/interfaces/chainlink/AggregatorV3Interface.sol + + + +115 + +AuraWETHPriceFeed +../contracts/oracle/AuraWETHPriceFeed.sol + + + +115->25 + + + + + +115->227 + + + + + +115->229 + + + + + diff --git a/contracts/docs/AuraWETHPriceFeedSquashed.svg b/contracts/docs/AuraWETHPriceFeedSquashed.svg new file mode 100644 index 0000000000..6046878f10 --- /dev/null +++ b/contracts/docs/AuraWETHPriceFeedSquashed.svg @@ -0,0 +1,72 @@ + + + + + + +UmlClassDiagram + + + +115 + +AuraWETHPriceFeed +../contracts/oracle/AuraWETHPriceFeed.sol + +Private: +   governorPosition: bytes32 <<Governable>> +   pendingGovernorPosition: bytes32 <<Governable>> +   reentryStatusPosition: bytes32 <<Governable>> +   __gap: uint256[50] <<Strategizable>> +Public: +   _NOT_ENTERED: uint256 <<Governable>> +   _ENTERED: uint256 <<Governable>> +   strategistAddr: address <<Strategizable>> +   paused: bool <<AuraWETHPriceFeed>> +   tolerance: uint256 <<AuraWETHPriceFeed>> +   decimals: uint8 <<AuraWETHPriceFeed>> +   description: string <<AuraWETHPriceFeed>> +   version: uint256 <<AuraWETHPriceFeed>> +   auraOracleWeightedPool: IOracleWeightedPool <<AuraWETHPriceFeed>> + +Internal: +    _governor(): (governorOut: address) <<Governable>> +    _pendingGovernor(): (pendingGovernor: address) <<Governable>> +    _setGovernor(newGovernor: address) <<Governable>> +    _setPendingGovernor(newGovernor: address) <<Governable>> +    _changeGovernor(_newGovernor: address) <<Governable>> +    _setStrategistAddr(_address: address) <<Strategizable>> +    _price(): int256 <<AuraWETHPriceFeed>> +External: +     decimals(): uint8 <<AggregatorV3Interface>> +     description(): string <<AggregatorV3Interface>> +     version(): uint256 <<AggregatorV3Interface>> +    getRoundData(uint80): (uint80, int256, uint256, uint256, uint80) <<AuraWETHPriceFeed>> +    latestRoundData(): (uint80, answer: int256, uint256, updatedAt: uint256, uint80) <<AuraWETHPriceFeed>> +    transferGovernance(_newGovernor: address) <<onlyGovernor>> <<Governable>> +    claimGovernance() <<Governable>> +    setStrategistAddr(_address: address) <<onlyGovernor>> <<Strategizable>> +    price(): int256 <<AuraWETHPriceFeed>> +    pause() <<onlyGovernorOrStrategist>> <<AuraWETHPriceFeed>> +    unpause() <<onlyGovernor>> <<AuraWETHPriceFeed>> +    setTolerance(_tolerance: uint256) <<onlyGovernor>> <<AuraWETHPriceFeed>> +Public: +    <<event>> PendingGovernorshipTransfer(previousGovernor: address, newGovernor: address) <<Governable>> +    <<event>> GovernorshipTransferred(previousGovernor: address, newGovernor: address) <<Governable>> +    <<event>> StrategistUpdated(_address: address) <<Strategizable>> +    <<event>> PriceFeedPaused() <<AuraWETHPriceFeed>> +    <<event>> PriceFeedUnpaused() <<AuraWETHPriceFeed>> +    <<event>> ToleranceChanged(oldTolerance: uint256, newTolerance: uint256) <<AuraWETHPriceFeed>> +    <<modifier>> onlyGovernor() <<Governable>> +    <<modifier>> nonReentrant() <<Governable>> +    <<modifier>> onlyGovernorOrStrategist() <<Strategizable>> +    constructor() <<Governable>> +    governor(): address <<Governable>> +    isGovernor(): bool <<Governable>> +    constructor(_auraOracleWeightedPool: address, _governor: address) <<AuraWETHPriceFeed>> + + + diff --git a/contracts/docs/AuraWETHPriceFeedStorage.svg b/contracts/docs/AuraWETHPriceFeedStorage.svg new file mode 100644 index 0000000000..b991cd1c99 --- /dev/null +++ b/contracts/docs/AuraWETHPriceFeedStorage.svg @@ -0,0 +1,79 @@ + + + + + + +StorageDiagram + + + +2 + +AuraWETHPriceFeed <<Contract>> + +slot + +0 + +1-50 + +51 + +52 + +type: <inherited contract>.variable (bytes) + +unallocated (12) + +address: Strategizable.strategistAddr (20) + +uint256[50]: Strategizable.__gap (1600) + +unallocated (31) + +bool: paused (1) + +uint256: tolerance (32) + + + +1 + +uint256[50]: __gap <<Array>> + +slot + +1 + +2 + +3-48 + +49 + +50 + +type: variable (bytes) + +uint256 (32) + +uint256 (32) + +---- (1472) + +uint256 (32) + +uint256 (32) + + + +2:7->1 + + + + + diff --git a/contracts/docs/BalancerMetaPoolStrategyHierarchy.svg b/contracts/docs/BalancerMetaPoolStrategyHierarchy.svg index e7487d8a50..fc8cfaa8c3 100644 --- a/contracts/docs/BalancerMetaPoolStrategyHierarchy.svg +++ b/contracts/docs/BalancerMetaPoolStrategyHierarchy.svg @@ -9,79 +9,79 @@ UmlClassDiagram - + -18 +20 Governable ../contracts/governance/Governable.sol - + -244 +263 BalancerMetaPoolStrategy ../contracts/strategies/balancer/BalancerMetaPoolStrategy.sol - + -245 +264 <<Abstract>> BaseAuraStrategy ../contracts/strategies/balancer/BaseAuraStrategy.sol - + -244->245 +263->264 - + -246 +265 <<Abstract>> BaseBalancerStrategy ../contracts/strategies/balancer/BaseBalancerStrategy.sol - + -245->246 +264->265 - + -185 +195 <<Abstract>> InitializableAbstractStrategy ../contracts/utils/InitializableAbstractStrategy.sol - + -246->185 +265->195 - + -184 +194 <<Abstract>> Initializable ../contracts/utils/Initializable.sol - + -185->18 +195->20 - + -185->184 +195->194 diff --git a/contracts/docs/BalancerMetaPoolStrategySquashed.svg b/contracts/docs/BalancerMetaPoolStrategySquashed.svg index ecf33d801b..0feadd9c97 100644 --- a/contracts/docs/BalancerMetaPoolStrategySquashed.svg +++ b/contracts/docs/BalancerMetaPoolStrategySquashed.svg @@ -9,9 +9,9 @@ UmlClassDiagram - + -244 +263 BalancerMetaPoolStrategy ../contracts/strategies/balancer/BalancerMetaPoolStrategy.sol @@ -94,38 +94,38 @@    withdraw(_recipient: address, _strategyAsset: address, _strategyAmount: uint256) <<onlyVault, nonReentrant>> <<BalancerMetaPoolStrategy>>    withdrawAll() <<onlyVaultOrGovernor, nonReentrant>> <<BalancerMetaPoolStrategy>>    checkBalance(_asset: address): (amount: uint256) <<whenNotInBalancerVaultContext>> <<BaseBalancerStrategy>> -    supportsAsset(_asset: address): bool <<BaseBalancerStrategy>> -    initialize(_rewardTokenAddresses: address[], _assets: address[], _pTokens: address[]) <<onlyGovernor, initializer>> <<BaseBalancerStrategy>> -    checkBalance(): (value: uint256) <<whenNotInBalancerVaultContext>> <<BaseBalancerStrategy>> -    setMaxWithdrawalDeviation(_maxWithdrawalDeviation: uint256) <<onlyVaultOrGovernorOrStrategist>> <<BaseBalancerStrategy>> -    setMaxDepositDeviation(_maxDepositDeviation: uint256) <<onlyVaultOrGovernorOrStrategist>> <<BaseBalancerStrategy>> -    deposit(address[], uint256[]) <<onlyVault, nonReentrant>> <<BalancerMetaPoolStrategy>> -    withdraw(_recipient: address, _strategyAssets: address[], _strategyAmounts: uint256[]) <<onlyVault, nonReentrant>> <<BalancerMetaPoolStrategy>> -Public: -    <<event>> PendingGovernorshipTransfer(previousGovernor: address, newGovernor: address) <<Governable>> -    <<event>> GovernorshipTransferred(previousGovernor: address, newGovernor: address) <<Governable>> -    <<event>> PTokenAdded(_asset: address, _pToken: address) <<InitializableAbstractStrategy>> -    <<event>> PTokenRemoved(_asset: address, _pToken: address) <<InitializableAbstractStrategy>> -    <<event>> Deposit(_asset: address, _pToken: address, _amount: uint256) <<InitializableAbstractStrategy>> -    <<event>> Withdrawal(_asset: address, _pToken: address, _amount: uint256) <<InitializableAbstractStrategy>> -    <<event>> RewardTokenCollected(recipient: address, rewardToken: address, amount: uint256) <<InitializableAbstractStrategy>> -    <<event>> RewardTokenAddressesUpdated(_oldAddresses: address[], _newAddresses: address[]) <<InitializableAbstractStrategy>> -    <<event>> HarvesterAddressesUpdated(_oldHarvesterAddress: address, _newHarvesterAddress: address) <<InitializableAbstractStrategy>> -    <<event>> MaxWithdrawalDeviationUpdated(_prevMaxDeviationPercentage: uint256, _newMaxDeviationPercentage: uint256) <<BaseBalancerStrategy>> -    <<event>> MaxDepositDeviationUpdated(_prevMaxDeviationPercentage: uint256, _newMaxDeviationPercentage: uint256) <<BaseBalancerStrategy>> -    <<modifier>> initializer() <<Initializable>> -    <<modifier>> onlyGovernor() <<Governable>> -    <<modifier>> nonReentrant() <<Governable>> -    <<modifier>> onlyVault() <<InitializableAbstractStrategy>> -    <<modifier>> onlyHarvester() <<InitializableAbstractStrategy>> -    <<modifier>> onlyVaultOrGovernor() <<InitializableAbstractStrategy>> -    <<modifier>> onlyVaultOrGovernorOrStrategist() <<InitializableAbstractStrategy>> -    <<modifier>> whenNotInBalancerVaultContext() <<BaseBalancerStrategy>> -    constructor() <<Governable>> -    governor(): address <<Governable>> -    isGovernor(): bool <<Governable>> -    constructor(_config: BaseStrategyConfig) <<InitializableAbstractStrategy>> -    transferToken(_asset: address, _amount: uint256) <<onlyGovernor>> <<InitializableAbstractStrategy>> +    initialize(_rewardTokenAddresses: address[], _assets: address[], _pTokens: address[]) <<onlyGovernor, initializer>> <<BaseBalancerStrategy>> +    checkBalance(): (value: uint256) <<whenNotInBalancerVaultContext>> <<BaseBalancerStrategy>> +    setMaxWithdrawalDeviation(_maxWithdrawalDeviation: uint256) <<onlyVaultOrGovernorOrStrategist>> <<BaseBalancerStrategy>> +    setMaxDepositDeviation(_maxDepositDeviation: uint256) <<onlyVaultOrGovernorOrStrategist>> <<BaseBalancerStrategy>> +    deposit(address[], uint256[]) <<onlyVault, nonReentrant>> <<BalancerMetaPoolStrategy>> +    withdraw(_recipient: address, _strategyAssets: address[], _strategyAmounts: uint256[]) <<onlyVault, nonReentrant>> <<BalancerMetaPoolStrategy>> +Public: +    <<event>> PendingGovernorshipTransfer(previousGovernor: address, newGovernor: address) <<Governable>> +    <<event>> GovernorshipTransferred(previousGovernor: address, newGovernor: address) <<Governable>> +    <<event>> PTokenAdded(_asset: address, _pToken: address) <<InitializableAbstractStrategy>> +    <<event>> PTokenRemoved(_asset: address, _pToken: address) <<InitializableAbstractStrategy>> +    <<event>> Deposit(_asset: address, _pToken: address, _amount: uint256) <<InitializableAbstractStrategy>> +    <<event>> Withdrawal(_asset: address, _pToken: address, _amount: uint256) <<InitializableAbstractStrategy>> +    <<event>> RewardTokenCollected(recipient: address, rewardToken: address, amount: uint256) <<InitializableAbstractStrategy>> +    <<event>> RewardTokenAddressesUpdated(_oldAddresses: address[], _newAddresses: address[]) <<InitializableAbstractStrategy>> +    <<event>> HarvesterAddressesUpdated(_oldHarvesterAddress: address, _newHarvesterAddress: address) <<InitializableAbstractStrategy>> +    <<event>> MaxWithdrawalDeviationUpdated(_prevMaxDeviationPercentage: uint256, _newMaxDeviationPercentage: uint256) <<BaseBalancerStrategy>> +    <<event>> MaxDepositDeviationUpdated(_prevMaxDeviationPercentage: uint256, _newMaxDeviationPercentage: uint256) <<BaseBalancerStrategy>> +    <<modifier>> initializer() <<Initializable>> +    <<modifier>> onlyGovernor() <<Governable>> +    <<modifier>> nonReentrant() <<Governable>> +    <<modifier>> onlyVault() <<InitializableAbstractStrategy>> +    <<modifier>> onlyHarvester() <<InitializableAbstractStrategy>> +    <<modifier>> onlyVaultOrGovernor() <<InitializableAbstractStrategy>> +    <<modifier>> onlyVaultOrGovernorOrStrategist() <<InitializableAbstractStrategy>> +    <<modifier>> whenNotInBalancerVaultContext() <<BaseBalancerStrategy>> +    constructor() <<Governable>> +    governor(): address <<Governable>> +    isGovernor(): bool <<Governable>> +    constructor(_config: BaseStrategyConfig) <<InitializableAbstractStrategy>> +    transferToken(_asset: address, _amount: uint256) <<onlyGovernor>> <<InitializableAbstractStrategy>> +    supportsAsset(_asset: address): bool <<BaseBalancerStrategy>>    constructor(_balancerConfig: BaseBalancerConfig) <<BaseBalancerStrategy>>    constructor(_auraRewardPoolAddress: address) <<BaseAuraStrategy>>    constructor(_stratConfig: BaseStrategyConfig, _balancerConfig: BaseBalancerConfig, _auraRewardPoolAddress: address) <<BalancerMetaPoolStrategy>> diff --git a/contracts/docs/ConvexEthMetaStrategyHierarchy.svg b/contracts/docs/ConvexEthMetaStrategyHierarchy.svg index 6e0a602a12..cce9738332 100644 --- a/contracts/docs/ConvexEthMetaStrategyHierarchy.svg +++ b/contracts/docs/ConvexEthMetaStrategyHierarchy.svg @@ -9,51 +9,51 @@ UmlClassDiagram - + -18 +20 Governable ../contracts/governance/Governable.sol - + -146 +156 ConvexEthMetaStrategy ../contracts/strategies/ConvexEthMetaStrategy.sol - + -185 +195 <<Abstract>> InitializableAbstractStrategy ../contracts/utils/InitializableAbstractStrategy.sol - + -146->185 +156->195 - + -184 +194 <<Abstract>> Initializable ../contracts/utils/Initializable.sol - + -185->18 +195->20 - + -185->184 +195->194 diff --git a/contracts/docs/ConvexEthMetaStrategySquashed.svg b/contracts/docs/ConvexEthMetaStrategySquashed.svg index cd5325b85c..94c4f6c787 100644 --- a/contracts/docs/ConvexEthMetaStrategySquashed.svg +++ b/contracts/docs/ConvexEthMetaStrategySquashed.svg @@ -9,9 +9,9 @@ UmlClassDiagram - + -146 +156 ConvexEthMetaStrategy ../contracts/strategies/ConvexEthMetaStrategy.sol @@ -90,36 +90,36 @@    depositAll() <<onlyVault, nonReentrant>> <<ConvexEthMetaStrategy>>    withdraw(_recipient: address, _weth: address, _amount: uint256) <<onlyVault, nonReentrant>> <<ConvexEthMetaStrategy>>    withdrawAll() <<onlyVaultOrGovernor, nonReentrant>> <<ConvexEthMetaStrategy>> -    supportsAsset(_asset: address): bool <<ConvexEthMetaStrategy>> -    initialize(_rewardTokenAddresses: address[], _assets: address[]) <<onlyGovernor, initializer>> <<ConvexEthMetaStrategy>> -    mintAndAddOTokens(_oTokens: uint256) <<onlyStrategist, nonReentrant, improvePoolBalance>> <<ConvexEthMetaStrategy>> -    removeAndBurnOTokens(_lpTokens: uint256) <<onlyStrategist, nonReentrant, improvePoolBalance>> <<ConvexEthMetaStrategy>> -    removeOnlyAssets(_lpTokens: uint256) <<onlyStrategist, nonReentrant, improvePoolBalance>> <<ConvexEthMetaStrategy>> -Public: -    <<event>> PendingGovernorshipTransfer(previousGovernor: address, newGovernor: address) <<Governable>> -    <<event>> GovernorshipTransferred(previousGovernor: address, newGovernor: address) <<Governable>> -    <<event>> PTokenAdded(_asset: address, _pToken: address) <<InitializableAbstractStrategy>> -    <<event>> PTokenRemoved(_asset: address, _pToken: address) <<InitializableAbstractStrategy>> -    <<event>> Deposit(_asset: address, _pToken: address, _amount: uint256) <<InitializableAbstractStrategy>> -    <<event>> Withdrawal(_asset: address, _pToken: address, _amount: uint256) <<InitializableAbstractStrategy>> -    <<event>> RewardTokenCollected(recipient: address, rewardToken: address, amount: uint256) <<InitializableAbstractStrategy>> -    <<event>> RewardTokenAddressesUpdated(_oldAddresses: address[], _newAddresses: address[]) <<InitializableAbstractStrategy>> -    <<event>> HarvesterAddressesUpdated(_oldHarvesterAddress: address, _newHarvesterAddress: address) <<InitializableAbstractStrategy>> -    <<modifier>> initializer() <<Initializable>> -    <<modifier>> onlyGovernor() <<Governable>> -    <<modifier>> nonReentrant() <<Governable>> -    <<modifier>> onlyVault() <<InitializableAbstractStrategy>> -    <<modifier>> onlyHarvester() <<InitializableAbstractStrategy>> -    <<modifier>> onlyVaultOrGovernor() <<InitializableAbstractStrategy>> -    <<modifier>> onlyVaultOrGovernorOrStrategist() <<InitializableAbstractStrategy>> -    <<modifier>> onlyStrategist() <<ConvexEthMetaStrategy>> -    <<modifier>> improvePoolBalance() <<ConvexEthMetaStrategy>> -    constructor() <<Governable>> -    governor(): address <<Governable>> -    isGovernor(): bool <<Governable>> -    constructor(_config: BaseStrategyConfig) <<InitializableAbstractStrategy>> -    transferToken(_asset: address, _amount: uint256) <<onlyGovernor>> <<InitializableAbstractStrategy>> -    checkBalance(_asset: address): (balance: uint256) <<ConvexEthMetaStrategy>> +    initialize(_rewardTokenAddresses: address[], _assets: address[]) <<onlyGovernor, initializer>> <<ConvexEthMetaStrategy>> +    mintAndAddOTokens(_oTokens: uint256) <<onlyStrategist, nonReentrant, improvePoolBalance>> <<ConvexEthMetaStrategy>> +    removeAndBurnOTokens(_lpTokens: uint256) <<onlyStrategist, nonReentrant, improvePoolBalance>> <<ConvexEthMetaStrategy>> +    removeOnlyAssets(_lpTokens: uint256) <<onlyStrategist, nonReentrant, improvePoolBalance>> <<ConvexEthMetaStrategy>> +Public: +    <<event>> PendingGovernorshipTransfer(previousGovernor: address, newGovernor: address) <<Governable>> +    <<event>> GovernorshipTransferred(previousGovernor: address, newGovernor: address) <<Governable>> +    <<event>> PTokenAdded(_asset: address, _pToken: address) <<InitializableAbstractStrategy>> +    <<event>> PTokenRemoved(_asset: address, _pToken: address) <<InitializableAbstractStrategy>> +    <<event>> Deposit(_asset: address, _pToken: address, _amount: uint256) <<InitializableAbstractStrategy>> +    <<event>> Withdrawal(_asset: address, _pToken: address, _amount: uint256) <<InitializableAbstractStrategy>> +    <<event>> RewardTokenCollected(recipient: address, rewardToken: address, amount: uint256) <<InitializableAbstractStrategy>> +    <<event>> RewardTokenAddressesUpdated(_oldAddresses: address[], _newAddresses: address[]) <<InitializableAbstractStrategy>> +    <<event>> HarvesterAddressesUpdated(_oldHarvesterAddress: address, _newHarvesterAddress: address) <<InitializableAbstractStrategy>> +    <<modifier>> initializer() <<Initializable>> +    <<modifier>> onlyGovernor() <<Governable>> +    <<modifier>> nonReentrant() <<Governable>> +    <<modifier>> onlyVault() <<InitializableAbstractStrategy>> +    <<modifier>> onlyHarvester() <<InitializableAbstractStrategy>> +    <<modifier>> onlyVaultOrGovernor() <<InitializableAbstractStrategy>> +    <<modifier>> onlyVaultOrGovernorOrStrategist() <<InitializableAbstractStrategy>> +    <<modifier>> onlyStrategist() <<ConvexEthMetaStrategy>> +    <<modifier>> improvePoolBalance() <<ConvexEthMetaStrategy>> +    constructor() <<Governable>> +    governor(): address <<Governable>> +    isGovernor(): bool <<Governable>> +    constructor(_config: BaseStrategyConfig) <<InitializableAbstractStrategy>> +    transferToken(_asset: address, _amount: uint256) <<onlyGovernor>> <<InitializableAbstractStrategy>> +    checkBalance(_asset: address): (balance: uint256) <<ConvexEthMetaStrategy>> +    supportsAsset(_asset: address): bool <<ConvexEthMetaStrategy>>    constructor(_baseConfig: BaseStrategyConfig, _convexConfig: ConvexEthMetaConfig) <<ConvexEthMetaStrategy>> diff --git a/contracts/docs/ConvexOUSDMetaStrategyHierarchy.svg b/contracts/docs/ConvexOUSDMetaStrategyHierarchy.svg index 017c43f9f0..71041626d5 100644 --- a/contracts/docs/ConvexOUSDMetaStrategyHierarchy.svg +++ b/contracts/docs/ConvexOUSDMetaStrategyHierarchy.svg @@ -9,79 +9,79 @@ UmlClassDiagram - + -18 +20 Governable ../contracts/governance/Governable.sol - + -142 +152 <<Abstract>> BaseConvexMetaStrategy ../contracts/strategies/BaseConvexMetaStrategy.sol - + -144 +154 <<Abstract>> BaseCurveStrategy ../contracts/strategies/BaseCurveStrategy.sol - + -142->144 +152->154 - + -185 +195 <<Abstract>> InitializableAbstractStrategy ../contracts/utils/InitializableAbstractStrategy.sol - + -144->185 +154->195 - + -149 +159 ConvexOUSDMetaStrategy ../contracts/strategies/ConvexOUSDMetaStrategy.sol - + -149->142 +159->152 - + -184 +194 <<Abstract>> Initializable ../contracts/utils/Initializable.sol - + -185->18 +195->20 - + -185->184 +195->194 diff --git a/contracts/docs/ConvexOUSDMetaStrategySquashed.svg b/contracts/docs/ConvexOUSDMetaStrategySquashed.svg index eee128b1d3..7555cc03c6 100644 --- a/contracts/docs/ConvexOUSDMetaStrategySquashed.svg +++ b/contracts/docs/ConvexOUSDMetaStrategySquashed.svg @@ -9,9 +9,9 @@ UmlClassDiagram - + -149 +159 ConvexOUSDMetaStrategy ../contracts/strategies/ConvexOUSDMetaStrategy.sol @@ -88,33 +88,33 @@    depositAll() <<onlyVault, nonReentrant>> <<BaseCurveStrategy>>    withdraw(_recipient: address, _asset: address, _amount: uint256) <<onlyVault, nonReentrant>> <<BaseCurveStrategy>>    withdrawAll() <<onlyVaultOrGovernor, nonReentrant>> <<BaseCurveStrategy>> -    supportsAsset(_asset: address): bool <<BaseCurveStrategy>> -    initialize(_rewardTokenAddresses: address[], _assets: address[], _pTokens: address[], initConfig: InitConfig) <<onlyGovernor, initializer>> <<BaseConvexMetaStrategy>> -    setMaxWithdrawalSlippage(_maxWithdrawalSlippage: uint256) <<onlyVaultOrGovernorOrStrategist>> <<BaseConvexMetaStrategy>> -Public: -    <<event>> PendingGovernorshipTransfer(previousGovernor: address, newGovernor: address) <<Governable>> -    <<event>> GovernorshipTransferred(previousGovernor: address, newGovernor: address) <<Governable>> -    <<event>> PTokenAdded(_asset: address, _pToken: address) <<InitializableAbstractStrategy>> -    <<event>> PTokenRemoved(_asset: address, _pToken: address) <<InitializableAbstractStrategy>> -    <<event>> Deposit(_asset: address, _pToken: address, _amount: uint256) <<InitializableAbstractStrategy>> -    <<event>> Withdrawal(_asset: address, _pToken: address, _amount: uint256) <<InitializableAbstractStrategy>> -    <<event>> RewardTokenCollected(recipient: address, rewardToken: address, amount: uint256) <<InitializableAbstractStrategy>> -    <<event>> RewardTokenAddressesUpdated(_oldAddresses: address[], _newAddresses: address[]) <<InitializableAbstractStrategy>> -    <<event>> HarvesterAddressesUpdated(_oldHarvesterAddress: address, _newHarvesterAddress: address) <<InitializableAbstractStrategy>> -    <<event>> MaxWithdrawalSlippageUpdated(_prevMaxSlippagePercentage: uint256, _newMaxSlippagePercentage: uint256) <<BaseConvexMetaStrategy>> -    <<modifier>> initializer() <<Initializable>> -    <<modifier>> onlyGovernor() <<Governable>> -    <<modifier>> nonReentrant() <<Governable>> -    <<modifier>> onlyVault() <<InitializableAbstractStrategy>> -    <<modifier>> onlyHarvester() <<InitializableAbstractStrategy>> -    <<modifier>> onlyVaultOrGovernor() <<InitializableAbstractStrategy>> -    <<modifier>> onlyVaultOrGovernorOrStrategist() <<InitializableAbstractStrategy>> -    constructor() <<Governable>> -    governor(): address <<Governable>> -    isGovernor(): bool <<Governable>> -    constructor(_stratConfig: BaseStrategyConfig) <<ConvexOUSDMetaStrategy>> -    transferToken(_asset: address, _amount: uint256) <<onlyGovernor>> <<InitializableAbstractStrategy>> -    checkBalance(_asset: address): (balance: uint256) <<BaseConvexMetaStrategy>> +    initialize(_rewardTokenAddresses: address[], _assets: address[], _pTokens: address[], initConfig: InitConfig) <<onlyGovernor, initializer>> <<BaseConvexMetaStrategy>> +    setMaxWithdrawalSlippage(_maxWithdrawalSlippage: uint256) <<onlyVaultOrGovernorOrStrategist>> <<BaseConvexMetaStrategy>> +Public: +    <<event>> PendingGovernorshipTransfer(previousGovernor: address, newGovernor: address) <<Governable>> +    <<event>> GovernorshipTransferred(previousGovernor: address, newGovernor: address) <<Governable>> +    <<event>> PTokenAdded(_asset: address, _pToken: address) <<InitializableAbstractStrategy>> +    <<event>> PTokenRemoved(_asset: address, _pToken: address) <<InitializableAbstractStrategy>> +    <<event>> Deposit(_asset: address, _pToken: address, _amount: uint256) <<InitializableAbstractStrategy>> +    <<event>> Withdrawal(_asset: address, _pToken: address, _amount: uint256) <<InitializableAbstractStrategy>> +    <<event>> RewardTokenCollected(recipient: address, rewardToken: address, amount: uint256) <<InitializableAbstractStrategy>> +    <<event>> RewardTokenAddressesUpdated(_oldAddresses: address[], _newAddresses: address[]) <<InitializableAbstractStrategy>> +    <<event>> HarvesterAddressesUpdated(_oldHarvesterAddress: address, _newHarvesterAddress: address) <<InitializableAbstractStrategy>> +    <<event>> MaxWithdrawalSlippageUpdated(_prevMaxSlippagePercentage: uint256, _newMaxSlippagePercentage: uint256) <<BaseConvexMetaStrategy>> +    <<modifier>> initializer() <<Initializable>> +    <<modifier>> onlyGovernor() <<Governable>> +    <<modifier>> nonReentrant() <<Governable>> +    <<modifier>> onlyVault() <<InitializableAbstractStrategy>> +    <<modifier>> onlyHarvester() <<InitializableAbstractStrategy>> +    <<modifier>> onlyVaultOrGovernor() <<InitializableAbstractStrategy>> +    <<modifier>> onlyVaultOrGovernorOrStrategist() <<InitializableAbstractStrategy>> +    constructor() <<Governable>> +    governor(): address <<Governable>> +    isGovernor(): bool <<Governable>> +    constructor(_stratConfig: BaseStrategyConfig) <<ConvexOUSDMetaStrategy>> +    transferToken(_asset: address, _amount: uint256) <<onlyGovernor>> <<InitializableAbstractStrategy>> +    checkBalance(_asset: address): (balance: uint256) <<BaseConvexMetaStrategy>> +    supportsAsset(_asset: address): bool <<BaseCurveStrategy>> diff --git a/contracts/docs/DripperHierarchy.svg b/contracts/docs/DripperHierarchy.svg index 320cfc9f34..903646a877 100644 --- a/contracts/docs/DripperHierarchy.svg +++ b/contracts/docs/DripperHierarchy.svg @@ -4,58 +4,136 @@ - - + + UmlClassDiagram - - + + -7 - -Governable -../contracts/governance/Governable.sol +20 + +Governable +../contracts/governance/Governable.sol - + -15 +30 Dripper ../contracts/harvest/Dripper.sol - + -15->7 - - +30->20 + + - + -35 - -<<Interface>> -IVault -../contracts/interfaces/IVault.sol +55 + +<<Interface>> +IVault +../contracts/interfaces/IVault.sol - + -15->35 - - +30->55 + + - - -436 - -<<Interface>> -IERC20 -../node_modules/@openzeppelin/contracts/token/ERC20/IERC20.sol + + +392 + +<<Interface>> +IERC20 +../node_modules/@openzeppelin/contracts/token/ERC20/IERC20.sol - + -15->436 - - +30->392 + + + + + +207 + +VaultStorage +../contracts/vault/VaultStorage.sol + + + +55->207 + + + + + +186 + +OUSD +../contracts/token/OUSD.sol + + + +186->20 + + + + + +194 + +<<Abstract>> +Initializable +../contracts/utils/Initializable.sol + + + +186->194 + + + + + +197 + +<<Abstract>> +InitializableERC20Detailed +../contracts/utils/InitializableERC20Detailed.sol + + + +186->197 + + + + + +197->392 + + + + + +207->20 + + + + + +207->186 + + + + + +207->194 + + diff --git a/contracts/docs/DripperSquashed.svg b/contracts/docs/DripperSquashed.svg index 69d49ad7a2..7dfd1bac36 100644 --- a/contracts/docs/DripperSquashed.svg +++ b/contracts/docs/DripperSquashed.svg @@ -9,9 +9,9 @@ UmlClassDiagram - + -15 +30 Dripper ../contracts/harvest/Dripper.sol diff --git a/contracts/docs/FlipperHierarchy.svg b/contracts/docs/FlipperHierarchy.svg index 67fd836dba..913ee75052 100644 --- a/contracts/docs/FlipperHierarchy.svg +++ b/contracts/docs/FlipperHierarchy.svg @@ -9,104 +9,104 @@ UmlClassDiagram - + -6 - -Flipper -../contracts/flipper/Flipper.sol +19 + +Flipper +../contracts/flipper/Flipper.sol - + -7 +20 Governable ../contracts/governance/Governable.sol - + -6->7 - - +19->20 + + - + -37 +58 <<Interface>> Tether ../contracts/interfaces/Tether.sol - + -6->37 - - +19->58 + + - + -142 +186 OUSD ../contracts/token/OUSD.sol - + -6->142 - - +19->186 + + - + -436 +392 <<Interface>> IERC20 ../node_modules/@openzeppelin/contracts/token/ERC20/IERC20.sol - + -6->436 - +19->392 + - + -142->7 +186->20 - + -149 +194 <<Abstract>> Initializable ../contracts/utils/Initializable.sol - + -142->149 +186->194 - + -151 +197 <<Abstract>> InitializableERC20Detailed ../contracts/utils/InitializableERC20Detailed.sol - + -142->151 +186->197 - + -151->436 +197->392 diff --git a/contracts/docs/FlipperSquashed.svg b/contracts/docs/FlipperSquashed.svg index 365ad7bb2f..162c4da57a 100644 --- a/contracts/docs/FlipperSquashed.svg +++ b/contracts/docs/FlipperSquashed.svg @@ -9,9 +9,9 @@ UmlClassDiagram - + -6 +19 Flipper ../contracts/flipper/Flipper.sol diff --git a/contracts/docs/FluxStrategyHierarchy.svg b/contracts/docs/FluxStrategyHierarchy.svg index a10a6fce5e..fec3306738 100644 --- a/contracts/docs/FluxStrategyHierarchy.svg +++ b/contracts/docs/FluxStrategyHierarchy.svg @@ -9,78 +9,78 @@ UmlClassDiagram - + -18 +20 Governable ../contracts/governance/Governable.sol - + -141 +151 <<Abstract>> BaseCompoundStrategy ../contracts/strategies/BaseCompoundStrategy.sol - + -185 +195 <<Abstract>> InitializableAbstractStrategy ../contracts/utils/InitializableAbstractStrategy.sol - + -141->185 +151->195 - + -145 +155 CompoundStrategy ../contracts/strategies/CompoundStrategy.sol - + -145->141 +155->151 - + -151 +161 FluxStrategy ../contracts/strategies/FluxStrategy.sol - + -151->145 +161->155 - + -184 +194 <<Abstract>> Initializable ../contracts/utils/Initializable.sol - + -185->18 +195->20 - + -185->184 +195->194 diff --git a/contracts/docs/FluxStrategySquashed.svg b/contracts/docs/FluxStrategySquashed.svg index b247ac9b54..39b26dd5dd 100644 --- a/contracts/docs/FluxStrategySquashed.svg +++ b/contracts/docs/FluxStrategySquashed.svg @@ -9,9 +9,9 @@ UmlClassDiagram - + -151 +161 FluxStrategy ../contracts/strategies/FluxStrategy.sol @@ -69,31 +69,31 @@    withdraw(_recipient: address, _asset: address, _amount: uint256) <<onlyVault, nonReentrant>> <<CompoundStrategy>>    withdrawAll() <<onlyVaultOrGovernor, nonReentrant>> <<CompoundStrategy>>    checkBalance(_asset: address): (balance: uint256) <<CompoundStrategy>> -    supportsAsset(_asset: address): bool <<BaseCompoundStrategy>> -    initialize(_rewardTokenAddresses: address[], _assets: address[], _pTokens: address[]) <<onlyGovernor, initializer>> <<CompoundStrategy>> -Public: -    <<event>> PendingGovernorshipTransfer(previousGovernor: address, newGovernor: address) <<Governable>> -    <<event>> GovernorshipTransferred(previousGovernor: address, newGovernor: address) <<Governable>> -    <<event>> PTokenAdded(_asset: address, _pToken: address) <<InitializableAbstractStrategy>> -    <<event>> PTokenRemoved(_asset: address, _pToken: address) <<InitializableAbstractStrategy>> -    <<event>> Deposit(_asset: address, _pToken: address, _amount: uint256) <<InitializableAbstractStrategy>> -    <<event>> Withdrawal(_asset: address, _pToken: address, _amount: uint256) <<InitializableAbstractStrategy>> -    <<event>> RewardTokenCollected(recipient: address, rewardToken: address, amount: uint256) <<InitializableAbstractStrategy>> -    <<event>> RewardTokenAddressesUpdated(_oldAddresses: address[], _newAddresses: address[]) <<InitializableAbstractStrategy>> -    <<event>> HarvesterAddressesUpdated(_oldHarvesterAddress: address, _newHarvesterAddress: address) <<InitializableAbstractStrategy>> -    <<event>> SkippedWithdrawal(asset: address, amount: uint256) <<CompoundStrategy>> -    <<modifier>> initializer() <<Initializable>> -    <<modifier>> onlyGovernor() <<Governable>> -    <<modifier>> nonReentrant() <<Governable>> -    <<modifier>> onlyVault() <<InitializableAbstractStrategy>> -    <<modifier>> onlyHarvester() <<InitializableAbstractStrategy>> -    <<modifier>> onlyVaultOrGovernor() <<InitializableAbstractStrategy>> -    <<modifier>> onlyVaultOrGovernorOrStrategist() <<InitializableAbstractStrategy>> -    constructor() <<Governable>> -    governor(): address <<Governable>> -    isGovernor(): bool <<Governable>> -    constructor(_stratConfig: BaseStrategyConfig) <<FluxStrategy>> -    transferToken(_asset: address, _amount: uint256) <<onlyGovernor>> <<InitializableAbstractStrategy>> +    initialize(_rewardTokenAddresses: address[], _assets: address[], _pTokens: address[]) <<onlyGovernor, initializer>> <<CompoundStrategy>> +Public: +    <<event>> PendingGovernorshipTransfer(previousGovernor: address, newGovernor: address) <<Governable>> +    <<event>> GovernorshipTransferred(previousGovernor: address, newGovernor: address) <<Governable>> +    <<event>> PTokenAdded(_asset: address, _pToken: address) <<InitializableAbstractStrategy>> +    <<event>> PTokenRemoved(_asset: address, _pToken: address) <<InitializableAbstractStrategy>> +    <<event>> Deposit(_asset: address, _pToken: address, _amount: uint256) <<InitializableAbstractStrategy>> +    <<event>> Withdrawal(_asset: address, _pToken: address, _amount: uint256) <<InitializableAbstractStrategy>> +    <<event>> RewardTokenCollected(recipient: address, rewardToken: address, amount: uint256) <<InitializableAbstractStrategy>> +    <<event>> RewardTokenAddressesUpdated(_oldAddresses: address[], _newAddresses: address[]) <<InitializableAbstractStrategy>> +    <<event>> HarvesterAddressesUpdated(_oldHarvesterAddress: address, _newHarvesterAddress: address) <<InitializableAbstractStrategy>> +    <<event>> SkippedWithdrawal(asset: address, amount: uint256) <<CompoundStrategy>> +    <<modifier>> initializer() <<Initializable>> +    <<modifier>> onlyGovernor() <<Governable>> +    <<modifier>> nonReentrant() <<Governable>> +    <<modifier>> onlyVault() <<InitializableAbstractStrategy>> +    <<modifier>> onlyHarvester() <<InitializableAbstractStrategy>> +    <<modifier>> onlyVaultOrGovernor() <<InitializableAbstractStrategy>> +    <<modifier>> onlyVaultOrGovernorOrStrategist() <<InitializableAbstractStrategy>> +    constructor() <<Governable>> +    governor(): address <<Governable>> +    isGovernor(): bool <<Governable>> +    constructor(_stratConfig: BaseStrategyConfig) <<FluxStrategy>> +    transferToken(_asset: address, _amount: uint256) <<onlyGovernor>> <<InitializableAbstractStrategy>> +    supportsAsset(_asset: address): bool <<BaseCompoundStrategy>> diff --git a/contracts/docs/FraxETHStrategyHierarchy.svg b/contracts/docs/FraxETHStrategyHierarchy.svg index ec5db548f9..6ed96c287b 100644 --- a/contracts/docs/FraxETHStrategyHierarchy.svg +++ b/contracts/docs/FraxETHStrategyHierarchy.svg @@ -9,64 +9,64 @@ UmlClassDiagram - + -18 +20 Governable ../contracts/governance/Governable.sol - + -152 +162 FraxETHStrategy ../contracts/strategies/FraxETHStrategy.sol - + -153 +163 Generalized4626Strategy ../contracts/strategies/Generalized4626Strategy.sol - + -152->153 +162->163 - + -185 +195 <<Abstract>> InitializableAbstractStrategy ../contracts/utils/InitializableAbstractStrategy.sol - + -153->185 +163->195 - + -184 +194 <<Abstract>> Initializable ../contracts/utils/Initializable.sol - + -185->18 +195->20 - + -185->184 +195->194 diff --git a/contracts/docs/FraxETHStrategySquashed.svg b/contracts/docs/FraxETHStrategySquashed.svg index 754d96bd37..a7415eca6c 100644 --- a/contracts/docs/FraxETHStrategySquashed.svg +++ b/contracts/docs/FraxETHStrategySquashed.svg @@ -9,9 +9,9 @@ UmlClassDiagram - + -152 +162 FraxETHStrategy ../contracts/strategies/FraxETHStrategy.sol @@ -74,30 +74,30 @@    withdraw(_recipient: address, _asset: address, _amount: uint256) <<onlyVault, nonReentrant>> <<Generalized4626Strategy>>    withdrawAll() <<onlyVaultOrGovernor, nonReentrant>> <<Generalized4626Strategy>>    checkBalance(_asset: address): (balance: uint256) <<FraxETHStrategy>> -    supportsAsset(_asset: address): bool <<FraxETHStrategy>> -    initialize() <<onlyGovernor, initializer>> <<Generalized4626Strategy>> -Public: -    <<event>> PendingGovernorshipTransfer(previousGovernor: address, newGovernor: address) <<Governable>> -    <<event>> GovernorshipTransferred(previousGovernor: address, newGovernor: address) <<Governable>> -    <<event>> PTokenAdded(_asset: address, _pToken: address) <<InitializableAbstractStrategy>> -    <<event>> PTokenRemoved(_asset: address, _pToken: address) <<InitializableAbstractStrategy>> -    <<event>> Deposit(_asset: address, _pToken: address, _amount: uint256) <<InitializableAbstractStrategy>> -    <<event>> Withdrawal(_asset: address, _pToken: address, _amount: uint256) <<InitializableAbstractStrategy>> -    <<event>> RewardTokenCollected(recipient: address, rewardToken: address, amount: uint256) <<InitializableAbstractStrategy>> -    <<event>> RewardTokenAddressesUpdated(_oldAddresses: address[], _newAddresses: address[]) <<InitializableAbstractStrategy>> -    <<event>> HarvesterAddressesUpdated(_oldHarvesterAddress: address, _newHarvesterAddress: address) <<InitializableAbstractStrategy>> -    <<modifier>> initializer() <<Initializable>> -    <<modifier>> onlyGovernor() <<Governable>> -    <<modifier>> nonReentrant() <<Governable>> -    <<modifier>> onlyVault() <<InitializableAbstractStrategy>> -    <<modifier>> onlyHarvester() <<InitializableAbstractStrategy>> -    <<modifier>> onlyVaultOrGovernor() <<InitializableAbstractStrategy>> -    <<modifier>> onlyVaultOrGovernorOrStrategist() <<InitializableAbstractStrategy>> -    constructor() <<Governable>> -    governor(): address <<Governable>> -    isGovernor(): bool <<Governable>> -    constructor(_config: BaseStrategyConfig) <<InitializableAbstractStrategy>> -    transferToken(_asset: address, _amount: uint256) <<onlyGovernor>> <<InitializableAbstractStrategy>> +    initialize() <<onlyGovernor, initializer>> <<FraxETHStrategy>> +Public: +    <<event>> PendingGovernorshipTransfer(previousGovernor: address, newGovernor: address) <<Governable>> +    <<event>> GovernorshipTransferred(previousGovernor: address, newGovernor: address) <<Governable>> +    <<event>> PTokenAdded(_asset: address, _pToken: address) <<InitializableAbstractStrategy>> +    <<event>> PTokenRemoved(_asset: address, _pToken: address) <<InitializableAbstractStrategy>> +    <<event>> Deposit(_asset: address, _pToken: address, _amount: uint256) <<InitializableAbstractStrategy>> +    <<event>> Withdrawal(_asset: address, _pToken: address, _amount: uint256) <<InitializableAbstractStrategy>> +    <<event>> RewardTokenCollected(recipient: address, rewardToken: address, amount: uint256) <<InitializableAbstractStrategy>> +    <<event>> RewardTokenAddressesUpdated(_oldAddresses: address[], _newAddresses: address[]) <<InitializableAbstractStrategy>> +    <<event>> HarvesterAddressesUpdated(_oldHarvesterAddress: address, _newHarvesterAddress: address) <<InitializableAbstractStrategy>> +    <<modifier>> initializer() <<Initializable>> +    <<modifier>> onlyGovernor() <<Governable>> +    <<modifier>> nonReentrant() <<Governable>> +    <<modifier>> onlyVault() <<InitializableAbstractStrategy>> +    <<modifier>> onlyHarvester() <<InitializableAbstractStrategy>> +    <<modifier>> onlyVaultOrGovernor() <<InitializableAbstractStrategy>> +    <<modifier>> onlyVaultOrGovernorOrStrategist() <<InitializableAbstractStrategy>> +    constructor() <<Governable>> +    governor(): address <<Governable>> +    isGovernor(): bool <<Governable>> +    constructor(_config: BaseStrategyConfig) <<InitializableAbstractStrategy>> +    transferToken(_asset: address, _amount: uint256) <<onlyGovernor>> <<InitializableAbstractStrategy>> +    supportsAsset(_asset: address): bool <<FraxETHStrategy>>    constructor(_baseConfig: BaseStrategyConfig, _assetToken: address) <<FraxETHStrategy>> diff --git a/contracts/docs/Generalized4626StrategyHierarchy.svg b/contracts/docs/Generalized4626StrategyHierarchy.svg index 601b7f1b2d..e864cfa167 100644 --- a/contracts/docs/Generalized4626StrategyHierarchy.svg +++ b/contracts/docs/Generalized4626StrategyHierarchy.svg @@ -9,51 +9,51 @@ UmlClassDiagram - + -18 +20 Governable ../contracts/governance/Governable.sol - + -153 +163 Generalized4626Strategy ../contracts/strategies/Generalized4626Strategy.sol - + -185 +195 <<Abstract>> InitializableAbstractStrategy ../contracts/utils/InitializableAbstractStrategy.sol - + -153->185 +163->195 - + -184 +194 <<Abstract>> Initializable ../contracts/utils/Initializable.sol - + -185->18 +195->20 - + -185->184 +195->194 diff --git a/contracts/docs/Generalized4626StrategySquashed.svg b/contracts/docs/Generalized4626StrategySquashed.svg index 1ea8d87b4f..7136fd2340 100644 --- a/contracts/docs/Generalized4626StrategySquashed.svg +++ b/contracts/docs/Generalized4626StrategySquashed.svg @@ -9,9 +9,9 @@ UmlClassDiagram - + -153 +163 Generalized4626Strategy ../contracts/strategies/Generalized4626Strategy.sol @@ -71,30 +71,30 @@    withdraw(_recipient: address, _asset: address, _amount: uint256) <<onlyVault, nonReentrant>> <<Generalized4626Strategy>>    withdrawAll() <<onlyVaultOrGovernor, nonReentrant>> <<Generalized4626Strategy>>    checkBalance(_asset: address): (balance: uint256) <<Generalized4626Strategy>> -    supportsAsset(_asset: address): bool <<Generalized4626Strategy>> -    initialize() <<onlyGovernor, initializer>> <<Generalized4626Strategy>> -Public: -    <<event>> PendingGovernorshipTransfer(previousGovernor: address, newGovernor: address) <<Governable>> -    <<event>> GovernorshipTransferred(previousGovernor: address, newGovernor: address) <<Governable>> -    <<event>> PTokenAdded(_asset: address, _pToken: address) <<InitializableAbstractStrategy>> -    <<event>> PTokenRemoved(_asset: address, _pToken: address) <<InitializableAbstractStrategy>> -    <<event>> Deposit(_asset: address, _pToken: address, _amount: uint256) <<InitializableAbstractStrategy>> -    <<event>> Withdrawal(_asset: address, _pToken: address, _amount: uint256) <<InitializableAbstractStrategy>> -    <<event>> RewardTokenCollected(recipient: address, rewardToken: address, amount: uint256) <<InitializableAbstractStrategy>> -    <<event>> RewardTokenAddressesUpdated(_oldAddresses: address[], _newAddresses: address[]) <<InitializableAbstractStrategy>> -    <<event>> HarvesterAddressesUpdated(_oldHarvesterAddress: address, _newHarvesterAddress: address) <<InitializableAbstractStrategy>> -    <<modifier>> initializer() <<Initializable>> -    <<modifier>> onlyGovernor() <<Governable>> -    <<modifier>> nonReentrant() <<Governable>> -    <<modifier>> onlyVault() <<InitializableAbstractStrategy>> -    <<modifier>> onlyHarvester() <<InitializableAbstractStrategy>> -    <<modifier>> onlyVaultOrGovernor() <<InitializableAbstractStrategy>> -    <<modifier>> onlyVaultOrGovernorOrStrategist() <<InitializableAbstractStrategy>> -    constructor() <<Governable>> -    governor(): address <<Governable>> -    isGovernor(): bool <<Governable>> -    constructor(_config: BaseStrategyConfig) <<InitializableAbstractStrategy>> -    transferToken(_asset: address, _amount: uint256) <<onlyGovernor>> <<InitializableAbstractStrategy>> +    initialize() <<onlyGovernor, initializer>> <<Generalized4626Strategy>> +Public: +    <<event>> PendingGovernorshipTransfer(previousGovernor: address, newGovernor: address) <<Governable>> +    <<event>> GovernorshipTransferred(previousGovernor: address, newGovernor: address) <<Governable>> +    <<event>> PTokenAdded(_asset: address, _pToken: address) <<InitializableAbstractStrategy>> +    <<event>> PTokenRemoved(_asset: address, _pToken: address) <<InitializableAbstractStrategy>> +    <<event>> Deposit(_asset: address, _pToken: address, _amount: uint256) <<InitializableAbstractStrategy>> +    <<event>> Withdrawal(_asset: address, _pToken: address, _amount: uint256) <<InitializableAbstractStrategy>> +    <<event>> RewardTokenCollected(recipient: address, rewardToken: address, amount: uint256) <<InitializableAbstractStrategy>> +    <<event>> RewardTokenAddressesUpdated(_oldAddresses: address[], _newAddresses: address[]) <<InitializableAbstractStrategy>> +    <<event>> HarvesterAddressesUpdated(_oldHarvesterAddress: address, _newHarvesterAddress: address) <<InitializableAbstractStrategy>> +    <<modifier>> initializer() <<Initializable>> +    <<modifier>> onlyGovernor() <<Governable>> +    <<modifier>> nonReentrant() <<Governable>> +    <<modifier>> onlyVault() <<InitializableAbstractStrategy>> +    <<modifier>> onlyHarvester() <<InitializableAbstractStrategy>> +    <<modifier>> onlyVaultOrGovernor() <<InitializableAbstractStrategy>> +    <<modifier>> onlyVaultOrGovernorOrStrategist() <<InitializableAbstractStrategy>> +    constructor() <<Governable>> +    governor(): address <<Governable>> +    isGovernor(): bool <<Governable>> +    constructor(_config: BaseStrategyConfig) <<InitializableAbstractStrategy>> +    transferToken(_asset: address, _amount: uint256) <<onlyGovernor>> <<InitializableAbstractStrategy>> +    supportsAsset(_asset: address): bool <<Generalized4626Strategy>>    constructor(_baseConfig: BaseStrategyConfig, _assetToken: address) <<Generalized4626Strategy>> diff --git a/contracts/docs/GovernorHierarchy.svg b/contracts/docs/GovernorHierarchy.svg index c4825e7617..e2537175b1 100644 --- a/contracts/docs/GovernorHierarchy.svg +++ b/contracts/docs/GovernorHierarchy.svg @@ -9,37 +9,37 @@ UmlClassDiagram - + -8 +21 Governor ../contracts/governance/Governor.sol - + -140 +184 Timelock ../contracts/timelock/Timelock.sol - + -8->140 +21->184 - + -139 +183 <<Interface>> CapitalPausable ../contracts/timelock/Timelock.sol - + -140->139 +184->183 diff --git a/contracts/docs/GovernorSquashed.svg b/contracts/docs/GovernorSquashed.svg index ac657f2984..610f587f2a 100644 --- a/contracts/docs/GovernorSquashed.svg +++ b/contracts/docs/GovernorSquashed.svg @@ -9,9 +9,9 @@ UmlClassDiagram - + -8 +21 Governor ../contracts/governance/Governor.sol diff --git a/contracts/docs/HarvesterHierarchy.svg b/contracts/docs/HarvesterHierarchy.svg index 935a4485f9..f455ae8685 100644 --- a/contracts/docs/HarvesterHierarchy.svg +++ b/contracts/docs/HarvesterHierarchy.svg @@ -4,132 +4,234 @@ - - + + UmlClassDiagram - - + + -7 - -Governable -../contracts/governance/Governable.sol +20 + +Governable +../contracts/governance/Governable.sol - + -13 - -<<Abstract>> -BaseHarvester -../contracts/harvest/BaseHarvester.sol +26 + +<<Abstract>> +BaseHarvester +../contracts/harvest/BaseHarvester.sol - + -13->7 - - +26->20 + + - + -30 - -<<Interface>> -IOracle -../contracts/interfaces/IOracle.sol +48 + +<<Interface>> +IOracle +../contracts/interfaces/IOracle.sol - - -13->30 - - + + +26->48 + + - + -33 - -<<Interface>> -IStrategy -../contracts/interfaces/IStrategy.sol +52 + +<<Interface>> +IStrategy +../contracts/interfaces/IStrategy.sol - - -13->33 - - + + +26->52 + + - + -35 - -<<Interface>> -IVault -../contracts/interfaces/IVault.sol +55 + +<<Interface>> +IVault +../contracts/interfaces/IVault.sol - + -13->35 - - - - - -436 - -<<Interface>> -IERC20 -../node_modules/@openzeppelin/contracts/token/ERC20/IERC20.sol +26->55 + + - - -13->436 - - + + +214 + +<<Interface>> +IBalancerVault +../contracts/interfaces/balancer/IBalancerVault.sol - - -17 - -Harvester -../contracts/harvest/Harvester.sol + + +26->214 + + - - -17->13 - - + + +245 + +<<Interface>> +IUniswapV2Router +../contracts/interfaces/uniswap/IUniswapV2Router02.sol - + + +26->245 + + + + + +246 + +<<Interface>> +IUniswapV3Router +../contracts/interfaces/uniswap/IUniswapV3Router.sol + + -17->30 - - - - - -17->35 - - +26->246 + + + + + +174 + +<<Interface>> +ICurvePool +../contracts/strategies/ICurvePool.sol + + + +26->174 + + + + + +392 + +<<Interface>> +IERC20 +../node_modules/@openzeppelin/contracts/token/ERC20/IERC20.sol + + + +26->392 + + - - -182 - -<<Interface>> -IUniswapV2Router -../contracts/interfaces/uniswap/IUniswapV2Router02.sol + + +32 + +Harvester +../contracts/harvest/Harvester.sol - + -17->182 - - - - - -17->436 - - +32->26 + + + + + +207 + +VaultStorage +../contracts/vault/VaultStorage.sol + + + +55->207 + + + + + +186 + +OUSD +../contracts/token/OUSD.sol + + + +186->20 + + + + + +194 + +<<Abstract>> +Initializable +../contracts/utils/Initializable.sol + + + +186->194 + + + + + +197 + +<<Abstract>> +InitializableERC20Detailed +../contracts/utils/InitializableERC20Detailed.sol + + + +186->197 + + + + + +197->392 + + + + + +207->20 + + + + + +207->186 + + + + + +207->194 + + diff --git a/contracts/docs/HarvesterSquashed.svg b/contracts/docs/HarvesterSquashed.svg index aabcef99e6..80a9d1631d 100644 --- a/contracts/docs/HarvesterSquashed.svg +++ b/contracts/docs/HarvesterSquashed.svg @@ -4,69 +4,76 @@ - - + + UmlClassDiagram - - + + -17 - -Harvester -../contracts/harvest/Harvester.sol - -Private: -   governorPosition: bytes32 <<Governable>> -   pendingGovernorPosition: bytes32 <<Governable>> -   reentryStatusPosition: bytes32 <<Governable>> -Public: -   _NOT_ENTERED: uint256 <<Governable>> -   _ENTERED: uint256 <<Governable>> -   rewardTokenConfigs: mapping(address=>RewardTokenConfig) <<BaseHarvester>> -   supportedStrategies: mapping(address=>bool) <<BaseHarvester>> -   vaultAddress: address <<BaseHarvester>> -   rewardProceedsAddress: address <<BaseHarvester>> -   usdtAddress: address <<Harvester>> - -Internal: -    _governor(): (governorOut: address) <<Governable>> -    _pendingGovernor(): (pendingGovernor: address) <<Governable>> -    _setGovernor(newGovernor: address) <<Governable>> -    _setPendingGovernor(newGovernor: address) <<Governable>> -    _changeGovernor(_newGovernor: address) <<Governable>> -    _harvest() <<BaseHarvester>> -    _harvestAndSwap(_strategyAddr: address, _rewardTo: address) <<BaseHarvester>> -    _harvest(_strategyAddr: address) <<BaseHarvester>> -    _swap(_rewardTo: address) <<BaseHarvester>> -    _swap(_swapToken: address, _rewardTo: address) <<Harvester>> -External: -    transferGovernance(_newGovernor: address) <<onlyGovernor>> <<Governable>> -    claimGovernance() <<Governable>> -    setRewardsProceedsAddress(_rewardProceedsAddress: address) <<onlyGovernor>> <<BaseHarvester>> -    setRewardTokenConfig(_tokenAddress: address, _allowedSlippageBps: uint16, _harvestRewardBps: uint16, _uniswapV2CompatibleAddr: address, _liquidationLimit: uint256, _doSwapRewardToken: bool) <<onlyGovernor>> <<BaseHarvester>> -    setSupportedStrategy(_strategyAddress: address, _isSupported: bool) <<onlyVaultOrGovernor>> <<BaseHarvester>> -    transferToken(_asset: address, _amount: uint256) <<onlyGovernor>> <<BaseHarvester>> -    harvest() <<onlyGovernor, nonReentrant>> <<BaseHarvester>> -    swap() <<onlyGovernor, nonReentrant>> <<BaseHarvester>> -    harvestAndSwap() <<onlyGovernor, nonReentrant>> <<BaseHarvester>> -    harvest(_strategyAddr: address) <<onlyGovernor, nonReentrant>> <<BaseHarvester>> -    harvestAndSwap(_strategyAddr: address) <<nonReentrant>> <<BaseHarvester>> -    harvestAndSwap(_strategyAddr: address, _rewardTo: address) <<nonReentrant>> <<BaseHarvester>> -    swapRewardToken(_swapToken: address) <<onlyGovernor, nonReentrant>> <<BaseHarvester>> +32 + +Harvester +../contracts/harvest/Harvester.sol + +Private: +   governorPosition: bytes32 <<Governable>> +   pendingGovernorPosition: bytes32 <<Governable>> +   reentryStatusPosition: bytes32 <<Governable>> +Public: +   _NOT_ENTERED: uint256 <<Governable>> +   _ENTERED: uint256 <<Governable>> +   rewardTokenConfigs: mapping(address=>RewardTokenConfig) <<BaseHarvester>> +   supportedStrategies: mapping(address=>bool) <<BaseHarvester>> +   vaultAddress: address <<BaseHarvester>> +   rewardProceedsAddress: address <<BaseHarvester>> +   baseTokenAddress: address <<BaseHarvester>> +   baseTokenDecimals: uint256 <<BaseHarvester>> +   uniswapV2Path: mapping(address=>address[]) <<BaseHarvester>> +   uniswapV3Path: mapping(address=>bytes) <<BaseHarvester>> +   balancerPoolId: mapping(address=>bytes32) <<BaseHarvester>> +   curvePoolIndices: mapping(address=>CurvePoolIndices) <<BaseHarvester>> + +Internal: +    _governor(): (governorOut: address) <<Governable>> +    _pendingGovernor(): (pendingGovernor: address) <<Governable>> +    _setGovernor(newGovernor: address) <<Governable>> +    _setPendingGovernor(newGovernor: address) <<Governable>> +    _changeGovernor(_newGovernor: address) <<Governable>> +    _decodeUniswapV2Path(data: bytes, token: address): (path: address[]) <<BaseHarvester>> +    _decodeUniswapV3Path(data: bytes, token: address): (path: bytes) <<BaseHarvester>> +    _decodeBalancerPoolId(data: bytes, balancerVault: address, token: address): (poolId: bytes32) <<BaseHarvester>> +    _decodeCurvePoolIndices(data: bytes, poolAddress: address, token: address): (indices: CurvePoolIndices) <<BaseHarvester>> +    _harvestAndSwap(_strategyAddr: address, _rewardTo: address) <<BaseHarvester>> +    _harvest(_strategyAddr: address) <<BaseHarvester>> +    _swap(_swapToken: address, _rewardTo: address, _priceProvider: IOracle) <<BaseHarvester>> +    _doSwap(swapPlatform: SwapPlatform, routerAddress: address, rewardTokenAddress: address, amountIn: uint256, minAmountOut: uint256): (amountOut: uint256) <<BaseHarvester>> +    _swapWithUniswapV2(routerAddress: address, swapToken: address, amountIn: uint256, minAmountOut: uint256): (amountOut: uint256) <<BaseHarvester>> +    _swapWithUniswapV3(routerAddress: address, swapToken: address, amountIn: uint256, minAmountOut: uint256): (amountOut: uint256) <<BaseHarvester>> +    _swapWithBalancer(balancerVaultAddress: address, swapToken: address, amountIn: uint256, minAmountOut: uint256): (amountOut: uint256) <<BaseHarvester>> +    _swapWithCurve(poolAddress: address, swapToken: address, amountIn: uint256, minAmountOut: uint256): (amountOut: uint256) <<BaseHarvester>> +External: +    transferGovernance(_newGovernor: address) <<onlyGovernor>> <<Governable>> +    claimGovernance() <<Governable>> +    setRewardProceedsAddress(_rewardProceedsAddress: address) <<onlyGovernor>> <<BaseHarvester>> +    setRewardTokenConfig(_tokenAddress: address, tokenConfig: RewardTokenConfig, swapData: bytes) <<onlyGovernor>> <<BaseHarvester>> +    setSupportedStrategy(_strategyAddress: address, _isSupported: bool) <<onlyGovernor>> <<BaseHarvester>> +    transferToken(_asset: address, _amount: uint256) <<onlyGovernor>> <<BaseHarvester>> +    harvestAndSwap(_strategyAddr: address) <<nonReentrant>> <<BaseHarvester>> +    harvestAndSwap(_strategyAddr: address, _rewardTo: address) <<nonReentrant>> <<BaseHarvester>> Public:    <<event>> PendingGovernorshipTransfer(previousGovernor: address, newGovernor: address) <<Governable>>    <<event>> GovernorshipTransferred(previousGovernor: address, newGovernor: address) <<Governable>> -    <<event>> UniswapUpdated(_address: address) <<BaseHarvester>> -    <<event>> SupportedStrategyUpdate(_address: address, _isSupported: bool) <<BaseHarvester>> -    <<event>> RewardTokenConfigUpdated(_tokenAddress: address, _allowedSlippageBps: uint16, _harvestRewardBps: uint16, _uniswapV2CompatibleAddr: address, _liquidationLimit: uint256, _doSwapRewardToken: bool) <<BaseHarvester>> -    <<modifier>> onlyGovernor() <<Governable>> -    <<modifier>> nonReentrant() <<Governable>> -    <<modifier>> onlyVaultOrGovernor() <<BaseHarvester>> -    constructor() <<Governable>> -    governor(): address <<Governable>> -    isGovernor(): bool <<Governable>> -    constructor(_vaultAddress: address) <<BaseHarvester>> +    <<event>> SupportedStrategyUpdate(strategyAddress: address, isSupported: bool) <<BaseHarvester>> +    <<event>> RewardTokenConfigUpdated(tokenAddress: address, allowedSlippageBps: uint16, harvestRewardBps: uint16, swapPlatform: SwapPlatform, swapPlatformAddr: address, swapData: bytes, liquidationLimit: uint256, doSwapRewardToken: bool) <<BaseHarvester>> +    <<event>> RewardTokenSwapped(rewardToken: address, swappedInto: address, swapPlatform: SwapPlatform, amountIn: uint256, amountOut: uint256) <<BaseHarvester>> +    <<event>> RewardProceedsTransferred(token: address, farmer: address, protcolYield: uint256, farmerFee: uint256) <<BaseHarvester>> +    <<event>> RewardProceedsAddressChanged(newProceedsAddress: address) <<BaseHarvester>> +    <<modifier>> onlyGovernor() <<Governable>> +    <<modifier>> nonReentrant() <<Governable>> +    constructor() <<Governable>> +    governor(): address <<Governable>> +    isGovernor(): bool <<Governable>>    constructor(_vault: address, _usdtAddress: address) <<Harvester>> diff --git a/contracts/docs/HarvesterStorage.svg b/contracts/docs/HarvesterStorage.svg index 650eebfe47..d4441f30f0 100644 --- a/contracts/docs/HarvesterStorage.svg +++ b/contracts/docs/HarvesterStorage.svg @@ -4,66 +4,106 @@ - - + + StorageDiagram - - + + -2 - -Harvester <<Contract>> - -slot - -0 - -1 - -2 - -type: <inherited contract>.variable (bytes) - -mapping(address=>RewardTokenConfig): BaseHarvester.rewardTokenConfigs (32) - -mapping(address=>bool): BaseHarvester.supportedStrategies (32) - -unallocated (12) - -address: BaseHarvester.rewardProceedsAddress (20) +3 + +Harvester <<Contract>> + +slot + +0 + +1 + +2 + +3 + +4 + +5 + +6 + +type: <inherited contract>.variable (bytes) + +mapping(address=>RewardTokenConfig): BaseHarvester.rewardTokenConfigs (32) + +mapping(address=>bool): BaseHarvester.supportedStrategies (32) + +unallocated (12) + +address: BaseHarvester.rewardProceedsAddress (20) + +mapping(address=>address[]): BaseHarvester.uniswapV2Path (32) + +mapping(address=>bytes): BaseHarvester.uniswapV3Path (32) + +mapping(address=>bytes32): BaseHarvester.balancerPoolId (32) + +mapping(address=>CurvePoolIndices): BaseHarvester.curvePoolIndices (32) 1 - -RewardTokenConfig <<Struct>> - -offset - -0 - -1 - -type: variable (bytes) - -unallocated (7) - -bool: doSwapRewardToken (1) - -address: uniswapV2CompatibleAddr (20) - -uint16: harvestRewardBps (2) - -uint16: allowedSlippageBps (2) - -uint256: liquidationLimit (32) + +RewardTokenConfig <<Struct>> + +offset + +0 + +1 + +type: variable (bytes) + +unallocated (6) + +SwapPlatform: swapPlatform (1) + +bool: doSwapRewardToken (1) + +address: swapPlatformAddr (20) + +uint16: harvestRewardBps (2) + +uint16: allowedSlippageBps (2) + +uint256: liquidationLimit (32) - + -2:6->1 - - +3:7->1 + + + + + +2 + +CurvePoolIndices <<Struct>> + +offset + +0 + +type: variable (bytes) + +uint128: baseTokenIndex (16) + +uint128: rewardTokenIndex (16) + + + +3:15->2 + + diff --git a/contracts/docs/InitializableAbstractStrategyHierarchy.svg b/contracts/docs/InitializableAbstractStrategyHierarchy.svg index c8b80d78f3..6b14aa969d 100644 --- a/contracts/docs/InitializableAbstractStrategyHierarchy.svg +++ b/contracts/docs/InitializableAbstractStrategyHierarchy.svg @@ -9,144 +9,138 @@ UmlClassDiagram - + -7 +20 Governable ../contracts/governance/Governable.sol - + -40 - -<<Interface>> -IVault -../contracts/interfaces/IVault.sol +55 + +<<Interface>> +IVault +../contracts/interfaces/IVault.sol - + -178 - -VaultStorage -../contracts/vault/VaultStorage.sol +207 + +VaultStorage +../contracts/vault/VaultStorage.sol - + -40->178 - - +55->207 + + - + -158 +186 OUSD ../contracts/token/OUSD.sol - + -158->7 +186->20 - + -165 +194 <<Abstract>> Initializable ../contracts/utils/Initializable.sol - + -158->165 +186->194 - + -168 +197 <<Abstract>> InitializableERC20Detailed ../contracts/utils/InitializableERC20Detailed.sol - + -158->168 +186->197 - + -166 - -<<Abstract>> -InitializableAbstractStrategy -../contracts/utils/InitializableAbstractStrategy.sol +195 + +<<Abstract>> +InitializableAbstractStrategy +../contracts/utils/InitializableAbstractStrategy.sol - + -166->7 - - +195->20 + + - - -166->40 - - + + +195->55 + + - + -166->165 - - - - - -166->166 - - +195->194 + + - + -340 +392 <<Interface>> IERC20 ../node_modules/@openzeppelin/contracts/token/ERC20/IERC20.sol - - -166->340 - + + +195->392 + - - -168->340 + + +197->392 - - -178->7 - - + + +207->20 + + - - -178->158 - - + + +207->186 + + - - -178->165 - - + + +207->194 + + diff --git a/contracts/docs/InitializableAbstractStrategySquashed.svg b/contracts/docs/InitializableAbstractStrategySquashed.svg index 21a1e38616..b9ba150809 100644 --- a/contracts/docs/InitializableAbstractStrategySquashed.svg +++ b/contracts/docs/InitializableAbstractStrategySquashed.svg @@ -4,69 +4,68 @@ - - + + UmlClassDiagram - - + + -166 - -InitializableAbstractStrategy -../contracts/utils/InitializableAbstractStrategy.sol - -Private: -   initialized: bool <<Initializable>> -   initializing: bool <<Initializable>> -   ______gap: uint256[50] <<Initializable>> -   governorPosition: bytes32 <<Governable>> -   pendingGovernorPosition: bytes32 <<Governable>> -   reentryStatusPosition: bytes32 <<Governable>> -   _deprecated_platformAddress: address <<InitializableAbstractStrategy>> -   _deprecated_vaultAddress: address <<InitializableAbstractStrategy>> -   _reserved: int256[98] <<InitializableAbstractStrategy>> -Internal: -   assetsMapped: address[] <<InitializableAbstractStrategy>> -Public: -   _NOT_ENTERED: uint256 <<Governable>> -   _ENTERED: uint256 <<Governable>> -   platformAddress: address <<InitializableAbstractStrategy>> -   vaultAddress: address <<InitializableAbstractStrategy>> -   assetToPToken: mapping(address=>address) <<InitializableAbstractStrategy>> -   _deprecated_rewardTokenAddress: address <<InitializableAbstractStrategy>> -   _deprecated_rewardLiquidationThreshold: uint256 <<InitializableAbstractStrategy>> -   harvesterAddress: address <<InitializableAbstractStrategy>> -   rewardTokenAddresses: address[] <<InitializableAbstractStrategy>> - -Internal: -     _abstractSetPToken(_asset: address, _pToken: address) <<InitializableAbstractStrategy>> -    _governor(): (governorOut: address) <<Governable>> -    _pendingGovernor(): (pendingGovernor: address) <<Governable>> -    _setGovernor(newGovernor: address) <<Governable>> -    _setPendingGovernor(newGovernor: address) <<Governable>> -    _changeGovernor(_newGovernor: address) <<Governable>> -    _initialize(_rewardTokenAddresses: address[], _assets: address[], _pTokens: address[]) <<InitializableAbstractStrategy>> -    _collectRewardTokens() <<InitializableAbstractStrategy>> -    _setPTokenAddress(_asset: address, _pToken: address) <<InitializableAbstractStrategy>> -External: -     safeApproveAllTokens() <<InitializableAbstractStrategy>> -     deposit(_asset: address, _amount: uint256) <<InitializableAbstractStrategy>> -     depositAll() <<InitializableAbstractStrategy>> -     withdraw(_recipient: address, _asset: address, _amount: uint256) <<InitializableAbstractStrategy>> -     withdrawAll() <<InitializableAbstractStrategy>> -     checkBalance(_asset: address): (balance: uint256) <<InitializableAbstractStrategy>> -     supportsAsset(_asset: address): bool <<InitializableAbstractStrategy>> +195 + +InitializableAbstractStrategy +../contracts/utils/InitializableAbstractStrategy.sol + +Private: +   initialized: bool <<Initializable>> +   initializing: bool <<Initializable>> +   ______gap: uint256[50] <<Initializable>> +   governorPosition: bytes32 <<Governable>> +   pendingGovernorPosition: bytes32 <<Governable>> +   reentryStatusPosition: bytes32 <<Governable>> +   _deprecated_platformAddress: address <<InitializableAbstractStrategy>> +   _deprecated_vaultAddress: address <<InitializableAbstractStrategy>> +   _deprecated_rewardTokenAddress: address <<InitializableAbstractStrategy>> +   _deprecated_rewardLiquidationThreshold: uint256 <<InitializableAbstractStrategy>> +   _reserved: int256[98] <<InitializableAbstractStrategy>> +Internal: +   assetsMapped: address[] <<InitializableAbstractStrategy>> +Public: +   _NOT_ENTERED: uint256 <<Governable>> +   _ENTERED: uint256 <<Governable>> +   platformAddress: address <<InitializableAbstractStrategy>> +   vaultAddress: address <<InitializableAbstractStrategy>> +   assetToPToken: mapping(address=>address) <<InitializableAbstractStrategy>> +   harvesterAddress: address <<InitializableAbstractStrategy>> +   rewardTokenAddresses: address[] <<InitializableAbstractStrategy>> + +Internal: +     _abstractSetPToken(_asset: address, _pToken: address) <<InitializableAbstractStrategy>> +    _governor(): (governorOut: address) <<Governable>> +    _pendingGovernor(): (pendingGovernor: address) <<Governable>> +    _setGovernor(newGovernor: address) <<Governable>> +    _setPendingGovernor(newGovernor: address) <<Governable>> +    _changeGovernor(_newGovernor: address) <<Governable>> +    _initialize(_rewardTokenAddresses: address[], _assets: address[], _pTokens: address[]) <<InitializableAbstractStrategy>> +    _collectRewardTokens() <<InitializableAbstractStrategy>> +    _setPTokenAddress(_asset: address, _pToken: address) <<InitializableAbstractStrategy>> +External: +     safeApproveAllTokens() <<InitializableAbstractStrategy>> +     deposit(_asset: address, _amount: uint256) <<InitializableAbstractStrategy>> +     depositAll() <<InitializableAbstractStrategy>> +     withdraw(_recipient: address, _asset: address, _amount: uint256) <<InitializableAbstractStrategy>> +     withdrawAll() <<InitializableAbstractStrategy>> +     checkBalance(_asset: address): (balance: uint256) <<InitializableAbstractStrategy>>    transferGovernance(_newGovernor: address) <<onlyGovernor>> <<Governable>>    claimGovernance() <<Governable>> -    initialize(_rewardTokenAddresses: address[], _assets: address[], _pTokens: address[]) <<onlyGovernor, initializer>> <<InitializableAbstractStrategy>> -    collectRewardTokens() <<onlyHarvester, nonReentrant>> <<InitializableAbstractStrategy>> -    setRewardTokenAddresses(_rewardTokenAddresses: address[]) <<onlyGovernor>> <<InitializableAbstractStrategy>> -    getRewardTokenAddresses(): address[] <<InitializableAbstractStrategy>> -    setPTokenAddress(_asset: address, _pToken: address) <<onlyGovernor>> <<InitializableAbstractStrategy>> -    removePToken(_assetIndex: uint256) <<onlyGovernor>> <<InitializableAbstractStrategy>> -    setHarvesterAddress(_harvesterAddress: address) <<onlyGovernor>> <<InitializableAbstractStrategy>> -Public: +    collectRewardTokens() <<onlyHarvester, nonReentrant>> <<InitializableAbstractStrategy>> +    setRewardTokenAddresses(_rewardTokenAddresses: address[]) <<onlyGovernor>> <<InitializableAbstractStrategy>> +    getRewardTokenAddresses(): address[] <<InitializableAbstractStrategy>> +    setPTokenAddress(_asset: address, _pToken: address) <<onlyGovernor>> <<InitializableAbstractStrategy>> +    removePToken(_assetIndex: uint256) <<onlyGovernor>> <<InitializableAbstractStrategy>> +    setHarvesterAddress(_harvesterAddress: address) <<onlyGovernor>> <<InitializableAbstractStrategy>> +Public: +     supportsAsset(_asset: address): bool <<InitializableAbstractStrategy>>    <<event>> PendingGovernorshipTransfer(previousGovernor: address, newGovernor: address) <<Governable>>    <<event>> GovernorshipTransferred(previousGovernor: address, newGovernor: address) <<Governable>>    <<event>> PTokenAdded(_asset: address, _pToken: address) <<InitializableAbstractStrategy>> @@ -89,11 +88,5 @@    constructor(_config: BaseStrategyConfig) <<InitializableAbstractStrategy>>    transferToken(_asset: address, _amount: uint256) <<onlyGovernor>> <<InitializableAbstractStrategy>> - - -166->166 - - - diff --git a/contracts/docs/InitializableERC20DetailedHierarchy.svg b/contracts/docs/InitializableERC20DetailedHierarchy.svg index a97d92d96b..fa9f975d92 100644 --- a/contracts/docs/InitializableERC20DetailedHierarchy.svg +++ b/contracts/docs/InitializableERC20DetailedHierarchy.svg @@ -9,25 +9,25 @@ UmlClassDiagram - + -168 +197 <<Abstract>> InitializableERC20Detailed ../contracts/utils/InitializableERC20Detailed.sol - + -340 +392 <<Interface>> IERC20 ../node_modules/@openzeppelin/contracts/token/ERC20/IERC20.sol - + -168->340 +197->392 diff --git a/contracts/docs/InitializableERC20DetailedSquashed.svg b/contracts/docs/InitializableERC20DetailedSquashed.svg index 2874db96eb..c87e2a4036 100644 --- a/contracts/docs/InitializableERC20DetailedSquashed.svg +++ b/contracts/docs/InitializableERC20DetailedSquashed.svg @@ -9,9 +9,9 @@ UmlClassDiagram - + -168 +197 InitializableERC20Detailed ../contracts/utils/InitializableERC20Detailed.sol diff --git a/contracts/docs/MorphoAaveStrategyHierarchy.svg b/contracts/docs/MorphoAaveStrategyHierarchy.svg index ba53346b65..4705321bfe 100644 --- a/contracts/docs/MorphoAaveStrategyHierarchy.svg +++ b/contracts/docs/MorphoAaveStrategyHierarchy.svg @@ -9,51 +9,51 @@ UmlClassDiagram - + -18 +20 Governable ../contracts/governance/Governable.sol - + -166 +176 MorphoAaveStrategy ../contracts/strategies/MorphoAaveStrategy.sol - + -185 +195 <<Abstract>> InitializableAbstractStrategy ../contracts/utils/InitializableAbstractStrategy.sol - + -166->185 +176->195 - + -184 +194 <<Abstract>> Initializable ../contracts/utils/Initializable.sol - + -185->18 +195->20 - + -185->184 +195->194 diff --git a/contracts/docs/MorphoAaveStrategySquashed.svg b/contracts/docs/MorphoAaveStrategySquashed.svg index 5c4f26052d..3b1982d39c 100644 --- a/contracts/docs/MorphoAaveStrategySquashed.svg +++ b/contracts/docs/MorphoAaveStrategySquashed.svg @@ -9,9 +9,9 @@ UmlClassDiagram - + -166 +176 MorphoAaveStrategy ../contracts/strategies/MorphoAaveStrategy.sol @@ -70,31 +70,31 @@    withdraw(_recipient: address, _asset: address, _amount: uint256) <<onlyVault, nonReentrant>> <<MorphoAaveStrategy>>    withdrawAll() <<onlyVaultOrGovernor, nonReentrant>> <<MorphoAaveStrategy>>    checkBalance(_asset: address): (balance: uint256) <<MorphoAaveStrategy>> -    supportsAsset(_asset: address): bool <<MorphoAaveStrategy>> -    initialize(_rewardTokenAddresses: address[], _assets: address[], _pTokens: address[]) <<onlyGovernor, initializer>> <<MorphoAaveStrategy>> -    getPendingRewards(): (balance: uint256) <<MorphoAaveStrategy>> -Public: -    <<event>> PendingGovernorshipTransfer(previousGovernor: address, newGovernor: address) <<Governable>> -    <<event>> GovernorshipTransferred(previousGovernor: address, newGovernor: address) <<Governable>> -    <<event>> PTokenAdded(_asset: address, _pToken: address) <<InitializableAbstractStrategy>> -    <<event>> PTokenRemoved(_asset: address, _pToken: address) <<InitializableAbstractStrategy>> -    <<event>> Deposit(_asset: address, _pToken: address, _amount: uint256) <<InitializableAbstractStrategy>> -    <<event>> Withdrawal(_asset: address, _pToken: address, _amount: uint256) <<InitializableAbstractStrategy>> -    <<event>> RewardTokenCollected(recipient: address, rewardToken: address, amount: uint256) <<InitializableAbstractStrategy>> -    <<event>> RewardTokenAddressesUpdated(_oldAddresses: address[], _newAddresses: address[]) <<InitializableAbstractStrategy>> -    <<event>> HarvesterAddressesUpdated(_oldHarvesterAddress: address, _newHarvesterAddress: address) <<InitializableAbstractStrategy>> -    <<modifier>> initializer() <<Initializable>> -    <<modifier>> onlyGovernor() <<Governable>> -    <<modifier>> nonReentrant() <<Governable>> -    <<modifier>> onlyVault() <<InitializableAbstractStrategy>> -    <<modifier>> onlyHarvester() <<InitializableAbstractStrategy>> -    <<modifier>> onlyVaultOrGovernor() <<InitializableAbstractStrategy>> -    <<modifier>> onlyVaultOrGovernorOrStrategist() <<InitializableAbstractStrategy>> -    constructor() <<Governable>> -    governor(): address <<Governable>> -    isGovernor(): bool <<Governable>> -    constructor(_stratConfig: BaseStrategyConfig) <<MorphoAaveStrategy>> -    transferToken(_asset: address, _amount: uint256) <<onlyGovernor>> <<InitializableAbstractStrategy>> +    initialize(_rewardTokenAddresses: address[], _assets: address[], _pTokens: address[]) <<onlyGovernor, initializer>> <<MorphoAaveStrategy>> +    getPendingRewards(): (balance: uint256) <<MorphoAaveStrategy>> +Public: +    <<event>> PendingGovernorshipTransfer(previousGovernor: address, newGovernor: address) <<Governable>> +    <<event>> GovernorshipTransferred(previousGovernor: address, newGovernor: address) <<Governable>> +    <<event>> PTokenAdded(_asset: address, _pToken: address) <<InitializableAbstractStrategy>> +    <<event>> PTokenRemoved(_asset: address, _pToken: address) <<InitializableAbstractStrategy>> +    <<event>> Deposit(_asset: address, _pToken: address, _amount: uint256) <<InitializableAbstractStrategy>> +    <<event>> Withdrawal(_asset: address, _pToken: address, _amount: uint256) <<InitializableAbstractStrategy>> +    <<event>> RewardTokenCollected(recipient: address, rewardToken: address, amount: uint256) <<InitializableAbstractStrategy>> +    <<event>> RewardTokenAddressesUpdated(_oldAddresses: address[], _newAddresses: address[]) <<InitializableAbstractStrategy>> +    <<event>> HarvesterAddressesUpdated(_oldHarvesterAddress: address, _newHarvesterAddress: address) <<InitializableAbstractStrategy>> +    <<modifier>> initializer() <<Initializable>> +    <<modifier>> onlyGovernor() <<Governable>> +    <<modifier>> nonReentrant() <<Governable>> +    <<modifier>> onlyVault() <<InitializableAbstractStrategy>> +    <<modifier>> onlyHarvester() <<InitializableAbstractStrategy>> +    <<modifier>> onlyVaultOrGovernor() <<InitializableAbstractStrategy>> +    <<modifier>> onlyVaultOrGovernorOrStrategist() <<InitializableAbstractStrategy>> +    constructor() <<Governable>> +    governor(): address <<Governable>> +    isGovernor(): bool <<Governable>> +    constructor(_stratConfig: BaseStrategyConfig) <<MorphoAaveStrategy>> +    transferToken(_asset: address, _amount: uint256) <<onlyGovernor>> <<InitializableAbstractStrategy>> +    supportsAsset(_asset: address): bool <<MorphoAaveStrategy>> diff --git a/contracts/docs/MorphoCompStrategyHierarchy.svg b/contracts/docs/MorphoCompStrategyHierarchy.svg index 17b9379722..2800787262 100644 --- a/contracts/docs/MorphoCompStrategyHierarchy.svg +++ b/contracts/docs/MorphoCompStrategyHierarchy.svg @@ -9,65 +9,65 @@ UmlClassDiagram - + -18 +20 Governable ../contracts/governance/Governable.sol - + -141 +151 <<Abstract>> BaseCompoundStrategy ../contracts/strategies/BaseCompoundStrategy.sol - + -185 +195 <<Abstract>> InitializableAbstractStrategy ../contracts/utils/InitializableAbstractStrategy.sol - + -141->185 +151->195 - + -167 +177 MorphoCompoundStrategy ../contracts/strategies/MorphoCompoundStrategy.sol - + -167->141 +177->151 - + -184 +194 <<Abstract>> Initializable ../contracts/utils/Initializable.sol - + -185->18 +195->20 - + -185->184 +195->194 diff --git a/contracts/docs/MorphoCompStrategySquashed.svg b/contracts/docs/MorphoCompStrategySquashed.svg index e7a51fa364..f862df9dd0 100644 --- a/contracts/docs/MorphoCompStrategySquashed.svg +++ b/contracts/docs/MorphoCompStrategySquashed.svg @@ -9,9 +9,9 @@ UmlClassDiagram - + -167 +177 MorphoCompoundStrategy ../contracts/strategies/MorphoCompoundStrategy.sol @@ -72,31 +72,31 @@    withdraw(_recipient: address, _asset: address, _amount: uint256) <<onlyVault, nonReentrant>> <<MorphoCompoundStrategy>>    withdrawAll() <<onlyVaultOrGovernor, nonReentrant>> <<MorphoCompoundStrategy>>    checkBalance(_asset: address): (balance: uint256) <<MorphoCompoundStrategy>> -    supportsAsset(_asset: address): bool <<BaseCompoundStrategy>> -    initialize(_rewardTokenAddresses: address[], _assets: address[], _pTokens: address[]) <<onlyGovernor, initializer>> <<MorphoCompoundStrategy>> -    getPendingRewards(): (balance: uint256) <<MorphoCompoundStrategy>> -Public: -    <<event>> PendingGovernorshipTransfer(previousGovernor: address, newGovernor: address) <<Governable>> -    <<event>> GovernorshipTransferred(previousGovernor: address, newGovernor: address) <<Governable>> -    <<event>> PTokenAdded(_asset: address, _pToken: address) <<InitializableAbstractStrategy>> -    <<event>> PTokenRemoved(_asset: address, _pToken: address) <<InitializableAbstractStrategy>> -    <<event>> Deposit(_asset: address, _pToken: address, _amount: uint256) <<InitializableAbstractStrategy>> -    <<event>> Withdrawal(_asset: address, _pToken: address, _amount: uint256) <<InitializableAbstractStrategy>> -    <<event>> RewardTokenCollected(recipient: address, rewardToken: address, amount: uint256) <<InitializableAbstractStrategy>> -    <<event>> RewardTokenAddressesUpdated(_oldAddresses: address[], _newAddresses: address[]) <<InitializableAbstractStrategy>> -    <<event>> HarvesterAddressesUpdated(_oldHarvesterAddress: address, _newHarvesterAddress: address) <<InitializableAbstractStrategy>> -    <<modifier>> initializer() <<Initializable>> -    <<modifier>> onlyGovernor() <<Governable>> -    <<modifier>> nonReentrant() <<Governable>> -    <<modifier>> onlyVault() <<InitializableAbstractStrategy>> -    <<modifier>> onlyHarvester() <<InitializableAbstractStrategy>> -    <<modifier>> onlyVaultOrGovernor() <<InitializableAbstractStrategy>> -    <<modifier>> onlyVaultOrGovernorOrStrategist() <<InitializableAbstractStrategy>> -    constructor() <<Governable>> -    governor(): address <<Governable>> -    isGovernor(): bool <<Governable>> -    constructor(_stratConfig: BaseStrategyConfig) <<MorphoCompoundStrategy>> -    transferToken(_asset: address, _amount: uint256) <<onlyGovernor>> <<InitializableAbstractStrategy>> +    initialize(_rewardTokenAddresses: address[], _assets: address[], _pTokens: address[]) <<onlyGovernor, initializer>> <<MorphoCompoundStrategy>> +    getPendingRewards(): (balance: uint256) <<MorphoCompoundStrategy>> +Public: +    <<event>> PendingGovernorshipTransfer(previousGovernor: address, newGovernor: address) <<Governable>> +    <<event>> GovernorshipTransferred(previousGovernor: address, newGovernor: address) <<Governable>> +    <<event>> PTokenAdded(_asset: address, _pToken: address) <<InitializableAbstractStrategy>> +    <<event>> PTokenRemoved(_asset: address, _pToken: address) <<InitializableAbstractStrategy>> +    <<event>> Deposit(_asset: address, _pToken: address, _amount: uint256) <<InitializableAbstractStrategy>> +    <<event>> Withdrawal(_asset: address, _pToken: address, _amount: uint256) <<InitializableAbstractStrategy>> +    <<event>> RewardTokenCollected(recipient: address, rewardToken: address, amount: uint256) <<InitializableAbstractStrategy>> +    <<event>> RewardTokenAddressesUpdated(_oldAddresses: address[], _newAddresses: address[]) <<InitializableAbstractStrategy>> +    <<event>> HarvesterAddressesUpdated(_oldHarvesterAddress: address, _newHarvesterAddress: address) <<InitializableAbstractStrategy>> +    <<modifier>> initializer() <<Initializable>> +    <<modifier>> onlyGovernor() <<Governable>> +    <<modifier>> nonReentrant() <<Governable>> +    <<modifier>> onlyVault() <<InitializableAbstractStrategy>> +    <<modifier>> onlyHarvester() <<InitializableAbstractStrategy>> +    <<modifier>> onlyVaultOrGovernor() <<InitializableAbstractStrategy>> +    <<modifier>> onlyVaultOrGovernorOrStrategist() <<InitializableAbstractStrategy>> +    constructor() <<Governable>> +    governor(): address <<Governable>> +    isGovernor(): bool <<Governable>> +    constructor(_stratConfig: BaseStrategyConfig) <<MorphoCompoundStrategy>> +    transferToken(_asset: address, _amount: uint256) <<onlyGovernor>> <<InitializableAbstractStrategy>> +    supportsAsset(_asset: address): bool <<BaseCompoundStrategy>> diff --git a/contracts/docs/OETHBuybackHierarchy.svg b/contracts/docs/OETHBuybackHierarchy.svg new file mode 100644 index 0000000000..b94f9b0376 --- /dev/null +++ b/contracts/docs/OETHBuybackHierarchy.svg @@ -0,0 +1,116 @@ + + + + + + +UmlClassDiagram + + + +0 + +<<Abstract>> +BaseBuyback +../contracts/buyback/BaseBuyback.sol + + + +25 + +Strategizable +../contracts/governance/Strategizable.sol + + + +0->25 + + + + + +37 + +<<Interface>> +ICVXLocker +../contracts/interfaces/ICVXLocker.sol + + + +0->37 + + + + + +243 + +<<Interface>> +IUniswapUniversalRouter +../contracts/interfaces/uniswap/IUniswapUniversalRouter.sol + + + +0->243 + + + + + +194 + +<<Abstract>> +Initializable +../contracts/utils/Initializable.sol + + + +0->194 + + + + + +392 + +<<Interface>> +IERC20 +../node_modules/@openzeppelin/contracts/token/ERC20/IERC20.sol + + + +0->392 + + + + + +1 + +OETHBuyback +../contracts/buyback/OETHBuyback.sol + + + +1->0 + + + + + +20 + +Governable +../contracts/governance/Governable.sol + + + +25->20 + + + + + diff --git a/contracts/docs/OETHBuybackSquashed.svg b/contracts/docs/OETHBuybackSquashed.svg new file mode 100644 index 0000000000..b2e5cf037d --- /dev/null +++ b/contracts/docs/OETHBuybackSquashed.svg @@ -0,0 +1,89 @@ + + + + + + +UmlClassDiagram + + + +1 + +OETHBuyback +../contracts/buyback/OETHBuyback.sol + +Private: +   initialized: bool <<Initializable>> +   initializing: bool <<Initializable>> +   ______gap: uint256[50] <<Initializable>> +   governorPosition: bytes32 <<Governable>> +   pendingGovernorPosition: bytes32 <<Governable>> +   reentryStatusPosition: bytes32 <<Governable>> +   __gap: uint256[50] <<Strategizable>> +   __deprecated_ousd: address <<BaseBuyback>> +   __deprecated_ogv: address <<BaseBuyback>> +   __deprecated_usdt: address <<BaseBuyback>> +   __deprecated_weth9: address <<BaseBuyback>> +   __deprecated_treasuryBps: uint256 <<BaseBuyback>> +   swapCommand: bytes <<BaseBuyback>> +Public: +   _NOT_ENTERED: uint256 <<Governable>> +   _ENTERED: uint256 <<Governable>> +   strategistAddr: address <<Strategizable>> +   universalRouter: address <<BaseBuyback>> +   rewardsSource: address <<BaseBuyback>> +   treasuryManager: address <<BaseBuyback>> +   oToken: address <<BaseBuyback>> +   ogv: address <<BaseBuyback>> +   cvx: address <<BaseBuyback>> +   cvxLocker: address <<BaseBuyback>> +   ogvPath: bytes <<OETHBuyback>> +   cvxPath: bytes <<OETHBuyback>> + +Internal: +    _governor(): (governorOut: address) <<Governable>> +    _pendingGovernor(): (pendingGovernor: address) <<Governable>> +    _setGovernor(newGovernor: address) <<Governable>> +    _setPendingGovernor(newGovernor: address) <<Governable>> +    _changeGovernor(_newGovernor: address) <<Governable>> +    _setStrategistAddr(_address: address) <<Strategizable>> +    _setUniswapUniversalRouter(_router: address) <<BaseBuyback>> +    _setRewardsSource(_address: address) <<BaseBuyback>> +    _setTreasuryManager(_address: address) <<BaseBuyback>> +    _lockAllCVX() <<BaseBuyback>> +    _getSwapPath(toToken: address): (path: bytes) <<OETHBuyback>> +External: +    transferGovernance(_newGovernor: address) <<onlyGovernor>> <<Governable>> +    claimGovernance() <<Governable>> +    setStrategistAddr(_address: address) <<onlyGovernor>> <<Strategizable>> +    initialize(_uniswapUniversalRouter: address, _strategistAddr: address, _treasuryManagerAddr: address, _rewardsSource: address) <<onlyGovernor, initializer>> <<BaseBuyback>> +    setUniswapUniversalRouter(_router: address) <<onlyGovernor>> <<BaseBuyback>> +    setRewardsSource(_address: address) <<onlyGovernor>> <<BaseBuyback>> +    setTreasuryManager(_address: address) <<onlyGovernor>> <<BaseBuyback>> +    swap(oTokenAmount: uint256, minOGV: uint256, minCVX: uint256) <<onlyGovernorOrStrategist, nonReentrant>> <<BaseBuyback>> +    lockAllCVX() <<onlyGovernorOrStrategist>> <<BaseBuyback>> +    safeApproveAllTokens() <<onlyGovernorOrStrategist>> <<BaseBuyback>> +    transferToken(token: address, amount: uint256) <<onlyGovernor, nonReentrant>> <<BaseBuyback>> +Public: +    <<event>> PendingGovernorshipTransfer(previousGovernor: address, newGovernor: address) <<Governable>> +    <<event>> GovernorshipTransferred(previousGovernor: address, newGovernor: address) <<Governable>> +    <<event>> StrategistUpdated(_address: address) <<Strategizable>> +    <<event>> UniswapUniversalRouterUpdated(_address: address) <<BaseBuyback>> +    <<event>> RewardsSourceUpdated(_address: address) <<BaseBuyback>> +    <<event>> TreasuryManagerUpdated(_address: address) <<BaseBuyback>> +    <<event>> OTokenBuyback(oToken: address, swappedFor: address, swapAmountIn: uint256, minExpected: uint256) <<BaseBuyback>> +    <<modifier>> initializer() <<Initializable>> +    <<modifier>> onlyGovernor() <<Governable>> +    <<modifier>> nonReentrant() <<Governable>> +    <<modifier>> onlyGovernorOrStrategist() <<Strategizable>> +    constructor() <<Governable>> +    governor(): address <<Governable>> +    isGovernor(): bool <<Governable>> +    constructor(_oToken: address, _ogv: address, _cvx: address, _cvxLocker: address) <<OETHBuyback>> + + + diff --git a/contracts/docs/OETHBuybackStorage.svg b/contracts/docs/OETHBuybackStorage.svg new file mode 100644 index 0000000000..c9ec88a950 --- /dev/null +++ b/contracts/docs/OETHBuybackStorage.svg @@ -0,0 +1,163 @@ + + + + + + +StorageDiagram + + + +3 + +OETHBuyback <<Contract>> + +slot + +0 + +1-50 + +51 + +52-101 + +102 + +103 + +104 + +105 + +106 + +107 + +108 + +109 + +type: <inherited contract>.variable (bytes) + +unallocated (30) + +bool: Initializable.initializing (1) + +bool: Initializable.initialized (1) + +uint256[50]: Initializable.______gap (1600) + +unallocated (12) + +address: Strategizable.strategistAddr (20) + +uint256[50]: Strategizable.__gap (1600) + +unallocated (12) + +address: BaseBuyback.universalRouter (20) + +unallocated (12) + +address: BaseBuyback.__deprecated_ousd (20) + +unallocated (12) + +address: BaseBuyback.__deprecated_ogv (20) + +unallocated (12) + +address: BaseBuyback.__deprecated_usdt (20) + +unallocated (12) + +address: BaseBuyback.__deprecated_weth9 (20) + +unallocated (12) + +address: BaseBuyback.rewardsSource (20) + +unallocated (12) + +address: BaseBuyback.treasuryManager (20) + +uint256: BaseBuyback.__deprecated_treasuryBps (32) + + + +1 + +uint256[50]: ______gap <<Array>> + +slot + +1 + +2 + +3-48 + +49 + +50 + +type: variable (bytes) + +uint256 (32) + +uint256 (32) + +---- (1472) + +uint256 (32) + +uint256 (32) + + + +3:8->1 + + + + + +2 + +uint256[50]: __gap <<Array>> + +slot + +52 + +53 + +54-99 + +100 + +101 + +type: variable (bytes) + +uint256 (32) + +uint256 (32) + +---- (1472) + +uint256 (32) + +uint256 (32) + + + +3:15->2 + + + + + diff --git a/contracts/docs/OETHDripperHierarchy.svg b/contracts/docs/OETHDripperHierarchy.svg index 97a023c9e8..bc84de1084 100644 --- a/contracts/docs/OETHDripperHierarchy.svg +++ b/contracts/docs/OETHDripperHierarchy.svg @@ -4,71 +4,149 @@ - - + + UmlClassDiagram - - + + -7 - -Governable -../contracts/governance/Governable.sol +20 + +Governable +../contracts/governance/Governable.sol - + -15 +30 Dripper ../contracts/harvest/Dripper.sol - + -15->7 - - +30->20 + + - + -35 - -<<Interface>> -IVault -../contracts/interfaces/IVault.sol +55 + +<<Interface>> +IVault +../contracts/interfaces/IVault.sol - + -15->35 - - +30->55 + + - - -436 - -<<Interface>> -IERC20 -../node_modules/@openzeppelin/contracts/token/ERC20/IERC20.sol + + +392 + +<<Interface>> +IERC20 +../node_modules/@openzeppelin/contracts/token/ERC20/IERC20.sol - + -15->436 - - +30->392 + + - + -18 +33 OETHDripper ../contracts/harvest/OETHDripper.sol - + -18->15 +33->30 + + +207 + +VaultStorage +../contracts/vault/VaultStorage.sol + + + +55->207 + + + + + +186 + +OUSD +../contracts/token/OUSD.sol + + + +186->20 + + + + + +194 + +<<Abstract>> +Initializable +../contracts/utils/Initializable.sol + + + +186->194 + + + + + +197 + +<<Abstract>> +InitializableERC20Detailed +../contracts/utils/InitializableERC20Detailed.sol + + + +186->197 + + + + + +197->392 + + + + + +207->20 + + + + + +207->186 + + + + + +207->194 + + + diff --git a/contracts/docs/OETHDripperSquashed.svg b/contracts/docs/OETHDripperSquashed.svg index 1140e6994e..7c76032208 100644 --- a/contracts/docs/OETHDripperSquashed.svg +++ b/contracts/docs/OETHDripperSquashed.svg @@ -9,9 +9,9 @@ UmlClassDiagram - + -18 +33 OETHDripper ../contracts/harvest/OETHDripper.sol diff --git a/contracts/docs/OETHHarvesterHierarchy.svg b/contracts/docs/OETHHarvesterHierarchy.svg index 5b8878a0a8..ac59a5c8f0 100644 --- a/contracts/docs/OETHHarvesterHierarchy.svg +++ b/contracts/docs/OETHHarvesterHierarchy.svg @@ -4,132 +4,234 @@ - - + + UmlClassDiagram - - + + -7 - -Governable -../contracts/governance/Governable.sol +20 + +Governable +../contracts/governance/Governable.sol - + -13 - -<<Abstract>> -BaseHarvester -../contracts/harvest/BaseHarvester.sol +26 + +<<Abstract>> +BaseHarvester +../contracts/harvest/BaseHarvester.sol - + -13->7 - - +26->20 + + - + -30 - -<<Interface>> -IOracle -../contracts/interfaces/IOracle.sol +48 + +<<Interface>> +IOracle +../contracts/interfaces/IOracle.sol - - -13->30 - - + + +26->48 + + - + -33 - -<<Interface>> -IStrategy -../contracts/interfaces/IStrategy.sol +52 + +<<Interface>> +IStrategy +../contracts/interfaces/IStrategy.sol - - -13->33 - - + + +26->52 + + - + -35 - -<<Interface>> -IVault -../contracts/interfaces/IVault.sol +55 + +<<Interface>> +IVault +../contracts/interfaces/IVault.sol - + -13->35 - - - - - -436 - -<<Interface>> -IERC20 -../node_modules/@openzeppelin/contracts/token/ERC20/IERC20.sol +26->55 + + - - -13->436 - - + + +214 + +<<Interface>> +IBalancerVault +../contracts/interfaces/balancer/IBalancerVault.sol - - -19 - -OETHHarvester -../contracts/harvest/OETHHarvester.sol + + +26->214 + + - - -19->13 - - + + +245 + +<<Interface>> +IUniswapV2Router +../contracts/interfaces/uniswap/IUniswapV2Router02.sol - + + +26->245 + + + + + +246 + +<<Interface>> +IUniswapV3Router +../contracts/interfaces/uniswap/IUniswapV3Router.sol + + -19->30 - - - - - -19->35 - - +26->246 + + + + + +174 + +<<Interface>> +ICurvePool +../contracts/strategies/ICurvePool.sol + + + +26->174 + + + + + +392 + +<<Interface>> +IERC20 +../node_modules/@openzeppelin/contracts/token/ERC20/IERC20.sol + + + +26->392 + + - - -182 - -<<Interface>> -IUniswapV2Router -../contracts/interfaces/uniswap/IUniswapV2Router02.sol + + +34 + +OETHHarvester +../contracts/harvest/OETHHarvester.sol - + -19->182 - - - - - -19->436 - - +34->26 + + + + + +207 + +VaultStorage +../contracts/vault/VaultStorage.sol + + + +55->207 + + + + + +186 + +OUSD +../contracts/token/OUSD.sol + + + +186->20 + + + + + +194 + +<<Abstract>> +Initializable +../contracts/utils/Initializable.sol + + + +186->194 + + + + + +197 + +<<Abstract>> +InitializableERC20Detailed +../contracts/utils/InitializableERC20Detailed.sol + + + +186->197 + + + + + +197->392 + + + + + +207->20 + + + + + +207->186 + + + + + +207->194 + + diff --git a/contracts/docs/OETHHarvesterSquashed.svg b/contracts/docs/OETHHarvesterSquashed.svg index f593dad324..31792aae4b 100644 --- a/contracts/docs/OETHHarvesterSquashed.svg +++ b/contracts/docs/OETHHarvesterSquashed.svg @@ -4,68 +4,77 @@ - - + + UmlClassDiagram - - + + -19 - -OETHHarvester -../contracts/harvest/OETHHarvester.sol - -Private: -   governorPosition: bytes32 <<Governable>> -   pendingGovernorPosition: bytes32 <<Governable>> -   reentryStatusPosition: bytes32 <<Governable>> -Public: -   _NOT_ENTERED: uint256 <<Governable>> -   _ENTERED: uint256 <<Governable>> -   rewardTokenConfigs: mapping(address=>RewardTokenConfig) <<BaseHarvester>> -   supportedStrategies: mapping(address=>bool) <<BaseHarvester>> -   vaultAddress: address <<BaseHarvester>> -   rewardProceedsAddress: address <<BaseHarvester>> - -Internal: -    _governor(): (governorOut: address) <<Governable>> -    _pendingGovernor(): (pendingGovernor: address) <<Governable>> -    _setGovernor(newGovernor: address) <<Governable>> -    _setPendingGovernor(newGovernor: address) <<Governable>> -    _changeGovernor(_newGovernor: address) <<Governable>> -    _harvest() <<BaseHarvester>> +34 + +OETHHarvester +../contracts/harvest/OETHHarvester.sol + +Private: +   governorPosition: bytes32 <<Governable>> +   pendingGovernorPosition: bytes32 <<Governable>> +   reentryStatusPosition: bytes32 <<Governable>> +Public: +   _NOT_ENTERED: uint256 <<Governable>> +   _ENTERED: uint256 <<Governable>> +   rewardTokenConfigs: mapping(address=>RewardTokenConfig) <<BaseHarvester>> +   supportedStrategies: mapping(address=>bool) <<BaseHarvester>> +   vaultAddress: address <<BaseHarvester>> +   rewardProceedsAddress: address <<BaseHarvester>> +   baseTokenAddress: address <<BaseHarvester>> +   baseTokenDecimals: uint256 <<BaseHarvester>> +   uniswapV2Path: mapping(address=>address[]) <<BaseHarvester>> +   uniswapV3Path: mapping(address=>bytes) <<BaseHarvester>> +   balancerPoolId: mapping(address=>bytes32) <<BaseHarvester>> +   curvePoolIndices: mapping(address=>CurvePoolIndices) <<BaseHarvester>> + +Internal: +    _governor(): (governorOut: address) <<Governable>> +    _pendingGovernor(): (pendingGovernor: address) <<Governable>> +    _setGovernor(newGovernor: address) <<Governable>> +    _setPendingGovernor(newGovernor: address) <<Governable>> +    _changeGovernor(_newGovernor: address) <<Governable>> +    _decodeUniswapV2Path(data: bytes, token: address): (path: address[]) <<BaseHarvester>> +    _decodeUniswapV3Path(data: bytes, token: address): (path: bytes) <<BaseHarvester>> +    _decodeBalancerPoolId(data: bytes, balancerVault: address, token: address): (poolId: bytes32) <<BaseHarvester>> +    _decodeCurvePoolIndices(data: bytes, poolAddress: address, token: address): (indices: CurvePoolIndices) <<BaseHarvester>>    _harvestAndSwap(_strategyAddr: address, _rewardTo: address) <<BaseHarvester>>    _harvest(_strategyAddr: address) <<BaseHarvester>> -    _swap(_rewardTo: address) <<BaseHarvester>> -    _swap(_swapToken: address, _rewardTo: address) <<OETHHarvester>> -External: -    transferGovernance(_newGovernor: address) <<onlyGovernor>> <<Governable>> -    claimGovernance() <<Governable>> -    setRewardsProceedsAddress(_rewardProceedsAddress: address) <<onlyGovernor>> <<BaseHarvester>> -    setRewardTokenConfig(_tokenAddress: address, _allowedSlippageBps: uint16, _harvestRewardBps: uint16, _uniswapV2CompatibleAddr: address, _liquidationLimit: uint256, _doSwapRewardToken: bool) <<onlyGovernor>> <<BaseHarvester>> -    setSupportedStrategy(_strategyAddress: address, _isSupported: bool) <<onlyVaultOrGovernor>> <<BaseHarvester>> -    transferToken(_asset: address, _amount: uint256) <<onlyGovernor>> <<BaseHarvester>> -    harvest() <<onlyGovernor, nonReentrant>> <<BaseHarvester>> -    swap() <<onlyGovernor, nonReentrant>> <<BaseHarvester>> -    harvestAndSwap() <<onlyGovernor, nonReentrant>> <<BaseHarvester>> -    harvest(_strategyAddr: address) <<onlyGovernor, nonReentrant>> <<BaseHarvester>> +    _swap(_swapToken: address, _rewardTo: address, _priceProvider: IOracle) <<BaseHarvester>> +    _doSwap(swapPlatform: SwapPlatform, routerAddress: address, rewardTokenAddress: address, amountIn: uint256, minAmountOut: uint256): (amountOut: uint256) <<BaseHarvester>> +    _swapWithUniswapV2(routerAddress: address, swapToken: address, amountIn: uint256, minAmountOut: uint256): (amountOut: uint256) <<BaseHarvester>> +    _swapWithUniswapV3(routerAddress: address, swapToken: address, amountIn: uint256, minAmountOut: uint256): (amountOut: uint256) <<BaseHarvester>> +    _swapWithBalancer(balancerVaultAddress: address, swapToken: address, amountIn: uint256, minAmountOut: uint256): (amountOut: uint256) <<BaseHarvester>> +    _swapWithCurve(poolAddress: address, swapToken: address, amountIn: uint256, minAmountOut: uint256): (amountOut: uint256) <<BaseHarvester>> +External: +    transferGovernance(_newGovernor: address) <<onlyGovernor>> <<Governable>> +    claimGovernance() <<Governable>> +    setRewardProceedsAddress(_rewardProceedsAddress: address) <<onlyGovernor>> <<BaseHarvester>> +    setRewardTokenConfig(_tokenAddress: address, tokenConfig: RewardTokenConfig, swapData: bytes) <<onlyGovernor>> <<BaseHarvester>> +    setSupportedStrategy(_strategyAddress: address, _isSupported: bool) <<onlyGovernor>> <<BaseHarvester>> +    transferToken(_asset: address, _amount: uint256) <<onlyGovernor>> <<BaseHarvester>>    harvestAndSwap(_strategyAddr: address) <<nonReentrant>> <<BaseHarvester>>    harvestAndSwap(_strategyAddr: address, _rewardTo: address) <<nonReentrant>> <<BaseHarvester>> -    swapRewardToken(_swapToken: address) <<onlyGovernor, nonReentrant>> <<BaseHarvester>> -Public: -    <<event>> PendingGovernorshipTransfer(previousGovernor: address, newGovernor: address) <<Governable>> -    <<event>> GovernorshipTransferred(previousGovernor: address, newGovernor: address) <<Governable>> -    <<event>> UniswapUpdated(_address: address) <<BaseHarvester>> -    <<event>> SupportedStrategyUpdate(_address: address, _isSupported: bool) <<BaseHarvester>> -    <<event>> RewardTokenConfigUpdated(_tokenAddress: address, _allowedSlippageBps: uint16, _harvestRewardBps: uint16, _uniswapV2CompatibleAddr: address, _liquidationLimit: uint256, _doSwapRewardToken: bool) <<BaseHarvester>> -    <<modifier>> onlyGovernor() <<Governable>> -    <<modifier>> nonReentrant() <<Governable>> -    <<modifier>> onlyVaultOrGovernor() <<BaseHarvester>> +Public: +    <<event>> PendingGovernorshipTransfer(previousGovernor: address, newGovernor: address) <<Governable>> +    <<event>> GovernorshipTransferred(previousGovernor: address, newGovernor: address) <<Governable>> +    <<event>> SupportedStrategyUpdate(strategyAddress: address, isSupported: bool) <<BaseHarvester>> +    <<event>> RewardTokenConfigUpdated(tokenAddress: address, allowedSlippageBps: uint16, harvestRewardBps: uint16, swapPlatform: SwapPlatform, swapPlatformAddr: address, swapData: bytes, liquidationLimit: uint256, doSwapRewardToken: bool) <<BaseHarvester>> +    <<event>> RewardTokenSwapped(rewardToken: address, swappedInto: address, swapPlatform: SwapPlatform, amountIn: uint256, amountOut: uint256) <<BaseHarvester>> +    <<event>> RewardProceedsTransferred(token: address, farmer: address, protcolYield: uint256, farmerFee: uint256) <<BaseHarvester>> +    <<event>> RewardProceedsAddressChanged(newProceedsAddress: address) <<BaseHarvester>> +    <<modifier>> onlyGovernor() <<Governable>> +    <<modifier>> nonReentrant() <<Governable>>    constructor() <<Governable>>    governor(): address <<Governable>>    isGovernor(): bool <<Governable>> -    constructor(_vault: address) <<OETHHarvester>> +    constructor(_vault: address, _wethAddress: address) <<OETHHarvester>> diff --git a/contracts/docs/OETHHarvesterStorage.svg b/contracts/docs/OETHHarvesterStorage.svg index 961cc78da0..efd66640bc 100644 --- a/contracts/docs/OETHHarvesterStorage.svg +++ b/contracts/docs/OETHHarvesterStorage.svg @@ -4,66 +4,106 @@ - - + + StorageDiagram - - + + -2 - -OETHHarvester <<Contract>> - -slot - -0 - -1 - -2 - -type: <inherited contract>.variable (bytes) - -mapping(address=>RewardTokenConfig): BaseHarvester.rewardTokenConfigs (32) - -mapping(address=>bool): BaseHarvester.supportedStrategies (32) - -unallocated (12) - -address: BaseHarvester.rewardProceedsAddress (20) +3 + +OETHHarvester <<Contract>> + +slot + +0 + +1 + +2 + +3 + +4 + +5 + +6 + +type: <inherited contract>.variable (bytes) + +mapping(address=>RewardTokenConfig): BaseHarvester.rewardTokenConfigs (32) + +mapping(address=>bool): BaseHarvester.supportedStrategies (32) + +unallocated (12) + +address: BaseHarvester.rewardProceedsAddress (20) + +mapping(address=>address[]): BaseHarvester.uniswapV2Path (32) + +mapping(address=>bytes): BaseHarvester.uniswapV3Path (32) + +mapping(address=>bytes32): BaseHarvester.balancerPoolId (32) + +mapping(address=>CurvePoolIndices): BaseHarvester.curvePoolIndices (32) 1 - -RewardTokenConfig <<Struct>> - -offset - -0 - -1 - -type: variable (bytes) - -unallocated (7) - -bool: doSwapRewardToken (1) - -address: uniswapV2CompatibleAddr (20) - -uint16: harvestRewardBps (2) - -uint16: allowedSlippageBps (2) - -uint256: liquidationLimit (32) + +RewardTokenConfig <<Struct>> + +offset + +0 + +1 + +type: variable (bytes) + +unallocated (6) + +SwapPlatform: swapPlatform (1) + +bool: doSwapRewardToken (1) + +address: swapPlatformAddr (20) + +uint16: harvestRewardBps (2) + +uint16: allowedSlippageBps (2) + +uint256: liquidationLimit (32) - + -2:6->1 - - +3:7->1 + + + + + +2 + +CurvePoolIndices <<Struct>> + +offset + +0 + +type: variable (bytes) + +uint128: baseTokenIndex (16) + +uint128: rewardTokenIndex (16) + + + +3:15->2 + + diff --git a/contracts/docs/OETHHierarchy.svg b/contracts/docs/OETHHierarchy.svg index 4ded593dc6..507830576d 100644 --- a/contracts/docs/OETHHierarchy.svg +++ b/contracts/docs/OETHHierarchy.svg @@ -9,78 +9,78 @@ UmlClassDiagram - + -7 +20 Governable ../contracts/governance/Governable.sol - + -141 +185 OETH ../contracts/token/OETH.sol - + -142 +186 OUSD ../contracts/token/OUSD.sol - + -141->142 +185->186 - + -142->7 +186->20 - + -149 +194 <<Abstract>> Initializable ../contracts/utils/Initializable.sol - + -142->149 +186->194 - + -151 +197 <<Abstract>> InitializableERC20Detailed ../contracts/utils/InitializableERC20Detailed.sol - + -142->151 +186->197 - + -436 +392 <<Interface>> IERC20 ../node_modules/@openzeppelin/contracts/token/ERC20/IERC20.sol - + -151->436 +197->392 diff --git a/contracts/docs/OETHOracleRouterHierarchy.svg b/contracts/docs/OETHOracleRouterHierarchy.svg index 1eefc3d304..ccdd8caf07 100644 --- a/contracts/docs/OETHOracleRouterHierarchy.svg +++ b/contracts/docs/OETHOracleRouterHierarchy.svg @@ -4,78 +4,65 @@ - - + + UmlClassDiagram - - + + -30 - -<<Interface>> -IOracle -../contracts/interfaces/IOracle.sol +48 + +<<Interface>> +IOracle +../contracts/interfaces/IOracle.sol - + -167 - -<<Interface>> -AggregatorV3Interface -../contracts/interfaces/chainlink/AggregatorV3Interface.sol +229 + +<<Interface>> +AggregatorV3Interface +../contracts/interfaces/chainlink/AggregatorV3Interface.sol - + -80 - -<<Abstract>> -OracleRouterBase -../contracts/oracle/OracleRouter.sol - - - -80->30 - - +116 + +OETHOracleRouter +../contracts/oracle/OETHOracleRouter.sol - + -80->167 - - +116->229 + + - + -81 - -OracleRouter -../contracts/oracle/OracleRouter.sol - - - -81->80 - - +118 + +<<Abstract>> +OracleRouterBase +../contracts/oracle/OracleRouterBase.sol - - -82 - -OETHOracleRouter -../contracts/oracle/OracleRouter.sol + + +116->118 + + - - -82->167 - - + + +118->48 + + - + -82->81 - - +118->229 + + diff --git a/contracts/docs/OETHOracleRouterSquashed.svg b/contracts/docs/OETHOracleRouterSquashed.svg index a445f60cbc..229d22bc32 100644 --- a/contracts/docs/OETHOracleRouterSquashed.svg +++ b/contracts/docs/OETHOracleRouterSquashed.svg @@ -4,37 +4,36 @@ - - + + UmlClassDiagram - - + + -82 - -OETHOracleRouter -../contracts/oracle/OracleRouter.sol - -Internal: -   decimalsCache: mapping(address=>uint8) <<OracleRouterBase>> -Public: -   MIN_DRIFT: uint256 <<OracleRouterBase>> -   MAX_DRIFT: uint256 <<OracleRouterBase>> -   FIXED_PRICE: address <<OracleRouterBase>> -   ZERO_ADDRESS: address <<OracleRouterBase>> - -Internal: -    feed(asset: address): address <<OracleRouter>> -    feed(asset_one: address, asset_two: address): address <<OETHOracleRouter>> -    _priceForFeedBase(_feed: address, isStablecoin: bool): uint256 <<OracleRouterBase>> +116 + +OETHOracleRouter +../contracts/oracle/OETHOracleRouter.sol + +Internal: +   MIN_DRIFT: uint256 <<OracleRouterBase>> +   MAX_DRIFT: uint256 <<OracleRouterBase>> +   FIXED_PRICE: address <<OracleRouterBase>> +   STALENESS_BUFFER: uint256 <<OracleRouterBase>> +   decimalsCache: mapping(address=>uint8) <<OracleRouterBase>> +Public: +   auraPriceFeed: address <<OETHOracleRouter>> + +Internal: +    feedMetadata(asset: address): (feedAddress: address, maxStaleness: uint256) <<OETHOracleRouter>>    getDecimals(_feed: address): uint8 <<OracleRouterBase>> -    isStablecoin(_asset: address): bool <<OracleRouterBase>> -    _priceForFeed(_feed: address): uint256 <<OETHOracleRouter>> -External: -    price(asset: address): uint256 <<OETHOracleRouter>> -    price(asset_one: address, asset_two: address): uint256 <<OETHOracleRouter>> -    cacheDecimals(asset_one: address, asset_two: address): uint8 <<OracleRouterBase>> +    shouldBePegged(_asset: address): bool <<OracleRouterBase>> +External: +    price(asset: address): uint256 <<OETHOracleRouter>> +    cacheDecimals(asset: address): uint8 <<OracleRouterBase>> +Public: +    constructor(_auraPriceFeed: address) <<OETHOracleRouter>> diff --git a/contracts/docs/OETHSquashed.svg b/contracts/docs/OETHSquashed.svg index 05f1a99df0..36093480d6 100644 --- a/contracts/docs/OETHSquashed.svg +++ b/contracts/docs/OETHSquashed.svg @@ -9,9 +9,9 @@ UmlClassDiagram - + -141 +185 OETH ../contracts/token/OETH.sol diff --git a/contracts/docs/OETHVaultAdminSquashed.svg b/contracts/docs/OETHVaultAdminSquashed.svg index 829aaeb60f..3a14885577 100644 --- a/contracts/docs/OETHVaultAdminSquashed.svg +++ b/contracts/docs/OETHVaultAdminSquashed.svg @@ -9,9 +9,9 @@ UmlClassDiagram - + -173 +200 OETHVaultAdmin ../contracts/vault/OETHVaultAdmin.sol diff --git a/contracts/docs/OETHVaultCoreSquashed.svg b/contracts/docs/OETHVaultCoreSquashed.svg index 4e78c26335..e2dbd8c5ef 100644 --- a/contracts/docs/OETHVaultCoreSquashed.svg +++ b/contracts/docs/OETHVaultCoreSquashed.svg @@ -9,9 +9,9 @@ UmlClassDiagram - + -174 +201 OETHVaultCore ../contracts/vault/OETHVaultCore.sol diff --git a/contracts/docs/OETHVaultHierarchy.svg b/contracts/docs/OETHVaultHierarchy.svg index f2bff71ea4..6e3546cc1d 100644 --- a/contracts/docs/OETHVaultHierarchy.svg +++ b/contracts/docs/OETHVaultHierarchy.svg @@ -9,261 +9,261 @@ UmlClassDiagram - + -7 +20 Governable ../contracts/governance/Governable.sol - + -26 +42 <<Interface>> IGetExchangeRateToken ../contracts/interfaces/IGetExchangeRateToken.sol - + -34 +48 <<Interface>> IOracle ../contracts/interfaces/IOracle.sol - + -37 +52 <<Interface>> IStrategy ../contracts/interfaces/IStrategy.sol - + -38 +53 <<Interface>> ISwapper ../contracts/interfaces/ISwapper.sol - + -40 +55 <<Interface>> IVault ../contracts/interfaces/IVault.sol - + -180 +207 VaultStorage ../contracts/vault/VaultStorage.sol - + -40->180 +55->207 - + -160 +186 OUSD ../contracts/token/OUSD.sol - + -160->7 +186->20 - + -167 +194 <<Abstract>> Initializable ../contracts/utils/Initializable.sol - + -160->167 +186->194 - + -170 +197 <<Abstract>> InitializableERC20Detailed ../contracts/utils/InitializableERC20Detailed.sol - + -160->170 +186->197 - + -342 +392 <<Interface>> IERC20 ../node_modules/@openzeppelin/contracts/token/ERC20/IERC20.sol - + -170->342 +197->392 - + -173 +200 OETHVaultAdmin ../contracts/vault/OETHVaultAdmin.sol - + -177 +204 VaultAdmin ../contracts/vault/VaultAdmin.sol - + -173->177 +200->204 - + -174 +201 OETHVaultCore ../contracts/vault/OETHVaultCore.sol - + -178 +205 VaultCore ../contracts/vault/VaultCore.sol - + -174->178 +201->205 - + -177->34 +204->48 - + -177->37 +204->52 - + -177->38 +204->53 - + -177->40 +204->55 - + -177->180 +204->207 - + -177->342 +204->392 - + -178->26 +205->42 - + -178->34 +205->48 - + -178->37 +205->52 - + -179 +206 VaultInitializer ../contracts/vault/VaultInitializer.sol - + -178->179 +205->206 - + -178->342 +205->392 - + -179->160 +206->186 - + -179->180 +206->207 - + -180->7 +207->20 - + -180->160 +207->186 - + -180->167 +207->194 diff --git a/contracts/docs/OUSDBuybackHierarchy.svg b/contracts/docs/OUSDBuybackHierarchy.svg new file mode 100644 index 0000000000..f086c7dc28 --- /dev/null +++ b/contracts/docs/OUSDBuybackHierarchy.svg @@ -0,0 +1,116 @@ + + + + + + +UmlClassDiagram + + + +0 + +<<Abstract>> +BaseBuyback +../contracts/buyback/BaseBuyback.sol + + + +25 + +Strategizable +../contracts/governance/Strategizable.sol + + + +0->25 + + + + + +37 + +<<Interface>> +ICVXLocker +../contracts/interfaces/ICVXLocker.sol + + + +0->37 + + + + + +243 + +<<Interface>> +IUniswapUniversalRouter +../contracts/interfaces/uniswap/IUniswapUniversalRouter.sol + + + +0->243 + + + + + +194 + +<<Abstract>> +Initializable +../contracts/utils/Initializable.sol + + + +0->194 + + + + + +392 + +<<Interface>> +IERC20 +../node_modules/@openzeppelin/contracts/token/ERC20/IERC20.sol + + + +0->392 + + + + + +2 + +OUSDBuyback +../contracts/buyback/OUSDBuyback.sol + + + +2->0 + + + + + +20 + +Governable +../contracts/governance/Governable.sol + + + +25->20 + + + + + diff --git a/contracts/docs/OUSDBuybackSquashed.svg b/contracts/docs/OUSDBuybackSquashed.svg new file mode 100644 index 0000000000..4922f4a525 --- /dev/null +++ b/contracts/docs/OUSDBuybackSquashed.svg @@ -0,0 +1,89 @@ + + + + + + +UmlClassDiagram + + + +2 + +OUSDBuyback +../contracts/buyback/OUSDBuyback.sol + +Private: +   initialized: bool <<Initializable>> +   initializing: bool <<Initializable>> +   ______gap: uint256[50] <<Initializable>> +   governorPosition: bytes32 <<Governable>> +   pendingGovernorPosition: bytes32 <<Governable>> +   reentryStatusPosition: bytes32 <<Governable>> +   __gap: uint256[50] <<Strategizable>> +   __deprecated_ousd: address <<BaseBuyback>> +   __deprecated_ogv: address <<BaseBuyback>> +   __deprecated_usdt: address <<BaseBuyback>> +   __deprecated_weth9: address <<BaseBuyback>> +   __deprecated_treasuryBps: uint256 <<BaseBuyback>> +   swapCommand: bytes <<BaseBuyback>> +Public: +   _NOT_ENTERED: uint256 <<Governable>> +   _ENTERED: uint256 <<Governable>> +   strategistAddr: address <<Strategizable>> +   universalRouter: address <<BaseBuyback>> +   rewardsSource: address <<BaseBuyback>> +   treasuryManager: address <<BaseBuyback>> +   oToken: address <<BaseBuyback>> +   ogv: address <<BaseBuyback>> +   cvx: address <<BaseBuyback>> +   cvxLocker: address <<BaseBuyback>> +   ogvPath: bytes <<OUSDBuyback>> +   cvxPath: bytes <<OUSDBuyback>> + +Internal: +    _governor(): (governorOut: address) <<Governable>> +    _pendingGovernor(): (pendingGovernor: address) <<Governable>> +    _setGovernor(newGovernor: address) <<Governable>> +    _setPendingGovernor(newGovernor: address) <<Governable>> +    _changeGovernor(_newGovernor: address) <<Governable>> +    _setStrategistAddr(_address: address) <<Strategizable>> +    _setUniswapUniversalRouter(_router: address) <<BaseBuyback>> +    _setRewardsSource(_address: address) <<BaseBuyback>> +    _setTreasuryManager(_address: address) <<BaseBuyback>> +    _lockAllCVX() <<BaseBuyback>> +    _getSwapPath(toToken: address): (path: bytes) <<OUSDBuyback>> +External: +    transferGovernance(_newGovernor: address) <<onlyGovernor>> <<Governable>> +    claimGovernance() <<Governable>> +    setStrategistAddr(_address: address) <<onlyGovernor>> <<Strategizable>> +    initialize(_uniswapUniversalRouter: address, _strategistAddr: address, _treasuryManagerAddr: address, _rewardsSource: address) <<onlyGovernor, initializer>> <<BaseBuyback>> +    setUniswapUniversalRouter(_router: address) <<onlyGovernor>> <<BaseBuyback>> +    setRewardsSource(_address: address) <<onlyGovernor>> <<BaseBuyback>> +    setTreasuryManager(_address: address) <<onlyGovernor>> <<BaseBuyback>> +    swap(oTokenAmount: uint256, minOGV: uint256, minCVX: uint256) <<onlyGovernorOrStrategist, nonReentrant>> <<BaseBuyback>> +    lockAllCVX() <<onlyGovernorOrStrategist>> <<BaseBuyback>> +    safeApproveAllTokens() <<onlyGovernorOrStrategist>> <<BaseBuyback>> +    transferToken(token: address, amount: uint256) <<onlyGovernor, nonReentrant>> <<BaseBuyback>> +Public: +    <<event>> PendingGovernorshipTransfer(previousGovernor: address, newGovernor: address) <<Governable>> +    <<event>> GovernorshipTransferred(previousGovernor: address, newGovernor: address) <<Governable>> +    <<event>> StrategistUpdated(_address: address) <<Strategizable>> +    <<event>> UniswapUniversalRouterUpdated(_address: address) <<BaseBuyback>> +    <<event>> RewardsSourceUpdated(_address: address) <<BaseBuyback>> +    <<event>> TreasuryManagerUpdated(_address: address) <<BaseBuyback>> +    <<event>> OTokenBuyback(oToken: address, swappedFor: address, swapAmountIn: uint256, minExpected: uint256) <<BaseBuyback>> +    <<modifier>> initializer() <<Initializable>> +    <<modifier>> onlyGovernor() <<Governable>> +    <<modifier>> nonReentrant() <<Governable>> +    <<modifier>> onlyGovernorOrStrategist() <<Strategizable>> +    constructor() <<Governable>> +    governor(): address <<Governable>> +    isGovernor(): bool <<Governable>> +    constructor(_oToken: address, _ogv: address, _cvx: address, _cvxLocker: address) <<OUSDBuyback>> + + + diff --git a/contracts/docs/OUSDBuybackStorage.svg b/contracts/docs/OUSDBuybackStorage.svg new file mode 100644 index 0000000000..b89da451e7 --- /dev/null +++ b/contracts/docs/OUSDBuybackStorage.svg @@ -0,0 +1,163 @@ + + + + + + +StorageDiagram + + + +3 + +OUSDBuyback <<Contract>> + +slot + +0 + +1-50 + +51 + +52-101 + +102 + +103 + +104 + +105 + +106 + +107 + +108 + +109 + +type: <inherited contract>.variable (bytes) + +unallocated (30) + +bool: Initializable.initializing (1) + +bool: Initializable.initialized (1) + +uint256[50]: Initializable.______gap (1600) + +unallocated (12) + +address: Strategizable.strategistAddr (20) + +uint256[50]: Strategizable.__gap (1600) + +unallocated (12) + +address: BaseBuyback.universalRouter (20) + +unallocated (12) + +address: BaseBuyback.__deprecated_ousd (20) + +unallocated (12) + +address: BaseBuyback.__deprecated_ogv (20) + +unallocated (12) + +address: BaseBuyback.__deprecated_usdt (20) + +unallocated (12) + +address: BaseBuyback.__deprecated_weth9 (20) + +unallocated (12) + +address: BaseBuyback.rewardsSource (20) + +unallocated (12) + +address: BaseBuyback.treasuryManager (20) + +uint256: BaseBuyback.__deprecated_treasuryBps (32) + + + +1 + +uint256[50]: ______gap <<Array>> + +slot + +1 + +2 + +3-48 + +49 + +50 + +type: variable (bytes) + +uint256 (32) + +uint256 (32) + +---- (1472) + +uint256 (32) + +uint256 (32) + + + +3:8->1 + + + + + +2 + +uint256[50]: __gap <<Array>> + +slot + +52 + +53 + +54-99 + +100 + +101 + +type: variable (bytes) + +uint256 (32) + +uint256 (32) + +---- (1472) + +uint256 (32) + +uint256 (32) + + + +3:15->2 + + + + + diff --git a/contracts/docs/OUSDHierarchy.svg b/contracts/docs/OUSDHierarchy.svg index 3618eefd72..69256b4124 100644 --- a/contracts/docs/OUSDHierarchy.svg +++ b/contracts/docs/OUSDHierarchy.svg @@ -9,65 +9,65 @@ UmlClassDiagram - + -7 +20 Governable ../contracts/governance/Governable.sol - + -142 +186 OUSD ../contracts/token/OUSD.sol - + -142->7 +186->20 - + -149 +194 <<Abstract>> Initializable ../contracts/utils/Initializable.sol - + -142->149 +186->194 - + -151 +197 <<Abstract>> InitializableERC20Detailed ../contracts/utils/InitializableERC20Detailed.sol - + -142->151 +186->197 - + -436 +392 <<Interface>> IERC20 ../node_modules/@openzeppelin/contracts/token/ERC20/IERC20.sol - + -151->436 +197->392 diff --git a/contracts/docs/OUSDProxyHierarchy.svg b/contracts/docs/OUSDProxyHierarchy.svg index 6ba9cf5f79..af7b172c35 100644 --- a/contracts/docs/OUSDProxyHierarchy.svg +++ b/contracts/docs/OUSDProxyHierarchy.svg @@ -9,36 +9,36 @@ UmlClassDiagram - + -7 +20 Governable ../contracts/governance/Governable.sol - + -84 +119 InitializeGovernedUpgradeabilityProxy ../contracts/proxies/InitializeGovernedUpgradeabilityProxy.sol - + -84->7 +119->20 - + -85 +120 OUSDProxy ../contracts/proxies/Proxies.sol - + -85->84 +120->119 diff --git a/contracts/docs/OUSDProxySquashed.svg b/contracts/docs/OUSDProxySquashed.svg index c815dea4ff..7bb2a5a97c 100644 --- a/contracts/docs/OUSDProxySquashed.svg +++ b/contracts/docs/OUSDProxySquashed.svg @@ -9,9 +9,9 @@ UmlClassDiagram - + -85 +120 OUSDProxy ../contracts/proxies/Proxies.sol diff --git a/contracts/docs/OUSDSquashed.svg b/contracts/docs/OUSDSquashed.svg index 7f3a7ac423..bf4f7032c0 100644 --- a/contracts/docs/OUSDSquashed.svg +++ b/contracts/docs/OUSDSquashed.svg @@ -9,9 +9,9 @@ UmlClassDiagram - + -142 +186 OUSD ../contracts/token/OUSD.sol diff --git a/contracts/docs/OracleRouterHierarchy.svg b/contracts/docs/OracleRouterHierarchy.svg new file mode 100644 index 0000000000..e8815f39a5 --- /dev/null +++ b/contracts/docs/OracleRouterHierarchy.svg @@ -0,0 +1,62 @@ + + + + + + +UmlClassDiagram + + + +48 + +<<Interface>> +IOracle +../contracts/interfaces/IOracle.sol + + + +229 + +<<Interface>> +AggregatorV3Interface +../contracts/interfaces/chainlink/AggregatorV3Interface.sol + + + +117 + +OracleRouter +../contracts/oracle/OracleRouter.sol + + + +118 + +<<Abstract>> +OracleRouterBase +../contracts/oracle/OracleRouterBase.sol + + + +117->118 + + + + + +118->48 + + + + + +118->229 + + + + + diff --git a/contracts/docs/OracleRouterSquashed.svg b/contracts/docs/OracleRouterSquashed.svg new file mode 100644 index 0000000000..86e5008061 --- /dev/null +++ b/contracts/docs/OracleRouterSquashed.svg @@ -0,0 +1,35 @@ + + + + + + +UmlClassDiagram + + + +117 + +OracleRouter +../contracts/oracle/OracleRouter.sol + +Internal: +   MIN_DRIFT: uint256 <<OracleRouterBase>> +   MAX_DRIFT: uint256 <<OracleRouterBase>> +   FIXED_PRICE: address <<OracleRouterBase>> +   STALENESS_BUFFER: uint256 <<OracleRouterBase>> +   decimalsCache: mapping(address=>uint8) <<OracleRouterBase>> + +Internal: +    feedMetadata(asset: address): (feedAddress: address, maxStaleness: uint256) <<OracleRouter>> +    getDecimals(_feed: address): uint8 <<OracleRouterBase>> +    shouldBePegged(_asset: address): bool <<OracleRouterBase>> +External: +    price(asset: address): uint256 <<OracleRouterBase>> +    cacheDecimals(asset: address): uint8 <<OracleRouterBase>> + + + diff --git a/contracts/docs/OracleRouterStorage.svg b/contracts/docs/OracleRouterStorage.svg new file mode 100644 index 0000000000..c3fe4faff3 --- /dev/null +++ b/contracts/docs/OracleRouterStorage.svg @@ -0,0 +1,27 @@ + + + + + + +StorageDiagram + + + +1 + +OracleRouter <<Contract>> + +slot + +0 + +type: <inherited contract>.variable (bytes) + +mapping(address=>uint8): OracleRouterBase.decimalsCache (32) + + + diff --git a/contracts/docs/StableMathHierarchy.svg b/contracts/docs/StableMathHierarchy.svg index c4ca244fcb..305ecdd3ce 100644 --- a/contracts/docs/StableMathHierarchy.svg +++ b/contracts/docs/StableMathHierarchy.svg @@ -9,25 +9,25 @@ UmlClassDiagram - + -169 +198 <<Library>> StableMath ../contracts/utils/StableMath.sol - + -366 +418 <<Library>> SafeMath ../node_modules/@openzeppelin/contracts/utils/math/SafeMath.sol - + -169->366 +198->418 diff --git a/contracts/docs/StableMathSquashed.svg b/contracts/docs/StableMathSquashed.svg index 2351e8510f..03570d9940 100644 --- a/contracts/docs/StableMathSquashed.svg +++ b/contracts/docs/StableMathSquashed.svg @@ -9,9 +9,9 @@ UmlClassDiagram - + -169 +198 StableMath ../contracts/utils/StableMath.sol diff --git a/contracts/docs/Swapper1InchV5Hierarchy.svg b/contracts/docs/Swapper1InchV5Hierarchy.svg index 3eb377824b..3dd6ab0860 100644 --- a/contracts/docs/Swapper1InchV5Hierarchy.svg +++ b/contracts/docs/Swapper1InchV5Hierarchy.svg @@ -9,78 +9,78 @@ UmlClassDiagram - + -31 +46 <<Interface>> IAggregationExecutor ../contracts/interfaces/IOneInch.sol - + -32 +47 <<Interface>> IOneInchRouter ../contracts/interfaces/IOneInch.sol - + -32->31 +47->46 - + -431 +392 <<Interface>> IERC20 ../node_modules/@openzeppelin/contracts/token/ERC20/IERC20.sol - + -32->431 +47->392 - + -37 +53 <<Interface>> ISwapper ../contracts/interfaces/ISwapper.sol - + -148 +182 Swapper1InchV5 ../contracts/swapper/Swapper1InchV5.sol - + -148->31 +182->46 - + -148->32 +182->47 - + -148->37 +182->53 - + -148->431 +182->392 diff --git a/contracts/docs/Swapper1InchV5Squashed.svg b/contracts/docs/Swapper1InchV5Squashed.svg index ea58e9a165..88a3764ea3 100644 --- a/contracts/docs/Swapper1InchV5Squashed.svg +++ b/contracts/docs/Swapper1InchV5Squashed.svg @@ -9,9 +9,9 @@ UmlClassDiagram - + -148 +182 Swapper1InchV5 ../contracts/swapper/Swapper1InchV5.sol diff --git a/contracts/docs/TimelockHierarchy.svg b/contracts/docs/TimelockHierarchy.svg index 700f84df38..71da4a94f8 100644 --- a/contracts/docs/TimelockHierarchy.svg +++ b/contracts/docs/TimelockHierarchy.svg @@ -9,24 +9,24 @@ UmlClassDiagram - + -139 +183 <<Interface>> CapitalPausable ../contracts/timelock/Timelock.sol - + -140 +184 Timelock ../contracts/timelock/Timelock.sol - + -140->139 +184->183 diff --git a/contracts/docs/TimelockSquashed.svg b/contracts/docs/TimelockSquashed.svg index cd020579e6..4758366787 100644 --- a/contracts/docs/TimelockSquashed.svg +++ b/contracts/docs/TimelockSquashed.svg @@ -9,9 +9,9 @@ UmlClassDiagram - + -140 +184 Timelock ../contracts/timelock/Timelock.sol diff --git a/contracts/docs/VaultAdminSquashed.svg b/contracts/docs/VaultAdminSquashed.svg index 3b55c3ea01..939e2c043f 100644 --- a/contracts/docs/VaultAdminSquashed.svg +++ b/contracts/docs/VaultAdminSquashed.svg @@ -9,9 +9,9 @@ UmlClassDiagram - + -177 +204 VaultAdmin ../contracts/vault/VaultAdmin.sol diff --git a/contracts/docs/VaultCoreSquashed.svg b/contracts/docs/VaultCoreSquashed.svg index 67e3533b62..64e07ea5fe 100644 --- a/contracts/docs/VaultCoreSquashed.svg +++ b/contracts/docs/VaultCoreSquashed.svg @@ -9,9 +9,9 @@ UmlClassDiagram - + -178 +205 VaultCore ../contracts/vault/VaultCore.sol diff --git a/contracts/docs/VaultHierarchy.svg b/contracts/docs/VaultHierarchy.svg index e268db6325..626d89b4bb 100644 --- a/contracts/docs/VaultHierarchy.svg +++ b/contracts/docs/VaultHierarchy.svg @@ -9,235 +9,235 @@ UmlClassDiagram - + -7 +20 Governable ../contracts/governance/Governable.sol - + -26 +42 <<Interface>> IGetExchangeRateToken ../contracts/interfaces/IGetExchangeRateToken.sol - + -34 +48 <<Interface>> IOracle ../contracts/interfaces/IOracle.sol - + -37 +52 <<Interface>> IStrategy ../contracts/interfaces/IStrategy.sol - + -38 +53 <<Interface>> ISwapper ../contracts/interfaces/ISwapper.sol - + -40 +55 <<Interface>> IVault ../contracts/interfaces/IVault.sol - + -180 +207 VaultStorage ../contracts/vault/VaultStorage.sol - + -40->180 +55->207 - + -160 +186 OUSD ../contracts/token/OUSD.sol - + -160->7 +186->20 - + -167 +194 <<Abstract>> Initializable ../contracts/utils/Initializable.sol - + -160->167 +186->194 - + -170 +197 <<Abstract>> InitializableERC20Detailed ../contracts/utils/InitializableERC20Detailed.sol - + -160->170 +186->197 - + -342 +392 <<Interface>> IERC20 ../node_modules/@openzeppelin/contracts/token/ERC20/IERC20.sol - + -170->342 +197->392 - + -177 +204 VaultAdmin ../contracts/vault/VaultAdmin.sol - + -177->34 +204->48 - + -177->37 +204->52 - + -177->38 +204->53 - + -177->40 +204->55 - + -177->180 +204->207 - + -177->342 +204->392 - + -178 +205 VaultCore ../contracts/vault/VaultCore.sol - + -178->26 +205->42 - + -178->34 +205->48 - + -178->37 +205->52 - + -179 +206 VaultInitializer ../contracts/vault/VaultInitializer.sol - + -178->179 +205->206 - + -178->342 +205->392 - + -179->160 +206->186 - + -179->180 +206->207 - + -180->7 +207->20 - + -180->160 +207->186 - + -180->167 +207->194 diff --git a/contracts/docs/WOETHHierarchy.svg b/contracts/docs/WOETHHierarchy.svg index 2941f86f71..ba45140737 100644 --- a/contracts/docs/WOETHHierarchy.svg +++ b/contracts/docs/WOETHHierarchy.svg @@ -9,214 +9,214 @@ UmlClassDiagram - + -7 +20 Governable ../contracts/governance/Governable.sol - + -141 +185 OETH ../contracts/token/OETH.sol - + -142 +186 OUSD ../contracts/token/OUSD.sol - + -141->142 +185->186 - + -142->7 +186->20 - + -149 +194 <<Abstract>> Initializable ../contracts/utils/Initializable.sol - + -142->149 +186->194 - + -151 +197 <<Abstract>> InitializableERC20Detailed ../contracts/utils/InitializableERC20Detailed.sol - + -142->151 +186->197 - + -146 +190 WOETH ../contracts/token/WOETH.sol - + -146->7 +190->20 - + -146->141 +190->185 - + -146->149 +190->194 - + -769 +745 <<Abstract>> ERC4626 ../lib/openzeppelin/contracts/token/ERC20/extensions/ERC4626.sol - + -146->769 +190->745 - + -435 +391 ERC20 ../node_modules/@openzeppelin/contracts/token/ERC20/ERC20.sol - + -146->435 +190->391 - + -436 +392 <<Interface>> IERC20 ../node_modules/@openzeppelin/contracts/token/ERC20/IERC20.sol - + -146->436 +190->392 - + -151->436 +197->392 - + -198 +268 <<Interface>> IERC4626 ../lib/openzeppelin/interfaces/IERC4626.sol - + -769->198 +745->268 - + -769->435 +745->391 - + -787 +763 <<Interface>> IERC20Metadata ../node_modules/@openzeppelin/contracts/token/ERC20/extensions/IERC20Metadata.sol - + -769->787 +745->763 - + -198->436 +268->392 - + -198->787 +268->763 - + -435->436 +391->392 - + -435->787 +391->763 - + -365 +319 <<Abstract>> Context ../node_modules/@openzeppelin/contracts/utils/Context.sol - + -435->365 +391->319 - + -787->436 +763->392 diff --git a/contracts/docs/WOETHSquashed.svg b/contracts/docs/WOETHSquashed.svg index a821c01f68..28524ff286 100644 --- a/contracts/docs/WOETHSquashed.svg +++ b/contracts/docs/WOETHSquashed.svg @@ -4,63 +4,77 @@ - - + + UmlClassDiagram - - + + -146 - -WOETH -../contracts/token/WOETH.sol - -Private: -   _asset: IERC20Metadata <<ERC4626>> -   governorPosition: bytes32 <<Governable>> -   pendingGovernorPosition: bytes32 <<Governable>> -   reentryStatusPosition: bytes32 <<Governable>> -   initialized: bool <<Initializable>> -   initializing: bool <<Initializable>> -   ______gap: uint256[50] <<Initializable>> -Public: -   _NOT_ENTERED: uint256 <<Governable>> -   _ENTERED: uint256 <<Governable>> - -Internal: -    _spendAllowance(owner: address, spender: address, amount: uint256) <<ERC4626>> -    _governor(): (governorOut: address) <<Governable>> -    _pendingGovernor(): (pendingGovernor: address) <<Governable>> -    _setGovernor(newGovernor: address) <<Governable>> -    _setPendingGovernor(newGovernor: address) <<Governable>> -    _changeGovernor(_newGovernor: address) <<Governable>> -External: -     allowance(owner: address, spender: address): uint256 <<IERC20>> -     totalSupply(): uint256 <<IERC20>> -     balanceOf(account: address): uint256 <<IERC20>> -     transfer(recipient: address, amount: uint256): bool <<IERC20>> -     approve(spender: address, amount: uint256): bool <<IERC20>> -     transferFrom(sender: address, recipient: address, amount: uint256): bool <<IERC20>> -     decimals(): uint8 <<IERC20Metadata>> -    transferGovernance(_newGovernor: address) <<onlyGovernor>> <<Governable>> -    claimGovernance() <<Governable>> -    initialize() <<onlyGovernor, initializer>> <<WOETH>> -    transferToken(asset_: address, amount_: uint256) <<onlyGovernor>> <<WOETH>> -Public: -     transferFrom(from: address, to: address, value: uint256) <<ERC20>> -     approve(spender: address, value: uint256) <<ERC20>> -    <<event>> Approval(owner: address, spender: address, value: uint256) <<IERC20>> -    <<event>> Transfer(from: address, to: address, value: uint256) <<IERC20>> -    <<event>> Deposit(caller: address, owner: address, assets: uint256, shares: uint256) <<IERC4626>> -    <<event>> Withdraw(caller: address, receiver: address, owner: address, assets: uint256, shares: uint256) <<IERC4626>> -    <<event>> PendingGovernorshipTransfer(previousGovernor: address, newGovernor: address) <<Governable>> -    <<event>> GovernorshipTransferred(previousGovernor: address, newGovernor: address) <<Governable>> -    <<modifier>> onlyGovernor() <<Governable>> -    <<modifier>> nonReentrant() <<Governable>> -    <<modifier>> initializer() <<Initializable>> -    name(): string <<WOETH>> -    symbol(): string <<WOETH>> +190 + +WOETH +../contracts/token/WOETH.sol + +Private: +   _balances: mapping(address=>uint256) <<ERC20>> +   _allowances: mapping(address=>mapping(address=>uint256)) <<ERC20>> +   _totalSupply: uint256 <<ERC20>> +   _name: string <<ERC20>> +   _symbol: string <<ERC20>> +   _asset: IERC20Metadata <<ERC4626>> +   governorPosition: bytes32 <<Governable>> +   pendingGovernorPosition: bytes32 <<Governable>> +   reentryStatusPosition: bytes32 <<Governable>> +   initialized: bool <<Initializable>> +   initializing: bool <<Initializable>> +   ______gap: uint256[50] <<Initializable>> +Public: +   _NOT_ENTERED: uint256 <<Governable>> +   _ENTERED: uint256 <<Governable>> + +Internal: +    _msgSender(): address <<Context>> +    _msgData(): bytes <<Context>> +    _transfer(sender: address, recipient: address, amount: uint256) <<ERC20>> +    _mint(account: address, amount: uint256) <<ERC20>> +    _burn(account: address, amount: uint256) <<ERC20>> +    _approve(owner: address, spender: address, amount: uint256) <<ERC20>> +    _beforeTokenTransfer(from: address, to: address, amount: uint256) <<ERC20>> +    _afterTokenTransfer(from: address, to: address, amount: uint256) <<ERC20>> +    _spendAllowance(owner: address, spender: address, amount: uint256) <<ERC4626>> +    _governor(): (governorOut: address) <<Governable>> +    _pendingGovernor(): (pendingGovernor: address) <<Governable>> +    _setGovernor(newGovernor: address) <<Governable>> +    _setPendingGovernor(newGovernor: address) <<Governable>> +    _changeGovernor(_newGovernor: address) <<Governable>> +External: +    transferGovernance(_newGovernor: address) <<onlyGovernor>> <<Governable>> +    claimGovernance() <<Governable>> +    initialize() <<onlyGovernor, initializer>> <<WOETH>> +    transferToken(asset_: address, amount_: uint256) <<onlyGovernor>> <<WOETH>> +Public: +    <<event>> Transfer(from: address, to: address, value: uint256) <<IERC20>> +    <<event>> Approval(owner: address, spender: address, value: uint256) <<IERC20>> +    <<event>> Deposit(caller: address, owner: address, assets: uint256, shares: uint256) <<IERC4626>> +    <<event>> Withdraw(caller: address, receiver: address, owner: address, assets: uint256, shares: uint256) <<IERC4626>> +    <<event>> PendingGovernorshipTransfer(previousGovernor: address, newGovernor: address) <<Governable>> +    <<event>> GovernorshipTransferred(previousGovernor: address, newGovernor: address) <<Governable>> +    <<modifier>> onlyGovernor() <<Governable>> +    <<modifier>> nonReentrant() <<Governable>> +    <<modifier>> initializer() <<Initializable>> +    totalSupply(): uint256 <<ERC20>> +    balanceOf(account: address): uint256 <<ERC20>> +    transfer(recipient: address, amount: uint256): bool <<ERC20>> +    allowance(owner: address, spender: address): uint256 <<ERC20>> +    approve(spender: address, amount: uint256): bool <<ERC20>> +    transferFrom(sender: address, recipient: address, amount: uint256): bool <<ERC20>> +    name(): string <<WOETH>> +    symbol(): string <<WOETH>> +    decimals(): uint8 <<ERC20>> +    constructor(name_: string, symbol_: string) <<ERC20>> +    increaseAllowance(spender: address, addedValue: uint256): bool <<ERC20>> +    decreaseAllowance(spender: address, subtractedValue: uint256): bool <<ERC20>>    asset(): address <<ERC4626>>    totalAssets(): uint256 <<ERC4626>>    convertToShares(assets: uint256): (shares: uint256) <<ERC4626>> diff --git a/contracts/docs/WOUSDHierarchy.svg b/contracts/docs/WOUSDHierarchy.svg index e6273f00f5..9b7ccc207b 100644 --- a/contracts/docs/WOUSDHierarchy.svg +++ b/contracts/docs/WOUSDHierarchy.svg @@ -9,201 +9,201 @@ UmlClassDiagram - + -7 +20 Governable ../contracts/governance/Governable.sol - + -142 +186 OUSD ../contracts/token/OUSD.sol - + -142->7 +186->20 - + -149 +194 <<Abstract>> Initializable ../contracts/utils/Initializable.sol - + -142->149 +186->194 - + -151 +197 <<Abstract>> InitializableERC20Detailed ../contracts/utils/InitializableERC20Detailed.sol - + -142->151 +186->197 - + -147 +191 WrappedOusd ../contracts/token/WrappedOusd.sol - + -147->7 +191->20 - + -147->142 +191->186 - + -147->149 +191->194 - + -769 +745 <<Abstract>> ERC4626 ../lib/openzeppelin/contracts/token/ERC20/extensions/ERC4626.sol - + -147->769 +191->745 - + -435 +391 ERC20 ../node_modules/@openzeppelin/contracts/token/ERC20/ERC20.sol - + -147->435 +191->391 - + -436 +392 <<Interface>> IERC20 ../node_modules/@openzeppelin/contracts/token/ERC20/IERC20.sol - + -147->436 +191->392 - + -151->436 +197->392 - + -198 +268 <<Interface>> IERC4626 ../lib/openzeppelin/interfaces/IERC4626.sol - + -769->198 +745->268 - + -769->435 +745->391 - + -787 +763 <<Interface>> IERC20Metadata ../node_modules/@openzeppelin/contracts/token/ERC20/extensions/IERC20Metadata.sol - + -769->787 +745->763 - + -198->436 +268->392 - + -198->787 +268->763 - + -435->436 +391->392 - + -435->787 +391->763 - + -365 +319 <<Abstract>> Context ../node_modules/@openzeppelin/contracts/utils/Context.sol - + -435->365 +391->319 - + -787->436 +763->392 diff --git a/contracts/docs/WOUSDSquashed.svg b/contracts/docs/WOUSDSquashed.svg index edb48090bd..f8f81d23cb 100644 --- a/contracts/docs/WOUSDSquashed.svg +++ b/contracts/docs/WOUSDSquashed.svg @@ -4,63 +4,77 @@ - - + + UmlClassDiagram - - + + -147 - -WrappedOusd -../contracts/token/WrappedOusd.sol - -Private: -   _asset: IERC20Metadata <<ERC4626>> -   governorPosition: bytes32 <<Governable>> -   pendingGovernorPosition: bytes32 <<Governable>> -   reentryStatusPosition: bytes32 <<Governable>> -   initialized: bool <<Initializable>> -   initializing: bool <<Initializable>> -   ______gap: uint256[50] <<Initializable>> -Public: -   _NOT_ENTERED: uint256 <<Governable>> -   _ENTERED: uint256 <<Governable>> - -Internal: -    _spendAllowance(owner: address, spender: address, amount: uint256) <<ERC4626>> -    _governor(): (governorOut: address) <<Governable>> -    _pendingGovernor(): (pendingGovernor: address) <<Governable>> -    _setGovernor(newGovernor: address) <<Governable>> -    _setPendingGovernor(newGovernor: address) <<Governable>> -    _changeGovernor(_newGovernor: address) <<Governable>> -External: -     allowance(owner: address, spender: address): uint256 <<IERC20>> -     totalSupply(): uint256 <<IERC20>> -     balanceOf(account: address): uint256 <<IERC20>> -     transfer(recipient: address, amount: uint256): bool <<IERC20>> -     approve(spender: address, amount: uint256): bool <<IERC20>> -     transferFrom(sender: address, recipient: address, amount: uint256): bool <<IERC20>> -     decimals(): uint8 <<IERC20Metadata>> -    transferGovernance(_newGovernor: address) <<onlyGovernor>> <<Governable>> -    claimGovernance() <<Governable>> -    initialize() <<onlyGovernor, initializer>> <<WrappedOusd>> -    transferToken(asset_: address, amount_: uint256) <<onlyGovernor>> <<WrappedOusd>> -Public: -     transferFrom(from: address, to: address, value: uint256) <<ERC20>> -     approve(spender: address, value: uint256) <<ERC20>> -    <<event>> Approval(owner: address, spender: address, value: uint256) <<IERC20>> -    <<event>> Transfer(from: address, to: address, value: uint256) <<IERC20>> -    <<event>> Deposit(caller: address, owner: address, assets: uint256, shares: uint256) <<IERC4626>> -    <<event>> Withdraw(caller: address, receiver: address, owner: address, assets: uint256, shares: uint256) <<IERC4626>> -    <<event>> PendingGovernorshipTransfer(previousGovernor: address, newGovernor: address) <<Governable>> -    <<event>> GovernorshipTransferred(previousGovernor: address, newGovernor: address) <<Governable>> -    <<modifier>> onlyGovernor() <<Governable>> -    <<modifier>> nonReentrant() <<Governable>> -    <<modifier>> initializer() <<Initializable>> -    name(): string <<WrappedOusd>> -    symbol(): string <<WrappedOusd>> +191 + +WrappedOusd +../contracts/token/WrappedOusd.sol + +Private: +   _balances: mapping(address=>uint256) <<ERC20>> +   _allowances: mapping(address=>mapping(address=>uint256)) <<ERC20>> +   _totalSupply: uint256 <<ERC20>> +   _name: string <<ERC20>> +   _symbol: string <<ERC20>> +   _asset: IERC20Metadata <<ERC4626>> +   governorPosition: bytes32 <<Governable>> +   pendingGovernorPosition: bytes32 <<Governable>> +   reentryStatusPosition: bytes32 <<Governable>> +   initialized: bool <<Initializable>> +   initializing: bool <<Initializable>> +   ______gap: uint256[50] <<Initializable>> +Public: +   _NOT_ENTERED: uint256 <<Governable>> +   _ENTERED: uint256 <<Governable>> + +Internal: +    _msgSender(): address <<Context>> +    _msgData(): bytes <<Context>> +    _transfer(sender: address, recipient: address, amount: uint256) <<ERC20>> +    _mint(account: address, amount: uint256) <<ERC20>> +    _burn(account: address, amount: uint256) <<ERC20>> +    _approve(owner: address, spender: address, amount: uint256) <<ERC20>> +    _beforeTokenTransfer(from: address, to: address, amount: uint256) <<ERC20>> +    _afterTokenTransfer(from: address, to: address, amount: uint256) <<ERC20>> +    _spendAllowance(owner: address, spender: address, amount: uint256) <<ERC4626>> +    _governor(): (governorOut: address) <<Governable>> +    _pendingGovernor(): (pendingGovernor: address) <<Governable>> +    _setGovernor(newGovernor: address) <<Governable>> +    _setPendingGovernor(newGovernor: address) <<Governable>> +    _changeGovernor(_newGovernor: address) <<Governable>> +External: +    transferGovernance(_newGovernor: address) <<onlyGovernor>> <<Governable>> +    claimGovernance() <<Governable>> +    initialize() <<onlyGovernor, initializer>> <<WrappedOusd>> +    transferToken(asset_: address, amount_: uint256) <<onlyGovernor>> <<WrappedOusd>> +Public: +    <<event>> Transfer(from: address, to: address, value: uint256) <<IERC20>> +    <<event>> Approval(owner: address, spender: address, value: uint256) <<IERC20>> +    <<event>> Deposit(caller: address, owner: address, assets: uint256, shares: uint256) <<IERC4626>> +    <<event>> Withdraw(caller: address, receiver: address, owner: address, assets: uint256, shares: uint256) <<IERC4626>> +    <<event>> PendingGovernorshipTransfer(previousGovernor: address, newGovernor: address) <<Governable>> +    <<event>> GovernorshipTransferred(previousGovernor: address, newGovernor: address) <<Governable>> +    <<modifier>> onlyGovernor() <<Governable>> +    <<modifier>> nonReentrant() <<Governable>> +    <<modifier>> initializer() <<Initializable>> +    totalSupply(): uint256 <<ERC20>> +    balanceOf(account: address): uint256 <<ERC20>> +    transfer(recipient: address, amount: uint256): bool <<ERC20>> +    allowance(owner: address, spender: address): uint256 <<ERC20>> +    approve(spender: address, amount: uint256): bool <<ERC20>> +    transferFrom(sender: address, recipient: address, amount: uint256): bool <<ERC20>> +    name(): string <<WrappedOusd>> +    symbol(): string <<WrappedOusd>> +    decimals(): uint8 <<ERC20>> +    constructor(name_: string, symbol_: string) <<ERC20>> +    increaseAllowance(spender: address, addedValue: uint256): bool <<ERC20>> +    decreaseAllowance(spender: address, subtractedValue: uint256): bool <<ERC20>>    asset(): address <<ERC4626>>    totalAssets(): uint256 <<ERC4626>>    convertToShares(assets: uint256): (shares: uint256) <<ERC4626>> diff --git a/contracts/docs/generate.sh b/contracts/docs/generate.sh index 88a9a8db53..1299504db7 100644 --- a/contracts/docs/generate.sh +++ b/contracts/docs/generate.sh @@ -1,4 +1,11 @@ +# contracts/buyback +sol2uml .. -v -hv -hf -he -hs -hl -b OUSDBuyback -o OUSDBuybackHierarchy.svg +sol2uml .. -s -d 0 -b OUSDBuyback -o OUSDBuybackSquashed.svg +sol2uml storage .. -c OUSDBuyback -o OUSDBuybackStorage.svg +sol2uml .. -v -hv -hf -he -hs -hl -b OETHBuyback -o OETHBuybackHierarchy.svg +sol2uml .. -s -d 0 -b OETHBuyback -o OETHBuybackSquashed.svg +sol2uml storage .. -c OETHBuyback -o OETHBuybackStorage.svg # contracts/flipper sol2uml .. -v -hv -hf -he -hs -hl -b Flipper -o FlipperHierarchy.svg @@ -32,6 +39,14 @@ sol2uml .. -v -hv -hf -he -hs -hl -b OETHOracleRouter -o OETHOracleRouterHierarc sol2uml .. -s -d 0 -b OETHOracleRouter -o OETHOracleRouterSquashed.svg sol2uml storage .. -c OETHOracleRouter -o OETHOracleRouterStorage.svg +sol2uml .. -v -hv -hf -he -hs -hl -b OracleRouter -o OracleRouterHierarchy.svg +sol2uml .. -s -d 0 -b OracleRouter -o OracleRouterSquashed.svg +sol2uml storage .. -c OracleRouter -o OracleRouterStorage.svg + +sol2uml .. -v -hv -hf -he -hs -hl -b AuraWETHPriceFeed -o AuraWETHPriceFeedHierarchy.svg +sol2uml .. -s -d 0 -b AuraWETHPriceFeed -o AuraWETHPriceFeedSquashed.svg +sol2uml storage .. -c AuraWETHPriceFeed -o AuraWETHPriceFeedStorage.svg + sol2uml .. -v -hv -hf -he -hs -hl -b MixOracle -o MixOracleHierarchy.svg sol2uml .. -s -d 0 -b MixOracle -o MixOracleSquashed.svg sol2uml storage .. -c MixOracle -o MixOracleStorage.svg