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

Generic ERC-20 token wrapper, sDai support #182

Closed
wants to merge 1 commit into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
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
182 changes: 182 additions & 0 deletions contracts/misc/token-wrapper/BaseTokenWrapper.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,182 @@
// SPDX-License-Identifier: AGPL-3.0
pragma solidity ^0.8.10;

import {Ownable} from '@aave/core-v3/contracts/dependencies/openzeppelin/contracts/Ownable.sol';
import {IERC20} from '@aave/core-v3/contracts/dependencies/openzeppelin/contracts/IERC20.sol';
import {IERC20WithPermit} from '@aave/core-v3/contracts/interfaces/IERC20WithPermit.sol';
import {GPv2SafeERC20} from '@aave/core-v3/contracts/dependencies/gnosis/contracts/GPv2SafeERC20.sol';
import {IPool} from '@aave/core-v3/contracts/interfaces/IPool.sol';
import {IAToken} from '@aave/core-v3/contracts/interfaces/IAToken.sol';

/**
* @title BaseTokenWrapper
* @author Aave
* @notice Base contract to enable intermediate wrap/unwrap of a token upon supply/withdraw from a Pool
*/
abstract contract BaseTokenWrapper is Ownable {
using GPv2SafeERC20 for IERC20;
using GPv2SafeERC20 for IERC20WithPermit;

struct PermitSignature {
uint256 deadline;
uint8 v;
bytes32 r;
bytes32 s;
}

address public immutable TOKEN_IN;
address public immutable TOKEN_OUT;
IPool public immutable POOL;

/**
* @dev Constructor
* @param tokenIn ERC-20 token that will be wrapped in supply operations
* @param tokenOut ERC-20 token received upon wrapping
* @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) {
TOKEN_IN = tokenIn;
TOKEN_OUT = tokenOut;
POOL = IPool(pool);
transferOwnership(owner);
IERC20(tokenOut).approve(address(pool), type(uint256).max);
}

/**
* @notice Converts amount of token to wrapped version and supplies to Pool
* @param amount The amount of the token to wrap and supply to the Pool
* @param onBehalfOf The address that will receive the aTokens
* @param referralCode Code used to register the integrator originating the operation, for potential rewards
*/
function supplyToken(uint256 amount, address onBehalfOf, uint16 referralCode) external {
_supplyToken(amount, onBehalfOf, referralCode);
}

/**
* @notice Converts amount of token to wrapped version and supplies to Pool, using permit for allowance
* @param amount The amount of the token to wrap and supply to the Pool
* @param onBehalfOf The address that will receive the aTokens
* @param referralCode Code used to register the integrator originating the operation, for potential rewards
* @param signature The EIP-712 signature data used for permit
*/
function supplyTokenWithPermit(
uint256 amount,
address onBehalfOf,
uint16 referralCode,
PermitSignature calldata signature
) external {
IERC20WithPermit(TOKEN_IN).permit(
msg.sender,
address(this),
amount,
signature.deadline,
signature.v,
signature.r,
signature.s
);
_supplyToken(amount, onBehalfOf, referralCode);
}

/**
* @notice Withdraws the wrapped token from the Pool and unwraps it, sending to the recipient
* @param amount The amount of the token to withdraw from the Pool and unwrap
* @param to The address that will receive the unwrapped token
*/
function withdrawToken(uint256 amount, address to) external {
IAToken aTokenOut = IAToken(POOL.getReserveData(TOKEN_OUT).aTokenAddress);
_withdrawToken(amount, to, aTokenOut);
}

/**
* @notice Withdraws the wrapped token from the Pool and unwraps it, sending to the recipient, using permit for allowance
* @param amount The amount of the token to withdraw from the Pool and unwrap
* @param to The address that will receive the unwrapped token
* @param signature The EIP-712 signature data used for permit
*/
function withdrawTokenWithPermit(
uint256 amount,
address to,
PermitSignature calldata signature
) external {
IAToken aTokenOut = IAToken(POOL.getReserveData(TOKEN_OUT).aTokenAddress);
aTokenOut.permit(
msg.sender,
address(this),
amount,
signature.deadline,
signature.v,
signature.r,
signature.s
);
_withdrawToken(amount, to, aTokenOut);
}

/**
* @notice Provides way for the contract owner to rescue ERC-20 tokens
* @param token The address of the token to withdraw from this contract
*/
function rescueTokens(IERC20 token) external onlyOwner {
token.safeTransfer(owner(), token.balanceOf(address(this)));
}

/**
* @notice Computes the amount of tokenOut received for a provided amount of tokenIn
* @param amount The amount of tokenIn
* @return The amount of tokenOut
*/
function getTokenOutForTokenIn(uint256 amount) external view virtual returns (uint256);

/**
* @notice Computes the amount of tokenIn received for a provided amount of tokenOut
* @param amount The amount of tokenOut
* @return The amount of tokenIn
*/
function getTokenInForTokenOut(uint256 amount) external view virtual returns (uint256);

/**
* @dev Helper to convert an amount of token to wrapped version and supplies to Pool
* @param amount The amount of the token to wrap and supply to the Pool
* @param onBehalfOf The address that will receive the aTokens
* @param referralCode Code used to register the integrator originating the operation, for potential rewards
*/
function _supplyToken(uint256 amount, address onBehalfOf, uint16 referralCode) internal {
require(amount > 0, 'INSUFFICIENT_AMOUNT_TO_SUPPLY');
IERC20(TOKEN_IN).safeTransferFrom(msg.sender, address(this), amount);
uint256 amountWrapped = _wrapTokenIn(amount);
require(amountWrapped > 0, 'INSUFFICIENT_WRAPPED_TOKEN_RECEIVED');
POOL.supply(TOKEN_OUT, amountWrapped, onBehalfOf, referralCode);
}

/**
* @notice Helper to withdraw the wrapped token from the Pool and unwraps it, sending to the recipient
* @param amount The amount of the token to withdraw from the Pool and unwrap
* @param to The address that will receive the unwrapped token
* @param aTokenOut The AToken that will be withdrawn from the Pool
*/
function _withdrawToken(uint256 amount, address to, IAToken aTokenOut) internal {
require(amount > 0, 'INSUFFICIENT_AMOUNT_TO_WITHDRAW');
if (amount == type(uint256).max) {
amount = aTokenOut.balanceOf(msg.sender);
}
aTokenOut.transferFrom(msg.sender, address(this), amount);
POOL.withdraw(TOKEN_OUT, amount, address(this));
uint256 amountUnwrapped = _unwrapTokenOut(amount);
require(amountUnwrapped > 0, 'INSUFFICIENT_UNWRAPPED_TOKEN_RECEIVED');
IERC20(TOKEN_IN).safeTransfer(to, amountUnwrapped);
}

/**
* @notice Helper to wrap an amount of tokenIn, receiving tokenOut
* @param amount The amount of tokenIn to wrap
* @return The amount of tokenOut received
*/
function _wrapTokenIn(uint256 amount) internal virtual returns (uint256);

/**
* @notice Helper to unwrap an amount of tokenOut, receiving tokenIn
* @param amount The amount of tokenOut to unwrap
* @return The amount of tokenIn received
*/
function _unwrapTokenOut(uint256 amount) internal virtual returns (uint256);
}
49 changes: 49 additions & 0 deletions contracts/misc/token-wrapper/SavingsDaiTokenWrapper.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
// SPDX-License-Identifier: AGPL-3.0
pragma solidity ^0.8.10;

