From 15a76913f082cd98fdad47d1a5f7932115a59c36 Mon Sep 17 00:00:00 2001 From: Daejun Park Date: Tue, 4 Oct 2022 17:23:21 -0700 Subject: [PATCH 1/3] feat: add ERC4626 standard property tests --- .gitmodules | 3 ++ lib/erc4626-tests | 1 + src/test/aave-v2/ERC4626.st.sol | 54 ++++++++++++++++++++++++ src/test/aave-v3/ERC4626.st.sol | 54 ++++++++++++++++++++++++ src/test/euler/ERC4626.st.sol | 75 +++++++++++++++++++++++++++++++++ 5 files changed, 187 insertions(+) create mode 160000 lib/erc4626-tests create mode 100644 src/test/aave-v2/ERC4626.st.sol create mode 100644 src/test/aave-v3/ERC4626.st.sol create mode 100644 src/test/euler/ERC4626.st.sol diff --git a/.gitmodules b/.gitmodules index 44b1da0..fb3b78c 100644 --- a/.gitmodules +++ b/.gitmodules @@ -7,3 +7,6 @@ [submodule "lib/create3-factory"] path = lib/create3-factory url = https://github.com/zeframlou/create3-factory +[submodule "lib/erc4626-tests"] + path = lib/erc4626-tests + url = git@github.com:a16z/erc4626-tests.git diff --git a/lib/erc4626-tests b/lib/erc4626-tests new file mode 160000 index 0000000..39c8abd --- /dev/null +++ b/lib/erc4626-tests @@ -0,0 +1 @@ +Subproject commit 39c8abdb25b325b14995b4d1d52be854353b267d diff --git a/src/test/aave-v2/ERC4626.st.sol b/src/test/aave-v2/ERC4626.st.sol new file mode 100644 index 0000000..0e163e8 --- /dev/null +++ b/src/test/aave-v2/ERC4626.st.sol @@ -0,0 +1,54 @@ +// SPDX-License-Identifier: AGPL-3.0 +pragma solidity >=0.8.0 <0.9.0; + +import "erc4626-tests/ERC4626.test.sol"; + +import {ERC20Mock} from "../mocks/ERC20Mock.sol"; +import {AaveMiningMock} from "./mocks/AaveMiningMock.sol"; +import {LendingPoolMock} from "./mocks/LendingPoolMock.sol"; +import {AaveV2ERC4626} from "../../aave-v2/AaveV2ERC4626.sol"; +import {IAaveMining} from "../../aave-v2/external/IAaveMining.sol"; +import {ILendingPool} from "../../aave-v2/external/ILendingPool.sol"; +import {AaveV2ERC4626Factory} from "../../aave-v2/AaveV2ERC4626Factory.sol"; + +contract ERC4626StdTest is ERC4626Test { + address public constant rewardRecipient = address(0x01); + + // copied from AaveV2ERC4626.t.sol + ERC20Mock public aave; + ERC20Mock public aToken; + AaveV2ERC4626 public vault; + ERC20Mock public underlying; + IAaveMining public aaveMining; + LendingPoolMock public lendingPool; + AaveV2ERC4626Factory public factory; + + function setUp() public override { + // copied from AaveV2ERC4626.t.sol + aave = new ERC20Mock(); + aToken = new ERC20Mock(); + underlying = new ERC20Mock(); + lendingPool = new LendingPoolMock(); + aaveMining = new AaveMiningMock(address(aave)); + factory = new AaveV2ERC4626Factory(aaveMining, rewardRecipient, lendingPool); + lendingPool.setReserveAToken(address(underlying), address(aToken)); + vault = AaveV2ERC4626(address(factory.createERC4626(underlying))); + + // for ERC4626Test setup + __underlying__ = address(underlying); + __vault__ = address(vault); + __delta__ = 0; + } + + // custom setup for yield + function setupYield(Init memory init) public override { + // setup initial yield + if (init.yield >= 0) { + uint gain = uint(init.yield); + try underlying.mint(address(lendingPool), gain) {} catch { vm.assume(false); } + try aToken.mint(address(vault), gain) {} catch { vm.assume(false); } + } else { + vm.assume(false); // TODO: test negative yield scenario + } + } +} diff --git a/src/test/aave-v3/ERC4626.st.sol b/src/test/aave-v3/ERC4626.st.sol new file mode 100644 index 0000000..26cc240 --- /dev/null +++ b/src/test/aave-v3/ERC4626.st.sol @@ -0,0 +1,54 @@ +// SPDX-License-Identifier: AGPL-3.0 +pragma solidity >=0.8.0 <0.9.0; + +import "erc4626-tests/ERC4626.test.sol"; + +import {PoolMock} from "./mocks/PoolMock.sol"; +import {ERC20Mock} from "../mocks/ERC20Mock.sol"; +import {IPool} from "../../aave-v3/external/IPool.sol"; +import {AaveV3ERC4626} from "../../aave-v3/AaveV3ERC4626.sol"; +import {RewardsControllerMock} from "./mocks/RewardsControllerMock.sol"; +import {AaveV3ERC4626Factory} from "../../aave-v3/AaveV3ERC4626Factory.sol"; +import {IRewardsController} from "../../aave-v3/external/IRewardsController.sol"; + +contract ERC4626StdTest is ERC4626Test { + address public constant rewardRecipient = address(0x01); + + // copied from AaveV3ERC4626.t.sol + ERC20Mock public aave; + ERC20Mock public aToken; + AaveV3ERC4626 public vault; + ERC20Mock public underlying; + PoolMock public lendingPool; + AaveV3ERC4626Factory public factory; + IRewardsController public rewardsController; + + function setUp() public override { + // copied from AaveV3ERC4626.t.sol + aave = new ERC20Mock(); + aToken = new ERC20Mock(); + underlying = new ERC20Mock(); + lendingPool = new PoolMock(); + rewardsController = new RewardsControllerMock(address(aave)); + factory = new AaveV3ERC4626Factory(lendingPool, rewardRecipient, rewardsController); + lendingPool.setReserveAToken(address(underlying), address(aToken)); + vault = AaveV3ERC4626(address(factory.createERC4626(underlying))); + + // for ERC4626Test setup + __underlying__ = address(underlying); + __vault__ = address(vault); + __delta__ = 0; + } + + // custom setup for yield + function setupYield(Init memory init) public override { + // setup initial yield + if (init.yield >= 0) { + uint gain = uint(init.yield); + try underlying.mint(address(lendingPool), gain) {} catch { vm.assume(false); } + try aToken.mint(address(vault), gain) {} catch { vm.assume(false); } + } else { + vm.assume(false); // TODO: test negative yield scenario + } + } +} diff --git a/src/test/euler/ERC4626.st.sol b/src/test/euler/ERC4626.st.sol new file mode 100644 index 0000000..4b1c6cc --- /dev/null +++ b/src/test/euler/ERC4626.st.sol @@ -0,0 +1,75 @@ +// SPDX-License-Identifier: AGPL-3.0 +pragma solidity >=0.8.0 <0.9.0; + +import "erc4626-tests/ERC4626.test.sol"; + +import {EulerMock} from "./mocks/EulerMock.sol"; +import {ERC20Mock} from "../mocks/ERC20Mock.sol"; +import {EulerERC4626} from "../../euler/EulerERC4626.sol"; +import {EulerETokenMock} from "./mocks/EulerETokenMock.sol"; +import {EulerMarketsMock} from "./mocks/EulerMarketsMock.sol"; +import {EulerERC4626Factory} from "../../euler/EulerERC4626Factory.sol"; + +contract ERC4626StdTest is ERC4626Test { + // copied from EulerERC4626.t.sol + EulerMock public euler; + EulerERC4626 public vault; + ERC20Mock public underlying; + EulerETokenMock public eToken; + EulerMarketsMock public markets; + EulerERC4626Factory public factory; + + function setUp() public override { + // copied from EulerERC4626.t.sol + euler = new EulerMock(); + underlying = new ERC20Mock(); + eToken = new EulerETokenMock(underlying, euler); + markets = new EulerMarketsMock(); + factory = new EulerERC4626Factory(address(euler), markets); + markets.setETokenForUnderlying(address(underlying), address(eToken)); + vault = EulerERC4626(address(factory.createERC4626(underlying))); + + // for ERC4626Test setup + __underlying__ = address(underlying); + __vault__ = address(vault); + __delta__ = 0; + } + + // custom setup for yield + function setupYield(Init memory init) public override { + // setup initial yield + if (init.yield >= 0) { + uint gain = uint(init.yield); + try underlying.mint(address(eToken), gain) {} catch { vm.assume(false); } + } else { + vm.assume(false); // TODO: test negative yield scenario + } + } + + // NOTE: The following tests are relaxed to consider only smaller values (of type uint120), + // since the totalAssets(), maxWithdraw(), and maxRedeem() functions fail with large values (due to overflow). + + function test_totalAssets(Init memory init) public override { + init = clamp(init, type(uint120).max); + super.test_totalAssets(init); + } + + function test_maxWithdraw(Init memory init) public override { + init = clamp(init, type(uint120).max); + super.test_maxWithdraw(init); + } + + function test_maxRedeem(Init memory init) public override { + init = clamp(init, type(uint120).max); + super.test_maxRedeem(init); + } + + function clamp(Init memory init, uint max) internal pure returns (Init memory) { + for (uint i = 0; i < N; i++) { + init.share[i] = init.share[i] % max; + init.asset[i] = init.asset[i] % max; + } + init.yield = init.yield % int(max); + return init; + } +} From 721cf4bd766805fd409455434aa5fd1a9b2df25c Mon Sep 17 00:00:00 2001 From: Daejun Park Date: Tue, 4 Oct 2022 17:26:30 -0700 Subject: [PATCH 2/3] fix(euler): EulerETokenMock.deposit() --- src/test/euler/mocks/EulerETokenMock.sol | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/test/euler/mocks/EulerETokenMock.sol b/src/test/euler/mocks/EulerETokenMock.sol index cd2c70f..f4b0525 100644 --- a/src/test/euler/mocks/EulerETokenMock.sol +++ b/src/test/euler/mocks/EulerETokenMock.sol @@ -26,11 +26,13 @@ contract EulerETokenMock is IEulerEToken, ERC20("EulerETokenMock", "eMOCK", 18) } function deposit(uint256, uint256 amount) external override { + uint shares = convertToShares(amount); + // call EulerMock to transfer tokens from sender euler.transferTokenFrom(underlying, msg.sender, address(this), amount); // mint shares - _mint(msg.sender, convertToShares(amount)); + _mint(msg.sender, shares); } function withdraw(uint256, uint256 amount) external override { From f2a746414f0f1a79088a7bc5383752deda974303 Mon Sep 17 00:00:00 2001 From: Daejun Park Date: Thu, 27 Oct 2022 14:43:59 -0700 Subject: [PATCH 3/3] chore: update erc4626-tests dependency --- lib/erc4626-tests | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/erc4626-tests b/lib/erc4626-tests index 39c8abd..0c20d2f 160000 --- a/lib/erc4626-tests +++ b/lib/erc4626-tests @@ -1 +1 @@ -Subproject commit 39c8abdb25b325b14995b4d1d52be854353b267d +Subproject commit 0c20d2f83fd885f3378e29d6cb997a0c847ddbe2