Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add USDS-SUSDS token wrappers #13

Open
wants to merge 29 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 8 commits
Commits
Show all changes
29 commits
Select commit Hold shift + click to select a range
3fb0e1b
init
foodaka Sep 17, 2024
fb66cf8
fork execution
foodaka Sep 18, 2024
17ed1e5
cleanup
foodaka Sep 18, 2024
50f3eb7
test: Don't assume 0 ETH balance on wrapper contracts
CheyenneAtapour Sep 18, 2024
d3784d0
Fix TokenBorrow for SUSDS (#15)
CheyenneAtapour Sep 19, 2024
df5dd2b
feat: Move borrow logic to base wrapper
CheyenneAtapour Sep 20, 2024
0f69b1c
test: Add borrow test to SDAI wrapper. Lock blocknumbers
CheyenneAtapour Sep 20, 2024
e22cbd3
test: Add borrow token test for steth
CheyenneAtapour Sep 20, 2024
340414f
pr comments
foodaka Sep 24, 2024
a2dec9e
updated files
foodaka Sep 24, 2024
00b6ed4
move borrow as virtual on base
foodaka Sep 24, 2024
0163386
fix: Fuzz test, rename test, reorder fns per convention
CheyenneAtapour Sep 25, 2024
2cce3f7
borrow not permitted
foodaka Sep 25, 2024
0db3322
Merge branch 'feat/susds-wrappers' of github.com:aave/token-wrappers …
foodaka Sep 25, 2024
6915b96
borrow with sig
foodaka Sep 25, 2024
c24b358
fix: Move borrow logic to internal function
CheyenneAtapour Sep 25, 2024
1ab43a0
fix: Address pr comments
CheyenneAtapour Sep 26, 2024
4f5f382
fix: Move borrow logic to base wrapper
CheyenneAtapour Sep 27, 2024
1886062
fix: Move dependencies to V3 origin (#16)
miguelmtzinf Sep 30, 2024
9d654dd
fix: Remove unlimited allowance (#17)
miguelmtzinf Oct 1, 2024
253000b
feat: Generic ERC4626 <> ERC20 Token Wrapper (#20)
CheyenneAtapour Oct 3, 2024
53c52bd
fix: Remove unneccesary file
miguelmtzinf Oct 3, 2024
37e17c4
fix: Disables supply with permit for DAI because DAI permit is not st…
miguelmtzinf Oct 3, 2024
a10494c
fix: Re-order functions based on solidity guideliens
miguelmtzinf Oct 3, 2024
11c19c8
fix: Remove solc warnings
miguelmtzinf Oct 3, 2024
596d521
Refactors tests (#21)
miguelmtzinf Oct 3, 2024
86fb6e2
test: Add fuzz test for borrow
CheyenneAtapour Oct 4, 2024
9118be9
fix: Revert change test name
CheyenneAtapour Oct 4, 2024
13224ae
Add Additional BorrowToken Tests (#22)
CheyenneAtapour Oct 7, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions foundry.toml
Original file line number Diff line number Diff line change
Expand Up @@ -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
8 changes: 8 additions & 0 deletions src/BaseTokenWrapper.sol
Original file line number Diff line number Diff line change
Expand Up @@ -103,6 +103,14 @@ abstract contract BaseTokenWrapper is Ownable, IBaseTokenWrapper {
return _withdrawToken(amount, to, aTokenOut);
}

/// @inheritdoc IBaseTokenWrapper
function borrowToken(uint256 amount, address to) external virtual {
miguelmtzinf marked this conversation as resolved.
Show resolved Hide resolved
require(amount > 0, 'INSUFFICIENT_AMOUNT_TO_BORROW');
POOL.borrow(TOKEN_OUT, amount, 2, 0, address(to));
miguelmtzinf marked this conversation as resolved.
Show resolved Hide resolved
uint256 amountIn = _unwrapTokenOut(amount);
miguelmtzinf marked this conversation as resolved.
Show resolved Hide resolved
IERC20(TOKEN_IN).transfer(to, amountIn);
}

/// @inheritdoc IBaseTokenWrapper
function rescueTokens(
IERC20 token,
Expand Down
54 changes: 54 additions & 0 deletions src/SavingsSuSDSTokenWrapper.sol
miguelmtzinf marked this conversation as resolved.
Show resolved Hide resolved
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
// 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';
import {BaseTokenWrapper} from './BaseTokenWrapper.sol';

/**
* @title SavingsSuSDSTokenWrapper
* @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 {
/**
* @dev Constructor
* @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
*/
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));
}
}
7 changes: 7 additions & 0 deletions src/interfaces/IBaseTokenWrapper.sol
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
49 changes: 49 additions & 0 deletions src/interfaces/ICreditDelegationToken.sol
Original file line number Diff line number Diff line change
@@ -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;
}
145 changes: 145 additions & 0 deletions src/interfaces/IUSDS.sol
miguelmtzinf marked this conversation as resolved.
Show resolved Hide resolved
Original file line number Diff line number Diff line change
@@ -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);
}
2 changes: 2 additions & 0 deletions test/BaseTokenWrapper.t.sol
Original file line number Diff line number Diff line change
Expand Up @@ -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),
Expand Down Expand Up @@ -638,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,
Expand Down
45 changes: 44 additions & 1 deletion test/SavingsDaiTokenWrapper.t.sol
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -41,4 +48,40 @@ contract SavingsDaiTokenWrapperTest is BaseTokenWrapperTest {
'Unexpected TOKEN_IN allowance'
);
}

function testBorrow() public {
miguelmtzinf marked this conversation as resolved.
Show resolved Hide resolved
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
);
}
}
Loading