import {IERC20} from '@aave/core-v3/contracts/dependencies/openzeppelin/contracts/IERC20.sol';
import {ISavingsDai} from './interfaces/ISavingsDai.sol';
import {BaseTokenWrapper} from './BaseTokenWrapper.sol';

/**
* @title SavingsDaiTokenWrapper
* @author Aave
* @notice Contract to wrap Dai to sDai on supply to Aave, or unwrap from sDai to Dai on withdrawal
*/
contract SavingsDaiTokenWrapper is BaseTokenWrapper {
/**
* @dev Constructor
* @param tokenIn Address for Dai
* @param tokenOut Address for sDai
* @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 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) {
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));
}
}
100 changes: 100 additions & 0 deletions contracts/misc/token-wrapper/interfaces/ISavingsDai.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,100 @@
// SPDX-License-Identifier: AGPL-3.0-or-later

// Copyright (C) 2021-2022 Dai Foundation
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see <https://www.gnu.org/licenses/>.

pragma solidity >=0.8.0;

interface ISavingsDai {
function totalSupply() external view returns (uint256);

function balanceOf(address) external view returns (uint256);

function allowance(address, address) external view returns (uint256);

function approve(address, uint256) external returns (bool);

function transfer(address, uint256) external returns (bool);

function transferFrom(address, address, uint256) external returns (bool);

function name() external view returns (string memory);

function symbol() external view returns (string memory);

function version() external view returns (string memory);

function decimals() external view returns (uint8);

function deploymentChainId() external view returns (uint256);

function PERMIT_TYPEHASH() external view returns (bytes32);

function DOMAIN_SEPARATOR() external view returns (bytes32);

function nonces(address) external view returns (uint256);

function vat() external view returns (address);

function daiJoin() external view returns (address);

function dai() external view returns (address);

function pot() external view returns (address);

function increaseAllowance(address, uint256) external returns (bool);

function decreaseAllowance(address, uint256) external returns (bool);

function asset() external view returns (address);

function totalAssets() external view returns (uint256);

function convertToShares(uint256) external view returns (uint256);

function convertToAssets(uint256) external view returns (uint256);

function maxDeposit(address) external view returns (uint256);

function previewDeposit(uint256) external view returns (uint256);

function deposit(uint256, address) external returns (uint256);

function deposit(uint256, address, uint16) external returns (uint256);

function maxMint(address) external view returns (uint256);

function previewMint(uint256) external view returns (uint256);

function mint(uint256, address) external returns (uint256);

function mint(uint256, address, uint16) external returns (uint256);

function maxWithdraw(address) external view returns (uint256);

function previewWithdraw(uint256) external view returns (uint256);

function withdraw(uint256, address, address) external returns (uint256);

function maxRedeem(address) external view returns (uint256);

function previewRedeem(uint256) external view returns (uint256);

function redeem(uint256, address, address) external returns (uint256);

function permit(address, address, uint256, uint256, bytes memory) external;

function permit(address, address, uint256, uint256, uint8, bytes32, bytes32) external;
}
Loading