From 3fb0e1bee8ff722026400a35913220d5df4dd21f Mon Sep 17 00:00:00 2001 From: Mark Hinschberger Date: Tue, 17 Sep 2024 18:00:36 +0200 Subject: [PATCH 01/28] init --- foundry.toml | 2 + src/SavingsSuSDSTokenWrapper.sol | 53 ++++++++++ src/interfaces/IUSDS.sol | 145 ++++++++++++++++++++++++++++ test/SavingsSuSDSTokenWrapper.t.sol | 65 +++++++++++++ 4 files changed, 265 insertions(+) create mode 100644 src/SavingsSuSDSTokenWrapper.sol create mode 100644 src/interfaces/IUSDS.sol create mode 100644 test/SavingsSuSDSTokenWrapper.t.sol diff --git a/foundry.toml b/foundry.toml index 25b918f..fb0dbf4 100644 --- a/foundry.toml +++ b/foundry.toml @@ -2,5 +2,7 @@ src = "src" out = "out" libs = ["lib"] +solc = "0.8.20" +evm_version = "shanghai" # See more config options https://github.com/foundry-rs/foundry/blob/master/crates/config/README.md#all-options diff --git a/src/SavingsSuSDSTokenWrapper.sol b/src/SavingsSuSDSTokenWrapper.sol new file mode 100644 index 0000000..9f5eb85 --- /dev/null +++ b/src/SavingsSuSDSTokenWrapper.sol @@ -0,0 +1,53 @@ +// SPDX-License-Identifier: AGPL-3.0 +pragma solidity ^0.8.10; + +import {IERC20} from 'aave-v3-core/contracts/dependencies/openzeppelin/contracts/IERC20.sol'; +import {IUSDS} from './interfaces/IUSDS.sol'; +import {BaseTokenWrapper} from './BaseTokenWrapper.sol'; + +/** + * @title SavingsSuSDSTokenWrapper + * @author Aave + * @notice Contract to wrap Dai to SuSDS on supply to Aave, or unwrap from SuSDS to Dai on withdrawal + */ +contract SavingsSuSDSTokenWrapper is BaseTokenWrapper { + /** + * @dev Constructor + * @param tokenIn Address for Dai + * @param tokenOut Address for SuSDS + * @param pool The address of the Aave Pool + * @param owner The address to transfer ownership to + */ + constructor( + address tokenIn, + address tokenOut, + address pool, + address owner + ) BaseTokenWrapper(tokenIn, tokenOut, pool, owner) { + IERC20(tokenIn).approve(tokenOut, type(uint256).max); + } + + /// @inheritdoc BaseTokenWrapper + function getTokenOutForTokenIn( + uint256 amount + ) external view override returns (uint256) { + return IUSDS(TOKEN_OUT).previewDeposit(amount); + } + + /// @inheritdoc BaseTokenWrapper + function getTokenInForTokenOut( + uint256 amount + ) external view override returns (uint256) { + return IUSDS(TOKEN_OUT).previewRedeem(amount); + } + + /// @inheritdoc BaseTokenWrapper + function _wrapTokenIn(uint256 amount) internal override returns (uint256) { + return IUSDS(TOKEN_OUT).deposit(amount, address(this)); + } + + /// @inheritdoc BaseTokenWrapper + function _unwrapTokenOut(uint256 amount) internal override returns (uint256) { + return IUSDS(TOKEN_OUT).redeem(amount, address(this), address(this)); + } +} diff --git a/src/interfaces/IUSDS.sol b/src/interfaces/IUSDS.sol new file mode 100644 index 0000000..a4e1dd7 --- /dev/null +++ b/src/interfaces/IUSDS.sol @@ -0,0 +1,145 @@ +// SPDX-License-Identifier: AGPL-3.0-or-later + +pragma solidity >=0.8.0; + +interface IUSDS { + function DOMAIN_SEPARATOR() external view returns (bytes32); + + function PERMIT_TYPEHASH() external view returns (bytes32); + + function UPGRADE_INTERFACE_VERSION() external view returns (string memory); + + function allowance( + address owner, + address spender + ) external view returns (uint256); + + function approve(address spender, uint256 value) external returns (bool); + + function asset() external view returns (address); + + function balanceOf(address account) external view returns (uint256); + + function chi() external view returns (uint192); + + function convertToAssets(uint256 shares) external view returns (uint256); + + function convertToShares(uint256 assets) external view returns (uint256); + + function decimals() external view returns (uint8); + + function deny(address usr) external; + + function deposit(uint256 assets, address receiver) external returns (uint256); + + function deposit( + uint256 assets, + address receiver, + uint16 referral + ) external returns (uint256); + + function drip() external returns (uint256); + + function file(bytes32 what, uint256 data) external; + + function getImplementation() external view returns (address); + + function initialize() external; + + function maxDeposit(address account) external view returns (uint256); + + function maxMint(address account) external view returns (uint256); + + function maxRedeem(address owner) external view returns (uint256); + + function maxWithdraw(address owner) external view returns (uint256); + + function mint( + uint256 shares, + address receiver, + uint16 referral + ) external returns (uint256); + + function mint(uint256 shares, address receiver) external returns (uint256); + + function name() external view returns (string memory); + + function nonces(address account) external view returns (uint256); + + function permit( + address owner, + address spender, + uint256 value, + uint256 deadline, + bytes memory signature + ) external; + + function permit( + address owner, + address spender, + uint256 value, + uint256 deadline, + uint8 v, + bytes32 r, + bytes32 s + ) external; + + function previewDeposit(uint256 assets) external view returns (uint256); + + function previewMint(uint256 shares) external view returns (uint256); + + function previewRedeem(uint256 shares) external view returns (uint256); + + function previewWithdraw(uint256 assets) external view returns (uint256); + + function proxiableUUID() external view returns (bytes32); + + function redeem( + uint256 shares, + address receiver, + address owner + ) external returns (uint256); + + function rely(address usr) external; + + function rho() external view returns (uint64); + + function ssr() external view returns (uint256); + + function symbol() external view returns (string memory); + + function totalAssets() external view returns (uint256); + + function totalSupply() external view returns (uint256); + + function transfer(address to, uint256 value) external returns (bool); + + function transferFrom( + address from, + address to, + uint256 value + ) external returns (bool); + + function upgradeToAndCall( + address newImplementation, + bytes memory data + ) external payable; + + function usds() external view returns (address); + + function usdsJoin() external view returns (address); + + function vat() external view returns (address); + + function version() external view returns (string memory); + + function vow() external view returns (address); + + function wards(address account) external view returns (uint256); + + function withdraw( + uint256 assets, + address receiver, + address owner + ) external returns (uint256); +} diff --git a/test/SavingsSuSDSTokenWrapper.t.sol b/test/SavingsSuSDSTokenWrapper.t.sol new file mode 100644 index 0000000..b278947 --- /dev/null +++ b/test/SavingsSuSDSTokenWrapper.t.sol @@ -0,0 +1,65 @@ +// SPDX-License-Identifier: AGPL-3.0 +pragma solidity ^0.8.10; +import 'forge-std/console2.sol'; + +import {IERC20} from 'aave-v3-core/contracts/dependencies/openzeppelin/contracts/IERC20.sol'; +import {BaseTokenWrapperTest} from './BaseTokenWrapper.t.sol'; +import {SavingsSuSDSTokenWrapper} from '../src/SavingsSuSDSTokenWrapper.sol'; + +interface IPayload { + function execute() external; + + function sUSDS() external view returns (address); +} + +// frontend deposits usds and automatically converted to susds on aave + +contract SavingsSuSDSTokenWrapperTest is BaseTokenWrapperTest { + address constant USDS = 0x1923DfeE706A8E78157416C29cBCCFDe7cdF4102; + address constant SUSDS = 0x4e7991e5C547ce825BdEb665EE14a3274f9F61e0; + + address constant AUSDS = 0x4C612E3B15b96Ff9A6faED838F8d07d479a8dD4c; + + function setUp() public { + vm.createSelectFork(vm.envString('ETH_RPC_URL')); + // short gov executor + + // //https://etherscan.io/address/0x2749Ef5641B90DCD17Ee2C0cbFbbA5b440e14fec#code + IPayload deployedPayload = IPayload( + 0x2749Ef5641B90DCD17Ee2C0cbFbbA5b440e14fec + ); + vm.prank(0xEE56e2B3D491590B5b31738cC34d5232F378a8D5); + + deployedPayload.execute(); + pool = 0x87870Bca3F3fD6335C3F4ce8392D69350B4fA4E2; + + // in, out, pool, owner + tokenWrapper = new SavingsSuSDSTokenWrapper(USDS, SUSDS, pool, OWNER); + aTokenOut = AUSDS; + tokenInDecimals = 18; + permitSupported = false; + } + + function testConstructor() public override { + SavingsSuSDSTokenWrapper tempTokenWrapper = new SavingsSuSDSTokenWrapper( + USDS, + SUSDS, + pool, + OWNER + ); + assertEq(tempTokenWrapper.TOKEN_IN(), USDS, 'Unexpected TOKEN_IN'); + assertEq(tempTokenWrapper.TOKEN_OUT(), SUSDS, 'Unexpected TOKEN_OUT'); + assertEq(address(tempTokenWrapper.POOL()), pool, 'Unexpected POOL'); + assertEq(tempTokenWrapper.owner(), OWNER, 'Unexpected owner'); + assertEq( + IERC20(SUSDS).allowance(address(tempTokenWrapper), pool), + type(uint256).max, + 'Unexpected TOKEN_OUT allowance' + ); + assertEq( + IERC20(USDS).allowance(address(tempTokenWrapper), SUSDS), + type(uint256).max, + 'Unexpected TOKEN_IN allowance' + ); + } +} From fb66cf821696243fda614a06bac85174e1946d04 Mon Sep 17 00:00:00 2001 From: Mark Hinschberger Date: Wed, 18 Sep 2024 10:59:19 +0200 Subject: [PATCH 02/28] fork execution --- test/BaseTokenWrapper.t.sol | 1 + test/SavingsSuSDSTokenWrapper.t.sol | 25 ++++++++++++------------- 2 files changed, 13 insertions(+), 13 deletions(-) diff --git a/test/BaseTokenWrapper.t.sol b/test/BaseTokenWrapper.t.sol index be6be7f..9c788c4 100644 --- a/test/BaseTokenWrapper.t.sol +++ b/test/BaseTokenWrapper.t.sol @@ -80,6 +80,7 @@ abstract contract BaseTokenWrapperTest is Test { vm.stopPrank(); assertEq(tokenIn.balanceOf(ALICE), 0, 'Unexpected ending tokenIn balance'); + assertEq( suppliedAmount, IAToken(aTokenOut).balanceOf(ALICE), diff --git a/test/SavingsSuSDSTokenWrapper.t.sol b/test/SavingsSuSDSTokenWrapper.t.sol index b278947..574f2d8 100644 --- a/test/SavingsSuSDSTokenWrapper.t.sol +++ b/test/SavingsSuSDSTokenWrapper.t.sol @@ -15,29 +15,28 @@ interface IPayload { // frontend deposits usds and automatically converted to susds on aave contract SavingsSuSDSTokenWrapperTest is BaseTokenWrapperTest { - address constant USDS = 0x1923DfeE706A8E78157416C29cBCCFDe7cdF4102; - address constant SUSDS = 0x4e7991e5C547ce825BdEb665EE14a3274f9F61e0; + address constant USDS = 0xdC035D45d973E3EC169d2276DDab16f1e407384F; + address constant SUSDS = 0xa3931d71877C0E7a3148CB7Eb4463524FEc27fbD; - address constant AUSDS = 0x4C612E3B15b96Ff9A6faED838F8d07d479a8dD4c; + // fork + address constant AUSDS = 0x5c647cE0Ae10658ec44FA4E11A51c96e94efd1Dd; - function setUp() public { - vm.createSelectFork(vm.envString('ETH_RPC_URL')); - // short gov executor + // fork + // address constant ASUSDS = 0x5c647ce0ae10658ec44fa4e11a51c96e94efd1dd; - // //https://etherscan.io/address/0x2749Ef5641B90DCD17Ee2C0cbFbbA5b440e14fec#code - IPayload deployedPayload = IPayload( - 0x2749Ef5641B90DCD17Ee2C0cbFbbA5b440e14fec + function setUp() public { + // vm.createSelectFork(vm.envString('ETH_RPC_URL')); + vm.createSelectFork( + 'https://rpc.tenderly.co/fork/26fdbc41-5ae7-4f5a-9b47-a4ae15e05ce0' ); - vm.prank(0xEE56e2B3D491590B5b31738cC34d5232F378a8D5); - - deployedPayload.execute(); + // short gov executor pool = 0x87870Bca3F3fD6335C3F4ce8392D69350B4fA4E2; // in, out, pool, owner tokenWrapper = new SavingsSuSDSTokenWrapper(USDS, SUSDS, pool, OWNER); aTokenOut = AUSDS; tokenInDecimals = 18; - permitSupported = false; + permitSupported = true; } function testConstructor() public override { From 17ed1e5129505b4214da1c37cf16b3813435c993 Mon Sep 17 00:00:00 2001 From: Mark Hinschberger Date: Wed, 18 Sep 2024 11:40:16 +0200 Subject: [PATCH 03/28] cleanup --- src/SavingsSuSDSTokenWrapper.sol | 6 +++--- test/SavingsSuSDSTokenWrapper.t.sol | 14 +------------- 2 files changed, 4 insertions(+), 16 deletions(-) diff --git a/src/SavingsSuSDSTokenWrapper.sol b/src/SavingsSuSDSTokenWrapper.sol index 9f5eb85..59273fa 100644 --- a/src/SavingsSuSDSTokenWrapper.sol +++ b/src/SavingsSuSDSTokenWrapper.sol @@ -8,13 +8,13 @@ import {BaseTokenWrapper} from './BaseTokenWrapper.sol'; /** * @title SavingsSuSDSTokenWrapper * @author Aave - * @notice Contract to wrap Dai to SuSDS on supply to Aave, or unwrap from SuSDS to Dai on withdrawal + * @notice Contract to wrap USDS to SuSDS on supply to Aave, or unwrap from SuSDS to USDS on withdrawal */ contract SavingsSuSDSTokenWrapper is BaseTokenWrapper { /** * @dev Constructor - * @param tokenIn Address for Dai - * @param tokenOut Address for SuSDS + * @param tokenIn Address for USDS + * @param tokenOut Address for SUSDS * @param pool The address of the Aave Pool * @param owner The address to transfer ownership to */ diff --git a/test/SavingsSuSDSTokenWrapper.t.sol b/test/SavingsSuSDSTokenWrapper.t.sol index 574f2d8..437a53a 100644 --- a/test/SavingsSuSDSTokenWrapper.t.sol +++ b/test/SavingsSuSDSTokenWrapper.t.sol @@ -6,33 +6,21 @@ import {IERC20} from 'aave-v3-core/contracts/dependencies/openzeppelin/contracts import {BaseTokenWrapperTest} from './BaseTokenWrapper.t.sol'; import {SavingsSuSDSTokenWrapper} from '../src/SavingsSuSDSTokenWrapper.sol'; -interface IPayload { - function execute() external; - - function sUSDS() external view returns (address); -} - // frontend deposits usds and automatically converted to susds on aave - contract SavingsSuSDSTokenWrapperTest is BaseTokenWrapperTest { address constant USDS = 0xdC035D45d973E3EC169d2276DDab16f1e407384F; address constant SUSDS = 0xa3931d71877C0E7a3148CB7Eb4463524FEc27fbD; - // fork + // TODO Actual Address --> fork address constant AUSDS = 0x5c647cE0Ae10658ec44FA4E11A51c96e94efd1Dd; - // fork - // address constant ASUSDS = 0x5c647ce0ae10658ec44fa4e11a51c96e94efd1dd; - function setUp() public { // vm.createSelectFork(vm.envString('ETH_RPC_URL')); vm.createSelectFork( 'https://rpc.tenderly.co/fork/26fdbc41-5ae7-4f5a-9b47-a4ae15e05ce0' ); - // short gov executor pool = 0x87870Bca3F3fD6335C3F4ce8392D69350B4fA4E2; - // in, out, pool, owner tokenWrapper = new SavingsSuSDSTokenWrapper(USDS, SUSDS, pool, OWNER); aTokenOut = AUSDS; tokenInDecimals = 18; From 50f3eb71f3f0d3faf98d6a76c73ffef07840b30f Mon Sep 17 00:00:00 2001 From: CheyenneAtapour Date: Wed, 18 Sep 2024 14:53:12 +0100 Subject: [PATCH 04/28] test: Don't assume 0 ETH balance on wrapper contracts --- test/BaseTokenWrapper.t.sol | 1 + 1 file changed, 1 insertion(+) diff --git a/test/BaseTokenWrapper.t.sol b/test/BaseTokenWrapper.t.sol index 9c788c4..98fcebb 100644 --- a/test/BaseTokenWrapper.t.sol +++ b/test/BaseTokenWrapper.t.sol @@ -639,6 +639,7 @@ abstract contract BaseTokenWrapperTest is Test { function testRescueETH() public { uint256 ethAmount = 100 ether; + vm.deal(address(tokenWrapper), 0); assertEq( address(tokenWrapper).balance, 0, From d3784d0db2a83f96911fd9fda4b67854d10dddb2 Mon Sep 17 00:00:00 2001 From: Cheyenne Atapour Date: Thu, 19 Sep 2024 09:34:28 -0700 Subject: [PATCH 05/28] Fix TokenBorrow for SUSDS (#15) * wip: borrowToken wrapper extension * wip: Trying credit delegation * testing * updated fork * fix: TokenBorrow working --------- Co-authored-by: Mark Hinschberger --- src/BaseTokenWrapper.sol | 3 + src/SavingsSuSDSTokenWrapper.sol | 11 +++ src/interfaces/IBaseTokenWrapper.sol | 7 ++ src/interfaces/ICreditDelegationToken.sol | 49 +++++++++++++ test/SavingsSuSDSTokenWrapper.t.sol | 83 ++++++++++++++++++++++- 5 files changed, 151 insertions(+), 2 deletions(-) create mode 100644 src/interfaces/ICreditDelegationToken.sol diff --git a/src/BaseTokenWrapper.sol b/src/BaseTokenWrapper.sol index 8da9901..2a508e3 100644 --- a/src/BaseTokenWrapper.sol +++ b/src/BaseTokenWrapper.sol @@ -103,6 +103,9 @@ abstract contract BaseTokenWrapper is Ownable, IBaseTokenWrapper { return _withdrawToken(amount, to, aTokenOut); } + /// @inheritdoc IBaseTokenWrapper + function borrowToken(uint256 amount, address to) external virtual {} + /// @inheritdoc IBaseTokenWrapper function rescueTokens( IERC20 token, diff --git a/src/SavingsSuSDSTokenWrapper.sol b/src/SavingsSuSDSTokenWrapper.sol index 59273fa..7f0cb09 100644 --- a/src/SavingsSuSDSTokenWrapper.sol +++ b/src/SavingsSuSDSTokenWrapper.sol @@ -1,5 +1,6 @@ // SPDX-License-Identifier: AGPL-3.0 pragma solidity ^0.8.10; +import 'forge-std/console2.sol'; import {IERC20} from 'aave-v3-core/contracts/dependencies/openzeppelin/contracts/IERC20.sol'; import {IUSDS} from './interfaces/IUSDS.sol'; @@ -27,6 +28,16 @@ contract SavingsSuSDSTokenWrapper is BaseTokenWrapper { IERC20(tokenIn).approve(tokenOut, type(uint256).max); } + ///@inheritdoc BaseTokenWrapper + function borrowToken(uint256 amount, address to) external override { + require(amount > 0, 'INSUFFICIENT_AMOUNT_TO_BORROW'); + + POOL.borrow(TOKEN_OUT, amount, 2, 0, address(to)); + + uint256 amountIn = _unwrapTokenOut(amount); + IERC20(TOKEN_IN).transfer(to, amountIn); + } + /// @inheritdoc BaseTokenWrapper function getTokenOutForTokenIn( uint256 amount diff --git a/src/interfaces/IBaseTokenWrapper.sol b/src/interfaces/IBaseTokenWrapper.sol index 12b7976..ce4986c 100644 --- a/src/interfaces/IBaseTokenWrapper.sol +++ b/src/interfaces/IBaseTokenWrapper.sol @@ -61,6 +61,13 @@ interface IBaseTokenWrapper { PermitSignature calldata signature ) external returns (uint256); + /** + * @notice Borrows token from the Pool and unwraps it, sending to the recipient + * @param amount The amount of token to borrow + * @param to The address that will receive the unwrapped token + */ + function borrowToken(uint256 amount, address to) external; + /** * @notice Provides way for the contract owner to rescue ERC-20 tokens * @param token The address of the token to withdraw from this contract diff --git a/src/interfaces/ICreditDelegationToken.sol b/src/interfaces/ICreditDelegationToken.sol new file mode 100644 index 0000000..265efa9 --- /dev/null +++ b/src/interfaces/ICreditDelegationToken.sol @@ -0,0 +1,49 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.0; + +/** + * @title ICreditDelegationToken + * @author Aave + * @notice Defines the basic interface for a token supporting credit delegation. + **/ +interface ICreditDelegationToken { + /** + * @notice Delegates borrowing power to a user on the specific debt token. + * Delegation will still respect the liquidation constraints (even if delegated, a + * delegatee cannot force a delegator HF to go below 1) + * @param delegatee The address receiving the delegated borrowing power + * @param amount The maximum amount being delegated. + **/ + function approveDelegation(address delegatee, uint256 amount) external; + + /** + * @notice Returns the borrow allowance of the user + * @param fromUser The user to giving allowance + * @param toUser The user to give allowance to + * @return The current allowance of `toUser` + **/ + function borrowAllowance( + address fromUser, + address toUser + ) external view returns (uint256); + + /** + * @notice Delegates borrowing power to a user on the specific debt token via ERC712 signature + * @param delegator The delegator of the credit + * @param delegatee The delegatee that can use the credit + * @param value The amount to be delegated + * @param deadline The deadline timestamp, type(uint256).max for max deadline + * @param v The V signature param + * @param s The S signature param + * @param r The R signature param + */ + function delegationWithSig( + address delegator, + address delegatee, + uint256 value, + uint256 deadline, + uint8 v, + bytes32 r, + bytes32 s + ) external; +} diff --git a/test/SavingsSuSDSTokenWrapper.t.sol b/test/SavingsSuSDSTokenWrapper.t.sol index 437a53a..1673890 100644 --- a/test/SavingsSuSDSTokenWrapper.t.sol +++ b/test/SavingsSuSDSTokenWrapper.t.sol @@ -2,22 +2,26 @@ pragma solidity ^0.8.10; import 'forge-std/console2.sol'; +import {DataTypes} from 'aave-v3-core/contracts/protocol/libraries/types/DataTypes.sol'; import {IERC20} from 'aave-v3-core/contracts/dependencies/openzeppelin/contracts/IERC20.sol'; +import {IPool} from 'aave-v3-core/contracts/interfaces/IPool.sol'; import {BaseTokenWrapperTest} from './BaseTokenWrapper.t.sol'; import {SavingsSuSDSTokenWrapper} from '../src/SavingsSuSDSTokenWrapper.sol'; +import {ICreditDelegationToken} from '../src/interfaces/ICreditDelegationToken.sol'; // frontend deposits usds and automatically converted to susds on aave contract SavingsSuSDSTokenWrapperTest is BaseTokenWrapperTest { address constant USDS = 0xdC035D45d973E3EC169d2276DDab16f1e407384F; address constant SUSDS = 0xa3931d71877C0E7a3148CB7Eb4463524FEc27fbD; + address constant WETH = 0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2; // TODO Actual Address --> fork - address constant AUSDS = 0x5c647cE0Ae10658ec44FA4E11A51c96e94efd1Dd; + address constant AUSDS = 0x10Ac93971cdb1F5c778144084242374473c350Da; function setUp() public { // vm.createSelectFork(vm.envString('ETH_RPC_URL')); vm.createSelectFork( - 'https://rpc.tenderly.co/fork/26fdbc41-5ae7-4f5a-9b47-a4ae15e05ce0' + 'https://rpc.tenderly.co/fork/881012fd-267f-41dc-93ba-8eb025b8bce2' ); pool = 0x87870Bca3F3fD6335C3F4ce8392D69350B4fA4E2; @@ -49,4 +53,79 @@ contract SavingsSuSDSTokenWrapperTest is BaseTokenWrapperTest { 'Unexpected TOKEN_IN allowance' ); } + + // function testBorrow() public { + // address alice = makeAddr('ALICE'); + // deal(USDS, alice, 1000e18); + // vm.startPrank(alice); + // IERC20(USDS).approve(address(pool), 1000e18); + // IPool(pool).supply(USDS, 1000e18, alice, 0); + // vm.stopPrank(); + + // // TODO: Instead of checking pool, need to check reserve contract + // /* + // assertEq( + // IERC20(SUSDS).balanceOf(address(pool)), + // 1e18, + // 'Unexpected post-deal pool USDS balance' + // );*/ + + // deal(WETH, address(this), 20 ether); + // IERC20(WETH).approve(pool, 20 ether); + // IPool(pool).supply(WETH, 20 ether, address(this), 0); + + // deal(USDS, address(this), 1e18); + // IERC20(USDS).approve(address(pool), 1e18); + // IPool(pool).supply(USDS, 1e18, address(this), 0); + // deal(SUSDS, address(this), 1e18); + // IERC20(SUSDS).approve(address(pool), 1e18); + // IPool(pool).supply(SUSDS, 1e18, address(this), 0); + + // uint256 amount = 1e18; + // uint256 amountOut = tokenWrapper.getTokenOutForTokenIn(amount); + // uint256 usdsBefore = IERC20(USDS).balanceOf(address(this)); + // ICreditDelegationToken(SUSDS).approveDelegation( + // address(tokenWrapper), + // amountOut + // ); + // tokenWrapper.borrowToken(amount, address(this)); + // uint256 usdsAfter = IERC20(USDS).balanceOf(address(this)); + + // assertEq( + // usdsAfter, + // usdsBefore + amount, + // 'Unexpected USDS balance after borrow' + // ); + // } + + function testBorrow() public { + address debtToken = IPool(pool) + .getReserveData(SUSDS) + .variableDebtTokenAddress; + + address alice = makeAddr('ALICE'); + uint256 collateralAmount = 1000e18; + + uint256 borrowAmount = 100e18; + + deal(WETH, alice, collateralAmount); + + vm.startPrank(alice); + IERC20(WETH).approve(address(pool), collateralAmount); + IPool(pool).supply(WETH, collateralAmount, alice, 0); + + ICreditDelegationToken(debtToken).approveDelegation( + address(tokenWrapper), + borrowAmount + ); + + // IPool(pool).borrow(SUSDS, borrowAmount, 2, 0, alice); + // console2.log('Borrowing USDS', address(tokenWrapper)); + tokenWrapper.borrowToken(borrowAmount, address(alice)); + + vm.stopPrank(); + + uint256 borrowedAmount = tokenWrapper.getTokenInForTokenOut(borrowAmount); + assertEq(IERC20(USDS).balanceOf(address(alice)), borrowedAmount); + } } From df5dd2b423a913b8cabb3311abfff0d2e64a6537 Mon Sep 17 00:00:00 2001 From: CheyenneAtapour Date: Fri, 20 Sep 2024 10:20:51 +0100 Subject: [PATCH 06/28] feat: Move borrow logic to base wrapper --- src/BaseTokenWrapper.sol | 7 +++- src/SavingsSuSDSTokenWrapper.sol | 10 ----- test/SavingsSuSDSTokenWrapper.t.sol | 62 ++++------------------------- 3 files changed, 14 insertions(+), 65 deletions(-) diff --git a/src/BaseTokenWrapper.sol b/src/BaseTokenWrapper.sol index 2a508e3..7ec314b 100644 --- a/src/BaseTokenWrapper.sol +++ b/src/BaseTokenWrapper.sol @@ -104,7 +104,12 @@ abstract contract BaseTokenWrapper is Ownable, IBaseTokenWrapper { } /// @inheritdoc IBaseTokenWrapper - function borrowToken(uint256 amount, address to) external virtual {} + function borrowToken(uint256 amount, address to) external virtual { + require(amount > 0, 'INSUFFICIENT_AMOUNT_TO_BORROW'); + POOL.borrow(TOKEN_OUT, amount, 2, 0, address(to)); + uint256 amountIn = _unwrapTokenOut(amount); + IERC20(TOKEN_IN).transfer(to, amountIn); + } /// @inheritdoc IBaseTokenWrapper function rescueTokens( diff --git a/src/SavingsSuSDSTokenWrapper.sol b/src/SavingsSuSDSTokenWrapper.sol index 7f0cb09..d54d1e8 100644 --- a/src/SavingsSuSDSTokenWrapper.sol +++ b/src/SavingsSuSDSTokenWrapper.sol @@ -28,16 +28,6 @@ contract SavingsSuSDSTokenWrapper is BaseTokenWrapper { IERC20(tokenIn).approve(tokenOut, type(uint256).max); } - ///@inheritdoc BaseTokenWrapper - function borrowToken(uint256 amount, address to) external override { - require(amount > 0, 'INSUFFICIENT_AMOUNT_TO_BORROW'); - - POOL.borrow(TOKEN_OUT, amount, 2, 0, address(to)); - - uint256 amountIn = _unwrapTokenOut(amount); - IERC20(TOKEN_IN).transfer(to, amountIn); - } - /// @inheritdoc BaseTokenWrapper function getTokenOutForTokenIn( uint256 amount diff --git a/test/SavingsSuSDSTokenWrapper.t.sol b/test/SavingsSuSDSTokenWrapper.t.sol index 1673890..7d04815 100644 --- a/test/SavingsSuSDSTokenWrapper.t.sol +++ b/test/SavingsSuSDSTokenWrapper.t.sol @@ -54,63 +54,17 @@ contract SavingsSuSDSTokenWrapperTest is BaseTokenWrapperTest { ); } - // function testBorrow() public { - // address alice = makeAddr('ALICE'); - // deal(USDS, alice, 1000e18); - // vm.startPrank(alice); - // IERC20(USDS).approve(address(pool), 1000e18); - // IPool(pool).supply(USDS, 1000e18, alice, 0); - // vm.stopPrank(); - - // // TODO: Instead of checking pool, need to check reserve contract - // /* - // assertEq( - // IERC20(SUSDS).balanceOf(address(pool)), - // 1e18, - // 'Unexpected post-deal pool USDS balance' - // );*/ - - // deal(WETH, address(this), 20 ether); - // IERC20(WETH).approve(pool, 20 ether); - // IPool(pool).supply(WETH, 20 ether, address(this), 0); - - // deal(USDS, address(this), 1e18); - // IERC20(USDS).approve(address(pool), 1e18); - // IPool(pool).supply(USDS, 1e18, address(this), 0); - // deal(SUSDS, address(this), 1e18); - // IERC20(SUSDS).approve(address(pool), 1e18); - // IPool(pool).supply(SUSDS, 1e18, address(this), 0); - - // uint256 amount = 1e18; - // uint256 amountOut = tokenWrapper.getTokenOutForTokenIn(amount); - // uint256 usdsBefore = IERC20(USDS).balanceOf(address(this)); - // ICreditDelegationToken(SUSDS).approveDelegation( - // address(tokenWrapper), - // amountOut - // ); - // tokenWrapper.borrowToken(amount, address(this)); - // uint256 usdsAfter = IERC20(USDS).balanceOf(address(this)); - - // assertEq( - // usdsAfter, - // usdsBefore + amount, - // 'Unexpected USDS balance after borrow' - // ); - // } - function testBorrow() public { + uint256 collateralAmount = 1000e18; + uint256 borrowAmount = 100e18; address debtToken = IPool(pool) - .getReserveData(SUSDS) + .getReserveData(tokenWrapper.TOKEN_OUT()) .variableDebtTokenAddress; address alice = makeAddr('ALICE'); - uint256 collateralAmount = 1000e18; - - uint256 borrowAmount = 100e18; - deal(WETH, alice, collateralAmount); - vm.startPrank(alice); + IERC20(WETH).approve(address(pool), collateralAmount); IPool(pool).supply(WETH, collateralAmount, alice, 0); @@ -119,13 +73,13 @@ contract SavingsSuSDSTokenWrapperTest is BaseTokenWrapperTest { borrowAmount ); - // IPool(pool).borrow(SUSDS, borrowAmount, 2, 0, alice); - // console2.log('Borrowing USDS', address(tokenWrapper)); tokenWrapper.borrowToken(borrowAmount, address(alice)); - vm.stopPrank(); uint256 borrowedAmount = tokenWrapper.getTokenInForTokenOut(borrowAmount); - assertEq(IERC20(USDS).balanceOf(address(alice)), borrowedAmount); + assertEq( + IERC20(tokenWrapper.TOKEN_IN()).balanceOf(address(alice)), + borrowedAmount + ); } } From 0f69b1cc305106f3f6fe40979f7fb26476ce2e10 Mon Sep 17 00:00:00 2001 From: CheyenneAtapour Date: Fri, 20 Sep 2024 15:27:32 +0100 Subject: [PATCH 07/28] test: Add borrow test to SDAI wrapper. Lock blocknumbers --- test/SavingsDaiTokenWrapper.t.sol | 45 ++++++++++++++++++++++++++++++- test/StakedEthTokenWrapper.t.sol | 2 +- 2 files changed, 45 insertions(+), 2 deletions(-) diff --git a/test/SavingsDaiTokenWrapper.t.sol b/test/SavingsDaiTokenWrapper.t.sol index 6b49de7..ab45123 100644 --- a/test/SavingsDaiTokenWrapper.t.sol +++ b/test/SavingsDaiTokenWrapper.t.sol @@ -2,16 +2,23 @@ pragma solidity ^0.8.10; import {IERC20} from 'aave-v3-core/contracts/dependencies/openzeppelin/contracts/IERC20.sol'; +import {IPool} from 'aave-v3-core/contracts/interfaces/IPool.sol'; +import {IPoolConfigurator} from 'aave-v3-core/contracts/interfaces/IPoolConfigurator.sol'; import {BaseTokenWrapperTest} from './BaseTokenWrapper.t.sol'; import {SavingsDaiTokenWrapper} from '../src/SavingsDaiTokenWrapper.sol'; +import {ICreditDelegationToken} from '../src/interfaces/ICreditDelegationToken.sol'; contract SavingsDaiTokenWrapperTest is BaseTokenWrapperTest { address constant DAI = 0x6B175474E89094C44Da98b954EedeAC495271d0F; address constant SDAI = 0x83F20F44975D03b1b09e64809B757c47f942BEeA; address constant ASDAI = 0x4C612E3B15b96Ff9A6faED838F8d07d479a8dD4c; + address constant WETH = 0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2; + address constant POOL_CONFIGURATOR = + 0x64b761D848206f447Fe2dd461b0c635Ec39EbB27; + address constant ADMIN = 0x5300A1a15135EA4dc7aD5a167152C01EFc9b192A; function setUp() public { - vm.createSelectFork(vm.envString('ETH_RPC_URL')); + vm.createSelectFork(vm.envString('ETH_RPC_URL'), 20784588); pool = 0x87870Bca3F3fD6335C3F4ce8392D69350B4fA4E2; tokenWrapper = new SavingsDaiTokenWrapper(DAI, SDAI, pool, OWNER); aTokenOut = ASDAI; @@ -41,4 +48,40 @@ contract SavingsDaiTokenWrapperTest is BaseTokenWrapperTest { 'Unexpected TOKEN_IN allowance' ); } + + function testBorrow() public { + uint256 collateralAmount = 1000e18; + uint256 borrowAmount = 100e18; + address debtToken = IPool(pool) + .getReserveData(tokenWrapper.TOKEN_OUT()) + .variableDebtTokenAddress; + + // Prank pool admin and set borrowing enabled for SDAI on pool configurator + vm.startPrank(ADMIN); + IPoolConfigurator(POOL_CONFIGURATOR).setReserveBorrowing( + tokenWrapper.TOKEN_OUT(), + true + ); + + address alice = makeAddr('ALICE'); + deal(WETH, alice, collateralAmount); + changePrank(alice); + + IERC20(WETH).approve(address(pool), collateralAmount); + IPool(pool).supply(WETH, collateralAmount, alice, 0); + + ICreditDelegationToken(debtToken).approveDelegation( + address(tokenWrapper), + borrowAmount + ); + + tokenWrapper.borrowToken(borrowAmount, address(alice)); + vm.stopPrank(); + + uint256 borrowedAmount = tokenWrapper.getTokenInForTokenOut(borrowAmount); + assertEq( + IERC20(tokenWrapper.TOKEN_IN()).balanceOf(address(alice)), + borrowedAmount + ); + } } diff --git a/test/StakedEthTokenWrapper.t.sol b/test/StakedEthTokenWrapper.t.sol index ebbc19a..7935ad7 100644 --- a/test/StakedEthTokenWrapper.t.sol +++ b/test/StakedEthTokenWrapper.t.sol @@ -11,7 +11,7 @@ contract StakedEthTokenWrapperTest is BaseTokenWrapperTest { address constant AWSTETH = 0x0B925eD163218f6662a35e0f0371Ac234f9E9371; function setUp() public { - vm.createSelectFork(vm.envString('ETH_RPC_URL')); + vm.createSelectFork(vm.envString('ETH_RPC_URL'), 20784588); pool = 0x87870Bca3F3fD6335C3F4ce8392D69350B4fA4E2; tokenWrapper = new StakedEthTokenWrapper(STETH, WSTETH, pool, OWNER); aTokenOut = AWSTETH; From e22cbd38e78feee94c41a64ae19eb8f8d6cfcd10 Mon Sep 17 00:00:00 2001 From: CheyenneAtapour Date: Fri, 20 Sep 2024 15:45:44 +0100 Subject: [PATCH 08/28] test: Add borrow token test for steth --- test/StakedEthTokenWrapper.t.sol | 34 ++++++++++++++++++++++++++++++++ 1 file changed, 34 insertions(+) diff --git a/test/StakedEthTokenWrapper.t.sol b/test/StakedEthTokenWrapper.t.sol index 7935ad7..0bb584b 100644 --- a/test/StakedEthTokenWrapper.t.sol +++ b/test/StakedEthTokenWrapper.t.sol @@ -2,13 +2,16 @@ pragma solidity ^0.8.10; import {IERC20} from 'aave-v3-core/contracts/dependencies/openzeppelin/contracts/IERC20.sol'; +import {IPool} from 'aave-v3-core/contracts/interfaces/IPool.sol'; import {BaseTokenWrapperTest} from './BaseTokenWrapper.t.sol'; import {StakedEthTokenWrapper} from '../src/StakedEthTokenWrapper.sol'; +import {ICreditDelegationToken} from '../src/interfaces/ICreditDelegationToken.sol'; contract StakedEthTokenWrapperTest is BaseTokenWrapperTest { address constant STETH = 0xae7ab96520DE3A18E5e111B5EaAb095312D7fE84; address constant WSTETH = 0x7f39C581F595B53c5cb19bD0b3f8dA6c935E2Ca0; address constant AWSTETH = 0x0B925eD163218f6662a35e0f0371Ac234f9E9371; + address constant WETH = 0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2; function setUp() public { vm.createSelectFork(vm.envString('ETH_RPC_URL'), 20784588); @@ -42,6 +45,37 @@ contract StakedEthTokenWrapperTest is BaseTokenWrapperTest { ); } + function testBorrow() public { + uint256 collateralAmount = 1000e18; + uint256 borrowAmount = 100e18; + address debtToken = IPool(pool) + .getReserveData(tokenWrapper.TOKEN_OUT()) + .variableDebtTokenAddress; + + address alice = makeAddr('ALICE'); + deal(WETH, alice, collateralAmount); + vm.startPrank(alice); + + IERC20(WETH).approve(address(pool), collateralAmount); + IPool(pool).supply(WETH, collateralAmount, alice, 0); + + ICreditDelegationToken(debtToken).approveDelegation( + address(tokenWrapper), + borrowAmount + ); + + tokenWrapper.borrowToken(borrowAmount, address(alice)); + vm.stopPrank(); + + uint256 borrowedAmount = tokenWrapper.getTokenInForTokenOut(borrowAmount); + // Allow OBOB for rounding to nearest wei + assertApproxEqAbs( + IERC20(tokenWrapper.TOKEN_IN()).balanceOf(address(alice)), + borrowedAmount, + 1 + ); + } + function _dealTokenIn(address user, uint256 amount) internal override { vm.deal(user, amount); vm.prank(user); From 340414fd777c2012469137465a4a20aab48fd5ef Mon Sep 17 00:00:00 2001 From: Mark Hinschberger Date: Tue, 24 Sep 2024 16:04:52 +0200 Subject: [PATCH 09/28] pr comments --- src/BaseTokenWrapper.sol | 15 ++++++-- ...Wrapper.sol => SavingUsdsTokenWrapper.sol} | 6 +-- src/{interfaces => dependencies}/IUSDS.sol | 0 src/interfaces/IBaseTokenWrapper.sol | 7 +++- test/BaseTokenWrapper.t.sol | 31 ++++++++++++++++ test/SavingsDaiTokenWrapper.t.sol | 37 ------------------- 6 files changed, 51 insertions(+), 45 deletions(-) rename src/{SavingsSuSDSTokenWrapper.sol => SavingUsdsTokenWrapper.sol} (91%) rename src/{interfaces => dependencies}/IUSDS.sol (100%) diff --git a/src/BaseTokenWrapper.sol b/src/BaseTokenWrapper.sol index 7ec314b..076111c 100644 --- a/src/BaseTokenWrapper.sol +++ b/src/BaseTokenWrapper.sol @@ -1,6 +1,5 @@ // SPDX-License-Identifier: AGPL-3.0 pragma solidity ^0.8.10; - import {Ownable} from 'aave-v3-core/contracts/dependencies/openzeppelin/contracts/Ownable.sol'; import {IERC20} from 'aave-v3-core/contracts/dependencies/openzeppelin/contracts/IERC20.sol'; import {IERC20WithPermit} from 'aave-v3-core/contracts/interfaces/IERC20WithPermit.sol'; @@ -104,10 +103,18 @@ abstract contract BaseTokenWrapper is Ownable, IBaseTokenWrapper { } /// @inheritdoc IBaseTokenWrapper - function borrowToken(uint256 amount, address to) external virtual { + function borrowToken( + uint256 amount, + address to, + uint16 referralCode + ) external virtual { require(amount > 0, 'INSUFFICIENT_AMOUNT_TO_BORROW'); - POOL.borrow(TOKEN_OUT, amount, 2, 0, address(to)); - uint256 amountIn = _unwrapTokenOut(amount); + uint256 balanceBeforeBorrow = IERC20(TOKEN_OUT).balanceOf(address(this)); + POOL.borrow(TOKEN_OUT, amount, 2, referralCode, address(to)); + uint256 balanceAfterBorrow = IERC20(TOKEN_OUT).balanceOf(address(this)); + uint256 amountIn = _unwrapTokenOut( + balanceAfterBorrow - balanceBeforeBorrow + ); IERC20(TOKEN_IN).transfer(to, amountIn); } diff --git a/src/SavingsSuSDSTokenWrapper.sol b/src/SavingUsdsTokenWrapper.sol similarity index 91% rename from src/SavingsSuSDSTokenWrapper.sol rename to src/SavingUsdsTokenWrapper.sol index d54d1e8..4a75ede 100644 --- a/src/SavingsSuSDSTokenWrapper.sol +++ b/src/SavingUsdsTokenWrapper.sol @@ -3,15 +3,15 @@ pragma solidity ^0.8.10; import 'forge-std/console2.sol'; import {IERC20} from 'aave-v3-core/contracts/dependencies/openzeppelin/contracts/IERC20.sol'; -import {IUSDS} from './interfaces/IUSDS.sol'; +import {IUSDS} from './dependencies/IUSDS.sol'; import {BaseTokenWrapper} from './BaseTokenWrapper.sol'; /** - * @title SavingsSuSDSTokenWrapper + * @title SavingUsdsTokenWrapper * @author Aave * @notice Contract to wrap USDS to SuSDS on supply to Aave, or unwrap from SuSDS to USDS on withdrawal */ -contract SavingsSuSDSTokenWrapper is BaseTokenWrapper { +contract SavingUsdsTokenWrapper is BaseTokenWrapper { /** * @dev Constructor * @param tokenIn Address for USDS diff --git a/src/interfaces/IUSDS.sol b/src/dependencies/IUSDS.sol similarity index 100% rename from src/interfaces/IUSDS.sol rename to src/dependencies/IUSDS.sol diff --git a/src/interfaces/IBaseTokenWrapper.sol b/src/interfaces/IBaseTokenWrapper.sol index ce4986c..3af799b 100644 --- a/src/interfaces/IBaseTokenWrapper.sol +++ b/src/interfaces/IBaseTokenWrapper.sol @@ -65,8 +65,13 @@ interface IBaseTokenWrapper { * @notice Borrows token from the Pool and unwraps it, sending to the recipient * @param amount The amount of token to borrow * @param to The address that will receive the unwrapped token + * @param referralCode Code used to register the integrator originating the operation, for potential rewards */ - function borrowToken(uint256 amount, address to) external; + function borrowToken( + uint256 amount, + address to, + uint16 referralCode + ) external; /** * @notice Provides way for the contract owner to rescue ERC-20 tokens diff --git a/test/BaseTokenWrapper.t.sol b/test/BaseTokenWrapper.t.sol index 98fcebb..e4b913f 100644 --- a/test/BaseTokenWrapper.t.sol +++ b/test/BaseTokenWrapper.t.sol @@ -8,6 +8,7 @@ import {IAToken} from 'aave-v3-core/contracts/interfaces/IAToken.sol'; import {MintableERC20} from 'aave-v3-core/contracts/mocks/tokens/MintableERC20.sol'; import {BaseTokenWrapper} from '../src/BaseTokenWrapper.sol'; import {IBaseTokenWrapper} from '../src/interfaces/IBaseTokenWrapper.sol'; +import {ICreditDelegationToken} from '../src/interfaces/ICreditDelegationToken.sol'; interface IERC2612 { function nonces(address owner) external view returns (uint256); @@ -42,6 +43,7 @@ abstract contract BaseTokenWrapperTest is Test { address aTokenOut; uint256 tokenInDecimals; bool permitSupported; + address constant WETH = 0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2; constructor() { (ALICE, ALICE_KEY) = makeAddrAndKey('alice'); @@ -733,6 +735,35 @@ abstract contract BaseTokenWrapperTest is Test { assertGt(withdrawnAmount, 0, 'Unexpected withdraw return/balance mismatch'); } + function testBorrow() public { + uint256 collateralAmount = 1000e18; + uint256 borrowAmount = 100e18; + address debtToken = IPool(pool) + .getReserveData(tokenWrapper.TOKEN_OUT()) + .variableDebtTokenAddress; + + address alice = makeAddr('ALICE'); + deal(WETH, alice, collateralAmount); + vm.startPrank(alice); + + IERC20(WETH).approve(address(pool), collateralAmount); + IPool(pool).supply(WETH, collateralAmount, alice, 0); + + ICreditDelegationToken(debtToken).approveDelegation( + address(tokenWrapper), + borrowAmount + ); + + tokenWrapper.borrowToken(borrowAmount, address(alice), 0); + vm.stopPrank(); + + uint256 borrowedAmount = tokenWrapper.getTokenInForTokenOut(borrowAmount); + assertEq( + IERC20(tokenWrapper.TOKEN_IN()).balanceOf(address(alice)), + borrowedAmount + ); + } + function _dealTokenIn(address user, uint256 amount) internal virtual { deal(tokenWrapper.TOKEN_IN(), user, amount); } diff --git a/test/SavingsDaiTokenWrapper.t.sol b/test/SavingsDaiTokenWrapper.t.sol index ab45123..ca27d79 100644 --- a/test/SavingsDaiTokenWrapper.t.sol +++ b/test/SavingsDaiTokenWrapper.t.sol @@ -12,7 +12,6 @@ contract SavingsDaiTokenWrapperTest is BaseTokenWrapperTest { address constant DAI = 0x6B175474E89094C44Da98b954EedeAC495271d0F; address constant SDAI = 0x83F20F44975D03b1b09e64809B757c47f942BEeA; address constant ASDAI = 0x4C612E3B15b96Ff9A6faED838F8d07d479a8dD4c; - address constant WETH = 0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2; address constant POOL_CONFIGURATOR = 0x64b761D848206f447Fe2dd461b0c635Ec39EbB27; address constant ADMIN = 0x5300A1a15135EA4dc7aD5a167152C01EFc9b192A; @@ -48,40 +47,4 @@ contract SavingsDaiTokenWrapperTest is BaseTokenWrapperTest { 'Unexpected TOKEN_IN allowance' ); } - - function testBorrow() public { - uint256 collateralAmount = 1000e18; - uint256 borrowAmount = 100e18; - address debtToken = IPool(pool) - .getReserveData(tokenWrapper.TOKEN_OUT()) - .variableDebtTokenAddress; - - // Prank pool admin and set borrowing enabled for SDAI on pool configurator - vm.startPrank(ADMIN); - IPoolConfigurator(POOL_CONFIGURATOR).setReserveBorrowing( - tokenWrapper.TOKEN_OUT(), - true - ); - - address alice = makeAddr('ALICE'); - deal(WETH, alice, collateralAmount); - changePrank(alice); - - IERC20(WETH).approve(address(pool), collateralAmount); - IPool(pool).supply(WETH, collateralAmount, alice, 0); - - ICreditDelegationToken(debtToken).approveDelegation( - address(tokenWrapper), - borrowAmount - ); - - tokenWrapper.borrowToken(borrowAmount, address(alice)); - vm.stopPrank(); - - uint256 borrowedAmount = tokenWrapper.getTokenInForTokenOut(borrowAmount); - assertEq( - IERC20(tokenWrapper.TOKEN_IN()).balanceOf(address(alice)), - borrowedAmount - ); - } } From a2dec9e1e57d64d346a168541653bd74445edf1c Mon Sep 17 00:00:00 2001 From: Mark Hinschberger Date: Tue, 24 Sep 2024 16:08:06 +0200 Subject: [PATCH 10/28] updated files --- ...apper.t.sol => SavingUsdsTokenWrapper.sol} | 38 ++----------------- test/StakedEthTokenWrapper.t.sol | 32 ---------------- 2 files changed, 4 insertions(+), 66 deletions(-) rename test/{SavingsSuSDSTokenWrapper.t.sol => SavingUsdsTokenWrapper.sol} (60%) diff --git a/test/SavingsSuSDSTokenWrapper.t.sol b/test/SavingUsdsTokenWrapper.sol similarity index 60% rename from test/SavingsSuSDSTokenWrapper.t.sol rename to test/SavingUsdsTokenWrapper.sol index 7d04815..55b45d4 100644 --- a/test/SavingsSuSDSTokenWrapper.t.sol +++ b/test/SavingUsdsTokenWrapper.sol @@ -6,14 +6,13 @@ import {DataTypes} from 'aave-v3-core/contracts/protocol/libraries/types/DataTyp import {IERC20} from 'aave-v3-core/contracts/dependencies/openzeppelin/contracts/IERC20.sol'; import {IPool} from 'aave-v3-core/contracts/interfaces/IPool.sol'; import {BaseTokenWrapperTest} from './BaseTokenWrapper.t.sol'; -import {SavingsSuSDSTokenWrapper} from '../src/SavingsSuSDSTokenWrapper.sol'; +import {SavingUsdsTokenWrapper} from '../src/SavingUsdsTokenWrapper.sol'; import {ICreditDelegationToken} from '../src/interfaces/ICreditDelegationToken.sol'; // frontend deposits usds and automatically converted to susds on aave -contract SavingsSuSDSTokenWrapperTest is BaseTokenWrapperTest { +contract SavingUsdsTokenWrapperTest is BaseTokenWrapperTest { address constant USDS = 0xdC035D45d973E3EC169d2276DDab16f1e407384F; address constant SUSDS = 0xa3931d71877C0E7a3148CB7Eb4463524FEc27fbD; - address constant WETH = 0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2; // TODO Actual Address --> fork address constant AUSDS = 0x10Ac93971cdb1F5c778144084242374473c350Da; @@ -25,14 +24,14 @@ contract SavingsSuSDSTokenWrapperTest is BaseTokenWrapperTest { ); pool = 0x87870Bca3F3fD6335C3F4ce8392D69350B4fA4E2; - tokenWrapper = new SavingsSuSDSTokenWrapper(USDS, SUSDS, pool, OWNER); + tokenWrapper = new SavingUsdsTokenWrapper(USDS, SUSDS, pool, OWNER); aTokenOut = AUSDS; tokenInDecimals = 18; permitSupported = true; } function testConstructor() public override { - SavingsSuSDSTokenWrapper tempTokenWrapper = new SavingsSuSDSTokenWrapper( + SavingUsdsTokenWrapper tempTokenWrapper = new SavingUsdsTokenWrapper( USDS, SUSDS, pool, @@ -53,33 +52,4 @@ contract SavingsSuSDSTokenWrapperTest is BaseTokenWrapperTest { 'Unexpected TOKEN_IN allowance' ); } - - function testBorrow() public { - uint256 collateralAmount = 1000e18; - uint256 borrowAmount = 100e18; - address debtToken = IPool(pool) - .getReserveData(tokenWrapper.TOKEN_OUT()) - .variableDebtTokenAddress; - - address alice = makeAddr('ALICE'); - deal(WETH, alice, collateralAmount); - vm.startPrank(alice); - - IERC20(WETH).approve(address(pool), collateralAmount); - IPool(pool).supply(WETH, collateralAmount, alice, 0); - - ICreditDelegationToken(debtToken).approveDelegation( - address(tokenWrapper), - borrowAmount - ); - - tokenWrapper.borrowToken(borrowAmount, address(alice)); - vm.stopPrank(); - - uint256 borrowedAmount = tokenWrapper.getTokenInForTokenOut(borrowAmount); - assertEq( - IERC20(tokenWrapper.TOKEN_IN()).balanceOf(address(alice)), - borrowedAmount - ); - } } diff --git a/test/StakedEthTokenWrapper.t.sol b/test/StakedEthTokenWrapper.t.sol index 0bb584b..2ac58ec 100644 --- a/test/StakedEthTokenWrapper.t.sol +++ b/test/StakedEthTokenWrapper.t.sol @@ -11,7 +11,6 @@ contract StakedEthTokenWrapperTest is BaseTokenWrapperTest { address constant STETH = 0xae7ab96520DE3A18E5e111B5EaAb095312D7fE84; address constant WSTETH = 0x7f39C581F595B53c5cb19bD0b3f8dA6c935E2Ca0; address constant AWSTETH = 0x0B925eD163218f6662a35e0f0371Ac234f9E9371; - address constant WETH = 0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2; function setUp() public { vm.createSelectFork(vm.envString('ETH_RPC_URL'), 20784588); @@ -45,37 +44,6 @@ contract StakedEthTokenWrapperTest is BaseTokenWrapperTest { ); } - function testBorrow() public { - uint256 collateralAmount = 1000e18; - uint256 borrowAmount = 100e18; - address debtToken = IPool(pool) - .getReserveData(tokenWrapper.TOKEN_OUT()) - .variableDebtTokenAddress; - - address alice = makeAddr('ALICE'); - deal(WETH, alice, collateralAmount); - vm.startPrank(alice); - - IERC20(WETH).approve(address(pool), collateralAmount); - IPool(pool).supply(WETH, collateralAmount, alice, 0); - - ICreditDelegationToken(debtToken).approveDelegation( - address(tokenWrapper), - borrowAmount - ); - - tokenWrapper.borrowToken(borrowAmount, address(alice)); - vm.stopPrank(); - - uint256 borrowedAmount = tokenWrapper.getTokenInForTokenOut(borrowAmount); - // Allow OBOB for rounding to nearest wei - assertApproxEqAbs( - IERC20(tokenWrapper.TOKEN_IN()).balanceOf(address(alice)), - borrowedAmount, - 1 - ); - } - function _dealTokenIn(address user, uint256 amount) internal override { vm.deal(user, amount); vm.prank(user); From 00b6ed449b3eb0b73315a74ff41e74947ca53b6f Mon Sep 17 00:00:00 2001 From: Mark Hinschberger Date: Tue, 24 Sep 2024 16:55:27 +0200 Subject: [PATCH 11/28] move borrow as virtual on base --- src/BaseTokenWrapper.sol | 11 +---------- src/SavingUsdsTokenWrapper.sol | 16 ++++++++++++++++ test/BaseTokenWrapper.t.sol | 29 ----------------------------- test/SavingUsdsTokenWrapper.sol | 29 +++++++++++++++++++++++++++++ 4 files changed, 46 insertions(+), 39 deletions(-) diff --git a/src/BaseTokenWrapper.sol b/src/BaseTokenWrapper.sol index 076111c..7849699 100644 --- a/src/BaseTokenWrapper.sol +++ b/src/BaseTokenWrapper.sol @@ -107,16 +107,7 @@ abstract contract BaseTokenWrapper is Ownable, IBaseTokenWrapper { uint256 amount, address to, uint16 referralCode - ) external virtual { - require(amount > 0, 'INSUFFICIENT_AMOUNT_TO_BORROW'); - uint256 balanceBeforeBorrow = IERC20(TOKEN_OUT).balanceOf(address(this)); - POOL.borrow(TOKEN_OUT, amount, 2, referralCode, address(to)); - uint256 balanceAfterBorrow = IERC20(TOKEN_OUT).balanceOf(address(this)); - uint256 amountIn = _unwrapTokenOut( - balanceAfterBorrow - balanceBeforeBorrow - ); - IERC20(TOKEN_IN).transfer(to, amountIn); - } + ) external virtual {} /// @inheritdoc IBaseTokenWrapper function rescueTokens( diff --git a/src/SavingUsdsTokenWrapper.sol b/src/SavingUsdsTokenWrapper.sol index 4a75ede..bc5a365 100644 --- a/src/SavingUsdsTokenWrapper.sol +++ b/src/SavingUsdsTokenWrapper.sol @@ -51,4 +51,20 @@ contract SavingUsdsTokenWrapper is BaseTokenWrapper { function _unwrapTokenOut(uint256 amount) internal override returns (uint256) { return IUSDS(TOKEN_OUT).redeem(amount, address(this), address(this)); } + + /// @inheritdoc BaseTokenWrapper + function borrowToken( + uint256 amount, + address to, + uint16 referralCode + ) external override { + require(amount > 0, 'INSUFFICIENT_AMOUNT_TO_BORROW'); + uint256 balanceBeforeBorrow = IERC20(TOKEN_OUT).balanceOf(address(this)); + POOL.borrow(TOKEN_OUT, amount, 2, referralCode, address(to)); + uint256 balanceAfterBorrow = IERC20(TOKEN_OUT).balanceOf(address(this)); + uint256 amountIn = _unwrapTokenOut( + balanceAfterBorrow - balanceBeforeBorrow + ); + IERC20(TOKEN_IN).transfer(to, amountIn); + } } diff --git a/test/BaseTokenWrapper.t.sol b/test/BaseTokenWrapper.t.sol index e4b913f..115e38d 100644 --- a/test/BaseTokenWrapper.t.sol +++ b/test/BaseTokenWrapper.t.sol @@ -735,35 +735,6 @@ abstract contract BaseTokenWrapperTest is Test { assertGt(withdrawnAmount, 0, 'Unexpected withdraw return/balance mismatch'); } - function testBorrow() public { - uint256 collateralAmount = 1000e18; - uint256 borrowAmount = 100e18; - address debtToken = IPool(pool) - .getReserveData(tokenWrapper.TOKEN_OUT()) - .variableDebtTokenAddress; - - address alice = makeAddr('ALICE'); - deal(WETH, alice, collateralAmount); - vm.startPrank(alice); - - IERC20(WETH).approve(address(pool), collateralAmount); - IPool(pool).supply(WETH, collateralAmount, alice, 0); - - ICreditDelegationToken(debtToken).approveDelegation( - address(tokenWrapper), - borrowAmount - ); - - tokenWrapper.borrowToken(borrowAmount, address(alice), 0); - vm.stopPrank(); - - uint256 borrowedAmount = tokenWrapper.getTokenInForTokenOut(borrowAmount); - assertEq( - IERC20(tokenWrapper.TOKEN_IN()).balanceOf(address(alice)), - borrowedAmount - ); - } - function _dealTokenIn(address user, uint256 amount) internal virtual { deal(tokenWrapper.TOKEN_IN(), user, amount); } diff --git a/test/SavingUsdsTokenWrapper.sol b/test/SavingUsdsTokenWrapper.sol index 55b45d4..956669f 100644 --- a/test/SavingUsdsTokenWrapper.sol +++ b/test/SavingUsdsTokenWrapper.sol @@ -52,4 +52,33 @@ contract SavingUsdsTokenWrapperTest is BaseTokenWrapperTest { 'Unexpected TOKEN_IN allowance' ); } + + function testBorrow() public { + uint256 collateralAmount = 1000e18; + uint256 borrowAmount = 100e18; + address debtToken = IPool(pool) + .getReserveData(tokenWrapper.TOKEN_OUT()) + .variableDebtTokenAddress; + + address alice = makeAddr('ALICE'); + deal(WETH, alice, collateralAmount); + vm.startPrank(alice); + + IERC20(WETH).approve(address(pool), collateralAmount); + IPool(pool).supply(WETH, collateralAmount, alice, 0); + + ICreditDelegationToken(debtToken).approveDelegation( + address(tokenWrapper), + borrowAmount + ); + + tokenWrapper.borrowToken(borrowAmount, address(alice), 0); + vm.stopPrank(); + + uint256 borrowedAmount = tokenWrapper.getTokenInForTokenOut(borrowAmount); + assertEq( + IERC20(tokenWrapper.TOKEN_IN()).balanceOf(address(alice)), + borrowedAmount + ); + } } From 01633863c00de20ed9d776afe456e308b5b02123 Mon Sep 17 00:00:00 2001 From: CheyenneAtapour Date: Tue, 24 Sep 2024 20:38:18 -0700 Subject: [PATCH 12/28] fix: Fuzz test, rename test, reorder fns per convention --- src/SavingUsdsTokenWrapper.sol | 32 +++++++++---------- test/BaseTokenWrapper.t.sol | 6 ++-- ...apper.sol => SavingUsdsTokenWrapper.t.sol} | 0 3 files changed, 20 insertions(+), 18 deletions(-) rename test/{SavingUsdsTokenWrapper.sol => SavingUsdsTokenWrapper.t.sol} (100%) diff --git a/src/SavingUsdsTokenWrapper.sol b/src/SavingUsdsTokenWrapper.sol index bc5a365..9427192 100644 --- a/src/SavingUsdsTokenWrapper.sol +++ b/src/SavingUsdsTokenWrapper.sol @@ -28,6 +28,22 @@ contract SavingUsdsTokenWrapper is BaseTokenWrapper { IERC20(tokenIn).approve(tokenOut, type(uint256).max); } + /// @inheritdoc BaseTokenWrapper + function borrowToken( + uint256 amount, + address to, + uint16 referralCode + ) external override { + require(amount > 0, 'INSUFFICIENT_AMOUNT_TO_BORROW'); + uint256 balanceBeforeBorrow = IERC20(TOKEN_OUT).balanceOf(address(this)); + POOL.borrow(TOKEN_OUT, amount, 2, referralCode, address(to)); + uint256 balanceAfterBorrow = IERC20(TOKEN_OUT).balanceOf(address(this)); + uint256 amountIn = _unwrapTokenOut( + balanceAfterBorrow - balanceBeforeBorrow + ); + IERC20(TOKEN_IN).transfer(to, amountIn); + } + /// @inheritdoc BaseTokenWrapper function getTokenOutForTokenIn( uint256 amount @@ -51,20 +67,4 @@ contract SavingUsdsTokenWrapper is BaseTokenWrapper { function _unwrapTokenOut(uint256 amount) internal override returns (uint256) { return IUSDS(TOKEN_OUT).redeem(amount, address(this), address(this)); } - - /// @inheritdoc BaseTokenWrapper - function borrowToken( - uint256 amount, - address to, - uint16 referralCode - ) external override { - require(amount > 0, 'INSUFFICIENT_AMOUNT_TO_BORROW'); - uint256 balanceBeforeBorrow = IERC20(TOKEN_OUT).balanceOf(address(this)); - POOL.borrow(TOKEN_OUT, amount, 2, referralCode, address(to)); - uint256 balanceAfterBorrow = IERC20(TOKEN_OUT).balanceOf(address(this)); - uint256 amountIn = _unwrapTokenOut( - balanceAfterBorrow - balanceBeforeBorrow - ); - IERC20(TOKEN_IN).transfer(to, amountIn); - } } diff --git a/test/BaseTokenWrapper.t.sol b/test/BaseTokenWrapper.t.sol index 115e38d..35bd007 100644 --- a/test/BaseTokenWrapper.t.sol +++ b/test/BaseTokenWrapper.t.sol @@ -682,6 +682,7 @@ abstract contract BaseTokenWrapperTest is Test { function testFuzzSupplyToken(uint256 amount, address referee) public { amount = bound(amount, 1, MAX_DEAL_AMOUNT); + vm.assume(IAToken(aTokenOut).balanceOf(referee) == 0); IERC20 tokenIn = IERC20(tokenWrapper.TOKEN_IN()); uint256 amountScaled = amount * 10 ** tokenInDecimals; @@ -700,8 +701,9 @@ abstract contract BaseTokenWrapperTest is Test { vm.stopPrank(); assertEq(tokenIn.balanceOf(ALICE), 0, 'Unexpected ending tokenIn balance'); - assertLe( - estimateFinalBalance - IAToken(aTokenOut).balanceOf(referee), + assertApproxEqAbs( + estimateFinalBalance, + IAToken(aTokenOut).balanceOf(referee), 1, 'Unexpected ending aToken balance' ); diff --git a/test/SavingUsdsTokenWrapper.sol b/test/SavingUsdsTokenWrapper.t.sol similarity index 100% rename from test/SavingUsdsTokenWrapper.sol rename to test/SavingUsdsTokenWrapper.t.sol From 2cce3f7540dbe6bd35ec9e654d57925731f63ee4 Mon Sep 17 00:00:00 2001 From: Mark Hinschberger Date: Wed, 25 Sep 2024 10:19:05 +0200 Subject: [PATCH 13/28] borrow not permitted --- src/BaseTokenWrapper.sol | 8 ++++++++ src/SavingsDaiTokenWrapper.sol | 7 +++++++ src/StakedEthTokenWrapper.sol | 7 +++++++ test/SavingsDaiTokenWrapper.t.sol | 23 +++++++++++++++++++++++ test/StakedEthTokenWrapper.t.sol | 23 +++++++++++++++++++++++ 5 files changed, 68 insertions(+) diff --git a/src/BaseTokenWrapper.sol b/src/BaseTokenWrapper.sol index 7849699..78d32b5 100644 --- a/src/BaseTokenWrapper.sol +++ b/src/BaseTokenWrapper.sol @@ -25,6 +25,14 @@ abstract contract BaseTokenWrapper is Ownable, IBaseTokenWrapper { /// @inheritdoc IBaseTokenWrapper IPool public immutable POOL; + /** + * @dev Throws if called by any token wrapper borrow function not permitted. + */ + modifier actionNotPermitted() { + require(false, 'INVALID_ACTION'); + _; + } + /** * @dev Constructor * @param tokenIn ERC-20 token that will be wrapped in supply operations diff --git a/src/SavingsDaiTokenWrapper.sol b/src/SavingsDaiTokenWrapper.sol index 4c07ea1..4e94c46 100644 --- a/src/SavingsDaiTokenWrapper.sol +++ b/src/SavingsDaiTokenWrapper.sol @@ -50,4 +50,11 @@ contract SavingsDaiTokenWrapper is BaseTokenWrapper { function _unwrapTokenOut(uint256 amount) internal override returns (uint256) { return ISavingsDai(TOKEN_OUT).redeem(amount, address(this), address(this)); } + + /// @inheritdoc BaseTokenWrapper + function borrowToken( + uint256 amount, + address to, + uint16 referralCode + ) external override actionNotPermitted {} } diff --git a/src/StakedEthTokenWrapper.sol b/src/StakedEthTokenWrapper.sol index 58c469c..42006ce 100644 --- a/src/StakedEthTokenWrapper.sol +++ b/src/StakedEthTokenWrapper.sol @@ -50,4 +50,11 @@ contract StakedEthTokenWrapper is BaseTokenWrapper { function _unwrapTokenOut(uint256 amount) internal override returns (uint256) { return IWstETH(TOKEN_OUT).unwrap(amount); } + + /// @inheritdoc BaseTokenWrapper + function borrowToken( + uint256 amount, + address to, + uint16 referralCode + ) external override actionNotPermitted {} } diff --git a/test/SavingsDaiTokenWrapper.t.sol b/test/SavingsDaiTokenWrapper.t.sol index ca27d79..5bbcfca 100644 --- a/test/SavingsDaiTokenWrapper.t.sol +++ b/test/SavingsDaiTokenWrapper.t.sol @@ -47,4 +47,27 @@ contract SavingsDaiTokenWrapperTest is BaseTokenWrapperTest { 'Unexpected TOKEN_IN allowance' ); } + + function testBorrowNotPermitted() public { + uint256 collateralAmount = 1000e18; + uint256 borrowAmount = 100e18; + address debtToken = IPool(pool) + .getReserveData(tokenWrapper.TOKEN_OUT()) + .variableDebtTokenAddress; + + address alice = makeAddr('ALICE'); + deal(WETH, alice, collateralAmount); + vm.startPrank(alice); + + IERC20(WETH).approve(address(pool), collateralAmount); + IPool(pool).supply(WETH, collateralAmount, alice, 0); + + ICreditDelegationToken(debtToken).approveDelegation( + address(tokenWrapper), + borrowAmount + ); + vm.expectRevert('INVALID_ACTION'); + tokenWrapper.borrowToken(borrowAmount, address(alice), 0); + vm.stopPrank(); + } } diff --git a/test/StakedEthTokenWrapper.t.sol b/test/StakedEthTokenWrapper.t.sol index 2ac58ec..745ca18 100644 --- a/test/StakedEthTokenWrapper.t.sol +++ b/test/StakedEthTokenWrapper.t.sol @@ -50,4 +50,27 @@ contract StakedEthTokenWrapperTest is BaseTokenWrapperTest { (bool success, ) = STETH.call{value: amount}(''); require(success); } + + function testBorrowNotPermitted() public { + uint256 collateralAmount = 1000e18; + uint256 borrowAmount = 100e18; + address debtToken = IPool(pool) + .getReserveData(tokenWrapper.TOKEN_OUT()) + .variableDebtTokenAddress; + + address alice = makeAddr('ALICE'); + deal(WETH, alice, collateralAmount); + vm.startPrank(alice); + + IERC20(WETH).approve(address(pool), collateralAmount); + IPool(pool).supply(WETH, collateralAmount, alice, 0); + + ICreditDelegationToken(debtToken).approveDelegation( + address(tokenWrapper), + borrowAmount + ); + vm.expectRevert('INVALID_ACTION'); + tokenWrapper.borrowToken(borrowAmount, address(alice), 0); + vm.stopPrank(); + } } From 6915b969897ebe65b6ee957bfd19abffa0beb009 Mon Sep 17 00:00:00 2001 From: Mark Hinschberger Date: Wed, 25 Sep 2024 16:14:39 +0200 Subject: [PATCH 14/28] borrow with sig --- src/BaseTokenWrapper.sol | 11 +++ src/SavingUsdsTokenWrapper.sol | 39 +++++++++ src/SavingsDaiTokenWrapper.sol | 11 +++ src/StakedEthTokenWrapper.sol | 11 +++ src/interfaces/IBaseTokenWrapper.sol | 20 +++++ test/SavingUsdsTokenWrapper.t.sol | 120 +++++++++++++++++++++++++++ test/utils/SigUtils.sol | 103 +++++++++++++++++++++++ 7 files changed, 315 insertions(+) create mode 100644 test/utils/SigUtils.sol diff --git a/src/BaseTokenWrapper.sol b/src/BaseTokenWrapper.sol index 78d32b5..bda9588 100644 --- a/src/BaseTokenWrapper.sol +++ b/src/BaseTokenWrapper.sol @@ -117,6 +117,17 @@ abstract contract BaseTokenWrapper is Ownable, IBaseTokenWrapper { uint16 referralCode ) external virtual {} + /// @inheritdoc IBaseTokenWrapper + function borrowTokenWithPermit( + uint256 amount, + address to, + uint16 referralCode, + uint256 deadline, + uint8 permitV, + bytes32 permitR, + bytes32 permitS + ) external virtual {} + /// @inheritdoc IBaseTokenWrapper function rescueTokens( IERC20 token, diff --git a/src/SavingUsdsTokenWrapper.sol b/src/SavingUsdsTokenWrapper.sol index 9427192..33b335b 100644 --- a/src/SavingUsdsTokenWrapper.sol +++ b/src/SavingUsdsTokenWrapper.sol @@ -2,6 +2,8 @@ pragma solidity ^0.8.10; import 'forge-std/console2.sol'; +import {IPool} from 'aave-v3-core/contracts/interfaces/IPool.sol'; +import {ICreditDelegationToken} from 'aave-v3-core/contracts/interfaces/ICreditDelegationToken.sol'; import {IERC20} from 'aave-v3-core/contracts/dependencies/openzeppelin/contracts/IERC20.sol'; import {IUSDS} from './dependencies/IUSDS.sol'; import {BaseTokenWrapper} from './BaseTokenWrapper.sol'; @@ -44,6 +46,43 @@ contract SavingUsdsTokenWrapper is BaseTokenWrapper { IERC20(TOKEN_IN).transfer(to, amountIn); } + function borrowTokenWithPermit( + uint256 amount, + address to, + uint16 referralCode, + uint256 deadline, + uint8 permitV, + bytes32 permitR, + bytes32 permitS + ) external override { + require(amount > 0, 'INSUFFICIENT_AMOUNT_TO_BORROW'); + + if (deadline != 0) { + address debtToken = IPool(0x87870Bca3F3fD6335C3F4ce8392D69350B4fA4E2) + .getReserveData(TOKEN_OUT) + .variableDebtTokenAddress; + + ICreditDelegationToken(debtToken).delegationWithSig( + msg.sender, + address(this), + amount, + deadline, + permitV, + permitR, + permitS + ); + } + uint256 balanceBeforeBorrow = IERC20(TOKEN_OUT).balanceOf(address(this)); + + POOL.borrow(TOKEN_OUT, amount, 2, referralCode, address(to)); + uint256 balanceAfterBorrow = IERC20(TOKEN_OUT).balanceOf(address(this)); + + uint256 amountIn = _unwrapTokenOut( + balanceAfterBorrow - balanceBeforeBorrow + ); + IERC20(TOKEN_IN).transfer(to, amountIn); + } + /// @inheritdoc BaseTokenWrapper function getTokenOutForTokenIn( uint256 amount diff --git a/src/SavingsDaiTokenWrapper.sol b/src/SavingsDaiTokenWrapper.sol index 4e94c46..9a80415 100644 --- a/src/SavingsDaiTokenWrapper.sol +++ b/src/SavingsDaiTokenWrapper.sol @@ -57,4 +57,15 @@ contract SavingsDaiTokenWrapper is BaseTokenWrapper { address to, uint16 referralCode ) external override actionNotPermitted {} + + /// @inheritdoc BaseTokenWrapper + function borrowTokenWithPermit( + uint256 amount, + address to, + uint16 referralCode, + uint256 deadline, + uint8 permitV, + bytes32 permitR, + bytes32 permitS + ) external override actionNotPermitted {} } diff --git a/src/StakedEthTokenWrapper.sol b/src/StakedEthTokenWrapper.sol index 42006ce..53fbbb8 100644 --- a/src/StakedEthTokenWrapper.sol +++ b/src/StakedEthTokenWrapper.sol @@ -57,4 +57,15 @@ contract StakedEthTokenWrapper is BaseTokenWrapper { address to, uint16 referralCode ) external override actionNotPermitted {} + + /// @inheritdoc BaseTokenWrapper + function borrowTokenWithPermit( + uint256 amount, + address to, + uint16 referralCode, + uint256 deadline, + uint8 permitV, + bytes32 permitR, + bytes32 permitS + ) external override actionNotPermitted {} } diff --git a/src/interfaces/IBaseTokenWrapper.sol b/src/interfaces/IBaseTokenWrapper.sol index 3af799b..f1a811d 100644 --- a/src/interfaces/IBaseTokenWrapper.sol +++ b/src/interfaces/IBaseTokenWrapper.sol @@ -73,6 +73,26 @@ interface IBaseTokenWrapper { uint16 referralCode ) external; + /** + * @notice Borrows token from the Pool, unwraps it, and sends it to the recipient using EIP-2612 permit + * @param amount The amount of token to borrow + * @param to The address that will receive the unwrapped token + * @param referralCode Code used to register the integrator originating the operation, for potential rewards + * @param deadline The deadline timestamp for the permit signature to be valid + * @param permitV The V parameter of the permit signature + * @param permitR The R parameter of the permit signature + * @param permitS The S parameter of the permit signature + */ + function borrowTokenWithPermit( + uint256 amount, + address to, + uint16 referralCode, + uint256 deadline, + uint8 permitV, + bytes32 permitR, + bytes32 permitS + ) external; + /** * @notice Provides way for the contract owner to rescue ERC-20 tokens * @param token The address of the token to withdraw from this contract diff --git a/test/SavingUsdsTokenWrapper.t.sol b/test/SavingUsdsTokenWrapper.t.sol index 956669f..e1df241 100644 --- a/test/SavingUsdsTokenWrapper.t.sol +++ b/test/SavingUsdsTokenWrapper.t.sol @@ -8,6 +8,9 @@ import {IPool} from 'aave-v3-core/contracts/interfaces/IPool.sol'; import {BaseTokenWrapperTest} from './BaseTokenWrapper.t.sol'; import {SavingUsdsTokenWrapper} from '../src/SavingUsdsTokenWrapper.sol'; import {ICreditDelegationToken} from '../src/interfaces/ICreditDelegationToken.sol'; +import {IERC20WithPermit} from 'aave-v3-core/contracts/interfaces/IERC20WithPermit.sol'; +import {SigUtils} from './utils/SigUtils.sol'; +import {IAToken} from 'aave-v3-core/contracts/interfaces/IAToken.sol'; // frontend deposits usds and automatically converted to susds on aave contract SavingUsdsTokenWrapperTest is BaseTokenWrapperTest { @@ -62,6 +65,7 @@ contract SavingUsdsTokenWrapperTest is BaseTokenWrapperTest { address alice = makeAddr('ALICE'); deal(WETH, alice, collateralAmount); + vm.startPrank(alice); IERC20(WETH).approve(address(pool), collateralAmount); @@ -81,4 +85,120 @@ contract SavingUsdsTokenWrapperTest is BaseTokenWrapperTest { borrowedAmount ); } + + function testBorrowTokenWithPermit() public { + uint256 borrowAmount = 100e18; + uint256 collateralAmount = 1000e18; + + uint256 userPrivateKey = 0xA11CE; + address alice = address(vm.addr(userPrivateKey)); + deal(WETH, alice, collateralAmount); + + address debtToken = IPool(pool) + .getReserveData(tokenWrapper.TOKEN_OUT()) + .variableDebtTokenAddress; + + vm.startPrank(alice); + + IERC20(WETH).approve(address(pool), collateralAmount); + + IPool(pool).supply(WETH, collateralAmount, alice, 0); + + uint256 deadline = block.timestamp + 1 hours; + uint256 nonce = IAToken(debtToken).nonces(alice); + + (uint8 v, bytes32 r, bytes32 s) = _signCreditDelegation( + userPrivateKey, + address(tokenWrapper), + borrowAmount, + nonce, + deadline, + debtToken + ); + + tokenWrapper.borrowTokenWithPermit( + borrowAmount, + alice, + 1, + deadline, + v, + r, + s + ); + + vm.stopPrank(); + + uint256 borrowedAmount = tokenWrapper.getTokenInForTokenOut(borrowAmount); + assertEq( + IERC20(tokenWrapper.TOKEN_IN()).balanceOf(address(alice)), + borrowedAmount + ); + } + + function testBorrowTokenWithPermitZeroAmount() public { + uint256 borrowAmount = 0; + uint256 collateralAmount = 1000e18; + + uint256 userPrivateKey = 0xA11CE; + address alice = address(vm.addr(userPrivateKey)); + deal(WETH, alice, collateralAmount); + + address debtToken = IPool(pool) + .getReserveData(tokenWrapper.TOKEN_OUT()) + .variableDebtTokenAddress; + + vm.startPrank(alice); + + IERC20(WETH).approve(address(pool), collateralAmount); + + IPool(pool).supply(WETH, collateralAmount, alice, 0); + + uint256 deadline = block.timestamp + 1 hours; + uint256 nonce = IAToken(debtToken).nonces(alice); + + (uint8 v, bytes32 r, bytes32 s) = _signCreditDelegation( + userPrivateKey, + address(tokenWrapper), + borrowAmount, + nonce, + deadline, + debtToken + ); + + vm.expectRevert('INSUFFICIENT_AMOUNT_TO_BORROW'); + tokenWrapper.borrowTokenWithPermit( + borrowAmount, + alice, + 1, + deadline, + v, + r, + s + ); + } + + function _signCreditDelegation( + uint256 privateKey, + address delegatee, + uint256 value, + uint256 nonce, + uint256 deadline, + address debtToken + ) internal view returns (uint8 v, bytes32 r, bytes32 s) { + SigUtils.CreditDelegation memory creditDelegation = SigUtils + .CreditDelegation({ + delegatee: delegatee, + value: value, + nonce: nonce, + deadline: deadline + }); + + bytes32 domainSeparator = IAToken(debtToken).DOMAIN_SEPARATOR(); + bytes32 digest = SigUtils.getCreditDelegationTypedDataHash( + creditDelegation, + domainSeparator + ); + + return vm.sign(privateKey, digest); + } } diff --git a/test/utils/SigUtils.sol b/test/utils/SigUtils.sol new file mode 100644 index 0000000..43a3b93 --- /dev/null +++ b/test/utils/SigUtils.sol @@ -0,0 +1,103 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.10; + +library SigUtils { + bytes32 public constant PERMIT_TYPEHASH = + keccak256( + 'Permit(address owner,address spender,uint256 value,uint256 nonce,uint256 deadline)' + ); + + bytes32 private constant CREDIT_DELEGATION_TYPEHASH = + keccak256( + 'DelegationWithSig(address delegatee,uint256 value,uint256 nonce,uint256 deadline)' + ); + + struct Permit { + address owner; + address spender; + uint256 value; + uint256 nonce; + uint256 deadline; + } + + struct CreditDelegation { + address delegatee; + uint256 value; + uint256 nonce; + uint256 deadline; + } + + // computes the hash of a permit + function getStructHash( + Permit memory _permit + ) internal pure returns (bytes32) { + return + keccak256( + abi.encode( + PERMIT_TYPEHASH, + _permit.owner, + _permit.spender, + _permit.value, + _permit.nonce, + _permit.deadline + ) + ); + } + + // computes the hash of the fully encoded EIP-712 message for the domain, which can be used to recover the signer + function getPermitTypedDataHash( + Permit memory _permit, + bytes32 domainSeparator + ) internal pure returns (bytes32) { + return + keccak256( + abi.encodePacked( + '\x19\x01', + domainSeparator, + keccak256( + abi.encode( + PERMIT_TYPEHASH, + _permit.owner, + _permit.spender, + _permit.value, + _permit.nonce, + _permit.deadline + ) + ) + ) + ); + } + + function getCreditDelegationTypedDataHash( + CreditDelegation memory _creditDelegation, + bytes32 domainSeparator + ) internal pure returns (bytes32) { + return + keccak256( + abi.encodePacked( + '\x19\x01', + domainSeparator, + keccak256( + abi.encode( + CREDIT_DELEGATION_TYPEHASH, + _creditDelegation.delegatee, + _creditDelegation.value, + _creditDelegation.nonce, + _creditDelegation.deadline + ) + ) + ) + ); + } + + // computes the hash of the fully encoded EIP-712 message for the domain, which can be used to recover the signer + function getTypedDataHash( + Permit memory permit, + bytes32 domainSeperator + ) internal pure returns (bytes32) { + return + keccak256( + abi.encodePacked('\x19\x01', domainSeperator, getStructHash(permit)) + ); + } +} From c24b35890804f450c30a0dbfad51547326d49e33 Mon Sep 17 00:00:00 2001 From: CheyenneAtapour Date: Wed, 25 Sep 2024 08:53:32 -0700 Subject: [PATCH 15/28] fix: Move borrow logic to internal function --- src/SavingUsdsTokenWrapper.sol | 37 +++++++++++++++++----------------- 1 file changed, 18 insertions(+), 19 deletions(-) diff --git a/src/SavingUsdsTokenWrapper.sol b/src/SavingUsdsTokenWrapper.sol index 33b335b..c2a2d66 100644 --- a/src/SavingUsdsTokenWrapper.sol +++ b/src/SavingUsdsTokenWrapper.sol @@ -36,16 +36,10 @@ contract SavingUsdsTokenWrapper is BaseTokenWrapper { address to, uint16 referralCode ) external override { - require(amount > 0, 'INSUFFICIENT_AMOUNT_TO_BORROW'); - uint256 balanceBeforeBorrow = IERC20(TOKEN_OUT).balanceOf(address(this)); - POOL.borrow(TOKEN_OUT, amount, 2, referralCode, address(to)); - uint256 balanceAfterBorrow = IERC20(TOKEN_OUT).balanceOf(address(this)); - uint256 amountIn = _unwrapTokenOut( - balanceAfterBorrow - balanceBeforeBorrow - ); - IERC20(TOKEN_IN).transfer(to, amountIn); + _borrowToken(amount, to, referralCode); } + /// @inheritdoc BaseTokenWrapper function borrowTokenWithPermit( uint256 amount, address to, @@ -55,8 +49,6 @@ contract SavingUsdsTokenWrapper is BaseTokenWrapper { bytes32 permitR, bytes32 permitS ) external override { - require(amount > 0, 'INSUFFICIENT_AMOUNT_TO_BORROW'); - if (deadline != 0) { address debtToken = IPool(0x87870Bca3F3fD6335C3F4ce8392D69350B4fA4E2) .getReserveData(TOKEN_OUT) @@ -72,15 +64,7 @@ contract SavingUsdsTokenWrapper is BaseTokenWrapper { permitS ); } - uint256 balanceBeforeBorrow = IERC20(TOKEN_OUT).balanceOf(address(this)); - - POOL.borrow(TOKEN_OUT, amount, 2, referralCode, address(to)); - uint256 balanceAfterBorrow = IERC20(TOKEN_OUT).balanceOf(address(this)); - - uint256 amountIn = _unwrapTokenOut( - balanceAfterBorrow - balanceBeforeBorrow - ); - IERC20(TOKEN_IN).transfer(to, amountIn); + _borrowToken(amount, to, referralCode); } /// @inheritdoc BaseTokenWrapper @@ -106,4 +90,19 @@ contract SavingUsdsTokenWrapper is BaseTokenWrapper { function _unwrapTokenOut(uint256 amount) internal override returns (uint256) { return IUSDS(TOKEN_OUT).redeem(amount, address(this), address(this)); } + + function _borrowToken( + uint256 amount, + address to, + uint16 referralCode + ) internal { + require(amount > 0, 'INSUFFICIENT_AMOUNT_TO_BORROW'); + uint256 balanceBeforeBorrow = IERC20(TOKEN_OUT).balanceOf(address(this)); + POOL.borrow(TOKEN_OUT, amount, 2, referralCode, address(to)); + uint256 balanceAfterBorrow = IERC20(TOKEN_OUT).balanceOf(address(this)); + uint256 amountIn = _unwrapTokenOut( + balanceAfterBorrow - balanceBeforeBorrow + ); + IERC20(TOKEN_IN).transfer(to, amountIn); + } } From 1ab43a08563f9e8a0bcaa33acf2cefb3d232359f Mon Sep 17 00:00:00 2001 From: CheyenneAtapour Date: Thu, 26 Sep 2024 09:23:48 -0700 Subject: [PATCH 16/28] fix: Address pr comments --- src/BaseTokenWrapper.sol | 42 ++++++++++++++++------------ src/SavingUsdsTokenWrapper.sol | 42 ++++++---------------------- src/SavingsDaiTokenWrapper.sol | 18 +++++------- src/StakedEthTokenWrapper.sol | 18 +++++------- src/interfaces/IBaseTokenWrapper.sol | 19 ++----------- test/BaseTokenWrapper.t.sol | 3 +- test/SavingUsdsTokenWrapper.t.sol | 34 ++++++++-------------- test/SavingsDaiTokenWrapper.t.sol | 3 +- test/StakedEthTokenWrapper.t.sol | 3 +- 9 files changed, 67 insertions(+), 115 deletions(-) diff --git a/src/BaseTokenWrapper.sol b/src/BaseTokenWrapper.sol index bda9588..c6cd16c 100644 --- a/src/BaseTokenWrapper.sol +++ b/src/BaseTokenWrapper.sol @@ -1,5 +1,6 @@ // SPDX-License-Identifier: AGPL-3.0 pragma solidity ^0.8.10; + import {Ownable} from 'aave-v3-core/contracts/dependencies/openzeppelin/contracts/Ownable.sol'; import {IERC20} from 'aave-v3-core/contracts/dependencies/openzeppelin/contracts/IERC20.sol'; import {IERC20WithPermit} from 'aave-v3-core/contracts/interfaces/IERC20WithPermit.sol'; @@ -25,14 +26,6 @@ abstract contract BaseTokenWrapper is Ownable, IBaseTokenWrapper { /// @inheritdoc IBaseTokenWrapper IPool public immutable POOL; - /** - * @dev Throws if called by any token wrapper borrow function not permitted. - */ - modifier actionNotPermitted() { - require(false, 'INVALID_ACTION'); - _; - } - /** * @dev Constructor * @param tokenIn ERC-20 token that will be wrapped in supply operations @@ -111,21 +104,13 @@ abstract contract BaseTokenWrapper is Ownable, IBaseTokenWrapper { } /// @inheritdoc IBaseTokenWrapper - function borrowToken( - uint256 amount, - address to, - uint16 referralCode - ) external virtual {} + function borrowToken(uint256 amount, uint16 referralCode) external virtual {} /// @inheritdoc IBaseTokenWrapper function borrowTokenWithPermit( uint256 amount, - address to, uint16 referralCode, - uint256 deadline, - uint8 permitV, - bytes32 permitR, - bytes32 permitS + PermitSignature calldata signature ) external virtual {} /// @inheritdoc IBaseTokenWrapper @@ -202,6 +187,27 @@ abstract contract BaseTokenWrapper is Ownable, IBaseTokenWrapper { return amountUnwrapped; } + /** + * @notice Helper to borrow token from the Pool and unwraps it, sending to the recipient + * @param amount The amount of token to borrow + * @param onBehalfOf The address that will receive the unwrapped token + * @param referralCode Code used to register the integrator originating the operation, for potential rewards + */ + function _borrowToken( + uint256 amount, + address onBehalfOf, + uint16 referralCode + ) internal { + require(amount > 0, 'INSUFFICIENT_AMOUNT_TO_BORROW'); + uint256 balanceBeforeBorrow = IERC20(TOKEN_OUT).balanceOf(address(this)); + POOL.borrow(TOKEN_OUT, amount, 2, referralCode, address(onBehalfOf)); + uint256 balanceAfterBorrow = IERC20(TOKEN_OUT).balanceOf(address(this)); + uint256 amountIn = _unwrapTokenOut( + balanceAfterBorrow - balanceBeforeBorrow + ); + IERC20(TOKEN_IN).transfer(onBehalfOf, amountIn); + } + /** * @notice Helper to wrap an amount of tokenIn, receiving tokenOut * @param amount The amount of tokenIn to wrap diff --git a/src/SavingUsdsTokenWrapper.sol b/src/SavingUsdsTokenWrapper.sol index c2a2d66..c0f25af 100644 --- a/src/SavingUsdsTokenWrapper.sol +++ b/src/SavingUsdsTokenWrapper.sol @@ -1,6 +1,5 @@ // SPDX-License-Identifier: AGPL-3.0 pragma solidity ^0.8.10; -import 'forge-std/console2.sol'; import {IPool} from 'aave-v3-core/contracts/interfaces/IPool.sol'; import {ICreditDelegationToken} from 'aave-v3-core/contracts/interfaces/ICreditDelegationToken.sol'; @@ -31,25 +30,17 @@ contract SavingUsdsTokenWrapper is BaseTokenWrapper { } /// @inheritdoc BaseTokenWrapper - function borrowToken( - uint256 amount, - address to, - uint16 referralCode - ) external override { - _borrowToken(amount, to, referralCode); + function borrowToken(uint256 amount, uint16 referralCode) external override { + _borrowToken(amount, msg.sender, referralCode); } /// @inheritdoc BaseTokenWrapper function borrowTokenWithPermit( uint256 amount, - address to, uint16 referralCode, - uint256 deadline, - uint8 permitV, - bytes32 permitR, - bytes32 permitS + PermitSignature calldata signature ) external override { - if (deadline != 0) { + if (signature.deadline != 0) { address debtToken = IPool(0x87870Bca3F3fD6335C3F4ce8392D69350B4fA4E2) .getReserveData(TOKEN_OUT) .variableDebtTokenAddress; @@ -58,13 +49,13 @@ contract SavingUsdsTokenWrapper is BaseTokenWrapper { msg.sender, address(this), amount, - deadline, - permitV, - permitR, - permitS + signature.deadline, + signature.v, + signature.r, + signature.s ); } - _borrowToken(amount, to, referralCode); + _borrowToken(amount, msg.sender, referralCode); } /// @inheritdoc BaseTokenWrapper @@ -90,19 +81,4 @@ contract SavingUsdsTokenWrapper is BaseTokenWrapper { function _unwrapTokenOut(uint256 amount) internal override returns (uint256) { return IUSDS(TOKEN_OUT).redeem(amount, address(this), address(this)); } - - function _borrowToken( - uint256 amount, - address to, - uint16 referralCode - ) internal { - require(amount > 0, 'INSUFFICIENT_AMOUNT_TO_BORROW'); - uint256 balanceBeforeBorrow = IERC20(TOKEN_OUT).balanceOf(address(this)); - POOL.borrow(TOKEN_OUT, amount, 2, referralCode, address(to)); - uint256 balanceAfterBorrow = IERC20(TOKEN_OUT).balanceOf(address(this)); - uint256 amountIn = _unwrapTokenOut( - balanceAfterBorrow - balanceBeforeBorrow - ); - IERC20(TOKEN_IN).transfer(to, amountIn); - } } diff --git a/src/SavingsDaiTokenWrapper.sol b/src/SavingsDaiTokenWrapper.sol index 9a80415..0e03ce1 100644 --- a/src/SavingsDaiTokenWrapper.sol +++ b/src/SavingsDaiTokenWrapper.sol @@ -52,20 +52,16 @@ contract SavingsDaiTokenWrapper is BaseTokenWrapper { } /// @inheritdoc BaseTokenWrapper - function borrowToken( - uint256 amount, - address to, - uint16 referralCode - ) external override actionNotPermitted {} + function borrowToken(uint256 amount, uint16 referralCode) external override { + revert('INVALID_ACTION'); + } /// @inheritdoc BaseTokenWrapper function borrowTokenWithPermit( uint256 amount, - address to, uint16 referralCode, - uint256 deadline, - uint8 permitV, - bytes32 permitR, - bytes32 permitS - ) external override actionNotPermitted {} + PermitSignature calldata signature + ) external override { + revert('INVALID_ACTION'); + } } diff --git a/src/StakedEthTokenWrapper.sol b/src/StakedEthTokenWrapper.sol index 53fbbb8..6a38645 100644 --- a/src/StakedEthTokenWrapper.sol +++ b/src/StakedEthTokenWrapper.sol @@ -52,20 +52,16 @@ contract StakedEthTokenWrapper is BaseTokenWrapper { } /// @inheritdoc BaseTokenWrapper - function borrowToken( - uint256 amount, - address to, - uint16 referralCode - ) external override actionNotPermitted {} + function borrowToken(uint256 amount, uint16 referralCode) external override { + revert('INVALID_ACTION'); + } /// @inheritdoc BaseTokenWrapper function borrowTokenWithPermit( uint256 amount, - address to, uint16 referralCode, - uint256 deadline, - uint8 permitV, - bytes32 permitR, - bytes32 permitS - ) external override actionNotPermitted {} + PermitSignature calldata signature + ) external override { + revert('INVALID_ACTION'); + } } diff --git a/src/interfaces/IBaseTokenWrapper.sol b/src/interfaces/IBaseTokenWrapper.sol index f1a811d..0d525ad 100644 --- a/src/interfaces/IBaseTokenWrapper.sol +++ b/src/interfaces/IBaseTokenWrapper.sol @@ -64,33 +64,20 @@ interface IBaseTokenWrapper { /** * @notice Borrows token from the Pool and unwraps it, sending to the recipient * @param amount The amount of token to borrow - * @param to The address that will receive the unwrapped token * @param referralCode Code used to register the integrator originating the operation, for potential rewards */ - function borrowToken( - uint256 amount, - address to, - uint16 referralCode - ) external; + function borrowToken(uint256 amount, uint16 referralCode) external; /** * @notice Borrows token from the Pool, unwraps it, and sends it to the recipient using EIP-2612 permit * @param amount The amount of token to borrow - * @param to The address that will receive the unwrapped token * @param referralCode Code used to register the integrator originating the operation, for potential rewards - * @param deadline The deadline timestamp for the permit signature to be valid - * @param permitV The V parameter of the permit signature - * @param permitR The R parameter of the permit signature - * @param permitS The S parameter of the permit signature + * @param signature The EIP-712 signature data used for permit */ function borrowTokenWithPermit( uint256 amount, - address to, uint16 referralCode, - uint256 deadline, - uint8 permitV, - bytes32 permitR, - bytes32 permitS + PermitSignature calldata signature ) external; /** diff --git a/test/BaseTokenWrapper.t.sol b/test/BaseTokenWrapper.t.sol index 35bd007..bf602e4 100644 --- a/test/BaseTokenWrapper.t.sol +++ b/test/BaseTokenWrapper.t.sol @@ -6,8 +6,8 @@ import {IERC20} from 'aave-v3-core/contracts/dependencies/openzeppelin/contracts import {IPool} from 'aave-v3-core/contracts/interfaces/IPool.sol'; import {IAToken} from 'aave-v3-core/contracts/interfaces/IAToken.sol'; import {MintableERC20} from 'aave-v3-core/contracts/mocks/tokens/MintableERC20.sol'; -import {BaseTokenWrapper} from '../src/BaseTokenWrapper.sol'; import {IBaseTokenWrapper} from '../src/interfaces/IBaseTokenWrapper.sol'; +import {BaseTokenWrapper} from '../src/BaseTokenWrapper.sol'; import {ICreditDelegationToken} from '../src/interfaces/ICreditDelegationToken.sol'; interface IERC2612 { @@ -43,7 +43,6 @@ abstract contract BaseTokenWrapperTest is Test { address aTokenOut; uint256 tokenInDecimals; bool permitSupported; - address constant WETH = 0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2; constructor() { (ALICE, ALICE_KEY) = makeAddrAndKey('alice'); diff --git a/test/SavingUsdsTokenWrapper.t.sol b/test/SavingUsdsTokenWrapper.t.sol index e1df241..8a4b0b7 100644 --- a/test/SavingUsdsTokenWrapper.t.sol +++ b/test/SavingUsdsTokenWrapper.t.sol @@ -4,18 +4,20 @@ import 'forge-std/console2.sol'; import {DataTypes} from 'aave-v3-core/contracts/protocol/libraries/types/DataTypes.sol'; import {IERC20} from 'aave-v3-core/contracts/dependencies/openzeppelin/contracts/IERC20.sol'; +import {IERC20WithPermit} from 'aave-v3-core/contracts/interfaces/IERC20WithPermit.sol'; import {IPool} from 'aave-v3-core/contracts/interfaces/IPool.sol'; -import {BaseTokenWrapperTest} from './BaseTokenWrapper.t.sol'; +import {IAToken} from 'aave-v3-core/contracts/interfaces/IAToken.sol'; import {SavingUsdsTokenWrapper} from '../src/SavingUsdsTokenWrapper.sol'; import {ICreditDelegationToken} from '../src/interfaces/ICreditDelegationToken.sol'; -import {IERC20WithPermit} from 'aave-v3-core/contracts/interfaces/IERC20WithPermit.sol'; +import {IBaseTokenWrapper} from '../src/interfaces/IBaseTokenWrapper.sol'; +import {BaseTokenWrapperTest} from './BaseTokenWrapper.t.sol'; import {SigUtils} from './utils/SigUtils.sol'; -import {IAToken} from 'aave-v3-core/contracts/interfaces/IAToken.sol'; // frontend deposits usds and automatically converted to susds on aave contract SavingUsdsTokenWrapperTest is BaseTokenWrapperTest { address constant USDS = 0xdC035D45d973E3EC169d2276DDab16f1e407384F; address constant SUSDS = 0xa3931d71877C0E7a3148CB7Eb4463524FEc27fbD; + address constant WETH = 0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2; // TODO Actual Address --> fork address constant AUSDS = 0x10Ac93971cdb1F5c778144084242374473c350Da; @@ -76,7 +78,7 @@ contract SavingUsdsTokenWrapperTest is BaseTokenWrapperTest { borrowAmount ); - tokenWrapper.borrowToken(borrowAmount, address(alice), 0); + tokenWrapper.borrowToken(borrowAmount, 0); vm.stopPrank(); uint256 borrowedAmount = tokenWrapper.getTokenInForTokenOut(borrowAmount); @@ -115,16 +117,10 @@ contract SavingUsdsTokenWrapperTest is BaseTokenWrapperTest { deadline, debtToken ); + IBaseTokenWrapper.PermitSignature memory signature = IBaseTokenWrapper + .PermitSignature({deadline: deadline, v: v, r: r, s: s}); - tokenWrapper.borrowTokenWithPermit( - borrowAmount, - alice, - 1, - deadline, - v, - r, - s - ); + tokenWrapper.borrowTokenWithPermit(borrowAmount, 1, signature); vm.stopPrank(); @@ -164,17 +160,11 @@ contract SavingUsdsTokenWrapperTest is BaseTokenWrapperTest { deadline, debtToken ); + IBaseTokenWrapper.PermitSignature memory signature = IBaseTokenWrapper + .PermitSignature({deadline: deadline, v: v, r: r, s: s}); vm.expectRevert('INSUFFICIENT_AMOUNT_TO_BORROW'); - tokenWrapper.borrowTokenWithPermit( - borrowAmount, - alice, - 1, - deadline, - v, - r, - s - ); + tokenWrapper.borrowTokenWithPermit(borrowAmount, 1, signature); } function _signCreditDelegation( diff --git a/test/SavingsDaiTokenWrapper.t.sol b/test/SavingsDaiTokenWrapper.t.sol index 5bbcfca..4be5466 100644 --- a/test/SavingsDaiTokenWrapper.t.sol +++ b/test/SavingsDaiTokenWrapper.t.sol @@ -12,6 +12,7 @@ contract SavingsDaiTokenWrapperTest is BaseTokenWrapperTest { address constant DAI = 0x6B175474E89094C44Da98b954EedeAC495271d0F; address constant SDAI = 0x83F20F44975D03b1b09e64809B757c47f942BEeA; address constant ASDAI = 0x4C612E3B15b96Ff9A6faED838F8d07d479a8dD4c; + address constant WETH = 0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2; address constant POOL_CONFIGURATOR = 0x64b761D848206f447Fe2dd461b0c635Ec39EbB27; address constant ADMIN = 0x5300A1a15135EA4dc7aD5a167152C01EFc9b192A; @@ -67,7 +68,7 @@ contract SavingsDaiTokenWrapperTest is BaseTokenWrapperTest { borrowAmount ); vm.expectRevert('INVALID_ACTION'); - tokenWrapper.borrowToken(borrowAmount, address(alice), 0); + tokenWrapper.borrowToken(borrowAmount, 0); vm.stopPrank(); } } diff --git a/test/StakedEthTokenWrapper.t.sol b/test/StakedEthTokenWrapper.t.sol index 745ca18..1fe2692 100644 --- a/test/StakedEthTokenWrapper.t.sol +++ b/test/StakedEthTokenWrapper.t.sol @@ -11,6 +11,7 @@ contract StakedEthTokenWrapperTest is BaseTokenWrapperTest { address constant STETH = 0xae7ab96520DE3A18E5e111B5EaAb095312D7fE84; address constant WSTETH = 0x7f39C581F595B53c5cb19bD0b3f8dA6c935E2Ca0; address constant AWSTETH = 0x0B925eD163218f6662a35e0f0371Ac234f9E9371; + address constant WETH = 0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2; function setUp() public { vm.createSelectFork(vm.envString('ETH_RPC_URL'), 20784588); @@ -70,7 +71,7 @@ contract StakedEthTokenWrapperTest is BaseTokenWrapperTest { borrowAmount ); vm.expectRevert('INVALID_ACTION'); - tokenWrapper.borrowToken(borrowAmount, address(alice), 0); + tokenWrapper.borrowToken(borrowAmount, 0); vm.stopPrank(); } } From 4f5f382df666cd1faaa83fded0587213d24f17e4 Mon Sep 17 00:00:00 2001 From: CheyenneAtapour Date: Fri, 27 Sep 2024 09:18:41 -0700 Subject: [PATCH 17/28] fix: Move borrow logic to base wrapper --- src/BaseTokenWrapper.sol | 24 ++++++++++++++++++++++-- src/SavingUsdsTokenWrapper.sol | 31 ------------------------------- src/SavingsDaiTokenWrapper.sol | 27 +++++++++++++++------------ src/StakedEthTokenWrapper.sol | 27 +++++++++++++++------------ 4 files changed, 52 insertions(+), 57 deletions(-) diff --git a/src/BaseTokenWrapper.sol b/src/BaseTokenWrapper.sol index c6cd16c..3cc86eb 100644 --- a/src/BaseTokenWrapper.sol +++ b/src/BaseTokenWrapper.sol @@ -7,6 +7,7 @@ import {IERC20WithPermit} from 'aave-v3-core/contracts/interfaces/IERC20WithPerm import {GPv2SafeERC20} from 'aave-v3-core/contracts/dependencies/gnosis/contracts/GPv2SafeERC20.sol'; import {IPool} from 'aave-v3-core/contracts/interfaces/IPool.sol'; import {IAToken} from 'aave-v3-core/contracts/interfaces/IAToken.sol'; +import {ICreditDelegationToken} from 'aave-v3-core/contracts/interfaces/ICreditDelegationToken.sol'; import {IBaseTokenWrapper} from './interfaces/IBaseTokenWrapper.sol'; /** @@ -104,14 +105,33 @@ abstract contract BaseTokenWrapper is Ownable, IBaseTokenWrapper { } /// @inheritdoc IBaseTokenWrapper - function borrowToken(uint256 amount, uint16 referralCode) external virtual {} + function borrowToken(uint256 amount, uint16 referralCode) external virtual { + _borrowToken(amount, msg.sender, referralCode); + } /// @inheritdoc IBaseTokenWrapper function borrowTokenWithPermit( uint256 amount, uint16 referralCode, PermitSignature calldata signature - ) external virtual {} + ) external virtual { + if (signature.deadline != 0) { + address debtToken = IPool(0x87870Bca3F3fD6335C3F4ce8392D69350B4fA4E2) + .getReserveData(TOKEN_OUT) + .variableDebtTokenAddress; + + ICreditDelegationToken(debtToken).delegationWithSig( + msg.sender, + address(this), + amount, + signature.deadline, + signature.v, + signature.r, + signature.s + ); + } + _borrowToken(amount, msg.sender, referralCode); + } /// @inheritdoc IBaseTokenWrapper function rescueTokens( diff --git a/src/SavingUsdsTokenWrapper.sol b/src/SavingUsdsTokenWrapper.sol index c0f25af..b17af32 100644 --- a/src/SavingUsdsTokenWrapper.sol +++ b/src/SavingUsdsTokenWrapper.sol @@ -1,8 +1,6 @@ // SPDX-License-Identifier: AGPL-3.0 pragma solidity ^0.8.10; -import {IPool} from 'aave-v3-core/contracts/interfaces/IPool.sol'; -import {ICreditDelegationToken} from 'aave-v3-core/contracts/interfaces/ICreditDelegationToken.sol'; import {IERC20} from 'aave-v3-core/contracts/dependencies/openzeppelin/contracts/IERC20.sol'; import {IUSDS} from './dependencies/IUSDS.sol'; import {BaseTokenWrapper} from './BaseTokenWrapper.sol'; @@ -29,35 +27,6 @@ contract SavingUsdsTokenWrapper is BaseTokenWrapper { IERC20(tokenIn).approve(tokenOut, type(uint256).max); } - /// @inheritdoc BaseTokenWrapper - function borrowToken(uint256 amount, uint16 referralCode) external override { - _borrowToken(amount, msg.sender, referralCode); - } - - /// @inheritdoc BaseTokenWrapper - function borrowTokenWithPermit( - uint256 amount, - uint16 referralCode, - PermitSignature calldata signature - ) external override { - if (signature.deadline != 0) { - address debtToken = IPool(0x87870Bca3F3fD6335C3F4ce8392D69350B4fA4E2) - .getReserveData(TOKEN_OUT) - .variableDebtTokenAddress; - - ICreditDelegationToken(debtToken).delegationWithSig( - msg.sender, - address(this), - amount, - signature.deadline, - signature.v, - signature.r, - signature.s - ); - } - _borrowToken(amount, msg.sender, referralCode); - } - /// @inheritdoc BaseTokenWrapper function getTokenOutForTokenIn( uint256 amount diff --git a/src/SavingsDaiTokenWrapper.sol b/src/SavingsDaiTokenWrapper.sol index 0e03ce1..403ac06 100644 --- a/src/SavingsDaiTokenWrapper.sol +++ b/src/SavingsDaiTokenWrapper.sol @@ -42,17 +42,10 @@ contract SavingsDaiTokenWrapper is BaseTokenWrapper { } /// @inheritdoc BaseTokenWrapper - function _wrapTokenIn(uint256 amount) internal override returns (uint256) { - return ISavingsDai(TOKEN_OUT).deposit(amount, address(this)); - } - - /// @inheritdoc BaseTokenWrapper - function _unwrapTokenOut(uint256 amount) internal override returns (uint256) { - return ISavingsDai(TOKEN_OUT).redeem(amount, address(this), address(this)); - } - - /// @inheritdoc BaseTokenWrapper - function borrowToken(uint256 amount, uint16 referralCode) external override { + function borrowToken( + uint256 amount, + uint16 referralCode + ) external pure override { revert('INVALID_ACTION'); } @@ -61,7 +54,17 @@ contract SavingsDaiTokenWrapper is BaseTokenWrapper { uint256 amount, uint16 referralCode, PermitSignature calldata signature - ) external override { + ) external pure override { revert('INVALID_ACTION'); } + + /// @inheritdoc BaseTokenWrapper + function _wrapTokenIn(uint256 amount) internal override returns (uint256) { + return ISavingsDai(TOKEN_OUT).deposit(amount, address(this)); + } + + /// @inheritdoc BaseTokenWrapper + function _unwrapTokenOut(uint256 amount) internal override returns (uint256) { + return ISavingsDai(TOKEN_OUT).redeem(amount, address(this), address(this)); + } } diff --git a/src/StakedEthTokenWrapper.sol b/src/StakedEthTokenWrapper.sol index 6a38645..3365128 100644 --- a/src/StakedEthTokenWrapper.sol +++ b/src/StakedEthTokenWrapper.sol @@ -42,17 +42,10 @@ contract StakedEthTokenWrapper is BaseTokenWrapper { } /// @inheritdoc BaseTokenWrapper - function _wrapTokenIn(uint256 amount) internal override returns (uint256) { - return IWstETH(TOKEN_OUT).wrap(amount); - } - - /// @inheritdoc BaseTokenWrapper - function _unwrapTokenOut(uint256 amount) internal override returns (uint256) { - return IWstETH(TOKEN_OUT).unwrap(amount); - } - - /// @inheritdoc BaseTokenWrapper - function borrowToken(uint256 amount, uint16 referralCode) external override { + function borrowToken( + uint256 amount, + uint16 referralCode + ) external pure override { revert('INVALID_ACTION'); } @@ -61,7 +54,17 @@ contract StakedEthTokenWrapper is BaseTokenWrapper { uint256 amount, uint16 referralCode, PermitSignature calldata signature - ) external override { + ) external pure override { revert('INVALID_ACTION'); } + + /// @inheritdoc BaseTokenWrapper + function _wrapTokenIn(uint256 amount) internal override returns (uint256) { + return IWstETH(TOKEN_OUT).wrap(amount); + } + + /// @inheritdoc BaseTokenWrapper + function _unwrapTokenOut(uint256 amount) internal override returns (uint256) { + return IWstETH(TOKEN_OUT).unwrap(amount); + } } From 18860628a69d9a8941f369871a3fe0a8f3f190fe Mon Sep 17 00:00:00 2001 From: miguelmtz <36620902+miguelmtzinf@users.noreply.github.com> Date: Mon, 30 Sep 2024 21:13:14 +0200 Subject: [PATCH 18/28] fix: Move dependencies to V3 origin (#16) * fix: Move dependencies to V3 origin * fix: Update aave address book dep * fix: Fix remappings * fix: remove old version * fix: Fix dep --- .gitmodules | 3 --- lib/aave-address-book | 2 +- lib/aave-v3-core | 1 - remappings.txt | 7 +++++++ 4 files changed, 8 insertions(+), 5 deletions(-) delete mode 160000 lib/aave-v3-core create mode 100644 remappings.txt diff --git a/.gitmodules b/.gitmodules index c82aa28..ae9d5ae 100644 --- a/.gitmodules +++ b/.gitmodules @@ -1,9 +1,6 @@ [submodule "lib/forge-std"] path = lib/forge-std url = https://github.com/foundry-rs/forge-std -[submodule "lib/aave-v3-core"] - path = lib/aave-v3-core - url = https://github.com/aave/aave-v3-core [submodule "lib/aave-address-book"] path = lib/aave-address-book url = https://github.com/bgd-labs/aave-address-book diff --git a/lib/aave-address-book b/lib/aave-address-book index 9d7dd12..fd4e3ab 160000 --- a/lib/aave-address-book +++ b/lib/aave-address-book @@ -1 +1 @@ -Subproject commit 9d7dd12291d140c444b9588ef6ced8f860f6b5c0 +Subproject commit fd4e3abfd238e1a1ab77d34e979d4229aae61290 diff --git a/lib/aave-v3-core b/lib/aave-v3-core deleted file mode 160000 index 6070e82..0000000 --- a/lib/aave-v3-core +++ /dev/null @@ -1 +0,0 @@ -Subproject commit 6070e82d962d9b12835c88e68210d0e63f08d035 diff --git a/remappings.txt b/remappings.txt new file mode 100644 index 0000000..6b2395e --- /dev/null +++ b/remappings.txt @@ -0,0 +1,7 @@ +aave-v3-core/=lib/aave-address-book/lib/aave-v3-origin/src/core/ +aave-v3-periphery/=lib/aave-address-book/lib/aave-v3-origin/src/periphery/ +aave-address-book/=lib/aave-address-book/src/ +aave-v3-origin/=lib/aave-address-book/lib/aave-v3-origin/src/ +ds-test/=lib/forge-std/lib/ds-test/src/ +forge-std/=lib/forge-std/src/ +solidity-utils/=lib/aave-address-book/lib/aave-v3-origin/lib/solidity-utils/src/ \ No newline at end of file From 9d654dd6689e54156f454af54b9bbb1640b6aba7 Mon Sep 17 00:00:00 2001 From: miguelmtz <36620902+miguelmtzinf@users.noreply.github.com> Date: Tue, 1 Oct 2024 09:40:41 +0200 Subject: [PATCH 19/28] fix: Remove unlimited allowance (#17) --- src/BaseTokenWrapper.sol | 4 +++- src/SavingUsdsTokenWrapper.sol | 8 ++++++-- src/SavingsDaiTokenWrapper.sol | 8 ++++++-- src/StakedEthTokenWrapper.sol | 8 ++++++-- test/BaseTokenWrapper.t.sol | 28 ++++++++++++++++++++++++++++ test/SavingUsdsTokenWrapper.t.sol | 10 ---------- test/SavingsDaiTokenWrapper.t.sol | 10 ---------- test/StakedEthTokenWrapper.t.sol | 10 ---------- 8 files changed, 49 insertions(+), 37 deletions(-) diff --git a/src/BaseTokenWrapper.sol b/src/BaseTokenWrapper.sol index 3cc86eb..21e8eb9 100644 --- a/src/BaseTokenWrapper.sol +++ b/src/BaseTokenWrapper.sol @@ -4,6 +4,7 @@ pragma solidity ^0.8.10; import {Ownable} from 'aave-v3-core/contracts/dependencies/openzeppelin/contracts/Ownable.sol'; import {IERC20} from 'aave-v3-core/contracts/dependencies/openzeppelin/contracts/IERC20.sol'; import {IERC20WithPermit} from 'aave-v3-core/contracts/interfaces/IERC20WithPermit.sol'; +import {SafeERC20} from 'aave-v3-core/contracts/dependencies/openzeppelin/contracts/SafeERC20.sol'; import {GPv2SafeERC20} from 'aave-v3-core/contracts/dependencies/gnosis/contracts/GPv2SafeERC20.sol'; import {IPool} from 'aave-v3-core/contracts/interfaces/IPool.sol'; import {IAToken} from 'aave-v3-core/contracts/interfaces/IAToken.sol'; @@ -39,7 +40,6 @@ abstract contract BaseTokenWrapper is Ownable, IBaseTokenWrapper { TOKEN_OUT = tokenOut; POOL = IPool(pool); transferOwnership(owner); - IERC20(tokenOut).approve(pool, type(uint256).max); } /// @inheritdoc IBaseTokenWrapper @@ -174,7 +174,9 @@ abstract contract BaseTokenWrapper is Ownable, IBaseTokenWrapper { IERC20(TOKEN_IN).safeTransferFrom(msg.sender, address(this), amount); uint256 amountWrapped = _wrapTokenIn(amount); require(amountWrapped > 0, 'INSUFFICIENT_WRAPPED_TOKEN_RECEIVED'); + SafeERC20.safeApprove(IERC20(TOKEN_OUT), address(POOL), amountWrapped); POOL.supply(TOKEN_OUT, amountWrapped, onBehalfOf, referralCode); + SafeERC20.safeApprove(IERC20(TOKEN_OUT), address(POOL), 0); return amountWrapped; } diff --git a/src/SavingUsdsTokenWrapper.sol b/src/SavingUsdsTokenWrapper.sol index b17af32..e9095be 100644 --- a/src/SavingUsdsTokenWrapper.sol +++ b/src/SavingUsdsTokenWrapper.sol @@ -1,6 +1,7 @@ // SPDX-License-Identifier: AGPL-3.0 pragma solidity ^0.8.10; +import {SafeERC20} from 'aave-v3-core/contracts/dependencies/openzeppelin/contracts/SafeERC20.sol'; import {IERC20} from 'aave-v3-core/contracts/dependencies/openzeppelin/contracts/IERC20.sol'; import {IUSDS} from './dependencies/IUSDS.sol'; import {BaseTokenWrapper} from './BaseTokenWrapper.sol'; @@ -24,7 +25,7 @@ contract SavingUsdsTokenWrapper is BaseTokenWrapper { address pool, address owner ) BaseTokenWrapper(tokenIn, tokenOut, pool, owner) { - IERC20(tokenIn).approve(tokenOut, type(uint256).max); + // Intentionally left blank } /// @inheritdoc BaseTokenWrapper @@ -43,7 +44,10 @@ contract SavingUsdsTokenWrapper is BaseTokenWrapper { /// @inheritdoc BaseTokenWrapper function _wrapTokenIn(uint256 amount) internal override returns (uint256) { - return IUSDS(TOKEN_OUT).deposit(amount, address(this)); + SafeERC20.safeApprove(IERC20(TOKEN_IN), TOKEN_OUT, amount); + uint256 wrappedAmount = IUSDS(TOKEN_OUT).deposit(amount, address(this)); + SafeERC20.safeApprove(IERC20(TOKEN_IN), TOKEN_OUT, 0); + return wrappedAmount; } /// @inheritdoc BaseTokenWrapper diff --git a/src/SavingsDaiTokenWrapper.sol b/src/SavingsDaiTokenWrapper.sol index 403ac06..85d85ed 100644 --- a/src/SavingsDaiTokenWrapper.sol +++ b/src/SavingsDaiTokenWrapper.sol @@ -1,6 +1,7 @@ // SPDX-License-Identifier: AGPL-3.0 pragma solidity ^0.8.10; +import {SafeERC20} from 'aave-v3-core/contracts/dependencies/openzeppelin/contracts/SafeERC20.sol'; import {IERC20} from 'aave-v3-core/contracts/dependencies/openzeppelin/contracts/IERC20.sol'; import {ISavingsDai} from './interfaces/ISavingsDai.sol'; import {BaseTokenWrapper} from './BaseTokenWrapper.sol'; @@ -24,7 +25,7 @@ contract SavingsDaiTokenWrapper is BaseTokenWrapper { address pool, address owner ) BaseTokenWrapper(tokenIn, tokenOut, pool, owner) { - IERC20(tokenIn).approve(tokenOut, type(uint256).max); + // Intentionally left blank } /// @inheritdoc BaseTokenWrapper @@ -60,7 +61,10 @@ contract SavingsDaiTokenWrapper is BaseTokenWrapper { /// @inheritdoc BaseTokenWrapper function _wrapTokenIn(uint256 amount) internal override returns (uint256) { - return ISavingsDai(TOKEN_OUT).deposit(amount, address(this)); + SafeERC20.safeApprove(IERC20(TOKEN_IN), TOKEN_OUT, amount); + uint256 wrappedAmount = ISavingsDai(TOKEN_OUT).deposit(amount, address(this)); + SafeERC20.safeApprove(IERC20(TOKEN_IN), TOKEN_OUT, 0); + return wrappedAmount; } /// @inheritdoc BaseTokenWrapper diff --git a/src/StakedEthTokenWrapper.sol b/src/StakedEthTokenWrapper.sol index 3365128..f5c3115 100644 --- a/src/StakedEthTokenWrapper.sol +++ b/src/StakedEthTokenWrapper.sol @@ -1,6 +1,7 @@ // SPDX-License-Identifier: AGPL-3.0 pragma solidity ^0.8.10; +import {SafeERC20} from 'aave-v3-core/contracts/dependencies/openzeppelin/contracts/SafeERC20.sol'; import {IERC20} from 'aave-v3-core/contracts/dependencies/openzeppelin/contracts/IERC20.sol'; import {IWstETH} from './interfaces/IWstETH.sol'; import {BaseTokenWrapper} from './BaseTokenWrapper.sol'; @@ -24,7 +25,7 @@ contract StakedEthTokenWrapper is BaseTokenWrapper { address pool, address owner ) BaseTokenWrapper(tokenIn, tokenOut, pool, owner) { - IERC20(tokenIn).approve(tokenOut, type(uint256).max); + // Intentionally left blank } /// @inheritdoc BaseTokenWrapper @@ -60,7 +61,10 @@ contract StakedEthTokenWrapper is BaseTokenWrapper { /// @inheritdoc BaseTokenWrapper function _wrapTokenIn(uint256 amount) internal override returns (uint256) { - return IWstETH(TOKEN_OUT).wrap(amount); + SafeERC20.safeApprove(IERC20(TOKEN_IN), TOKEN_OUT, amount); + uint256 wrappedAmount = IWstETH(TOKEN_OUT).wrap(amount); + SafeERC20.safeApprove(IERC20(TOKEN_IN), TOKEN_OUT, 0); + return wrappedAmount; } /// @inheritdoc BaseTokenWrapper diff --git a/test/BaseTokenWrapper.t.sol b/test/BaseTokenWrapper.t.sol index bf602e4..070ed8b 100644 --- a/test/BaseTokenWrapper.t.sol +++ b/test/BaseTokenWrapper.t.sol @@ -54,6 +54,7 @@ abstract contract BaseTokenWrapperTest is Test { function testSupplyToken() public { IERC20 tokenIn = IERC20(tokenWrapper.TOKEN_IN()); + IERC20 tokenOut = IERC20(tokenWrapper.TOKEN_OUT()); assertEq( tokenIn.balanceOf(ALICE), 0, @@ -92,10 +93,17 @@ abstract contract BaseTokenWrapperTest is Test { 1, 'Unexpected ending aToken balance' ); + + assertEq( + tokenOut.allowance(address(tokenWrapper), pool), + 0, + 'Unexpected TOKEN_OUT allowance' + ); } function testSupplyTokenToOther() public { IERC20 tokenIn = IERC20(tokenWrapper.TOKEN_IN()); + IERC20 tokenOut = IERC20(tokenWrapper.TOKEN_OUT()); assertEq( tokenIn.balanceOf(ALICE), 0, @@ -138,10 +146,17 @@ abstract contract BaseTokenWrapperTest is Test { 1, 'Unexpected ending aToken balance' ); + + assertEq( + tokenOut.allowance(address(tokenWrapper), pool), + 0, + 'Unexpected TOKEN_OUT allowance' + ); } function testSupplyTokenWithPermit() public { IERC20 tokenIn = IERC20(tokenWrapper.TOKEN_IN()); + IERC20 tokenOut = IERC20(tokenWrapper.TOKEN_OUT()); assertEq( tokenIn.balanceOf(ALICE), 0, @@ -206,6 +221,12 @@ abstract contract BaseTokenWrapperTest is Test { 1, 'Unexpected ending aToken balance' ); + + assertEq( + tokenOut.allowance(address(tokenWrapper), pool), + 0, + 'Unexpected TOKEN_OUT allowance' + ); } else { vm.startPrank(ALICE); vm.expectRevert(); @@ -221,6 +242,7 @@ abstract contract BaseTokenWrapperTest is Test { function testPermitGriefingSupplyTokenWithPermit() public { IERC20 tokenIn = IERC20(tokenWrapper.TOKEN_IN()); + IERC20 tokenOut = IERC20(tokenWrapper.TOKEN_OUT()); assertEq( tokenIn.balanceOf(ALICE), 0, @@ -296,6 +318,12 @@ abstract contract BaseTokenWrapperTest is Test { 1, 'Unexpected ending aToken balance' ); + + assertEq( + tokenOut.allowance(address(tokenWrapper), pool), + 0, + 'Unexpected TOKEN_OUT allowance' + ); } else { vm.startPrank(ALICE); vm.expectRevert(); diff --git a/test/SavingUsdsTokenWrapper.t.sol b/test/SavingUsdsTokenWrapper.t.sol index 8a4b0b7..206acea 100644 --- a/test/SavingUsdsTokenWrapper.t.sol +++ b/test/SavingUsdsTokenWrapper.t.sol @@ -46,16 +46,6 @@ contract SavingUsdsTokenWrapperTest is BaseTokenWrapperTest { assertEq(tempTokenWrapper.TOKEN_OUT(), SUSDS, 'Unexpected TOKEN_OUT'); assertEq(address(tempTokenWrapper.POOL()), pool, 'Unexpected POOL'); assertEq(tempTokenWrapper.owner(), OWNER, 'Unexpected owner'); - assertEq( - IERC20(SUSDS).allowance(address(tempTokenWrapper), pool), - type(uint256).max, - 'Unexpected TOKEN_OUT allowance' - ); - assertEq( - IERC20(USDS).allowance(address(tempTokenWrapper), SUSDS), - type(uint256).max, - 'Unexpected TOKEN_IN allowance' - ); } function testBorrow() public { diff --git a/test/SavingsDaiTokenWrapper.t.sol b/test/SavingsDaiTokenWrapper.t.sol index 4be5466..f57c553 100644 --- a/test/SavingsDaiTokenWrapper.t.sol +++ b/test/SavingsDaiTokenWrapper.t.sol @@ -37,16 +37,6 @@ contract SavingsDaiTokenWrapperTest is BaseTokenWrapperTest { assertEq(tempTokenWrapper.TOKEN_OUT(), SDAI, 'Unexpected TOKEN_OUT'); assertEq(address(tempTokenWrapper.POOL()), pool, 'Unexpected POOL'); assertEq(tempTokenWrapper.owner(), OWNER, 'Unexpected owner'); - assertEq( - IERC20(SDAI).allowance(address(tempTokenWrapper), pool), - type(uint256).max, - 'Unexpected TOKEN_OUT allowance' - ); - assertEq( - IERC20(DAI).allowance(address(tempTokenWrapper), SDAI), - type(uint256).max, - 'Unexpected TOKEN_IN allowance' - ); } function testBorrowNotPermitted() public { diff --git a/test/StakedEthTokenWrapper.t.sol b/test/StakedEthTokenWrapper.t.sol index 1fe2692..2f84c73 100644 --- a/test/StakedEthTokenWrapper.t.sol +++ b/test/StakedEthTokenWrapper.t.sol @@ -33,16 +33,6 @@ contract StakedEthTokenWrapperTest is BaseTokenWrapperTest { assertEq(tempTokenWrapper.TOKEN_OUT(), WSTETH, 'Unexpected TOKEN_OUT'); assertEq(address(tempTokenWrapper.POOL()), pool, 'Unexpected POOL'); assertEq(tempTokenWrapper.owner(), OWNER, 'Unexpected owner'); - assertEq( - IERC20(WSTETH).allowance(address(tempTokenWrapper), pool), - type(uint256).max, - 'Unexpected TOKEN_OUT allowance' - ); - assertEq( - IERC20(STETH).allowance(address(tempTokenWrapper), WSTETH), - type(uint256).max, - 'Unexpected TOKEN_IN allowance' - ); } function _dealTokenIn(address user, uint256 amount) internal override { From 253000bdf637db757edb7285500c059eea0c2489 Mon Sep 17 00:00:00 2001 From: Cheyenne Atapour Date: Thu, 3 Oct 2024 01:47:18 -0700 Subject: [PATCH 20/28] feat: Generic ERC4626 <> ERC20 Token Wrapper (#20) * wip: Testing list new asset * fix: Deploy tokens after setup * wip: Borrow token test passes * fix: Use correct aToken address * fix: Use erc20 permit for mock token * fix: Remove unused code * fix: Cleanup and clarifications * fix: Address pr comments * fix: Remove redundant dependencies * fix: Remove max approval and reset approvals on generic wrapper * test: Add back generic 4626 wrapper test * fix: Remove unused imports * fix: Generalize comment * fix: Remove usds wrapper, use address book --- foundry.toml | 4 +- remappings.txt | 3 +- src/BaseTokenWrapper.sol | 24 +- ...okenWrapper.sol => Generic4626Wrapper.sol} | 20 +- test/BaseTokenWrapper.t.sol | 24 +- test/Generic4626Wrapper.t.sol | 259 ++++++++++++++++++ test/SavingUsdsTokenWrapper.t.sol | 184 ------------- test/SavingsDaiTokenWrapper.t.sol | 15 +- test/StakedEthTokenWrapper.t.sol | 12 +- test/mocks/MockERC20.sol | 11 + test/mocks/MockERC4626.sol | 10 + 11 files changed, 326 insertions(+), 240 deletions(-) rename src/{SavingUsdsTokenWrapper.sol => Generic4626Wrapper.sol} (68%) create mode 100644 test/Generic4626Wrapper.t.sol delete mode 100644 test/SavingUsdsTokenWrapper.t.sol create mode 100644 test/mocks/MockERC20.sol create mode 100644 test/mocks/MockERC4626.sol diff --git a/foundry.toml b/foundry.toml index fb0dbf4..11c911f 100644 --- a/foundry.toml +++ b/foundry.toml @@ -2,7 +2,7 @@ src = "src" out = "out" libs = ["lib"] -solc = "0.8.20" -evm_version = "shanghai" +solc = "0.8.24" +evm_version = "cancun" # See more config options https://github.com/foundry-rs/foundry/blob/master/crates/config/README.md#all-options diff --git a/remappings.txt b/remappings.txt index 6b2395e..0e89b05 100644 --- a/remappings.txt +++ b/remappings.txt @@ -4,4 +4,5 @@ aave-address-book/=lib/aave-address-book/src/ aave-v3-origin/=lib/aave-address-book/lib/aave-v3-origin/src/ ds-test/=lib/forge-std/lib/ds-test/src/ forge-std/=lib/forge-std/src/ -solidity-utils/=lib/aave-address-book/lib/aave-v3-origin/lib/solidity-utils/src/ \ No newline at end of file +solidity-utils/=lib/aave-address-book/lib/aave-v3-origin/lib/solidity-utils/src/ +openzeppelin/=lib/aave-address-book/lib/aave-v3-origin/lib/solidity-utils/lib/openzeppelin-contracts-upgradeable/lib/openzeppelin-contracts/contracts \ No newline at end of file diff --git a/src/BaseTokenWrapper.sol b/src/BaseTokenWrapper.sol index 21e8eb9..60c316c 100644 --- a/src/BaseTokenWrapper.sol +++ b/src/BaseTokenWrapper.sol @@ -116,19 +116,21 @@ abstract contract BaseTokenWrapper is Ownable, IBaseTokenWrapper { PermitSignature calldata signature ) external virtual { if (signature.deadline != 0) { - address debtToken = IPool(0x87870Bca3F3fD6335C3F4ce8392D69350B4fA4E2) + address debtToken = POOL .getReserveData(TOKEN_OUT) .variableDebtTokenAddress; - - ICreditDelegationToken(debtToken).delegationWithSig( - msg.sender, - address(this), - amount, - signature.deadline, - signature.v, - signature.r, - signature.s - ); + // explicitly left try-catch block blank to protect users from permit griefing + try + ICreditDelegationToken(debtToken).delegationWithSig( + msg.sender, + address(this), + amount, + signature.deadline, + signature.v, + signature.r, + signature.s + ) + {} catch {} } _borrowToken(amount, msg.sender, referralCode); } diff --git a/src/SavingUsdsTokenWrapper.sol b/src/Generic4626Wrapper.sol similarity index 68% rename from src/SavingUsdsTokenWrapper.sol rename to src/Generic4626Wrapper.sol index e9095be..ea2257a 100644 --- a/src/SavingUsdsTokenWrapper.sol +++ b/src/Generic4626Wrapper.sol @@ -3,19 +3,19 @@ pragma solidity ^0.8.10; import {SafeERC20} from 'aave-v3-core/contracts/dependencies/openzeppelin/contracts/SafeERC20.sol'; import {IERC20} from 'aave-v3-core/contracts/dependencies/openzeppelin/contracts/IERC20.sol'; -import {IUSDS} from './dependencies/IUSDS.sol'; +import {IERC4626} from 'openzeppelin/interfaces/IERC4626.sol'; import {BaseTokenWrapper} from './BaseTokenWrapper.sol'; /** - * @title SavingUsdsTokenWrapper + * @title Generic4626Wrapper * @author Aave - * @notice Contract to wrap USDS to SuSDS on supply to Aave, or unwrap from SuSDS to USDS on withdrawal + * @notice Generic contract to wrap an ERC20 to ERC4626 to on supply to Aave, or unwrap from ERC4626 to ERC20 on withdrawal */ -contract SavingUsdsTokenWrapper is BaseTokenWrapper { +contract Generic4626Wrapper is BaseTokenWrapper { /** * @dev Constructor - * @param tokenIn Address for USDS - * @param tokenOut Address for SUSDS + * @param tokenIn Address for the ERC20 token + * @param tokenOut Address for the ERC4626 token * @param pool The address of the Aave Pool * @param owner The address to transfer ownership to */ @@ -32,26 +32,26 @@ contract SavingUsdsTokenWrapper is BaseTokenWrapper { function getTokenOutForTokenIn( uint256 amount ) external view override returns (uint256) { - return IUSDS(TOKEN_OUT).previewDeposit(amount); + return IERC4626(TOKEN_OUT).previewDeposit(amount); } /// @inheritdoc BaseTokenWrapper function getTokenInForTokenOut( uint256 amount ) external view override returns (uint256) { - return IUSDS(TOKEN_OUT).previewRedeem(amount); + return IERC4626(TOKEN_OUT).previewRedeem(amount); } /// @inheritdoc BaseTokenWrapper function _wrapTokenIn(uint256 amount) internal override returns (uint256) { SafeERC20.safeApprove(IERC20(TOKEN_IN), TOKEN_OUT, amount); - uint256 wrappedAmount = IUSDS(TOKEN_OUT).deposit(amount, address(this)); + uint256 wrappedAmount = IERC4626(TOKEN_OUT).deposit(amount, address(this)); SafeERC20.safeApprove(IERC20(TOKEN_IN), TOKEN_OUT, 0); return wrappedAmount; } /// @inheritdoc BaseTokenWrapper function _unwrapTokenOut(uint256 amount) internal override returns (uint256) { - return IUSDS(TOKEN_OUT).redeem(amount, address(this), address(this)); + return IERC4626(TOKEN_OUT).redeem(amount, address(this), address(this)); } } diff --git a/test/BaseTokenWrapper.t.sol b/test/BaseTokenWrapper.t.sol index 070ed8b..6251afe 100644 --- a/test/BaseTokenWrapper.t.sol +++ b/test/BaseTokenWrapper.t.sol @@ -3,12 +3,10 @@ pragma solidity ^0.8.10; import {Test} from 'forge-std/Test.sol'; import {IERC20} from 'aave-v3-core/contracts/dependencies/openzeppelin/contracts/IERC20.sol'; -import {IPool} from 'aave-v3-core/contracts/interfaces/IPool.sol'; import {IAToken} from 'aave-v3-core/contracts/interfaces/IAToken.sol'; import {MintableERC20} from 'aave-v3-core/contracts/mocks/tokens/MintableERC20.sol'; import {IBaseTokenWrapper} from '../src/interfaces/IBaseTokenWrapper.sol'; import {BaseTokenWrapper} from '../src/BaseTokenWrapper.sol'; -import {ICreditDelegationToken} from '../src/interfaces/ICreditDelegationToken.sol'; interface IERC2612 { function nonces(address owner) external view returns (uint256); @@ -319,11 +317,11 @@ abstract contract BaseTokenWrapperTest is Test { 'Unexpected ending aToken balance' ); - assertEq( - tokenOut.allowance(address(tokenWrapper), pool), - 0, - 'Unexpected TOKEN_OUT allowance' - ); + assertEq( + tokenOut.allowance(address(tokenWrapper), pool), + 0, + 'Unexpected TOKEN_OUT allowance' + ); } else { vm.startPrank(ALICE); vm.expectRevert(); @@ -402,9 +400,6 @@ abstract contract BaseTokenWrapperTest is Test { 0, 'Unexpected starting tokenIn balance' ); - uint256 estimateFinalBalance = tokenWrapper.getTokenInForTokenOut( - aTokenBalance - ); vm.startPrank(ALICE); IAToken(aTokenOut).approve(address(tokenWrapper), aTokenBalance); @@ -720,11 +715,7 @@ abstract contract BaseTokenWrapperTest is Test { vm.startPrank(ALICE); tokenIn.approve(address(tokenWrapper), amountScaled); - uint256 suppliedAmount = tokenWrapper.supplyToken( - amountScaled, - referee, - REFERRAL_CODE - ); + tokenWrapper.supplyToken(amountScaled, referee, REFERRAL_CODE); vm.stopPrank(); assertEq(tokenIn.balanceOf(ALICE), 0, 'Unexpected ending tokenIn balance'); @@ -747,9 +738,6 @@ abstract contract BaseTokenWrapperTest is Test { 0, 'Unexpected starting tokenIn balance' ); - uint256 estimateFinalBalance = tokenWrapper.getTokenInForTokenOut( - aTokenBalance - ); vm.startPrank(ALICE); IAToken(aTokenOut).approve(address(tokenWrapper), aTokenBalance); uint256 withdrawnAmount = tokenWrapper.withdrawToken(aTokenBalance, ALICE); diff --git a/test/Generic4626Wrapper.t.sol b/test/Generic4626Wrapper.t.sol new file mode 100644 index 0000000..8bd4e1e --- /dev/null +++ b/test/Generic4626Wrapper.t.sol @@ -0,0 +1,259 @@ +// SPDX-License-Identifier: AGPL-3.0 +pragma solidity ^0.8.10; +import 'forge-std/console2.sol'; + +import {AaveV3Ethereum, AaveV3EthereumAssets} from 'aave-address-book/AaveV3Ethereum.sol'; +import {IDefaultInterestRateStrategyV2} from 'aave-v3-core/contracts/interfaces/IDefaultInterestRateStrategyV2.sol'; +import {IAaveOracle} from 'aave-v3-core/contracts/interfaces/IAaveOracle.sol'; +import {MockAggregator} from 'aave-v3-core/contracts/mocks/oracle/CLAggregators/MockAggregator.sol'; +import {IPool} from 'aave-v3-core/contracts/interfaces/IPool.sol'; +import {IPoolConfigurator} from 'aave-v3-core/contracts/interfaces/IPoolConfigurator.sol'; +import {ConfiguratorInputTypes} from 'aave-v3-core/contracts/protocol/libraries/types/ConfiguratorInputTypes.sol'; +import {IAToken} from 'aave-v3-core/contracts/interfaces/IAToken.sol'; +import {IERC20} from 'aave-v3-core/contracts/dependencies/openzeppelin/contracts/IERC20.sol'; +import {Generic4626Wrapper} from '../src/Generic4626Wrapper.sol'; +import {ICreditDelegationToken} from '../src/interfaces/ICreditDelegationToken.sol'; +import {IBaseTokenWrapper} from '../src/interfaces/IBaseTokenWrapper.sol'; +import {MockERC4626} from './mocks/MockERC4626.sol'; +import {MockERC20} from './mocks/MockERC20.sol'; +import {SigUtils} from './utils/SigUtils.sol'; +import {BaseTokenWrapperTest} from './BaseTokenWrapper.t.sol'; + +contract Generic4626WrapperTest is BaseTokenWrapperTest { + address constant WETH = AaveV3EthereumAssets.WETH_UNDERLYING; + address constant ADMIN = AaveV3Ethereum.ACL_ADMIN; + IPoolConfigurator constant POOL_CONFIGURATOR = + AaveV3Ethereum.POOL_CONFIGURATOR; + IAaveOracle constant AAVE_ORACLE = AaveV3Ethereum.ORACLE; + MockERC20 unwrappedToken; + MockERC4626 wrappedToken; + address unwrapped; + address wrapped; + + function setUp() public { + vm.createSelectFork(vm.envString('ETH_RPC_URL'), 20784588); + pool = address(AaveV3Ethereum.POOL); + unwrappedToken = new MockERC20('UNWRAPPED'); + wrappedToken = new MockERC4626(unwrappedToken); + unwrapped = address(unwrappedToken); + wrapped = address(wrappedToken); + + // Put some underlying asset into the ERC4626 vault + unwrappedToken.approve(wrapped, 1e50); + wrappedToken.deposit(1e50, address(this)); + + // Airdrop some extra underlying asset to the vault + deal(unwrapped, address(this), 10e18); + unwrappedToken.transfer(wrapped, 10e18); + + tokenWrapper = new Generic4626Wrapper(unwrapped, wrapped, pool, OWNER); + tokenInDecimals = 18; + permitSupported = true; + + IDefaultInterestRateStrategyV2.InterestRateData + memory interestRateData = IDefaultInterestRateStrategyV2 + .InterestRateData({ + optimalUsageRatio: 8000, + baseVariableBorrowRate: 1000, + variableRateSlope1: 1000, + variableRateSlope2: 1000 + }); + + ConfiguratorInputTypes.InitReserveInput[] + memory reserveInputs = new ConfiguratorInputTypes.InitReserveInput[](1); + reserveInputs[0] = ConfiguratorInputTypes.InitReserveInput({ + aTokenImpl: AaveV3Ethereum.DEFAULT_A_TOKEN_IMPL_REV_1, + stableDebtTokenImpl: AaveV3Ethereum.DEFAULT_STABLE_DEBT_TOKEN_IMPL_REV_1, + variableDebtTokenImpl: AaveV3Ethereum + .DEFAULT_VARIABLE_DEBT_TOKEN_IMPL_REV_1, + useVirtualBalance: true, + interestRateStrategyAddress: AaveV3EthereumAssets + .WETH_INTEREST_RATE_STRATEGY, + underlyingAsset: tokenWrapper.TOKEN_OUT(), + treasury: address(AaveV3Ethereum.COLLECTOR), + incentivesController: AaveV3Ethereum.DEFAULT_INCENTIVES_CONTROLLER, + aTokenName: 'AaveWrapped', + aTokenSymbol: 'AWrapped', + variableDebtTokenName: 'VariableDebtWrapped', + variableDebtTokenSymbol: 'VWrapped', + stableDebtTokenName: 'StableDebtWrapped', + stableDebtTokenSymbol: 'SWrapped', + params: bytes(''), + interestRateData: abi.encode(interestRateData) + }); + + vm.startPrank(ADMIN); + POOL_CONFIGURATOR.initReserves(reserveInputs); + POOL_CONFIGURATOR.setReserveActive(tokenWrapper.TOKEN_OUT(), true); + POOL_CONFIGURATOR.setReserveBorrowing(tokenWrapper.TOKEN_OUT(), true); + + // Set asset oracle + MockAggregator oracle = new MockAggregator(1); + address[] memory assets = new address[](1); + assets[0] = tokenWrapper.TOKEN_OUT(); + address[] memory sources = new address[](1); + sources[0] = address(oracle); + AAVE_ORACLE.setAssetSources(assets, sources); + vm.stopPrank(); + + // Supply some of the new asset to pool + uint256 collateralAmount = 1000e18; + deal(tokenWrapper.TOKEN_OUT(), address(this), collateralAmount); + IERC20(tokenWrapper.TOKEN_OUT()).approve(address(pool), collateralAmount); + IPool(pool).supply( + tokenWrapper.TOKEN_OUT(), + collateralAmount, + address(this), + 0 + ); + + aTokenOut = IPool(pool) + .getReserveData(tokenWrapper.TOKEN_OUT()) + .aTokenAddress; + } + + function testConstructor() public override { + Generic4626Wrapper tempTokenWrapper = new Generic4626Wrapper( + unwrapped, + wrapped, + pool, + OWNER + ); + assertEq(tempTokenWrapper.TOKEN_IN(), unwrapped, 'Unexpected TOKEN_IN'); + assertEq(tempTokenWrapper.TOKEN_OUT(), wrapped, 'Unexpected TOKEN_OUT'); + assertEq(address(tempTokenWrapper.POOL()), pool, 'Unexpected POOL'); + assertEq(tempTokenWrapper.owner(), OWNER, 'Unexpected owner'); + } + + function testBorrow() public { + uint256 collateralAmount = 1000e18; + uint256 borrowAmount = 100e18; + address debtToken = IPool(pool) + .getReserveData(tokenWrapper.TOKEN_OUT()) + .variableDebtTokenAddress; + + address alice = makeAddr('ALICE'); + deal(WETH, alice, collateralAmount); + + vm.startPrank(alice); + + IERC20(WETH).approve(address(pool), collateralAmount); + IPool(pool).supply(WETH, collateralAmount, alice, 0); + + ICreditDelegationToken(debtToken).approveDelegation( + address(tokenWrapper), + borrowAmount + ); + + tokenWrapper.borrowToken(borrowAmount, 0); + vm.stopPrank(); + + uint256 borrowedAmount = tokenWrapper.getTokenInForTokenOut(borrowAmount); + assertEq( + IERC20(tokenWrapper.TOKEN_IN()).balanceOf(address(alice)), + borrowedAmount + ); + } + + function testBorrowTokenWithPermit() public { + uint256 borrowAmount = 100e18; + uint256 collateralAmount = 1000e18; + + (address alice, uint256 userPrivateKey) = makeAddrAndKey('ALICE'); + deal(WETH, alice, collateralAmount); + + address debtToken = IPool(pool) + .getReserveData(tokenWrapper.TOKEN_OUT()) + .variableDebtTokenAddress; + + vm.startPrank(alice); + + IERC20(WETH).approve(address(pool), collateralAmount); + + IPool(pool).supply(WETH, collateralAmount, alice, 0); + + uint256 deadline = block.timestamp + 1 hours; + uint256 nonce = IAToken(debtToken).nonces(alice); + + (uint8 v, bytes32 r, bytes32 s) = _signCreditDelegation( + userPrivateKey, + address(tokenWrapper), + borrowAmount, + nonce, + deadline, + debtToken + ); + IBaseTokenWrapper.PermitSignature memory signature = IBaseTokenWrapper + .PermitSignature({deadline: deadline, v: v, r: r, s: s}); + + tokenWrapper.borrowTokenWithPermit(borrowAmount, 1, signature); + + vm.stopPrank(); + + uint256 borrowedAmount = tokenWrapper.getTokenInForTokenOut(borrowAmount); + assertEq( + IERC20(tokenWrapper.TOKEN_IN()).balanceOf(address(alice)), + borrowedAmount + ); + } + + function testBorrowTokenWithPermitZeroAmount() public { + uint256 borrowAmount = 0; + uint256 collateralAmount = 1000e18; + + (address alice, uint256 userPrivateKey) = makeAddrAndKey('ALICE'); + deal(WETH, alice, collateralAmount); + + address debtToken = IPool(pool) + .getReserveData(tokenWrapper.TOKEN_OUT()) + .variableDebtTokenAddress; + + vm.startPrank(alice); + + IERC20(WETH).approve(address(pool), collateralAmount); + + IPool(pool).supply(WETH, collateralAmount, alice, 0); + + uint256 deadline = block.timestamp + 1 hours; + uint256 nonce = IAToken(debtToken).nonces(alice); + + (uint8 v, bytes32 r, bytes32 s) = _signCreditDelegation( + userPrivateKey, + address(tokenWrapper), + borrowAmount, + nonce, + deadline, + debtToken + ); + IBaseTokenWrapper.PermitSignature memory signature = IBaseTokenWrapper + .PermitSignature({deadline: deadline, v: v, r: r, s: s}); + + vm.expectRevert('INSUFFICIENT_AMOUNT_TO_BORROW'); + tokenWrapper.borrowTokenWithPermit(borrowAmount, 1, signature); + } + + function _signCreditDelegation( + uint256 privateKey, + address delegatee, + uint256 value, + uint256 nonce, + uint256 deadline, + address debtToken + ) internal view returns (uint8 v, bytes32 r, bytes32 s) { + SigUtils.CreditDelegation memory creditDelegation = SigUtils + .CreditDelegation({ + delegatee: delegatee, + value: value, + nonce: nonce, + deadline: deadline + }); + + bytes32 domainSeparator = IAToken(debtToken).DOMAIN_SEPARATOR(); + bytes32 digest = SigUtils.getCreditDelegationTypedDataHash( + creditDelegation, + domainSeparator + ); + + return vm.sign(privateKey, digest); + } +} diff --git a/test/SavingUsdsTokenWrapper.t.sol b/test/SavingUsdsTokenWrapper.t.sol deleted file mode 100644 index 206acea..0000000 --- a/test/SavingUsdsTokenWrapper.t.sol +++ /dev/null @@ -1,184 +0,0 @@ -// SPDX-License-Identifier: AGPL-3.0 -pragma solidity ^0.8.10; -import 'forge-std/console2.sol'; - -import {DataTypes} from 'aave-v3-core/contracts/protocol/libraries/types/DataTypes.sol'; -import {IERC20} from 'aave-v3-core/contracts/dependencies/openzeppelin/contracts/IERC20.sol'; -import {IERC20WithPermit} from 'aave-v3-core/contracts/interfaces/IERC20WithPermit.sol'; -import {IPool} from 'aave-v3-core/contracts/interfaces/IPool.sol'; -import {IAToken} from 'aave-v3-core/contracts/interfaces/IAToken.sol'; -import {SavingUsdsTokenWrapper} from '../src/SavingUsdsTokenWrapper.sol'; -import {ICreditDelegationToken} from '../src/interfaces/ICreditDelegationToken.sol'; -import {IBaseTokenWrapper} from '../src/interfaces/IBaseTokenWrapper.sol'; -import {BaseTokenWrapperTest} from './BaseTokenWrapper.t.sol'; -import {SigUtils} from './utils/SigUtils.sol'; - -// frontend deposits usds and automatically converted to susds on aave -contract SavingUsdsTokenWrapperTest is BaseTokenWrapperTest { - address constant USDS = 0xdC035D45d973E3EC169d2276DDab16f1e407384F; - address constant SUSDS = 0xa3931d71877C0E7a3148CB7Eb4463524FEc27fbD; - address constant WETH = 0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2; - - // TODO Actual Address --> fork - address constant AUSDS = 0x10Ac93971cdb1F5c778144084242374473c350Da; - - function setUp() public { - // vm.createSelectFork(vm.envString('ETH_RPC_URL')); - vm.createSelectFork( - 'https://rpc.tenderly.co/fork/881012fd-267f-41dc-93ba-8eb025b8bce2' - ); - pool = 0x87870Bca3F3fD6335C3F4ce8392D69350B4fA4E2; - - tokenWrapper = new SavingUsdsTokenWrapper(USDS, SUSDS, pool, OWNER); - aTokenOut = AUSDS; - tokenInDecimals = 18; - permitSupported = true; - } - - function testConstructor() public override { - SavingUsdsTokenWrapper tempTokenWrapper = new SavingUsdsTokenWrapper( - USDS, - SUSDS, - pool, - OWNER - ); - assertEq(tempTokenWrapper.TOKEN_IN(), USDS, 'Unexpected TOKEN_IN'); - assertEq(tempTokenWrapper.TOKEN_OUT(), SUSDS, 'Unexpected TOKEN_OUT'); - assertEq(address(tempTokenWrapper.POOL()), pool, 'Unexpected POOL'); - assertEq(tempTokenWrapper.owner(), OWNER, 'Unexpected owner'); - } - - function testBorrow() public { - uint256 collateralAmount = 1000e18; - uint256 borrowAmount = 100e18; - address debtToken = IPool(pool) - .getReserveData(tokenWrapper.TOKEN_OUT()) - .variableDebtTokenAddress; - - address alice = makeAddr('ALICE'); - deal(WETH, alice, collateralAmount); - - vm.startPrank(alice); - - IERC20(WETH).approve(address(pool), collateralAmount); - IPool(pool).supply(WETH, collateralAmount, alice, 0); - - ICreditDelegationToken(debtToken).approveDelegation( - address(tokenWrapper), - borrowAmount - ); - - tokenWrapper.borrowToken(borrowAmount, 0); - vm.stopPrank(); - - uint256 borrowedAmount = tokenWrapper.getTokenInForTokenOut(borrowAmount); - assertEq( - IERC20(tokenWrapper.TOKEN_IN()).balanceOf(address(alice)), - borrowedAmount - ); - } - - function testBorrowTokenWithPermit() public { - uint256 borrowAmount = 100e18; - uint256 collateralAmount = 1000e18; - - uint256 userPrivateKey = 0xA11CE; - address alice = address(vm.addr(userPrivateKey)); - deal(WETH, alice, collateralAmount); - - address debtToken = IPool(pool) - .getReserveData(tokenWrapper.TOKEN_OUT()) - .variableDebtTokenAddress; - - vm.startPrank(alice); - - IERC20(WETH).approve(address(pool), collateralAmount); - - IPool(pool).supply(WETH, collateralAmount, alice, 0); - - uint256 deadline = block.timestamp + 1 hours; - uint256 nonce = IAToken(debtToken).nonces(alice); - - (uint8 v, bytes32 r, bytes32 s) = _signCreditDelegation( - userPrivateKey, - address(tokenWrapper), - borrowAmount, - nonce, - deadline, - debtToken - ); - IBaseTokenWrapper.PermitSignature memory signature = IBaseTokenWrapper - .PermitSignature({deadline: deadline, v: v, r: r, s: s}); - - tokenWrapper.borrowTokenWithPermit(borrowAmount, 1, signature); - - vm.stopPrank(); - - uint256 borrowedAmount = tokenWrapper.getTokenInForTokenOut(borrowAmount); - assertEq( - IERC20(tokenWrapper.TOKEN_IN()).balanceOf(address(alice)), - borrowedAmount - ); - } - - function testBorrowTokenWithPermitZeroAmount() public { - uint256 borrowAmount = 0; - uint256 collateralAmount = 1000e18; - - uint256 userPrivateKey = 0xA11CE; - address alice = address(vm.addr(userPrivateKey)); - deal(WETH, alice, collateralAmount); - - address debtToken = IPool(pool) - .getReserveData(tokenWrapper.TOKEN_OUT()) - .variableDebtTokenAddress; - - vm.startPrank(alice); - - IERC20(WETH).approve(address(pool), collateralAmount); - - IPool(pool).supply(WETH, collateralAmount, alice, 0); - - uint256 deadline = block.timestamp + 1 hours; - uint256 nonce = IAToken(debtToken).nonces(alice); - - (uint8 v, bytes32 r, bytes32 s) = _signCreditDelegation( - userPrivateKey, - address(tokenWrapper), - borrowAmount, - nonce, - deadline, - debtToken - ); - IBaseTokenWrapper.PermitSignature memory signature = IBaseTokenWrapper - .PermitSignature({deadline: deadline, v: v, r: r, s: s}); - - vm.expectRevert('INSUFFICIENT_AMOUNT_TO_BORROW'); - tokenWrapper.borrowTokenWithPermit(borrowAmount, 1, signature); - } - - function _signCreditDelegation( - uint256 privateKey, - address delegatee, - uint256 value, - uint256 nonce, - uint256 deadline, - address debtToken - ) internal view returns (uint8 v, bytes32 r, bytes32 s) { - SigUtils.CreditDelegation memory creditDelegation = SigUtils - .CreditDelegation({ - delegatee: delegatee, - value: value, - nonce: nonce, - deadline: deadline - }); - - bytes32 domainSeparator = IAToken(debtToken).DOMAIN_SEPARATOR(); - bytes32 digest = SigUtils.getCreditDelegationTypedDataHash( - creditDelegation, - domainSeparator - ); - - return vm.sign(privateKey, digest); - } -} diff --git a/test/SavingsDaiTokenWrapper.t.sol b/test/SavingsDaiTokenWrapper.t.sol index f57c553..14bd286 100644 --- a/test/SavingsDaiTokenWrapper.t.sol +++ b/test/SavingsDaiTokenWrapper.t.sol @@ -1,25 +1,22 @@ // SPDX-License-Identifier: AGPL-3.0 pragma solidity ^0.8.10; +import {AaveV3Ethereum, AaveV3EthereumAssets} from 'aave-address-book/AaveV3Ethereum.sol'; import {IERC20} from 'aave-v3-core/contracts/dependencies/openzeppelin/contracts/IERC20.sol'; import {IPool} from 'aave-v3-core/contracts/interfaces/IPool.sol'; -import {IPoolConfigurator} from 'aave-v3-core/contracts/interfaces/IPoolConfigurator.sol'; import {BaseTokenWrapperTest} from './BaseTokenWrapper.t.sol'; import {SavingsDaiTokenWrapper} from '../src/SavingsDaiTokenWrapper.sol'; import {ICreditDelegationToken} from '../src/interfaces/ICreditDelegationToken.sol'; contract SavingsDaiTokenWrapperTest is BaseTokenWrapperTest { - address constant DAI = 0x6B175474E89094C44Da98b954EedeAC495271d0F; - address constant SDAI = 0x83F20F44975D03b1b09e64809B757c47f942BEeA; - address constant ASDAI = 0x4C612E3B15b96Ff9A6faED838F8d07d479a8dD4c; - address constant WETH = 0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2; - address constant POOL_CONFIGURATOR = - 0x64b761D848206f447Fe2dd461b0c635Ec39EbB27; - address constant ADMIN = 0x5300A1a15135EA4dc7aD5a167152C01EFc9b192A; + address constant DAI = AaveV3EthereumAssets.DAI_UNDERLYING; + address constant SDAI = AaveV3EthereumAssets.sDAI_UNDERLYING; + address constant ASDAI = AaveV3EthereumAssets.sDAI_A_TOKEN; + address constant WETH = AaveV3EthereumAssets.WETH_UNDERLYING; function setUp() public { vm.createSelectFork(vm.envString('ETH_RPC_URL'), 20784588); - pool = 0x87870Bca3F3fD6335C3F4ce8392D69350B4fA4E2; + pool = address(AaveV3Ethereum.POOL); tokenWrapper = new SavingsDaiTokenWrapper(DAI, SDAI, pool, OWNER); aTokenOut = ASDAI; tokenInDecimals = 18; diff --git a/test/StakedEthTokenWrapper.t.sol b/test/StakedEthTokenWrapper.t.sol index 2f84c73..0bf59e2 100644 --- a/test/StakedEthTokenWrapper.t.sol +++ b/test/StakedEthTokenWrapper.t.sol @@ -1,6 +1,8 @@ // SPDX-License-Identifier: AGPL-3.0 pragma solidity ^0.8.10; +import {AaveV2EthereumAssets} from 'aave-address-book/AaveV2Ethereum.sol'; +import {AaveV3Ethereum, AaveV3EthereumAssets} from 'aave-address-book/AaveV3Ethereum.sol'; import {IERC20} from 'aave-v3-core/contracts/dependencies/openzeppelin/contracts/IERC20.sol'; import {IPool} from 'aave-v3-core/contracts/interfaces/IPool.sol'; import {BaseTokenWrapperTest} from './BaseTokenWrapper.t.sol'; @@ -8,14 +10,14 @@ import {StakedEthTokenWrapper} from '../src/StakedEthTokenWrapper.sol'; import {ICreditDelegationToken} from '../src/interfaces/ICreditDelegationToken.sol'; contract StakedEthTokenWrapperTest is BaseTokenWrapperTest { - address constant STETH = 0xae7ab96520DE3A18E5e111B5EaAb095312D7fE84; - address constant WSTETH = 0x7f39C581F595B53c5cb19bD0b3f8dA6c935E2Ca0; - address constant AWSTETH = 0x0B925eD163218f6662a35e0f0371Ac234f9E9371; - address constant WETH = 0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2; + address constant STETH = AaveV2EthereumAssets.stETH_UNDERLYING; + address constant WSTETH = AaveV3EthereumAssets.wstETH_UNDERLYING; + address constant AWSTETH = AaveV3EthereumAssets.wstETH_A_TOKEN; + address constant WETH = AaveV3EthereumAssets.WETH_UNDERLYING; function setUp() public { vm.createSelectFork(vm.envString('ETH_RPC_URL'), 20784588); - pool = 0x87870Bca3F3fD6335C3F4ce8392D69350B4fA4E2; + pool = address(AaveV3Ethereum.POOL); tokenWrapper = new StakedEthTokenWrapper(STETH, WSTETH, pool, OWNER); aTokenOut = AWSTETH; tokenInDecimals = 18; diff --git a/test/mocks/MockERC20.sol b/test/mocks/MockERC20.sol new file mode 100644 index 0000000..1a8b5bd --- /dev/null +++ b/test/mocks/MockERC20.sol @@ -0,0 +1,11 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.0; + +import {ERC20Permit} from 'openzeppelin/token/ERC20/extensions/ERC20Permit.sol'; +import {ERC20} from 'openzeppelin/token/ERC20/ERC20.sol'; + +contract MockERC20 is ERC20Permit { + constructor(string memory name) ERC20Permit(name) ERC20(name, name) { + _mint(msg.sender, 1e50); + } +} diff --git a/test/mocks/MockERC4626.sol b/test/mocks/MockERC4626.sol new file mode 100644 index 0000000..f505a8f --- /dev/null +++ b/test/mocks/MockERC4626.sol @@ -0,0 +1,10 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.0; + +import {IERC20} from 'openzeppelin/token/ERC20/IERC20.sol'; +import {ERC20} from 'openzeppelin/token/ERC20/ERC20.sol'; +import {ERC4626} from 'openzeppelin/token/ERC20/extensions/ERC4626.sol'; + +contract MockERC4626 is ERC4626 { + constructor(IERC20 _asset) ERC20('SMOCK', 'SMOCK') ERC4626(_asset) {} +} From 53c52bd888c995d1a4215fb1396dce014be79083 Mon Sep 17 00:00:00 2001 From: miguelmtzinf Date: Thu, 3 Oct 2024 10:53:51 +0200 Subject: [PATCH 21/28] fix: Remove unneccesary file --- src/interfaces/ICreditDelegationToken.sol | 49 ----------------------- test/Generic4626Wrapper.t.sol | 6 +-- test/SavingsDaiTokenWrapper.t.sol | 2 +- test/StakedEthTokenWrapper.t.sol | 2 +- 4 files changed, 5 insertions(+), 54 deletions(-) delete mode 100644 src/interfaces/ICreditDelegationToken.sol diff --git a/src/interfaces/ICreditDelegationToken.sol b/src/interfaces/ICreditDelegationToken.sol deleted file mode 100644 index 265efa9..0000000 --- a/src/interfaces/ICreditDelegationToken.sol +++ /dev/null @@ -1,49 +0,0 @@ -// SPDX-License-Identifier: MIT -pragma solidity ^0.8.0; - -/** - * @title ICreditDelegationToken - * @author Aave - * @notice Defines the basic interface for a token supporting credit delegation. - **/ -interface ICreditDelegationToken { - /** - * @notice Delegates borrowing power to a user on the specific debt token. - * Delegation will still respect the liquidation constraints (even if delegated, a - * delegatee cannot force a delegator HF to go below 1) - * @param delegatee The address receiving the delegated borrowing power - * @param amount The maximum amount being delegated. - **/ - function approveDelegation(address delegatee, uint256 amount) external; - - /** - * @notice Returns the borrow allowance of the user - * @param fromUser The user to giving allowance - * @param toUser The user to give allowance to - * @return The current allowance of `toUser` - **/ - function borrowAllowance( - address fromUser, - address toUser - ) external view returns (uint256); - - /** - * @notice Delegates borrowing power to a user on the specific debt token via ERC712 signature - * @param delegator The delegator of the credit - * @param delegatee The delegatee that can use the credit - * @param value The amount to be delegated - * @param deadline The deadline timestamp, type(uint256).max for max deadline - * @param v The V signature param - * @param s The S signature param - * @param r The R signature param - */ - function delegationWithSig( - address delegator, - address delegatee, - uint256 value, - uint256 deadline, - uint8 v, - bytes32 r, - bytes32 s - ) external; -} diff --git a/test/Generic4626Wrapper.t.sol b/test/Generic4626Wrapper.t.sol index 8bd4e1e..2df5123 100644 --- a/test/Generic4626Wrapper.t.sol +++ b/test/Generic4626Wrapper.t.sol @@ -11,9 +11,9 @@ import {IPoolConfigurator} from 'aave-v3-core/contracts/interfaces/IPoolConfigur import {ConfiguratorInputTypes} from 'aave-v3-core/contracts/protocol/libraries/types/ConfiguratorInputTypes.sol'; import {IAToken} from 'aave-v3-core/contracts/interfaces/IAToken.sol'; import {IERC20} from 'aave-v3-core/contracts/dependencies/openzeppelin/contracts/IERC20.sol'; -import {Generic4626Wrapper} from '../src/Generic4626Wrapper.sol'; -import {ICreditDelegationToken} from '../src/interfaces/ICreditDelegationToken.sol'; -import {IBaseTokenWrapper} from '../src/interfaces/IBaseTokenWrapper.sol'; +import {ICreditDelegationToken} from 'aave-v3-core/contracts/interfaces/ICreditDelegationToken.sol'; +import {Generic4626Wrapper} from 'src/Generic4626Wrapper.sol'; +import {IBaseTokenWrapper} from 'src/interfaces/IBaseTokenWrapper.sol'; import {MockERC4626} from './mocks/MockERC4626.sol'; import {MockERC20} from './mocks/MockERC20.sol'; import {SigUtils} from './utils/SigUtils.sol'; diff --git a/test/SavingsDaiTokenWrapper.t.sol b/test/SavingsDaiTokenWrapper.t.sol index 14bd286..afc3f9f 100644 --- a/test/SavingsDaiTokenWrapper.t.sol +++ b/test/SavingsDaiTokenWrapper.t.sol @@ -4,9 +4,9 @@ pragma solidity ^0.8.10; import {AaveV3Ethereum, AaveV3EthereumAssets} from 'aave-address-book/AaveV3Ethereum.sol'; import {IERC20} from 'aave-v3-core/contracts/dependencies/openzeppelin/contracts/IERC20.sol'; import {IPool} from 'aave-v3-core/contracts/interfaces/IPool.sol'; +import {ICreditDelegationToken} from 'aave-v3-core/contracts/interfaces/ICreditDelegationToken.sol'; import {BaseTokenWrapperTest} from './BaseTokenWrapper.t.sol'; import {SavingsDaiTokenWrapper} from '../src/SavingsDaiTokenWrapper.sol'; -import {ICreditDelegationToken} from '../src/interfaces/ICreditDelegationToken.sol'; contract SavingsDaiTokenWrapperTest is BaseTokenWrapperTest { address constant DAI = AaveV3EthereumAssets.DAI_UNDERLYING; diff --git a/test/StakedEthTokenWrapper.t.sol b/test/StakedEthTokenWrapper.t.sol index 0bf59e2..59e3958 100644 --- a/test/StakedEthTokenWrapper.t.sol +++ b/test/StakedEthTokenWrapper.t.sol @@ -5,9 +5,9 @@ import {AaveV2EthereumAssets} from 'aave-address-book/AaveV2Ethereum.sol'; import {AaveV3Ethereum, AaveV3EthereumAssets} from 'aave-address-book/AaveV3Ethereum.sol'; import {IERC20} from 'aave-v3-core/contracts/dependencies/openzeppelin/contracts/IERC20.sol'; import {IPool} from 'aave-v3-core/contracts/interfaces/IPool.sol'; +import {ICreditDelegationToken} from 'aave-v3-core/contracts/interfaces/ICreditDelegationToken.sol'; import {BaseTokenWrapperTest} from './BaseTokenWrapper.t.sol'; import {StakedEthTokenWrapper} from '../src/StakedEthTokenWrapper.sol'; -import {ICreditDelegationToken} from '../src/interfaces/ICreditDelegationToken.sol'; contract StakedEthTokenWrapperTest is BaseTokenWrapperTest { address constant STETH = AaveV2EthereumAssets.stETH_UNDERLYING; From 37e17c4d7138c3fcc90b27416f26d833b6c7d04c Mon Sep 17 00:00:00 2001 From: miguelmtzinf Date: Thu, 3 Oct 2024 11:03:18 +0200 Subject: [PATCH 22/28] fix: Disables supply with permit for DAI because DAI permit is not standard --- src/BaseTokenWrapper.sol | 18 +++++++++++++----- src/SavingsDaiTokenWrapper.sol | 14 +++++++++++++- 2 files changed, 26 insertions(+), 6 deletions(-) diff --git a/src/BaseTokenWrapper.sol b/src/BaseTokenWrapper.sol index 60c316c..209a8c6 100644 --- a/src/BaseTokenWrapper.sol +++ b/src/BaseTokenWrapper.sol @@ -28,6 +28,8 @@ abstract contract BaseTokenWrapper is Ownable, IBaseTokenWrapper { /// @inheritdoc IBaseTokenWrapper IPool public immutable POOL; + uint256 private constant VARIABLE_INTEREST_RATE_MODE = 2; + /** * @dev Constructor * @param tokenIn ERC-20 token that will be wrapped in supply operations @@ -47,7 +49,7 @@ abstract contract BaseTokenWrapper is Ownable, IBaseTokenWrapper { uint256 amount, address onBehalfOf, uint16 referralCode - ) external returns (uint256) { + ) external virtual returns (uint256) { return _supplyToken(amount, onBehalfOf, referralCode); } @@ -57,7 +59,7 @@ abstract contract BaseTokenWrapper is Ownable, IBaseTokenWrapper { address onBehalfOf, uint16 referralCode, PermitSignature calldata signature - ) external returns (uint256) { + ) external virtual returns (uint256) { // explicitly left try-catch block blank to protect users from permit griefing try IERC20WithPermit(TOKEN_IN).permit( @@ -77,7 +79,7 @@ abstract contract BaseTokenWrapper is Ownable, IBaseTokenWrapper { function withdrawToken( uint256 amount, address to - ) external returns (uint256) { + ) external virtual returns (uint256) { IAToken aTokenOut = IAToken(POOL.getReserveData(TOKEN_OUT).aTokenAddress); return _withdrawToken(amount, to, aTokenOut); } @@ -87,7 +89,7 @@ abstract contract BaseTokenWrapper is Ownable, IBaseTokenWrapper { uint256 amount, address to, PermitSignature calldata signature - ) external returns (uint256) { + ) external virtual returns (uint256) { IAToken aTokenOut = IAToken(POOL.getReserveData(TOKEN_OUT).aTokenAddress); // explicitly left try-catch block blank to protect users from permit griefing try @@ -224,7 +226,13 @@ abstract contract BaseTokenWrapper is Ownable, IBaseTokenWrapper { ) internal { require(amount > 0, 'INSUFFICIENT_AMOUNT_TO_BORROW'); uint256 balanceBeforeBorrow = IERC20(TOKEN_OUT).balanceOf(address(this)); - POOL.borrow(TOKEN_OUT, amount, 2, referralCode, address(onBehalfOf)); + POOL.borrow( + TOKEN_OUT, + amount, + VARIABLE_INTEREST_RATE_MODE, + referralCode, + address(onBehalfOf) + ); uint256 balanceAfterBorrow = IERC20(TOKEN_OUT).balanceOf(address(this)); uint256 amountIn = _unwrapTokenOut( balanceAfterBorrow - balanceBeforeBorrow diff --git a/src/SavingsDaiTokenWrapper.sol b/src/SavingsDaiTokenWrapper.sol index 85d85ed..f5d888e 100644 --- a/src/SavingsDaiTokenWrapper.sol +++ b/src/SavingsDaiTokenWrapper.sol @@ -42,6 +42,15 @@ contract SavingsDaiTokenWrapper is BaseTokenWrapper { return ISavingsDai(TOKEN_OUT).previewRedeem(amount); } + function supplyTokenWithPermit( + uint256 amount, + address onBehalfOf, + uint16 referralCode, + PermitSignature calldata signature + ) external pure override returns (uint256) { + revert('INVALID_ACTION'); + } + /// @inheritdoc BaseTokenWrapper function borrowToken( uint256 amount, @@ -62,7 +71,10 @@ contract SavingsDaiTokenWrapper is BaseTokenWrapper { /// @inheritdoc BaseTokenWrapper function _wrapTokenIn(uint256 amount) internal override returns (uint256) { SafeERC20.safeApprove(IERC20(TOKEN_IN), TOKEN_OUT, amount); - uint256 wrappedAmount = ISavingsDai(TOKEN_OUT).deposit(amount, address(this)); + uint256 wrappedAmount = ISavingsDai(TOKEN_OUT).deposit( + amount, + address(this) + ); SafeERC20.safeApprove(IERC20(TOKEN_IN), TOKEN_OUT, 0); return wrappedAmount; } From a10494c2d86f21e965e81e96ce7cddc1efc99ab3 Mon Sep 17 00:00:00 2001 From: miguelmtzinf Date: Thu, 3 Oct 2024 11:04:45 +0200 Subject: [PATCH 23/28] fix: Re-order functions based on solidity guideliens --- src/SavingsDaiTokenWrapper.sol | 27 ++++++++++++++------------- src/StakedEthTokenWrapper.sol | 28 ++++++++++++++-------------- 2 files changed, 28 insertions(+), 27 deletions(-) diff --git a/src/SavingsDaiTokenWrapper.sol b/src/SavingsDaiTokenWrapper.sol index f5d888e..d41dc63 100644 --- a/src/SavingsDaiTokenWrapper.sol +++ b/src/SavingsDaiTokenWrapper.sol @@ -29,19 +29,6 @@ contract SavingsDaiTokenWrapper is BaseTokenWrapper { } /// @inheritdoc BaseTokenWrapper - function getTokenOutForTokenIn( - uint256 amount - ) external view override returns (uint256) { - return ISavingsDai(TOKEN_OUT).previewDeposit(amount); - } - - /// @inheritdoc BaseTokenWrapper - function getTokenInForTokenOut( - uint256 amount - ) external view override returns (uint256) { - return ISavingsDai(TOKEN_OUT).previewRedeem(amount); - } - function supplyTokenWithPermit( uint256 amount, address onBehalfOf, @@ -68,6 +55,20 @@ contract SavingsDaiTokenWrapper is BaseTokenWrapper { revert('INVALID_ACTION'); } + /// @inheritdoc BaseTokenWrapper + function getTokenOutForTokenIn( + uint256 amount + ) external view override returns (uint256) { + return ISavingsDai(TOKEN_OUT).previewDeposit(amount); + } + + /// @inheritdoc BaseTokenWrapper + function getTokenInForTokenOut( + uint256 amount + ) external view override returns (uint256) { + return ISavingsDai(TOKEN_OUT).previewRedeem(amount); + } + /// @inheritdoc BaseTokenWrapper function _wrapTokenIn(uint256 amount) internal override returns (uint256) { SafeERC20.safeApprove(IERC20(TOKEN_IN), TOKEN_OUT, amount); diff --git a/src/StakedEthTokenWrapper.sol b/src/StakedEthTokenWrapper.sol index f5c3115..4632d32 100644 --- a/src/StakedEthTokenWrapper.sol +++ b/src/StakedEthTokenWrapper.sol @@ -28,20 +28,6 @@ contract StakedEthTokenWrapper is BaseTokenWrapper { // Intentionally left blank } - /// @inheritdoc BaseTokenWrapper - function getTokenOutForTokenIn( - uint256 amount - ) external view override returns (uint256) { - return IWstETH(TOKEN_OUT).getWstETHByStETH(amount); - } - - /// @inheritdoc BaseTokenWrapper - function getTokenInForTokenOut( - uint256 amount - ) external view override returns (uint256) { - return IWstETH(TOKEN_OUT).getStETHByWstETH(amount); - } - /// @inheritdoc BaseTokenWrapper function borrowToken( uint256 amount, @@ -59,6 +45,20 @@ contract StakedEthTokenWrapper is BaseTokenWrapper { revert('INVALID_ACTION'); } + /// @inheritdoc BaseTokenWrapper + function getTokenOutForTokenIn( + uint256 amount + ) external view override returns (uint256) { + return IWstETH(TOKEN_OUT).getWstETHByStETH(amount); + } + + /// @inheritdoc BaseTokenWrapper + function getTokenInForTokenOut( + uint256 amount + ) external view override returns (uint256) { + return IWstETH(TOKEN_OUT).getStETHByWstETH(amount); + } + /// @inheritdoc BaseTokenWrapper function _wrapTokenIn(uint256 amount) internal override returns (uint256) { SafeERC20.safeApprove(IERC20(TOKEN_IN), TOKEN_OUT, amount); From 11c19c8aed55ec9c70a4e28caa54708cf3750e24 Mon Sep 17 00:00:00 2001 From: miguelmtzinf Date: Thu, 3 Oct 2024 11:06:43 +0200 Subject: [PATCH 24/28] fix: Remove solc warnings --- src/SavingsDaiTokenWrapper.sol | 19 ++++++++----------- src/StakedEthTokenWrapper.sol | 11 ++++------- 2 files changed, 12 insertions(+), 18 deletions(-) diff --git a/src/SavingsDaiTokenWrapper.sol b/src/SavingsDaiTokenWrapper.sol index d41dc63..1176144 100644 --- a/src/SavingsDaiTokenWrapper.sol +++ b/src/SavingsDaiTokenWrapper.sol @@ -30,27 +30,24 @@ contract SavingsDaiTokenWrapper is BaseTokenWrapper { /// @inheritdoc BaseTokenWrapper function supplyTokenWithPermit( - uint256 amount, - address onBehalfOf, - uint16 referralCode, - PermitSignature calldata signature + uint256, + address, + uint16, + PermitSignature calldata ) external pure override returns (uint256) { revert('INVALID_ACTION'); } /// @inheritdoc BaseTokenWrapper - function borrowToken( - uint256 amount, - uint16 referralCode - ) external pure override { + function borrowToken(uint256, uint16) external pure override { revert('INVALID_ACTION'); } /// @inheritdoc BaseTokenWrapper function borrowTokenWithPermit( - uint256 amount, - uint16 referralCode, - PermitSignature calldata signature + uint256, + uint16, + PermitSignature calldata ) external pure override { revert('INVALID_ACTION'); } diff --git a/src/StakedEthTokenWrapper.sol b/src/StakedEthTokenWrapper.sol index 4632d32..e6578bd 100644 --- a/src/StakedEthTokenWrapper.sol +++ b/src/StakedEthTokenWrapper.sol @@ -29,18 +29,15 @@ contract StakedEthTokenWrapper is BaseTokenWrapper { } /// @inheritdoc BaseTokenWrapper - function borrowToken( - uint256 amount, - uint16 referralCode - ) external pure override { + function borrowToken(uint256, uint16) external pure override { revert('INVALID_ACTION'); } /// @inheritdoc BaseTokenWrapper function borrowTokenWithPermit( - uint256 amount, - uint16 referralCode, - PermitSignature calldata signature + uint256, + uint16, + PermitSignature calldata ) external pure override { revert('INVALID_ACTION'); } From 596d52123cdd9d4feff0a20eca1eca300765f410 Mon Sep 17 00:00:00 2001 From: miguelmtz <36620902+miguelmtzinf@users.noreply.github.com> Date: Fri, 4 Oct 2024 01:15:55 +0200 Subject: [PATCH 25/28] Refactors tests (#21) * fix: Refactor tests and clean ups * fix: Move dependencies to external dir --- src/SavingsDaiTokenWrapper.sol | 2 +- src/StakedEthTokenWrapper.sol | 2 +- .../ISavingsDai.sol | 0 src/{interfaces => dependencies}/IWstETH.sol | 0 test/BaseTokenWrapper.t.sol | 171 ++++++++++++++- test/Generic4626Wrapper.t.sol | 204 +++--------------- test/SavingsDaiTokenWrapper.t.sol | 31 +-- test/StakedEthTokenWrapper.t.sol | 40 +--- 8 files changed, 210 insertions(+), 240 deletions(-) rename src/{interfaces => dependencies}/ISavingsDai.sol (100%) rename src/{interfaces => dependencies}/IWstETH.sol (100%) diff --git a/src/SavingsDaiTokenWrapper.sol b/src/SavingsDaiTokenWrapper.sol index 1176144..b136645 100644 --- a/src/SavingsDaiTokenWrapper.sol +++ b/src/SavingsDaiTokenWrapper.sol @@ -3,7 +3,7 @@ pragma solidity ^0.8.10; import {SafeERC20} from 'aave-v3-core/contracts/dependencies/openzeppelin/contracts/SafeERC20.sol'; import {IERC20} from 'aave-v3-core/contracts/dependencies/openzeppelin/contracts/IERC20.sol'; -import {ISavingsDai} from './interfaces/ISavingsDai.sol'; +import {ISavingsDai} from './dependencies/ISavingsDai.sol'; import {BaseTokenWrapper} from './BaseTokenWrapper.sol'; /** diff --git a/src/StakedEthTokenWrapper.sol b/src/StakedEthTokenWrapper.sol index e6578bd..ab58a39 100644 --- a/src/StakedEthTokenWrapper.sol +++ b/src/StakedEthTokenWrapper.sol @@ -3,7 +3,7 @@ pragma solidity ^0.8.10; import {SafeERC20} from 'aave-v3-core/contracts/dependencies/openzeppelin/contracts/SafeERC20.sol'; import {IERC20} from 'aave-v3-core/contracts/dependencies/openzeppelin/contracts/IERC20.sol'; -import {IWstETH} from './interfaces/IWstETH.sol'; +import {IWstETH} from './dependencies/IWstETH.sol'; import {BaseTokenWrapper} from './BaseTokenWrapper.sol'; /** diff --git a/src/interfaces/ISavingsDai.sol b/src/dependencies/ISavingsDai.sol similarity index 100% rename from src/interfaces/ISavingsDai.sol rename to src/dependencies/ISavingsDai.sol diff --git a/src/interfaces/IWstETH.sol b/src/dependencies/IWstETH.sol similarity index 100% rename from src/interfaces/IWstETH.sol rename to src/dependencies/IWstETH.sol diff --git a/test/BaseTokenWrapper.t.sol b/test/BaseTokenWrapper.t.sol index 6251afe..054d259 100644 --- a/test/BaseTokenWrapper.t.sol +++ b/test/BaseTokenWrapper.t.sol @@ -4,9 +4,13 @@ pragma solidity ^0.8.10; import {Test} from 'forge-std/Test.sol'; import {IERC20} from 'aave-v3-core/contracts/dependencies/openzeppelin/contracts/IERC20.sol'; import {IAToken} from 'aave-v3-core/contracts/interfaces/IAToken.sol'; +import {IPool} from 'aave-v3-core/contracts/interfaces/IPool.sol'; +import {ICreditDelegationToken} from 'aave-v3-core/contracts/interfaces/ICreditDelegationToken.sol'; import {MintableERC20} from 'aave-v3-core/contracts/mocks/tokens/MintableERC20.sol'; -import {IBaseTokenWrapper} from '../src/interfaces/IBaseTokenWrapper.sol'; -import {BaseTokenWrapper} from '../src/BaseTokenWrapper.sol'; +import {SigUtils} from './utils/SigUtils.sol'; + +import {IBaseTokenWrapper} from 'src/interfaces/IBaseTokenWrapper.sol'; +import {BaseTokenWrapper} from 'src/BaseTokenWrapper.sol'; interface IERC2612 { function nonces(address owner) external view returns (uint256); @@ -40,7 +44,9 @@ abstract contract BaseTokenWrapperTest is Test { BaseTokenWrapper tokenWrapper; address aTokenOut; uint256 tokenInDecimals; + address collateralAsset; bool permitSupported; + bool borrowSupported; constructor() { (ALICE, ALICE_KEY) = makeAddrAndKey('alice'); @@ -50,7 +56,7 @@ abstract contract BaseTokenWrapperTest is Test { function testConstructor() public virtual; - function testSupplyToken() public { + function testSupplyToken2() public { IERC20 tokenIn = IERC20(tokenWrapper.TOKEN_IN()); IERC20 tokenOut = IERC20(tokenWrapper.TOKEN_OUT()); assertEq( @@ -353,7 +359,7 @@ abstract contract BaseTokenWrapperTest is Test { } function testWithdrawToken() public { - testSupplyToken(); + testSupplyToken2(); IERC20 tokenIn = IERC20(tokenWrapper.TOKEN_IN()); uint256 aTokenBalance = IAToken(aTokenOut).balanceOf(ALICE); @@ -390,7 +396,7 @@ abstract contract BaseTokenWrapperTest is Test { } function testWithdrawTokenInsufficientBalance() public { - testSupplyToken(); + testSupplyToken2(); IERC20 tokenIn = IERC20(tokenWrapper.TOKEN_IN()); uint256 aTokenBalance = IAToken(aTokenOut).balanceOf(ALICE); @@ -409,7 +415,7 @@ abstract contract BaseTokenWrapperTest is Test { } function testWithdrawTokenMaxValue() public { - testSupplyToken(); + testSupplyToken2(); IERC20 tokenIn = IERC20(tokenWrapper.TOKEN_IN()); uint256 aTokenBalance = IAToken(aTokenOut).balanceOf(ALICE); @@ -450,7 +456,7 @@ abstract contract BaseTokenWrapperTest is Test { } function testWithdrawTokenToOther() public { - testSupplyToken(); + testSupplyToken2(); IERC20 tokenIn = IERC20(tokenWrapper.TOKEN_IN()); uint256 aTokenBalance = IAToken(aTokenOut).balanceOf(ALICE); @@ -489,7 +495,7 @@ abstract contract BaseTokenWrapperTest is Test { } function testWithdrawTokenWithPermit() public { - testSupplyToken(); + testSupplyToken2(); IERC20 tokenIn = IERC20(tokenWrapper.TOKEN_IN()); uint256 aTokenBalance = IAToken(aTokenOut).balanceOf(ALICE); @@ -551,7 +557,7 @@ abstract contract BaseTokenWrapperTest is Test { } function testPermitGriefingWithdrawTokenWithPermit() public { - testSupplyToken(); + testSupplyToken2(); IERC20 tokenIn = IERC20(tokenWrapper.TOKEN_IN()); uint256 aTokenBalance = IAToken(aTokenOut).balanceOf(ALICE); @@ -728,7 +734,7 @@ abstract contract BaseTokenWrapperTest is Test { } function testFuzzWithdrawToken(uint256 aTokenBalance) public { - testSupplyToken(); + testSupplyToken2(); IERC20 tokenIn = IERC20(tokenWrapper.TOKEN_IN()); uint256 aTokenBalanceOriginal = IAToken(aTokenOut).balanceOf(ALICE); aTokenBalance = bound(aTokenBalance, 1000, aTokenBalanceOriginal - 1); //using 1000 as min to ignore dust amounts @@ -752,6 +758,151 @@ abstract contract BaseTokenWrapperTest is Test { assertGt(withdrawnAmount, 0, 'Unexpected withdraw return/balance mismatch'); } + function testBorrow() public { + uint256 collateralAmount = 1000e18; + uint256 borrowAmount = 100e18; + address debtToken = IPool(pool) + .getReserveData(tokenWrapper.TOKEN_OUT()) + .variableDebtTokenAddress; + + address alice = makeAddr('ALICE'); + deal(collateralAsset, alice, collateralAmount); + + vm.startPrank(alice); + + IERC20(collateralAsset).approve(address(pool), collateralAmount); + IPool(pool).supply(collateralAsset, collateralAmount, alice, 0); + + ICreditDelegationToken(debtToken).approveDelegation( + address(tokenWrapper), + borrowAmount + ); + + if (borrowSupported) { + tokenWrapper.borrowToken(borrowAmount, 0); + uint256 borrowedAmount = tokenWrapper.getTokenInForTokenOut(borrowAmount); + assertEq( + IERC20(tokenWrapper.TOKEN_IN()).balanceOf(address(alice)), + borrowedAmount + ); + } else { + vm.expectRevert(); + tokenWrapper.borrowToken(borrowAmount, 0); + } + vm.stopPrank(); + } + + function testBorrowTokenWithPermit() public { + uint256 borrowAmount = 100e18; + uint256 collateralAmount = 1000e18; + + (address alice, uint256 userPrivateKey) = makeAddrAndKey('ALICE'); + deal(collateralAsset, alice, collateralAmount); + + address debtToken = IPool(pool) + .getReserveData(tokenWrapper.TOKEN_OUT()) + .variableDebtTokenAddress; + + vm.startPrank(alice); + + IERC20(collateralAsset).approve(address(pool), collateralAmount); + + IPool(pool).supply(collateralAsset, collateralAmount, alice, 0); + + uint256 deadline = block.timestamp + 1 hours; + uint256 nonce = IAToken(debtToken).nonces(alice); + + (uint8 v, bytes32 r, bytes32 s) = _signCreditDelegation( + userPrivateKey, + address(tokenWrapper), + borrowAmount, + nonce, + deadline, + debtToken + ); + IBaseTokenWrapper.PermitSignature memory signature = IBaseTokenWrapper + .PermitSignature({deadline: deadline, v: v, r: r, s: s}); + + if (borrowSupported) { + tokenWrapper.borrowTokenWithPermit(borrowAmount, 1, signature); + + vm.stopPrank(); + + uint256 borrowedAmount = tokenWrapper.getTokenInForTokenOut(borrowAmount); + assertEq( + IERC20(tokenWrapper.TOKEN_IN()).balanceOf(address(alice)), + borrowedAmount + ); + } else { + vm.expectRevert(); + tokenWrapper.borrowTokenWithPermit(borrowAmount, 1, signature); + } + } + + function testBorrowTokenWithPermitZeroAmount() public { + uint256 borrowAmount = 0; + uint256 collateralAmount = 1000e18; + + (address alice, uint256 userPrivateKey) = makeAddrAndKey('ALICE'); + deal(collateralAsset, alice, collateralAmount); + + address debtToken = IPool(pool) + .getReserveData(tokenWrapper.TOKEN_OUT()) + .variableDebtTokenAddress; + + vm.startPrank(alice); + + IERC20(collateralAsset).approve(address(pool), collateralAmount); + + IPool(pool).supply(collateralAsset, collateralAmount, alice, 0); + + uint256 deadline = block.timestamp + 1 hours; + uint256 nonce = IAToken(debtToken).nonces(alice); + + (uint8 v, bytes32 r, bytes32 s) = _signCreditDelegation( + userPrivateKey, + address(tokenWrapper), + borrowAmount, + nonce, + deadline, + debtToken + ); + IBaseTokenWrapper.PermitSignature memory signature = IBaseTokenWrapper + .PermitSignature({deadline: deadline, v: v, r: r, s: s}); + if (borrowSupported) { + vm.expectRevert('INSUFFICIENT_AMOUNT_TO_BORROW'); + tokenWrapper.borrowTokenWithPermit(borrowAmount, 1, signature); + } else { + vm.expectRevert(); + tokenWrapper.borrowTokenWithPermit(borrowAmount, 1, signature); + } + } + + function _signCreditDelegation( + uint256 privateKey, + address delegatee, + uint256 value, + uint256 nonce, + uint256 deadline, + address debtToken + ) internal view returns (uint8 v, bytes32 r, bytes32 s) { + SigUtils.CreditDelegation memory creditDelegation = SigUtils + .CreditDelegation({ + delegatee: delegatee, + value: value, + nonce: nonce, + deadline: deadline + }); + + bytes32 domainSeparator = IAToken(debtToken).DOMAIN_SEPARATOR(); + bytes32 digest = SigUtils.getCreditDelegationTypedDataHash( + creditDelegation, + domainSeparator + ); + + return vm.sign(privateKey, digest); + } + function _dealTokenIn(address user, uint256 amount) internal virtual { deal(tokenWrapper.TOKEN_IN(), user, amount); } diff --git a/test/Generic4626Wrapper.t.sol b/test/Generic4626Wrapper.t.sol index 2df5123..9f97a2e 100644 --- a/test/Generic4626Wrapper.t.sol +++ b/test/Generic4626Wrapper.t.sol @@ -1,6 +1,5 @@ // SPDX-License-Identifier: AGPL-3.0 pragma solidity ^0.8.10; -import 'forge-std/console2.sol'; import {AaveV3Ethereum, AaveV3EthereumAssets} from 'aave-address-book/AaveV3Ethereum.sol'; import {IDefaultInterestRateStrategyV2} from 'aave-v3-core/contracts/interfaces/IDefaultInterestRateStrategyV2.sol'; @@ -9,18 +8,14 @@ import {MockAggregator} from 'aave-v3-core/contracts/mocks/oracle/CLAggregators/ import {IPool} from 'aave-v3-core/contracts/interfaces/IPool.sol'; import {IPoolConfigurator} from 'aave-v3-core/contracts/interfaces/IPoolConfigurator.sol'; import {ConfiguratorInputTypes} from 'aave-v3-core/contracts/protocol/libraries/types/ConfiguratorInputTypes.sol'; -import {IAToken} from 'aave-v3-core/contracts/interfaces/IAToken.sol'; import {IERC20} from 'aave-v3-core/contracts/dependencies/openzeppelin/contracts/IERC20.sol'; -import {ICreditDelegationToken} from 'aave-v3-core/contracts/interfaces/ICreditDelegationToken.sol'; -import {Generic4626Wrapper} from 'src/Generic4626Wrapper.sol'; -import {IBaseTokenWrapper} from 'src/interfaces/IBaseTokenWrapper.sol'; import {MockERC4626} from './mocks/MockERC4626.sol'; import {MockERC20} from './mocks/MockERC20.sol'; -import {SigUtils} from './utils/SigUtils.sol'; import {BaseTokenWrapperTest} from './BaseTokenWrapper.t.sol'; +import {Generic4626Wrapper} from 'src/Generic4626Wrapper.sol'; + contract Generic4626WrapperTest is BaseTokenWrapperTest { - address constant WETH = AaveV3EthereumAssets.WETH_UNDERLYING; address constant ADMIN = AaveV3Ethereum.ACL_ADMIN; IPoolConfigurator constant POOL_CONFIGURATOR = AaveV3Ethereum.POOL_CONFIGURATOR; @@ -49,7 +44,34 @@ contract Generic4626WrapperTest is BaseTokenWrapperTest { tokenWrapper = new Generic4626Wrapper(unwrapped, wrapped, pool, OWNER); tokenInDecimals = 18; permitSupported = true; + borrowSupported = true; + collateralAsset = AaveV3EthereumAssets.WETH_UNDERLYING; + + _listAsset(wrapped); + + // Supply some of the new asset to pool + uint256 collateralAmount = 1000e18; + deal(wrapped, address(this), collateralAmount); + IERC20(wrapped).approve(address(pool), collateralAmount); + IPool(pool).supply(wrapped, collateralAmount, address(this), 0); + + aTokenOut = IPool(pool).getReserveData(wrapped).aTokenAddress; + } + + function testConstructor() public override { + Generic4626Wrapper tempTokenWrapper = new Generic4626Wrapper( + unwrapped, + wrapped, + pool, + OWNER + ); + assertEq(tempTokenWrapper.TOKEN_IN(), unwrapped, 'Unexpected TOKEN_IN'); + assertEq(tempTokenWrapper.TOKEN_OUT(), wrapped, 'Unexpected TOKEN_OUT'); + assertEq(address(tempTokenWrapper.POOL()), pool, 'Unexpected POOL'); + assertEq(tempTokenWrapper.owner(), OWNER, 'Unexpected owner'); + } + function _listAsset(address underlying) internal { IDefaultInterestRateStrategyV2.InterestRateData memory interestRateData = IDefaultInterestRateStrategyV2 .InterestRateData({ @@ -69,7 +91,7 @@ contract Generic4626WrapperTest is BaseTokenWrapperTest { useVirtualBalance: true, interestRateStrategyAddress: AaveV3EthereumAssets .WETH_INTEREST_RATE_STRATEGY, - underlyingAsset: tokenWrapper.TOKEN_OUT(), + underlyingAsset: underlying, treasury: address(AaveV3Ethereum.COLLECTOR), incentivesController: AaveV3Ethereum.DEFAULT_INCENTIVES_CONTROLLER, aTokenName: 'AaveWrapped', @@ -84,176 +106,16 @@ contract Generic4626WrapperTest is BaseTokenWrapperTest { vm.startPrank(ADMIN); POOL_CONFIGURATOR.initReserves(reserveInputs); - POOL_CONFIGURATOR.setReserveActive(tokenWrapper.TOKEN_OUT(), true); - POOL_CONFIGURATOR.setReserveBorrowing(tokenWrapper.TOKEN_OUT(), true); + POOL_CONFIGURATOR.setReserveActive(underlying, true); + POOL_CONFIGURATOR.setReserveBorrowing(underlying, true); // Set asset oracle MockAggregator oracle = new MockAggregator(1); address[] memory assets = new address[](1); - assets[0] = tokenWrapper.TOKEN_OUT(); + assets[0] = underlying; address[] memory sources = new address[](1); sources[0] = address(oracle); AAVE_ORACLE.setAssetSources(assets, sources); vm.stopPrank(); - - // Supply some of the new asset to pool - uint256 collateralAmount = 1000e18; - deal(tokenWrapper.TOKEN_OUT(), address(this), collateralAmount); - IERC20(tokenWrapper.TOKEN_OUT()).approve(address(pool), collateralAmount); - IPool(pool).supply( - tokenWrapper.TOKEN_OUT(), - collateralAmount, - address(this), - 0 - ); - - aTokenOut = IPool(pool) - .getReserveData(tokenWrapper.TOKEN_OUT()) - .aTokenAddress; - } - - function testConstructor() public override { - Generic4626Wrapper tempTokenWrapper = new Generic4626Wrapper( - unwrapped, - wrapped, - pool, - OWNER - ); - assertEq(tempTokenWrapper.TOKEN_IN(), unwrapped, 'Unexpected TOKEN_IN'); - assertEq(tempTokenWrapper.TOKEN_OUT(), wrapped, 'Unexpected TOKEN_OUT'); - assertEq(address(tempTokenWrapper.POOL()), pool, 'Unexpected POOL'); - assertEq(tempTokenWrapper.owner(), OWNER, 'Unexpected owner'); - } - - function testBorrow() public { - uint256 collateralAmount = 1000e18; - uint256 borrowAmount = 100e18; - address debtToken = IPool(pool) - .getReserveData(tokenWrapper.TOKEN_OUT()) - .variableDebtTokenAddress; - - address alice = makeAddr('ALICE'); - deal(WETH, alice, collateralAmount); - - vm.startPrank(alice); - - IERC20(WETH).approve(address(pool), collateralAmount); - IPool(pool).supply(WETH, collateralAmount, alice, 0); - - ICreditDelegationToken(debtToken).approveDelegation( - address(tokenWrapper), - borrowAmount - ); - - tokenWrapper.borrowToken(borrowAmount, 0); - vm.stopPrank(); - - uint256 borrowedAmount = tokenWrapper.getTokenInForTokenOut(borrowAmount); - assertEq( - IERC20(tokenWrapper.TOKEN_IN()).balanceOf(address(alice)), - borrowedAmount - ); - } - - function testBorrowTokenWithPermit() public { - uint256 borrowAmount = 100e18; - uint256 collateralAmount = 1000e18; - - (address alice, uint256 userPrivateKey) = makeAddrAndKey('ALICE'); - deal(WETH, alice, collateralAmount); - - address debtToken = IPool(pool) - .getReserveData(tokenWrapper.TOKEN_OUT()) - .variableDebtTokenAddress; - - vm.startPrank(alice); - - IERC20(WETH).approve(address(pool), collateralAmount); - - IPool(pool).supply(WETH, collateralAmount, alice, 0); - - uint256 deadline = block.timestamp + 1 hours; - uint256 nonce = IAToken(debtToken).nonces(alice); - - (uint8 v, bytes32 r, bytes32 s) = _signCreditDelegation( - userPrivateKey, - address(tokenWrapper), - borrowAmount, - nonce, - deadline, - debtToken - ); - IBaseTokenWrapper.PermitSignature memory signature = IBaseTokenWrapper - .PermitSignature({deadline: deadline, v: v, r: r, s: s}); - - tokenWrapper.borrowTokenWithPermit(borrowAmount, 1, signature); - - vm.stopPrank(); - - uint256 borrowedAmount = tokenWrapper.getTokenInForTokenOut(borrowAmount); - assertEq( - IERC20(tokenWrapper.TOKEN_IN()).balanceOf(address(alice)), - borrowedAmount - ); - } - - function testBorrowTokenWithPermitZeroAmount() public { - uint256 borrowAmount = 0; - uint256 collateralAmount = 1000e18; - - (address alice, uint256 userPrivateKey) = makeAddrAndKey('ALICE'); - deal(WETH, alice, collateralAmount); - - address debtToken = IPool(pool) - .getReserveData(tokenWrapper.TOKEN_OUT()) - .variableDebtTokenAddress; - - vm.startPrank(alice); - - IERC20(WETH).approve(address(pool), collateralAmount); - - IPool(pool).supply(WETH, collateralAmount, alice, 0); - - uint256 deadline = block.timestamp + 1 hours; - uint256 nonce = IAToken(debtToken).nonces(alice); - - (uint8 v, bytes32 r, bytes32 s) = _signCreditDelegation( - userPrivateKey, - address(tokenWrapper), - borrowAmount, - nonce, - deadline, - debtToken - ); - IBaseTokenWrapper.PermitSignature memory signature = IBaseTokenWrapper - .PermitSignature({deadline: deadline, v: v, r: r, s: s}); - - vm.expectRevert('INSUFFICIENT_AMOUNT_TO_BORROW'); - tokenWrapper.borrowTokenWithPermit(borrowAmount, 1, signature); - } - - function _signCreditDelegation( - uint256 privateKey, - address delegatee, - uint256 value, - uint256 nonce, - uint256 deadline, - address debtToken - ) internal view returns (uint8 v, bytes32 r, bytes32 s) { - SigUtils.CreditDelegation memory creditDelegation = SigUtils - .CreditDelegation({ - delegatee: delegatee, - value: value, - nonce: nonce, - deadline: deadline - }); - - bytes32 domainSeparator = IAToken(debtToken).DOMAIN_SEPARATOR(); - bytes32 digest = SigUtils.getCreditDelegationTypedDataHash( - creditDelegation, - domainSeparator - ); - - return vm.sign(privateKey, digest); } } diff --git a/test/SavingsDaiTokenWrapper.t.sol b/test/SavingsDaiTokenWrapper.t.sol index afc3f9f..3ecbc1a 100644 --- a/test/SavingsDaiTokenWrapper.t.sol +++ b/test/SavingsDaiTokenWrapper.t.sol @@ -2,11 +2,9 @@ pragma solidity ^0.8.10; import {AaveV3Ethereum, AaveV3EthereumAssets} from 'aave-address-book/AaveV3Ethereum.sol'; -import {IERC20} from 'aave-v3-core/contracts/dependencies/openzeppelin/contracts/IERC20.sol'; -import {IPool} from 'aave-v3-core/contracts/interfaces/IPool.sol'; -import {ICreditDelegationToken} from 'aave-v3-core/contracts/interfaces/ICreditDelegationToken.sol'; import {BaseTokenWrapperTest} from './BaseTokenWrapper.t.sol'; -import {SavingsDaiTokenWrapper} from '../src/SavingsDaiTokenWrapper.sol'; + +import {SavingsDaiTokenWrapper} from 'src/SavingsDaiTokenWrapper.sol'; contract SavingsDaiTokenWrapperTest is BaseTokenWrapperTest { address constant DAI = AaveV3EthereumAssets.DAI_UNDERLYING; @@ -21,6 +19,8 @@ contract SavingsDaiTokenWrapperTest is BaseTokenWrapperTest { aTokenOut = ASDAI; tokenInDecimals = 18; permitSupported = false; + collateralAsset = AaveV3EthereumAssets.WETH_UNDERLYING; + borrowSupported = false; } function testConstructor() public override { @@ -35,27 +35,4 @@ contract SavingsDaiTokenWrapperTest is BaseTokenWrapperTest { assertEq(address(tempTokenWrapper.POOL()), pool, 'Unexpected POOL'); assertEq(tempTokenWrapper.owner(), OWNER, 'Unexpected owner'); } - - function testBorrowNotPermitted() public { - uint256 collateralAmount = 1000e18; - uint256 borrowAmount = 100e18; - address debtToken = IPool(pool) - .getReserveData(tokenWrapper.TOKEN_OUT()) - .variableDebtTokenAddress; - - address alice = makeAddr('ALICE'); - deal(WETH, alice, collateralAmount); - vm.startPrank(alice); - - IERC20(WETH).approve(address(pool), collateralAmount); - IPool(pool).supply(WETH, collateralAmount, alice, 0); - - ICreditDelegationToken(debtToken).approveDelegation( - address(tokenWrapper), - borrowAmount - ); - vm.expectRevert('INVALID_ACTION'); - tokenWrapper.borrowToken(borrowAmount, 0); - vm.stopPrank(); - } } diff --git a/test/StakedEthTokenWrapper.t.sol b/test/StakedEthTokenWrapper.t.sol index 59e3958..19474b0 100644 --- a/test/StakedEthTokenWrapper.t.sol +++ b/test/StakedEthTokenWrapper.t.sol @@ -4,10 +4,10 @@ pragma solidity ^0.8.10; import {AaveV2EthereumAssets} from 'aave-address-book/AaveV2Ethereum.sol'; import {AaveV3Ethereum, AaveV3EthereumAssets} from 'aave-address-book/AaveV3Ethereum.sol'; import {IERC20} from 'aave-v3-core/contracts/dependencies/openzeppelin/contracts/IERC20.sol'; -import {IPool} from 'aave-v3-core/contracts/interfaces/IPool.sol'; -import {ICreditDelegationToken} from 'aave-v3-core/contracts/interfaces/ICreditDelegationToken.sol'; + import {BaseTokenWrapperTest} from './BaseTokenWrapper.t.sol'; -import {StakedEthTokenWrapper} from '../src/StakedEthTokenWrapper.sol'; + +import {StakedEthTokenWrapper} from 'src/StakedEthTokenWrapper.sol'; contract StakedEthTokenWrapperTest is BaseTokenWrapperTest { address constant STETH = AaveV2EthereumAssets.stETH_UNDERLYING; @@ -22,6 +22,8 @@ contract StakedEthTokenWrapperTest is BaseTokenWrapperTest { aTokenOut = AWSTETH; tokenInDecimals = 18; permitSupported = true; + collateralAsset = AaveV3EthereumAssets.WETH_UNDERLYING; + borrowSupported = false; } function testConstructor() public override { @@ -38,32 +40,10 @@ contract StakedEthTokenWrapperTest is BaseTokenWrapperTest { } function _dealTokenIn(address user, uint256 amount) internal override { - vm.deal(user, amount); - vm.prank(user); - (bool success, ) = STETH.call{value: amount}(''); - require(success); - } - - function testBorrowNotPermitted() public { - uint256 collateralAmount = 1000e18; - uint256 borrowAmount = 100e18; - address debtToken = IPool(pool) - .getReserveData(tokenWrapper.TOKEN_OUT()) - .variableDebtTokenAddress; - - address alice = makeAddr('ALICE'); - deal(WETH, alice, collateralAmount); - vm.startPrank(alice); - - IERC20(WETH).approve(address(pool), collateralAmount); - IPool(pool).supply(WETH, collateralAmount, alice, 0); - - ICreditDelegationToken(debtToken).approveDelegation( - address(tokenWrapper), - borrowAmount - ); - vm.expectRevert('INVALID_ACTION'); - tokenWrapper.borrowToken(borrowAmount, 0); - vm.stopPrank(); + // Custom deal function for stETH + deal(address(this), amount); + (bool success, ) = payable(tokenWrapper.TOKEN_IN()).call{value: amount}(''); + require(success, 'DEAL_FAILURE'); + IERC20(tokenWrapper.TOKEN_IN()).transfer(user, amount); } } From 86fb6e2e4116c131a2bc47df855454a459e83d87 Mon Sep 17 00:00:00 2001 From: CheyenneAtapour Date: Thu, 3 Oct 2024 19:35:04 -0700 Subject: [PATCH 26/28] test: Add fuzz test for borrow --- test/BaseTokenWrapper.t.sol | 38 ++++++++++++++++++++++++++++++++++++- 1 file changed, 37 insertions(+), 1 deletion(-) diff --git a/test/BaseTokenWrapper.t.sol b/test/BaseTokenWrapper.t.sol index 054d259..94b1b3b 100644 --- a/test/BaseTokenWrapper.t.sol +++ b/test/BaseTokenWrapper.t.sol @@ -758,7 +758,7 @@ abstract contract BaseTokenWrapperTest is Test { assertGt(withdrawnAmount, 0, 'Unexpected withdraw return/balance mismatch'); } - function testBorrow() public { + function testBorrowToken() public { uint256 collateralAmount = 1000e18; uint256 borrowAmount = 100e18; address debtToken = IPool(pool) @@ -878,6 +878,42 @@ abstract contract BaseTokenWrapperTest is Test { } } + function testFuzzBorrowToken(uint256 borrowAmount) public { + borrowAmount = bound(borrowAmount, 1, MAX_DEAL_AMOUNT); + borrowAmount *= 10 ** tokenInDecimals; + uint256 collateralAmount = borrowAmount * 10; + + address debtToken = IPool(pool) + .getReserveData(tokenWrapper.TOKEN_OUT()) + .variableDebtTokenAddress; + + address alice = makeAddr('ALICE'); + deal(collateralAsset, alice, collateralAmount); + + vm.startPrank(alice); + + IERC20(collateralAsset).approve(address(pool), collateralAmount); + IPool(pool).supply(collateralAsset, collateralAmount, alice, 0); + + ICreditDelegationToken(debtToken).approveDelegation( + address(tokenWrapper), + borrowAmount + ); + + if (borrowSupported) { + tokenWrapper.borrowToken(borrowAmount, 0); + uint256 borrowedAmount = tokenWrapper.getTokenInForTokenOut(borrowAmount); + assertEq( + IERC20(tokenWrapper.TOKEN_IN()).balanceOf(address(alice)), + borrowedAmount + ); + } else { + vm.expectRevert(); + tokenWrapper.borrowToken(borrowAmount, 0); + } + vm.stopPrank(); + } + function _signCreditDelegation( uint256 privateKey, address delegatee, From 9118be9bf9696e1aadf87c002ea788c21872b2f5 Mon Sep 17 00:00:00 2001 From: CheyenneAtapour Date: Fri, 4 Oct 2024 09:44:44 -0700 Subject: [PATCH 27/28] fix: Revert change test name --- test/BaseTokenWrapper.t.sol | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/test/BaseTokenWrapper.t.sol b/test/BaseTokenWrapper.t.sol index 94b1b3b..640fb51 100644 --- a/test/BaseTokenWrapper.t.sol +++ b/test/BaseTokenWrapper.t.sol @@ -56,7 +56,7 @@ abstract contract BaseTokenWrapperTest is Test { function testConstructor() public virtual; - function testSupplyToken2() public { + function testSupplyToken() public { IERC20 tokenIn = IERC20(tokenWrapper.TOKEN_IN()); IERC20 tokenOut = IERC20(tokenWrapper.TOKEN_OUT()); assertEq( @@ -359,7 +359,7 @@ abstract contract BaseTokenWrapperTest is Test { } function testWithdrawToken() public { - testSupplyToken2(); + testSupplyToken(); IERC20 tokenIn = IERC20(tokenWrapper.TOKEN_IN()); uint256 aTokenBalance = IAToken(aTokenOut).balanceOf(ALICE); @@ -396,7 +396,7 @@ abstract contract BaseTokenWrapperTest is Test { } function testWithdrawTokenInsufficientBalance() public { - testSupplyToken2(); + testSupplyToken(); IERC20 tokenIn = IERC20(tokenWrapper.TOKEN_IN()); uint256 aTokenBalance = IAToken(aTokenOut).balanceOf(ALICE); @@ -415,7 +415,7 @@ abstract contract BaseTokenWrapperTest is Test { } function testWithdrawTokenMaxValue() public { - testSupplyToken2(); + testSupplyToken(); IERC20 tokenIn = IERC20(tokenWrapper.TOKEN_IN()); uint256 aTokenBalance = IAToken(aTokenOut).balanceOf(ALICE); @@ -456,7 +456,7 @@ abstract contract BaseTokenWrapperTest is Test { } function testWithdrawTokenToOther() public { - testSupplyToken2(); + testSupplyToken(); IERC20 tokenIn = IERC20(tokenWrapper.TOKEN_IN()); uint256 aTokenBalance = IAToken(aTokenOut).balanceOf(ALICE); @@ -495,7 +495,7 @@ abstract contract BaseTokenWrapperTest is Test { } function testWithdrawTokenWithPermit() public { - testSupplyToken2(); + testSupplyToken(); IERC20 tokenIn = IERC20(tokenWrapper.TOKEN_IN()); uint256 aTokenBalance = IAToken(aTokenOut).balanceOf(ALICE); @@ -557,7 +557,7 @@ abstract contract BaseTokenWrapperTest is Test { } function testPermitGriefingWithdrawTokenWithPermit() public { - testSupplyToken2(); + testSupplyToken(); IERC20 tokenIn = IERC20(tokenWrapper.TOKEN_IN()); uint256 aTokenBalance = IAToken(aTokenOut).balanceOf(ALICE); @@ -734,7 +734,7 @@ abstract contract BaseTokenWrapperTest is Test { } function testFuzzWithdrawToken(uint256 aTokenBalance) public { - testSupplyToken2(); + testSupplyToken(); IERC20 tokenIn = IERC20(tokenWrapper.TOKEN_IN()); uint256 aTokenBalanceOriginal = IAToken(aTokenOut).balanceOf(ALICE); aTokenBalance = bound(aTokenBalance, 1000, aTokenBalanceOriginal - 1); //using 1000 as min to ignore dust amounts From 13224ae4f2ac201633d55d8bac61fbb8f08155f5 Mon Sep 17 00:00:00 2001 From: Cheyenne Atapour Date: Mon, 7 Oct 2024 12:59:28 -0700 Subject: [PATCH 28/28] Add Additional BorrowToken Tests (#22) * test: Add zero amount borrow and permit deadline expired * test: Test borrowing more than underlying asset in vault * test: Insufficient collateral and delegation tests --- test/BaseTokenWrapper.t.sol | 171 +++++++++++++++++++++++++++++++++--- 1 file changed, 159 insertions(+), 12 deletions(-) diff --git a/test/BaseTokenWrapper.t.sol b/test/BaseTokenWrapper.t.sol index 640fb51..5708948 100644 --- a/test/BaseTokenWrapper.t.sol +++ b/test/BaseTokenWrapper.t.sol @@ -765,13 +765,12 @@ abstract contract BaseTokenWrapperTest is Test { .getReserveData(tokenWrapper.TOKEN_OUT()) .variableDebtTokenAddress; - address alice = makeAddr('ALICE'); - deal(collateralAsset, alice, collateralAmount); + deal(collateralAsset, ALICE, collateralAmount); - vm.startPrank(alice); + vm.startPrank(ALICE); IERC20(collateralAsset).approve(address(pool), collateralAmount); - IPool(pool).supply(collateralAsset, collateralAmount, alice, 0); + IPool(pool).supply(collateralAsset, collateralAmount, ALICE, 0); ICreditDelegationToken(debtToken).approveDelegation( address(tokenWrapper), @@ -782,7 +781,7 @@ abstract contract BaseTokenWrapperTest is Test { tokenWrapper.borrowToken(borrowAmount, 0); uint256 borrowedAmount = tokenWrapper.getTokenInForTokenOut(borrowAmount); assertEq( - IERC20(tokenWrapper.TOKEN_IN()).balanceOf(address(alice)), + IERC20(tokenWrapper.TOKEN_IN()).balanceOf(address(ALICE)), borrowedAmount ); } else { @@ -792,6 +791,34 @@ abstract contract BaseTokenWrapperTest is Test { vm.stopPrank(); } + function testBorrowTokenZeroAmount() public { + uint256 collateralAmount = 1000e18; + uint256 borrowAmount = 0; + address debtToken = IPool(pool) + .getReserveData(tokenWrapper.TOKEN_OUT()) + .variableDebtTokenAddress; + + deal(collateralAsset, ALICE, collateralAmount); + + vm.startPrank(ALICE); + + IERC20(collateralAsset).approve(address(pool), collateralAmount); + IPool(pool).supply(collateralAsset, collateralAmount, ALICE, 0); + + ICreditDelegationToken(debtToken).approveDelegation( + address(tokenWrapper), + borrowAmount + ); + + if (borrowSupported) { + vm.expectRevert('INSUFFICIENT_AMOUNT_TO_BORROW'); + tokenWrapper.borrowToken(borrowAmount, 0); + } else { + vm.expectRevert(); + tokenWrapper.borrowToken(borrowAmount, 0); + } + } + function testBorrowTokenWithPermit() public { uint256 borrowAmount = 100e18; uint256 collateralAmount = 1000e18; @@ -806,7 +833,6 @@ abstract contract BaseTokenWrapperTest is Test { vm.startPrank(alice); IERC20(collateralAsset).approve(address(pool), collateralAmount); - IPool(pool).supply(collateralAsset, collateralAmount, alice, 0); uint256 deadline = block.timestamp + 1 hours; @@ -853,7 +879,6 @@ abstract contract BaseTokenWrapperTest is Test { vm.startPrank(alice); IERC20(collateralAsset).approve(address(pool), collateralAmount); - IPool(pool).supply(collateralAsset, collateralAmount, alice, 0); uint256 deadline = block.timestamp + 1 hours; @@ -878,6 +903,129 @@ abstract contract BaseTokenWrapperTest is Test { } } + function testBorrowTokenWithPermitDeadlineExpired() public { + uint256 borrowAmount = 100e18; + uint256 collateralAmount = 1000e18; + + (address alice, uint256 userPrivateKey) = makeAddrAndKey('ALICE'); + deal(collateralAsset, alice, collateralAmount); + + address debtToken = IPool(pool) + .getReserveData(tokenWrapper.TOKEN_OUT()) + .variableDebtTokenAddress; + + vm.startPrank(alice); + + IERC20(collateralAsset).approve(address(pool), collateralAmount); + IPool(pool).supply(collateralAsset, collateralAmount, alice, 0); + + uint256 deadline = block.timestamp - 1; + uint256 nonce = IAToken(debtToken).nonces(alice); + + (uint8 v, bytes32 r, bytes32 s) = _signCreditDelegation( + userPrivateKey, + address(tokenWrapper), + borrowAmount, + nonce, + deadline, + debtToken + ); + IBaseTokenWrapper.PermitSignature memory signature = IBaseTokenWrapper + .PermitSignature({deadline: deadline, v: v, r: r, s: s}); + + if (borrowSupported) { + vm.expectRevert(); + tokenWrapper.borrowTokenWithPermit(borrowAmount, 1, signature); + } else { + vm.expectRevert(); + tokenWrapper.borrowTokenWithPermit(borrowAmount, 1, signature); + } + } + + function testBorrowTokenMoreThanUnwrappableInVault() public { + address vault = tokenWrapper.TOKEN_OUT(); + uint256 vaultUnderlying = IERC20(tokenWrapper.TOKEN_IN()).balanceOf(vault); + uint256 borrowAmount = tokenWrapper.getTokenOutForTokenIn(vaultUnderlying) * + 2; + uint256 collateralAmount = 1000e18; + + deal(collateralAsset, ALICE, collateralAmount); + + address debtToken = IPool(pool) + .getReserveData(tokenWrapper.TOKEN_OUT()) + .variableDebtTokenAddress; + + vm.startPrank(ALICE); + + IERC20(collateralAsset).approve(address(pool), collateralAmount); + IPool(pool).supply(collateralAsset, collateralAmount, ALICE, 0); + + ICreditDelegationToken(debtToken).approveDelegation( + address(tokenWrapper), + borrowAmount + ); + + if (borrowSupported) { + vm.expectRevert(); + tokenWrapper.borrowToken(borrowAmount, 0); + } else { + vm.expectRevert(); + tokenWrapper.borrowToken(borrowAmount, 0); + } + } + + function testBorrowTokenInsufficientCollateral() public { + uint256 borrowAmount = 100e18; + + address debtToken = IPool(pool) + .getReserveData(tokenWrapper.TOKEN_OUT()) + .variableDebtTokenAddress; + + vm.startPrank(ALICE); + + ICreditDelegationToken(debtToken).approveDelegation( + address(tokenWrapper), + borrowAmount + ); + + if (borrowSupported) { + vm.expectRevert(); + tokenWrapper.borrowToken(borrowAmount, 0); + } else { + vm.expectRevert(); + tokenWrapper.borrowToken(borrowAmount, 0); + } + } + + function testBorrowTokenInsufficientDelegation() public { + uint256 borrowAmount = 100e18; + uint256 collateralAmount = 1000e18; + + address debtToken = IPool(pool) + .getReserveData(tokenWrapper.TOKEN_OUT()) + .variableDebtTokenAddress; + + deal(collateralAsset, ALICE, collateralAmount); + + vm.startPrank(ALICE); + + IERC20(collateralAsset).approve(address(pool), collateralAmount); + IPool(pool).supply(collateralAsset, collateralAmount, ALICE, 0); + + ICreditDelegationToken(debtToken).approveDelegation( + address(tokenWrapper), + borrowAmount - 1 + ); + + if (borrowSupported) { + vm.expectRevert(); + tokenWrapper.borrowToken(borrowAmount, 0); + } else { + vm.expectRevert(); + tokenWrapper.borrowToken(borrowAmount, 0); + } + } + function testFuzzBorrowToken(uint256 borrowAmount) public { borrowAmount = bound(borrowAmount, 1, MAX_DEAL_AMOUNT); borrowAmount *= 10 ** tokenInDecimals; @@ -887,13 +1035,12 @@ abstract contract BaseTokenWrapperTest is Test { .getReserveData(tokenWrapper.TOKEN_OUT()) .variableDebtTokenAddress; - address alice = makeAddr('ALICE'); - deal(collateralAsset, alice, collateralAmount); + deal(collateralAsset, ALICE, collateralAmount); - vm.startPrank(alice); + vm.startPrank(ALICE); IERC20(collateralAsset).approve(address(pool), collateralAmount); - IPool(pool).supply(collateralAsset, collateralAmount, alice, 0); + IPool(pool).supply(collateralAsset, collateralAmount, ALICE, 0); ICreditDelegationToken(debtToken).approveDelegation( address(tokenWrapper), @@ -904,7 +1051,7 @@ abstract contract BaseTokenWrapperTest is Test { tokenWrapper.borrowToken(borrowAmount, 0); uint256 borrowedAmount = tokenWrapper.getTokenInForTokenOut(borrowAmount); assertEq( - IERC20(tokenWrapper.TOKEN_IN()).balanceOf(address(alice)), + IERC20(tokenWrapper.TOKEN_IN()).balanceOf(ALICE), borrowedAmount ); } else {