mirror of
https://github.com/Instadapp/aave-protocol-v2.git
synced 2024-07-29 21:47:30 +00:00
- Initial version of StaticAToken
This commit is contained in:
parent
2708551bcf
commit
28cb41b8ca
contracts
|
@ -85,4 +85,6 @@ interface IAToken is IERC20, IScaledBalanceToken {
|
|||
* @return The amount transferred
|
||||
**/
|
||||
function transferUnderlyingTo(address user, uint256 amount) external returns (uint256);
|
||||
|
||||
function UNDERLYING_ASSET_ADDRESS() external returns (address);
|
||||
}
|
||||
|
|
|
@ -27,7 +27,7 @@ contract AToken is VersionedInitializable, IncentivizedERC20, IAToken {
|
|||
|
||||
uint256 public constant UINT_MAX_VALUE = uint256(-1);
|
||||
uint256 public constant ATOKEN_REVISION = 0x1;
|
||||
address public immutable UNDERLYING_ASSET_ADDRESS;
|
||||
address public immutable override UNDERLYING_ASSET_ADDRESS;
|
||||
address public immutable RESERVE_TREASURY_ADDRESS;
|
||||
ILendingPool public immutable POOL;
|
||||
|
||||
|
|
333
contracts/protocol/tokenization/StaticAToken.sol
Normal file
333
contracts/protocol/tokenization/StaticAToken.sol
Normal file
|
@ -0,0 +1,333 @@
|
|||
// SPDX-License-Identifier: agpl-3.0
|
||||
pragma solidity 0.6.12;
|
||||
|
||||
import {ILendingPool} from '../../interfaces/ILendingPool.sol';
|
||||
import {IERC20} from '../../dependencies/openzeppelin/contracts/IERC20.sol';
|
||||
import {IAToken} from '../../interfaces/IAToken.sol';
|
||||
import {ERC20} from '../../dependencies/openzeppelin/contracts/ERC20.sol';
|
||||
import {SafeERC20} from '../../dependencies/openzeppelin/contracts/SafeERC20.sol';
|
||||
import {WadRayMath} from '../../protocol/libraries/math/WadRayMath.sol';
|
||||
|
||||
/**
|
||||
* @title StaticAToken
|
||||
* @dev Wrapper token that allows to deposit tokens on the Aave protocol and receive
|
||||
* a token which balance doesn't increase automatically, but uses an ever-increasing exchange rate
|
||||
* - Only supporting deposits and withdrawals
|
||||
* @author Aave
|
||||
**/
|
||||
contract StaticAToken is ERC20 {
|
||||
using SafeERC20 for IERC20;
|
||||
using WadRayMath for uint256;
|
||||
|
||||
bytes public constant EIP712_REVISION = bytes('1');
|
||||
bytes32 internal constant EIP712_DOMAIN =
|
||||
keccak256('EIP712Domain(string name,string version,uint256 chainId,address verifyingContract)');
|
||||
bytes32 public constant PERMIT_TYPEHASH =
|
||||
keccak256('Permit(address owner,address spender,uint256 value,uint256 nonce,uint256 deadline)');
|
||||
bytes32 public constant METADEPOSIT_TYPEHASH =
|
||||
keccak256(
|
||||
'Deposit(address depositor,address recipient,uint256 value,uint16 referralCode,bool fromUnderlying,uint256 nonce,uint256 deadline)'
|
||||
);
|
||||
bytes32 public constant METAWITHDRAWAL_TYPEHASH =
|
||||
keccak256(
|
||||
'Withdraw(address owner,address recipient,uint256 value, bool toUnderlying, uint256 nonce,uint256 deadline)'
|
||||
);
|
||||
|
||||
ILendingPool public immutable LENDING_POOL;
|
||||
IERC20 public immutable ATOKEN;
|
||||
IERC20 public immutable ASSET;
|
||||
|
||||
/// @dev owner => next valid nonce to submit with permit(), metaDeposit() and metaWithdraw()
|
||||
/// We choose to have sequentiality on them for each user to avoid potentially dangerous/bad UX cases
|
||||
mapping(address => uint256) public _nonces;
|
||||
|
||||
constructor(
|
||||
ILendingPool lendingPool,
|
||||
address aToken,
|
||||
string memory wrappedTokenName,
|
||||
string memory wrappedTokenSymbol
|
||||
) public ERC20(wrappedTokenName, wrappedTokenSymbol) {
|
||||
LENDING_POOL = lendingPool;
|
||||
ATOKEN = IERC20(aToken);
|
||||
|
||||
IERC20 underlyingAsset = IERC20(IAToken(aToken).UNDERLYING_ASSET_ADDRESS());
|
||||
ASSET = underlyingAsset;
|
||||
underlyingAsset.approve(address(lendingPool), type(uint256).max);
|
||||
}
|
||||
|
||||
/**
|
||||
* @dev Deposits `ASSET` in the Aave protocol and mints static aTokens to msg.sender
|
||||
* @param recipient The address that will receive the static aTokens
|
||||
* @param amount The amount of underlying `ASSET` to deposit (e.g. deposit of 100 USDC)
|
||||
* @param referralCode Code used to register the integrator originating the operation, for potential rewards.
|
||||
* 0 if the action is executed directly by the user, without any middle-man
|
||||
* @param fromUnderlying bool
|
||||
* - `true` if the msg.sender comes with underlying tokens (e.g. USDC)
|
||||
* - `false` if the msg.sender comes already with aTokens (e.g. aUSDC)
|
||||
**/
|
||||
function deposit(
|
||||
address recipient,
|
||||
uint256 amount,
|
||||
uint16 referralCode,
|
||||
bool fromUnderlying
|
||||
) external {
|
||||
_deposit(msg.sender, recipient, amount, referralCode, fromUnderlying);
|
||||
}
|
||||
|
||||
/**
|
||||
* @dev Burns `amount` of static aToken, with recipient receiving the corresponding amount of `ASSET`
|
||||
* @param recipient The address that will receive the amount of `ASSET` withdrawn from the Aave protocol
|
||||
* @param amount The amount of static aToken to burn
|
||||
* @param toUnderlying bool
|
||||
* - `true` for the recipient to get underlying tokens (e.g. USDC)
|
||||
* - `false` for the recipient to get aTokens (e.g. aUSDC)
|
||||
**/
|
||||
function withdraw(
|
||||
address recipient,
|
||||
uint256 amount,
|
||||
bool toUnderlying
|
||||
) external {
|
||||
_withdraw(msg.sender, recipient, amount, toUnderlying);
|
||||
}
|
||||
|
||||
/**
|
||||
* @dev Implements the permit function as for
|
||||
* https://github.com/ethereum/EIPs/blob/8a34d644aacf0f9f8f00815307fd7dd5da07655f/EIPS/eip-2612.md
|
||||
* @param owner The owner of the funds
|
||||
* @param spender The spender
|
||||
* @param value The amount
|
||||
* @param deadline The deadline timestamp, type(uint256).max for max deadline
|
||||
* @param v Signature param
|
||||
* @param s Signature param
|
||||
* @param r Signature param
|
||||
* @param chainId Passing the chainId in order to be fork-compatible
|
||||
*/
|
||||
function permit(
|
||||
address owner,
|
||||
address spender,
|
||||
uint256 value,
|
||||
uint256 deadline,
|
||||
uint8 v,
|
||||
bytes32 r,
|
||||
bytes32 s,
|
||||
uint256 chainId
|
||||
) external {
|
||||
require(owner != address(0), 'INVALID_OWNER');
|
||||
//solium-disable-next-line
|
||||
require(block.timestamp <= deadline, 'INVALID_EXPIRATION');
|
||||
uint256 currentValidNonce = _nonces[owner];
|
||||
bytes32 digest =
|
||||
keccak256(
|
||||
abi.encodePacked(
|
||||
'\x19\x01',
|
||||
getDomainSeparator(chainId),
|
||||
keccak256(abi.encode(PERMIT_TYPEHASH, owner, spender, value, currentValidNonce, deadline))
|
||||
)
|
||||
);
|
||||
require(owner == ecrecover(digest, v, r, s), 'INVALID_SIGNATURE');
|
||||
_nonces[owner] = currentValidNonce.add(1);
|
||||
_approve(owner, spender, value);
|
||||
}
|
||||
|
||||
/**
|
||||
* @dev Allows to deposit on Aave via meta-transaction
|
||||
* https://github.com/ethereum/EIPs/blob/8a34d644aacf0f9f8f00815307fd7dd5da07655f/EIPS/eip-2612.md
|
||||
* @param depositor Address from which the funds to deposit are going to be pulled
|
||||
* @param recipient Address that will receive the staticATokens, in the average case, same as the `depositor`
|
||||
* @param value The amount to deposit
|
||||
* @param referralCode Code used to register the integrator originating the operation, for potential rewards.
|
||||
* 0 if the action is executed directly by the user, without any middle-man
|
||||
* @param fromUnderlying bool
|
||||
* - `true` if the msg.sender comes with underlying tokens (e.g. USDC)
|
||||
* - `false` if the msg.sender comes already with aTokens (e.g. aUSDC)
|
||||
* @param deadline The deadline timestamp, type(uint256).max for max deadline
|
||||
* @param v Signature param
|
||||
* @param s Signature param
|
||||
* @param r Signature param
|
||||
* @param chainId Passing the chainId in order to be fork-compatible
|
||||
*/
|
||||
function metaDeposit(
|
||||
address depositor,
|
||||
address recipient,
|
||||
uint256 value,
|
||||
uint16 referralCode,
|
||||
bool fromUnderlying,
|
||||
uint256 deadline,
|
||||
uint8 v,
|
||||
bytes32 r,
|
||||
bytes32 s,
|
||||
uint256 chainId
|
||||
) external {
|
||||
require(depositor != address(0), 'INVALID_DEPOSITOR');
|
||||
//solium-disable-next-line
|
||||
require(block.timestamp <= deadline, 'INVALID_EXPIRATION');
|
||||
uint256 currentValidNonce = _nonces[depositor];
|
||||
bytes32 digest =
|
||||
keccak256(
|
||||
abi.encodePacked(
|
||||
'\x19\x01',
|
||||
getDomainSeparator(chainId),
|
||||
keccak256(
|
||||
abi.encode(
|
||||
METADEPOSIT_TYPEHASH,
|
||||
depositor,
|
||||
recipient,
|
||||
value,
|
||||
referralCode,
|
||||
fromUnderlying,
|
||||
currentValidNonce,
|
||||
deadline
|
||||
)
|
||||
)
|
||||
)
|
||||
);
|
||||
require(depositor == ecrecover(digest, v, r, s), 'INVALID_SIGNATURE');
|
||||
_nonces[depositor] = currentValidNonce.add(1);
|
||||
_deposit(depositor, recipient, value, referralCode, fromUnderlying);
|
||||
}
|
||||
|
||||
/**
|
||||
* @dev Allows to withdraw from Aave via meta-transaction
|
||||
* https://github.com/ethereum/EIPs/blob/8a34d644aacf0f9f8f00815307fd7dd5da07655f/EIPS/eip-2612.md
|
||||
* @param owner Address owning the staticATokens
|
||||
* @param recipient Address that will receive the underlying withdrawn from Aave
|
||||
* @param value The amount of staticAToken to withdraw
|
||||
* @param toUnderlying bool
|
||||
* - `true` for the recipient to get underlying tokens (e.g. USDC)
|
||||
* - `false` for the recipient to get aTokens (e.g. aUSDC)
|
||||
* @param deadline The deadline timestamp, type(uint256).max for max deadline
|
||||
* @param v Signature param
|
||||
* @param s Signature param
|
||||
* @param r Signature param
|
||||
* @param chainId Passing the chainId in order to be fork-compatible
|
||||
*/
|
||||
function metaWithdraw(
|
||||
address owner,
|
||||
address recipient,
|
||||
uint256 value,
|
||||
bool toUnderlying,
|
||||
uint256 deadline,
|
||||
uint8 v,
|
||||
bytes32 r,
|
||||
bytes32 s,
|
||||
uint256 chainId
|
||||
) external {
|
||||
require(owner != address(0), 'INVALID_DEPOSITOR');
|
||||
//solium-disable-next-line
|
||||
require(block.timestamp <= deadline, 'INVALID_EXPIRATION');
|
||||
uint256 currentValidNonce = _nonces[owner];
|
||||
bytes32 digest =
|
||||
keccak256(
|
||||
abi.encodePacked(
|
||||
'\x19\x01',
|
||||
getDomainSeparator(chainId),
|
||||
keccak256(
|
||||
abi.encode(
|
||||
METAWITHDRAWAL_TYPEHASH,
|
||||
owner,
|
||||
recipient,
|
||||
value,
|
||||
toUnderlying,
|
||||
currentValidNonce,
|
||||
deadline
|
||||
)
|
||||
)
|
||||
)
|
||||
);
|
||||
require(owner == ecrecover(digest, v, r, s), 'INVALID_SIGNATURE');
|
||||
_nonces[owner] = currentValidNonce.add(1);
|
||||
_withdraw(owner, recipient, value, toUnderlying);
|
||||
}
|
||||
|
||||
/**
|
||||
* @dev Utility method to get the current aToken balance of an user, from his staticAToken balance
|
||||
* @param account The address of the user
|
||||
* @return uint256 The aToken balance
|
||||
**/
|
||||
function dynamicBalanceOf(address account) external view returns (uint256) {
|
||||
return staticToDynamicAmount(balanceOf(account));
|
||||
}
|
||||
|
||||
/**
|
||||
* @dev Converts a static amount (scaled balance on aToken) to the aToken/underlying value,
|
||||
* using the current liquidity index on Aave
|
||||
* @param amount The amount to convert from
|
||||
* @return uint256 The dynamic amount
|
||||
**/
|
||||
function staticToDynamicAmount(uint256 amount) public view returns (uint256) {
|
||||
return amount.rayMul(rate());
|
||||
}
|
||||
|
||||
/**
|
||||
* @dev Converts an aToken or underlying amount to the what it is denominated on the aToken as
|
||||
* scaled balance, function of the principal and the liquidity index
|
||||
* @param amount The amount to convert from
|
||||
* @return uint256 The static (scaled) amount
|
||||
**/
|
||||
function dynamicToStaticAmount(uint256 amount) public view returns (uint256) {
|
||||
return amount.rayDiv(rate());
|
||||
}
|
||||
|
||||
/**
|
||||
* @dev Returns the Aave liquidity index of the underlying aToken, denominated rate here
|
||||
* as it can be considered as an ever-increasing exchange rate
|
||||
* @return bytes32 The domain separator
|
||||
**/
|
||||
function rate() public view returns (uint256) {
|
||||
return LENDING_POOL.getReserveNormalizedIncome(address(ASSET));
|
||||
}
|
||||
|
||||
/**
|
||||
* @dev Function to return a dynamic domain separator, in order to be compatible with forks changing chainId
|
||||
* @param chainId The chain id
|
||||
* @return bytes32 The domain separator
|
||||
**/
|
||||
function getDomainSeparator(uint256 chainId) public view returns (bytes32) {
|
||||
return
|
||||
keccak256(
|
||||
abi.encode(
|
||||
EIP712_DOMAIN,
|
||||
keccak256(bytes(name())),
|
||||
keccak256(EIP712_REVISION),
|
||||
chainId,
|
||||
address(this)
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
function _deposit(
|
||||
address depositor,
|
||||
address recipient,
|
||||
uint256 amount,
|
||||
uint16 referralCode,
|
||||
bool fromUnderlying
|
||||
) internal {
|
||||
require(recipient != address(0), 'INVALID_RECIPIENT');
|
||||
|
||||
if (fromUnderlying) {
|
||||
ASSET.safeTransferFrom(depositor, address(this), amount);
|
||||
LENDING_POOL.deposit(address(ASSET), amount, recipient, referralCode);
|
||||
} else {
|
||||
ATOKEN.safeTransferFrom(depositor, address(this), amount);
|
||||
}
|
||||
|
||||
_mint(recipient, dynamicToStaticAmount(amount));
|
||||
}
|
||||
|
||||
function _withdraw(
|
||||
address owner,
|
||||
address recipient,
|
||||
uint256 amount,
|
||||
bool toUnderlying
|
||||
) internal {
|
||||
require(recipient != address(0), 'INVALID_RECIPIENT');
|
||||
|
||||
_burn(owner, amount);
|
||||
|
||||
if (toUnderlying) {
|
||||
LENDING_POOL.withdraw(address(ASSET), staticToDynamicAmount(amount), recipient);
|
||||
} else {
|
||||
ATOKEN.safeTransfer(recipient, staticToDynamicAmount(amount));
|
||||
}
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue
Block a user