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..0c20d2f --- /dev/null +++ b/lib/erc4626-tests @@ -0,0 +1 @@ +Subproject commit 0c20d2f83fd885f3378e29d6cb997a0c847ddbe2 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; + } +} 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 {