mirror of
https://github.com/Instadapp/aave-protocol-v2.git
synced 2024-07-29 21:47:30 +00:00
Tested and Fixed contract
This commit is contained in:
parent
a3e11800a4
commit
314aa37de2
201
contracts/interfaces/IStaticAToken.sol
Normal file
201
contracts/interfaces/IStaticAToken.sol
Normal file
|
|
@ -0,0 +1,201 @@
|
|||
// SPDX-License-Identifier: agpl-3.0
|
||||
pragma solidity 0.6.12;
|
||||
pragma experimental ABIEncoderV2;
|
||||
|
||||
import {ILendingPool} from './ILendingPool.sol';
|
||||
import {IERC20} from '../dependencies/openzeppelin/contracts/IERC20.sol';
|
||||
|
||||
/**
|
||||
* @title IStaticAToken
|
||||
* @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
|
||||
**/
|
||||
interface IStaticAToken {
|
||||
struct SignatureParams {
|
||||
uint8 v;
|
||||
bytes32 r;
|
||||
bytes32 s;
|
||||
}
|
||||
|
||||
/**
|
||||
* @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)
|
||||
* @return uint256 The amount of StaticAToken minted, static balance
|
||||
**/
|
||||
function deposit(
|
||||
address recipient,
|
||||
uint256 amount,
|
||||
uint16 referralCode,
|
||||
bool fromUnderlying
|
||||
) external returns (uint256);
|
||||
|
||||
/**
|
||||
* @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 to withdraw, in static balance of StaticAToken
|
||||
* @param toUnderlying bool
|
||||
* - `true` for the recipient to get underlying tokens (e.g. USDC)
|
||||
* - `false` for the recipient to get aTokens (e.g. aUSDC)
|
||||
* @return amountToBurn: StaticATokens burnt, static balance
|
||||
* @return amountToWithdraw: underlying/aToken send to `recipient`, dynamic balance
|
||||
**/
|
||||
function withdraw(
|
||||
address recipient,
|
||||
uint256 amount,
|
||||
bool toUnderlying
|
||||
) external returns (uint256, uint256);
|
||||
|
||||
/**
|
||||
* @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 to withdraw, in dynamic balance of aToken/underlying asset
|
||||
* @param toUnderlying bool
|
||||
* - `true` for the recipient to get underlying tokens (e.g. USDC)
|
||||
* - `false` for the recipient to get aTokens (e.g. aUSDC)
|
||||
* @return amountToBurn: StaticATokens burnt, static balance
|
||||
* @return amountToWithdraw: underlying/aToken send to `recipient`, dynamic balance
|
||||
**/
|
||||
function withdrawDynamicAmount(
|
||||
address recipient,
|
||||
uint256 amount,
|
||||
bool toUnderlying
|
||||
) external returns (uint256, uint256);
|
||||
|
||||
/**
|
||||
* @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;
|
||||
|
||||
/**
|
||||
* @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 sigParams Signature params: v,r,s
|
||||
* @param chainId Passing the chainId in order to be fork-compatible
|
||||
* @return uint256 The amount of StaticAToken minted, static balance
|
||||
*/
|
||||
function metaDeposit(
|
||||
address depositor,
|
||||
address recipient,
|
||||
uint256 value,
|
||||
uint16 referralCode,
|
||||
bool fromUnderlying,
|
||||
uint256 deadline,
|
||||
SignatureParams calldata sigParams,
|
||||
uint256 chainId
|
||||
) external returns (uint256);
|
||||
|
||||
/**
|
||||
* @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 staticAmount The amount of staticAToken to withdraw. If > 0, `dynamicAmount` needs to be 0
|
||||
* @param dynamicAmount The amount of underlying/aToken to withdraw. If > 0, `staticAmount` needs to be 0
|
||||
* @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 sigParams Signature params: v,r,s
|
||||
* @param chainId Passing the chainId in order to be fork-compatible
|
||||
* @return amountToBurn: StaticATokens burnt, static balance
|
||||
* @return amountToWithdraw: underlying/aToken send to `recipient`, dynamic balance
|
||||
*/
|
||||
function metaWithdraw(
|
||||
address owner,
|
||||
address recipient,
|
||||
uint256 staticAmount,
|
||||
uint256 dynamicAmount,
|
||||
bool toUnderlying,
|
||||
uint256 deadline,
|
||||
SignatureParams calldata sigParams,
|
||||
uint256 chainId
|
||||
) external returns (uint256, uint256);
|
||||
|
||||
/**
|
||||
* @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);
|
||||
|
||||
/**
|
||||
* @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) external view returns (uint256);
|
||||
|
||||
/**
|
||||
* @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) external view returns (uint256);
|
||||
|
||||
/**
|
||||
* @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() external view returns (uint256);
|
||||
|
||||
/**
|
||||
* @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) external view returns (bytes32);
|
||||
|
||||
function LENDING_POOL() external pure returns (ILendingPool);
|
||||
|
||||
function ATOKEN() external pure returns (IERC20);
|
||||
|
||||
function ASSET() external pure returns (IERC20);
|
||||
|
||||
function EIP712_REVISION() external view returns (bytes memory);
|
||||
|
||||
function EIP712_DOMAIN() external view returns (bytes32);
|
||||
|
||||
function PERMIT_TYPEHASH() external view returns (bytes32);
|
||||
|
||||
function METADEPOSIT_TYPEHASH() external view returns (bytes32);
|
||||
|
||||
function METAWITHDRAWAL_TYPEHASH() external view returns (bytes32);
|
||||
}
|
||||
49
contracts/mocks/tokens/StaticATokenMetaTransactionMock.sol
Normal file
49
contracts/mocks/tokens/StaticATokenMetaTransactionMock.sol
Normal file
|
|
@ -0,0 +1,49 @@
|
|||
// SPDX-License-Identifier: agpl-3.0
|
||||
pragma solidity 0.6.12;
|
||||
pragma experimental ABIEncoderV2;
|
||||
|
||||
import {IAToken} from '../../interfaces/IAToken.sol';
|
||||
import {IERC20WithPermit} from '../../interfaces/IERC20WithPermit.sol';
|
||||
import {IStaticAToken} from '../../interfaces/IStaticAToken.sol';
|
||||
|
||||
contract StaticATokenMetaTransactionMock {
|
||||
function permitAndDeposit(
|
||||
IStaticAToken staticToken,
|
||||
address recipient,
|
||||
uint256 value,
|
||||
uint16 referralCode,
|
||||
bool fromUnderlying,
|
||||
uint256 deadline,
|
||||
IStaticAToken.SignatureParams calldata sigParamsPermit,
|
||||
IStaticAToken.SignatureParams calldata sigParamsDeposit,
|
||||
uint256 chainId
|
||||
) external returns (uint256) {
|
||||
// will throw if not permit underlying token
|
||||
try
|
||||
IERC20WithPermit(
|
||||
fromUnderlying ? address(staticToken.ASSET()) : address(staticToken.ATOKEN())
|
||||
)
|
||||
.permit(
|
||||
msg.sender,
|
||||
address(staticToken),
|
||||
value,
|
||||
deadline,
|
||||
sigParamsPermit.v,
|
||||
sigParamsPermit.r,
|
||||
sigParamsPermit.s
|
||||
)
|
||||
{} catch {
|
||||
require(false, 'UNDERLYING_TOKEN_NO_PERMIT');
|
||||
}
|
||||
staticToken.metaDeposit(
|
||||
msg.sender,
|
||||
recipient,
|
||||
value,
|
||||
referralCode,
|
||||
fromUnderlying,
|
||||
deadline,
|
||||
sigParamsDeposit,
|
||||
chainId
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
@ -3,6 +3,7 @@ pragma solidity 0.6.12;
|
|||
pragma experimental ABIEncoderV2;
|
||||
|
||||
import {ILendingPool} from '../../interfaces/ILendingPool.sol';
|
||||
import {IStaticAToken} from '../../interfaces/IStaticAToken.sol';
|
||||
import {IERC20} from '../../dependencies/openzeppelin/contracts/IERC20.sol';
|
||||
import {IAToken} from '../../interfaces/IAToken.sol';
|
||||
import {ERC20} from '../../dependencies/openzeppelin/contracts/ERC20.sol';
|
||||
|
|
@ -16,33 +17,27 @@ import {WadRayMath} from '../../protocol/libraries/math/WadRayMath.sol';
|
|||
* - Only supporting deposits and withdrawals
|
||||
* @author Aave
|
||||
**/
|
||||
contract StaticAToken is ERC20 {
|
||||
contract StaticAToken is IStaticAToken, ERC20 {
|
||||
using SafeERC20 for IERC20;
|
||||
using WadRayMath for uint256;
|
||||
|
||||
struct SignatureParams {
|
||||
uint8 v;
|
||||
bytes32 r;
|
||||
bytes32 s;
|
||||
}
|
||||
|
||||
bytes public constant EIP712_REVISION = bytes('1');
|
||||
bytes32 internal constant EIP712_DOMAIN =
|
||||
bytes public constant override EIP712_REVISION = bytes('1');
|
||||
bytes32 public constant override EIP712_DOMAIN =
|
||||
keccak256('EIP712Domain(string name,string version,uint256 chainId,address verifyingContract)');
|
||||
bytes32 public constant PERMIT_TYPEHASH =
|
||||
bytes32 public constant override PERMIT_TYPEHASH =
|
||||
keccak256('Permit(address owner,address spender,uint256 value,uint256 nonce,uint256 deadline)');
|
||||
bytes32 public constant METADEPOSIT_TYPEHASH =
|
||||
bytes32 public constant override METADEPOSIT_TYPEHASH =
|
||||
keccak256(
|
||||
'Deposit(address depositor,address recipient,uint256 value,uint16 referralCode,bool fromUnderlying,uint256 nonce,uint256 deadline)'
|
||||
);
|
||||
bytes32 public constant METAWITHDRAWAL_TYPEHASH =
|
||||
bytes32 public constant override METAWITHDRAWAL_TYPEHASH =
|
||||
keccak256(
|
||||
'Withdraw(address owner,address recipient,uint256 staticAmount, uint256 dynamicAmount, bool toUnderlying, uint256 nonce,uint256 deadline)'
|
||||
'Withdraw(address owner,address recipient,uint256 staticAmount,uint256 dynamicAmount,bool toUnderlying,uint256 nonce,uint256 deadline)'
|
||||
);
|
||||
|
||||
ILendingPool public immutable LENDING_POOL;
|
||||
IERC20 public immutable ATOKEN;
|
||||
IERC20 public immutable ASSET;
|
||||
ILendingPool public immutable override LENDING_POOL;
|
||||
IERC20 public immutable override ATOKEN;
|
||||
IERC20 public immutable override 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
|
||||
|
|
@ -78,7 +73,7 @@ contract StaticAToken is ERC20 {
|
|||
uint256 amount,
|
||||
uint16 referralCode,
|
||||
bool fromUnderlying
|
||||
) external returns (uint256) {
|
||||
) external override returns (uint256) {
|
||||
return _deposit(msg.sender, recipient, amount, referralCode, fromUnderlying);
|
||||
}
|
||||
|
||||
|
|
@ -96,7 +91,7 @@ contract StaticAToken is ERC20 {
|
|||
address recipient,
|
||||
uint256 amount,
|
||||
bool toUnderlying
|
||||
) external returns (uint256, uint256) {
|
||||
) external override returns (uint256, uint256) {
|
||||
return _withdraw(msg.sender, recipient, amount, 0, toUnderlying);
|
||||
}
|
||||
|
||||
|
|
@ -114,7 +109,7 @@ contract StaticAToken is ERC20 {
|
|||
address recipient,
|
||||
uint256 amount,
|
||||
bool toUnderlying
|
||||
) external returns (uint256, uint256) {
|
||||
) external override returns (uint256, uint256) {
|
||||
return _withdraw(msg.sender, recipient, 0, amount, toUnderlying);
|
||||
}
|
||||
|
||||
|
|
@ -139,7 +134,7 @@ contract StaticAToken is ERC20 {
|
|||
bytes32 r,
|
||||
bytes32 s,
|
||||
uint256 chainId
|
||||
) external {
|
||||
) external override {
|
||||
require(owner != address(0), 'INVALID_OWNER');
|
||||
//solium-disable-next-line
|
||||
require(block.timestamp <= deadline, 'INVALID_EXPIRATION');
|
||||
|
|
@ -182,7 +177,7 @@ contract StaticAToken is ERC20 {
|
|||
uint256 deadline,
|
||||
SignatureParams calldata sigParams,
|
||||
uint256 chainId
|
||||
) external returns (uint256) {
|
||||
) external override returns (uint256) {
|
||||
require(depositor != address(0), 'INVALID_DEPOSITOR');
|
||||
//solium-disable-next-line
|
||||
require(block.timestamp <= deadline, 'INVALID_EXPIRATION');
|
||||
|
|
@ -239,7 +234,7 @@ contract StaticAToken is ERC20 {
|
|||
uint256 deadline,
|
||||
SignatureParams calldata sigParams,
|
||||
uint256 chainId
|
||||
) external returns (uint256, uint256) {
|
||||
) external override returns (uint256, uint256) {
|
||||
require(owner != address(0), 'INVALID_DEPOSITOR');
|
||||
//solium-disable-next-line
|
||||
require(block.timestamp <= deadline, 'INVALID_EXPIRATION');
|
||||
|
|
@ -273,7 +268,7 @@ contract StaticAToken is ERC20 {
|
|||
* @param account The address of the user
|
||||
* @return uint256 The aToken balance
|
||||
**/
|
||||
function dynamicBalanceOf(address account) external view returns (uint256) {
|
||||
function dynamicBalanceOf(address account) external view override returns (uint256) {
|
||||
return staticToDynamicAmount(balanceOf(account));
|
||||
}
|
||||
|
||||
|
|
@ -283,7 +278,7 @@ contract StaticAToken is ERC20 {
|
|||
* @param amount The amount to convert from
|
||||
* @return uint256 The dynamic amount
|
||||
**/
|
||||
function staticToDynamicAmount(uint256 amount) public view returns (uint256) {
|
||||
function staticToDynamicAmount(uint256 amount) public view override returns (uint256) {
|
||||
return amount.rayMul(rate());
|
||||
}
|
||||
|
||||
|
|
@ -293,7 +288,7 @@ contract StaticAToken is ERC20 {
|
|||
* @param amount The amount to convert from
|
||||
* @return uint256 The static (scaled) amount
|
||||
**/
|
||||
function dynamicToStaticAmount(uint256 amount) public view returns (uint256) {
|
||||
function dynamicToStaticAmount(uint256 amount) public view override returns (uint256) {
|
||||
return amount.rayDiv(rate());
|
||||
}
|
||||
|
||||
|
|
@ -302,7 +297,7 @@ contract StaticAToken is ERC20 {
|
|||
* as it can be considered as an ever-increasing exchange rate
|
||||
* @return bytes32 The domain separator
|
||||
**/
|
||||
function rate() public view returns (uint256) {
|
||||
function rate() public view override returns (uint256) {
|
||||
return LENDING_POOL.getReserveNormalizedIncome(address(ASSET));
|
||||
}
|
||||
|
||||
|
|
@ -311,7 +306,7 @@ contract StaticAToken is ERC20 {
|
|||
* @param chainId The chain id
|
||||
* @return bytes32 The domain separator
|
||||
**/
|
||||
function getDomainSeparator(uint256 chainId) public view returns (bytes32) {
|
||||
function getDomainSeparator(uint256 chainId) public view override returns (bytes32) {
|
||||
return
|
||||
keccak256(
|
||||
abi.encode(
|
||||
|
|
|
|||
|
|
@ -26,7 +26,7 @@ const ETHERSCAN_KEY = process.env.ETHERSCAN_KEY || '';
|
|||
const MNEMONIC_PATH = "m/44'/60'/0'/0";
|
||||
const MNEMONIC = process.env.MNEMONIC || '';
|
||||
const MAINNET_FORK = process.env.MAINNET_FORK === 'true';
|
||||
|
||||
const FORKING_BLOCK = Number(process.env.FORKING_BLOCK || '11608298');
|
||||
// Prevent to load scripts before compilation and typechain
|
||||
if (!SKIP_LOAD) {
|
||||
['misc', 'migrations', 'dev', 'full', 'verifications', 'deployments', 'helpers'].forEach(
|
||||
|
|
@ -65,7 +65,7 @@ const getCommonNetworkConfig = (networkName: eEthereumNetwork, networkId: number
|
|||
|
||||
const mainnetFork = MAINNET_FORK
|
||||
? {
|
||||
blockNumber: 11608298,
|
||||
blockNumber: FORKING_BLOCK,
|
||||
url: ALCHEMY_KEY
|
||||
? `https://eth-mainnet.alchemyapi.io/v2/${ALCHEMY_KEY}`
|
||||
: `https://mainnet.infura.io/v3/${INFURA_KEY}`,
|
||||
|
|
|
|||
|
|
@ -225,6 +225,102 @@ export const buildPermitParams = (
|
|||
},
|
||||
});
|
||||
|
||||
export const buildMetaDepositParams = (
|
||||
chainId: number,
|
||||
token: tEthereumAddress,
|
||||
revision: string,
|
||||
tokenName: string,
|
||||
depositor: tEthereumAddress,
|
||||
recipient: tEthereumAddress,
|
||||
nonce: number,
|
||||
deadline: string,
|
||||
fromUnderlying: boolean,
|
||||
referralCode: number,
|
||||
value: tStringTokenSmallUnits
|
||||
) => ({
|
||||
types: {
|
||||
EIP712Domain: [
|
||||
{ name: 'name', type: 'string' },
|
||||
{ name: 'version', type: 'string' },
|
||||
{ name: 'chainId', type: 'uint256' },
|
||||
{ name: 'verifyingContract', type: 'address' },
|
||||
],
|
||||
Deposit: [
|
||||
{ name: 'depositor', type: 'address' },
|
||||
{ name: 'recipient', type: 'address' },
|
||||
{ name: 'value', type: 'uint256' },
|
||||
{ name: 'referralCode', type: 'uint16' },
|
||||
{ name: 'fromUnderlying', type: 'bool' },
|
||||
{ name: 'nonce', type: 'uint256' },
|
||||
{ name: 'deadline', type: 'uint256' },
|
||||
],
|
||||
},
|
||||
primaryType: 'Deposit' as const,
|
||||
domain: {
|
||||
name: tokenName,
|
||||
version: revision,
|
||||
chainId: chainId,
|
||||
verifyingContract: token,
|
||||
},
|
||||
message: {
|
||||
depositor,
|
||||
fromUnderlying,
|
||||
recipient,
|
||||
value,
|
||||
nonce,
|
||||
deadline,
|
||||
referralCode,
|
||||
},
|
||||
});
|
||||
|
||||
export const buildMetaWithdrawParams = (
|
||||
chainId: number,
|
||||
token: tEthereumAddress,
|
||||
revision: string,
|
||||
tokenName: string,
|
||||
owner: tEthereumAddress,
|
||||
recipient: tEthereumAddress,
|
||||
nonce: number,
|
||||
deadline: string,
|
||||
toUnderlying: boolean,
|
||||
staticAmount: tStringTokenSmallUnits,
|
||||
dynamicAmount: tStringTokenSmallUnits
|
||||
) => ({
|
||||
types: {
|
||||
EIP712Domain: [
|
||||
{ name: 'name', type: 'string' },
|
||||
{ name: 'version', type: 'string' },
|
||||
{ name: 'chainId', type: 'uint256' },
|
||||
{ name: 'verifyingContract', type: 'address' },
|
||||
],
|
||||
Withdraw: [
|
||||
{ name: 'owner', type: 'address' },
|
||||
{ name: 'recipient', type: 'address' },
|
||||
{ name: 'staticAmount', type: 'uint256' },
|
||||
{ name: 'dynamicAmount', type: 'uint256' },
|
||||
{ name: 'toUnderlying', type: 'bool' },
|
||||
{ name: 'nonce', type: 'uint256' },
|
||||
{ name: 'deadline', type: 'uint256' },
|
||||
],
|
||||
},
|
||||
primaryType: 'Withdraw' as const,
|
||||
domain: {
|
||||
name: tokenName,
|
||||
version: revision,
|
||||
chainId: chainId,
|
||||
verifyingContract: token,
|
||||
},
|
||||
message: {
|
||||
owner,
|
||||
toUnderlying,
|
||||
recipient,
|
||||
staticAmount,
|
||||
dynamicAmount,
|
||||
nonce,
|
||||
deadline,
|
||||
},
|
||||
});
|
||||
|
||||
export const getSignatureFromTypedData = (
|
||||
privateKey: string,
|
||||
typedData: any // TODO: should be TypedData, from eth-sig-utils, but TS doesn't accept it
|
||||
|
|
|
|||
|
|
@ -33,7 +33,7 @@
|
|||
"test-weth": "hardhat test test/__setup.spec.ts test/weth-gateway.spec.ts",
|
||||
"test-uniswap": "hardhat test test/__setup.spec.ts test/uniswapAdapters*.spec.ts",
|
||||
"test:main:check-list": "MAINNET_FORK=true TS_NODE_TRANSPILE_ONLY=1 hardhat test test/__setup.spec.ts test/mainnet/check-list.spec.ts",
|
||||
"test:main:staticAToken": "MAINNET_FORK=true TS_NODE_TRANSPILE_ONLY=1 hardhat test test/mainnet/static-atoken.spec.ts",
|
||||
"test:main:staticAToken": "MAINNET_FORK=true FORKING_BLOCK=12063148 TS_NODE_TRANSPILE_ONLY=1 hardhat test test/mainnet/static-atoken.spec.ts",
|
||||
"dev:coverage": "buidler compile --force && buidler coverage --network coverage",
|
||||
"aave:evm:dev:migration": "npm run compile && hardhat aave:dev",
|
||||
"aave:docker:full:migration": "npm run compile && npm run hardhat:docker -- aave:mainnet",
|
||||
|
|
|
|||
|
|
@ -1,5 +1,8 @@
|
|||
import rawDRE from 'hardhat';
|
||||
import BigNumber from 'bignumber.js';
|
||||
import chai, { expect } from 'chai';
|
||||
import bignumberChai from 'chai-bignumber';
|
||||
import { solidity } from 'ethereum-waffle';
|
||||
import {
|
||||
LendingPoolFactory,
|
||||
WETH9Factory,
|
||||
|
|
@ -7,14 +10,30 @@ import {
|
|||
ATokenFactory,
|
||||
ERC20,
|
||||
LendingPool,
|
||||
StaticATokenMetaTransactionMock,
|
||||
StaticATokenMetaTransactionMockFactory,
|
||||
WETH9,
|
||||
AToken,
|
||||
StaticAToken,
|
||||
} from '../../types';
|
||||
import {
|
||||
buildPermitParams,
|
||||
buildMetaDepositParams,
|
||||
buildMetaWithdrawParams,
|
||||
getSignatureFromTypedData,
|
||||
} from '../../helpers/contracts-helpers';
|
||||
import { impersonateAccountsHardhat, DRE, waitForTx } from '../../helpers/misc-utils';
|
||||
import { utils } from 'ethers';
|
||||
import { rayMul } from '../../helpers/ray-math';
|
||||
import { rayDiv, rayMul } from '../../helpers/ray-math';
|
||||
import { MAX_UINT_AMOUNT } from '../../helpers/constants';
|
||||
import { tEthereumAddress } from '../../helpers/types';
|
||||
import { JsonRpcSigner } from '@ethersproject/providers';
|
||||
import { SignerWithAddress } from '@nomiclabs/hardhat-ethers/dist/src/signer-with-address';
|
||||
import { parseEther } from '@ethersproject/units';
|
||||
import { parse } from 'path';
|
||||
|
||||
const { expect } = require('chai');
|
||||
chai.use(bignumberChai());
|
||||
chai.use(solidity);
|
||||
|
||||
const DEFAULT_GAS_LIMIT = 10000000;
|
||||
const DEFAULT_GAS_PRICE = utils.parseUnits('100', 'gwei');
|
||||
|
|
@ -30,6 +49,8 @@ const AWETH = '0x030bA81f1c18d280636F32af80b9AAd02Cf0854e';
|
|||
|
||||
const TEST_USERS = ['0x0F4ee9631f4be0a63756515141281A3E2B293Bbe'];
|
||||
|
||||
const AWETH_HOLDER = '0x928477dabc0eD2a6CE6c33966a52eA58CbDEA212';
|
||||
|
||||
type tBalancesInvolved = {
|
||||
aTokenBalanceStaticAToken: BigNumber;
|
||||
aTokenBalanceUser: BigNumber;
|
||||
|
|
@ -75,62 +96,74 @@ const getContext = async ({
|
|||
staticATokenSupply: new BigNumber((await staticAToken.totalSupply()).toString()),
|
||||
});
|
||||
|
||||
before(async () => {
|
||||
await rawDRE.run('set-DRE');
|
||||
const getInterestAccrued = (ctxBefore: tBalancesInvolved, ctxAfter: tBalancesInvolved) =>
|
||||
rayMul(
|
||||
rayDiv(ctxBefore.aTokenBalanceStaticAToken.toString(), ctxBefore.currentRate.toString()),
|
||||
ctxAfter.currentRate.toString()
|
||||
).minus(ctxBefore.aTokenBalanceStaticAToken.toString());
|
||||
|
||||
// Impersonations
|
||||
await impersonateAccountsHardhat([ETHER_BANK, ...TEST_USERS]);
|
||||
|
||||
const ethHolderSigner = DRE.ethers.provider.getSigner(ETHER_BANK);
|
||||
for (const recipientOfEth of [...TEST_USERS]) {
|
||||
await ethHolderSigner.sendTransaction({
|
||||
from: ethHolderSigner._address,
|
||||
to: recipientOfEth,
|
||||
value: utils.parseEther('100'),
|
||||
...defaultTxParams,
|
||||
});
|
||||
}
|
||||
|
||||
console.log('\n***************');
|
||||
console.log('Test setup finished');
|
||||
console.log('***************\n');
|
||||
});
|
||||
const getUserInterestAccrued = (ctxBefore: tBalancesInvolved, ctxAfter: tBalancesInvolved) =>
|
||||
rayMul(
|
||||
rayDiv(ctxBefore.aTokenBalanceUser.toString(), ctxBefore.currentRate.toString()),
|
||||
ctxAfter.currentRate.toString()
|
||||
).minus(ctxBefore.aTokenBalanceUser.toString());
|
||||
|
||||
describe('StaticAToken: aToken wrapper with static balances', () => {
|
||||
it('Deposit WETH on stataWETH, then withdraw of the whole balance in underlying', async () => {
|
||||
const userSigner = DRE.ethers.provider.getSigner(TEST_USERS[0]);
|
||||
let weth: WETH9;
|
||||
let lendingPool: LendingPool;
|
||||
let aweth: AToken;
|
||||
let user1Signer: JsonRpcSigner;
|
||||
let controlledPkSigner: SignerWithAddress;
|
||||
let staticAWeth: StaticAToken;
|
||||
before(async () => {
|
||||
await rawDRE.run('set-DRE');
|
||||
// Impersonations
|
||||
await impersonateAccountsHardhat([ETHER_BANK, ...TEST_USERS, AWETH_HOLDER]);
|
||||
const ethHolderSigner = rawDRE.ethers.provider.getSigner(ETHER_BANK);
|
||||
const awethHolderSigner = rawDRE.ethers.provider.getSigner(AWETH_HOLDER);
|
||||
user1Signer = DRE.ethers.provider.getSigner(TEST_USERS[0]);
|
||||
controlledPkSigner = (await rawDRE.ethers.getSigners())[0];
|
||||
|
||||
const lendingPool = LendingPoolFactory.connect(LENDING_POOL, userSigner);
|
||||
lendingPool = LendingPoolFactory.connect(LENDING_POOL, user1Signer);
|
||||
weth = WETH9Factory.connect(WETH, user1Signer);
|
||||
aweth = ATokenFactory.connect(AWETH, user1Signer);
|
||||
|
||||
const weth = WETH9Factory.connect(WETH, userSigner);
|
||||
await aweth.connect(awethHolderSigner).transfer(controlledPkSigner.address, parseEther('5.0'));
|
||||
|
||||
const aweth = ATokenFactory.connect(AWETH, userSigner);
|
||||
for (const recipientOfEth of [...TEST_USERS]) {
|
||||
await ethHolderSigner.sendTransaction({
|
||||
from: ethHolderSigner._address,
|
||||
to: recipientOfEth,
|
||||
value: utils.parseEther('100'),
|
||||
...defaultTxParams,
|
||||
});
|
||||
}
|
||||
await waitForTx(await weth.deposit({ value: parseEther('5') }));
|
||||
|
||||
const amountToDeposit = utils.parseEther('5');
|
||||
|
||||
await waitForTx(await weth.deposit({ value: amountToDeposit }));
|
||||
|
||||
const staticAToken = await new StaticATokenFactory(userSigner).deploy(
|
||||
staticAWeth = await new StaticATokenFactory(user1Signer).deploy(
|
||||
LENDING_POOL,
|
||||
AWETH,
|
||||
'Static Aave Interest Bearing WETH',
|
||||
'stataAAVE'
|
||||
);
|
||||
});
|
||||
it('Deposit WETH on stataWETH, then withdraw of the whole balance in underlying', async () => {
|
||||
const amountToDeposit = utils.parseEther('5');
|
||||
|
||||
const ctxtParams: tContextParams = {
|
||||
staticAToken: <ERC20>staticAToken,
|
||||
staticAToken: <ERC20>staticAWeth,
|
||||
underlying: <ERC20>(<unknown>weth),
|
||||
aToken: <ERC20>aweth,
|
||||
user: userSigner._address,
|
||||
user: user1Signer._address,
|
||||
lendingPool,
|
||||
};
|
||||
|
||||
const ctxtBeforeDeposit = await getContext(ctxtParams);
|
||||
|
||||
await waitForTx(await weth.approve(staticAToken.address, amountToDeposit, defaultTxParams));
|
||||
await waitForTx(await weth.approve(staticAWeth.address, amountToDeposit, defaultTxParams));
|
||||
|
||||
await waitForTx(
|
||||
await staticAToken.deposit(userSigner._address, amountToDeposit, 0, true, defaultTxParams)
|
||||
await staticAWeth.deposit(user1Signer._address, amountToDeposit, 0, true, defaultTxParams)
|
||||
);
|
||||
|
||||
const ctxtAfterDeposit = await getContext(ctxtParams);
|
||||
|
|
@ -166,7 +199,7 @@ describe('StaticAToken: aToken wrapper with static balances', () => {
|
|||
const amountToWithdraw = MAX_UINT_AMOUNT;
|
||||
|
||||
await waitForTx(
|
||||
await staticAToken.withdraw(userSigner._address, amountToWithdraw, true, defaultTxParams)
|
||||
await staticAWeth.withdraw(user1Signer._address, amountToWithdraw, true, defaultTxParams)
|
||||
);
|
||||
|
||||
const ctxtAfterWithdrawal = await getContext(ctxtParams);
|
||||
|
|
@ -176,13 +209,9 @@ describe('StaticAToken: aToken wrapper with static balances', () => {
|
|||
'INVALID_ATOKEN_BALANCE_ON_STATICATOKEN_AFTER_WITHDRAW'
|
||||
).to.be.equal(
|
||||
rayMul(
|
||||
ctxtAfterWithdrawal.staticATokenSupply.plus(ctxtBeforeWithdrawal.userStaticATokenBalance),
|
||||
ctxtBeforeWithdrawal.staticATokenSupply.minus(ctxtBeforeWithdrawal.userStaticATokenBalance),
|
||||
ctxtAfterWithdrawal.currentRate
|
||||
)
|
||||
.minus(
|
||||
rayMul(ctxtBeforeWithdrawal.userStaticATokenBalance, ctxtAfterWithdrawal.currentRate)
|
||||
)
|
||||
.toString()
|
||||
).toString()
|
||||
);
|
||||
|
||||
expect(
|
||||
|
|
@ -200,25 +229,501 @@ describe('StaticAToken: aToken wrapper with static balances', () => {
|
|||
).to.be.equal('0');
|
||||
|
||||
expect(
|
||||
ctxtAfterDeposit.underlyingBalanceStaticAToken.toString(),
|
||||
ctxtAfterWithdrawal.underlyingBalanceStaticAToken.toString(),
|
||||
'INVALID_UNDERLYNG_BALANCE_OF_STATICATOKEN_AFTER_WITHDRAWAL'
|
||||
).to.be.equal(ctxtBeforeDeposit.underlyingBalanceStaticAToken.toString());
|
||||
).to.be.equal(ctxtBeforeWithdrawal.underlyingBalanceStaticAToken.toString());
|
||||
|
||||
expect(
|
||||
ctxtBeforeDeposit.aTokenBalanceUser.toString(),
|
||||
ctxtAfterWithdrawal.aTokenBalanceUser.toString(),
|
||||
'INVALID_ATOKEN_BALANCE_OF_USER_AFTER_WITHDRAWAL'
|
||||
).to.be.equal(ctxtAfterDeposit.aTokenBalanceUser.toString());
|
||||
).to.be.equal(ctxtBeforeWithdrawal.aTokenBalanceUser.toString());
|
||||
});
|
||||
|
||||
it('Deposit WETH on stataWETH and then withdraw some balance in underlying', async () => {});
|
||||
it('Deposit WETH on stataWETH and then withdraw to some balance in underlying', async () => {
|
||||
const amountToDeposit = utils.parseEther('5');
|
||||
|
||||
it('Deposit WETH on stataWETH and then withdraw all the balance in aToken', async () => {});
|
||||
const ctxtParams: tContextParams = {
|
||||
staticAToken: <ERC20>staticAWeth,
|
||||
underlying: <ERC20>(<unknown>weth),
|
||||
aToken: <ERC20>aweth,
|
||||
user: user1Signer._address,
|
||||
lendingPool,
|
||||
};
|
||||
|
||||
it('Deposit aWETH on stataWETH and then withdraw some balance in aToken', async () => {});
|
||||
const ctxtBeforeDeposit = await getContext(ctxtParams);
|
||||
|
||||
it('Deposit using metaDeposit()', async () => {});
|
||||
await waitForTx(await weth.approve(staticAWeth.address, amountToDeposit, defaultTxParams));
|
||||
|
||||
it('Withdraw using withdrawDynamicAmount()', async () => {});
|
||||
await waitForTx(
|
||||
await staticAWeth.deposit(user1Signer._address, amountToDeposit, 0, true, defaultTxParams)
|
||||
);
|
||||
|
||||
it('Withdraw using metaWithdraw()', async () => {});
|
||||
const ctxtAfterDeposit = await getContext(ctxtParams);
|
||||
|
||||
expect(ctxtAfterDeposit.aTokenBalanceStaticAToken.toString()).to.be.equal(
|
||||
ctxtBeforeDeposit.aTokenBalanceStaticAToken
|
||||
.plus(new BigNumber(amountToDeposit.toString()))
|
||||
.toString()
|
||||
);
|
||||
|
||||
expect(ctxtAfterDeposit.underlyingBalanceUser.toString()).to.be.equal(
|
||||
ctxtBeforeDeposit.underlyingBalanceUser
|
||||
.minus(new BigNumber(amountToDeposit.toString()))
|
||||
.toString()
|
||||
);
|
||||
|
||||
expect(ctxtAfterDeposit.userDynamicStaticATokenBalance.toString()).to.be.equal(
|
||||
ctxtBeforeDeposit.userDynamicStaticATokenBalance
|
||||
.plus(new BigNumber(amountToDeposit.toString()))
|
||||
.toString()
|
||||
);
|
||||
|
||||
expect(ctxtAfterDeposit.underlyingBalanceStaticAToken.toString()).to.be.equal(
|
||||
ctxtBeforeDeposit.underlyingBalanceStaticAToken.toString()
|
||||
);
|
||||
|
||||
expect(ctxtBeforeDeposit.aTokenBalanceUser.toString()).to.be.equal(
|
||||
ctxtAfterDeposit.aTokenBalanceUser.toString()
|
||||
);
|
||||
|
||||
const ctxtBeforeWithdrawal = await getContext(ctxtParams);
|
||||
|
||||
const amountToWithdraw = parseEther('2.0');
|
||||
|
||||
await waitForTx(
|
||||
await staticAWeth.withdraw(user1Signer._address, amountToWithdraw, true, defaultTxParams)
|
||||
);
|
||||
|
||||
const ctxtAfterWithdrawal = await getContext(ctxtParams);
|
||||
|
||||
expect(
|
||||
ctxtAfterWithdrawal.aTokenBalanceStaticAToken.toString(),
|
||||
'INVALID_ATOKEN_BALANCE_ON_STATICATOKEN_AFTER_WITHDRAW'
|
||||
).to.be.equal(
|
||||
rayMul(
|
||||
ctxtBeforeWithdrawal.staticATokenSupply.minus(amountToWithdraw.toString()),
|
||||
ctxtAfterWithdrawal.currentRate
|
||||
).toString()
|
||||
);
|
||||
|
||||
expect(
|
||||
ctxtAfterWithdrawal.underlyingBalanceUser.toString(),
|
||||
'INVALID_UNDERLYING_BALANCE_OF_USER_AFTER_WITHDRAWAL'
|
||||
).to.be.equal(
|
||||
ctxtBeforeWithdrawal.underlyingBalanceUser
|
||||
.plus(rayMul(amountToWithdraw.toString(), ctxtAfterWithdrawal.currentRate))
|
||||
.toString()
|
||||
);
|
||||
|
||||
expect(
|
||||
ctxtAfterWithdrawal.userStaticATokenBalance.toString(),
|
||||
'INVALID_STATICATOKEN_BALANCE_OF_USER_AFTER_WITHDRAWAL'
|
||||
).to.be.equal(
|
||||
ctxtBeforeWithdrawal.userStaticATokenBalance.minus(amountToWithdraw.toString()).toString()
|
||||
);
|
||||
|
||||
expect(
|
||||
ctxtAfterWithdrawal.underlyingBalanceStaticAToken.toString(),
|
||||
'INVALID_UNDERLYNG_BALANCE_OF_STATICATOKEN_AFTER_WITHDRAWAL'
|
||||
).to.be.equal(ctxtBeforeWithdrawal.underlyingBalanceStaticAToken.toString());
|
||||
|
||||
expect(
|
||||
ctxtAfterWithdrawal.aTokenBalanceUser.toString(),
|
||||
'INVALID_ATOKEN_BALANCE_OF_USER_AFTER_WITHDRAWAL'
|
||||
).to.be.equal(ctxtBeforeWithdrawal.aTokenBalanceUser.toString());
|
||||
});
|
||||
|
||||
it('Deposit WETH on stataWETH and then withdrawDynamic some balance in aToken', async () => {
|
||||
const amountToDeposit = utils.parseEther('4');
|
||||
|
||||
const ctxtParams: tContextParams = {
|
||||
staticAToken: <ERC20>staticAWeth,
|
||||
underlying: <ERC20>(<unknown>weth),
|
||||
aToken: <ERC20>aweth,
|
||||
user: user1Signer._address,
|
||||
lendingPool,
|
||||
};
|
||||
|
||||
const ctxtBeforeDeposit = await getContext(ctxtParams);
|
||||
|
||||
await waitForTx(await weth.approve(staticAWeth.address, amountToDeposit, defaultTxParams));
|
||||
|
||||
await waitForTx(
|
||||
await staticAWeth.deposit(user1Signer._address, amountToDeposit, 0, true, defaultTxParams)
|
||||
);
|
||||
|
||||
const ctxtAfterDeposit = await getContext(ctxtParams);
|
||||
const interestAccrued = getInterestAccrued(ctxtBeforeDeposit, ctxtAfterDeposit);
|
||||
expect(ctxtAfterDeposit.aTokenBalanceStaticAToken.toString()).to.be.equal(
|
||||
ctxtBeforeDeposit.aTokenBalanceStaticAToken
|
||||
.plus(new BigNumber(amountToDeposit.toString()))
|
||||
.plus(interestAccrued)
|
||||
.toString()
|
||||
);
|
||||
expect(ctxtAfterDeposit.underlyingBalanceUser.toString()).to.be.equal(
|
||||
ctxtBeforeDeposit.underlyingBalanceUser
|
||||
.minus(new BigNumber(amountToDeposit.toString()))
|
||||
.toString()
|
||||
);
|
||||
expect(ctxtAfterDeposit.userDynamicStaticATokenBalance.toString()).to.be.equal(
|
||||
ctxtBeforeDeposit.userDynamicStaticATokenBalance
|
||||
.plus(new BigNumber(amountToDeposit.toString()))
|
||||
.plus(interestAccrued)
|
||||
.toString()
|
||||
);
|
||||
expect(ctxtAfterDeposit.underlyingBalanceStaticAToken.toString()).to.be.equal(
|
||||
ctxtBeforeDeposit.underlyingBalanceStaticAToken.toString()
|
||||
);
|
||||
expect(ctxtBeforeDeposit.aTokenBalanceUser.toString()).to.be.equal(
|
||||
ctxtAfterDeposit.aTokenBalanceUser.toString()
|
||||
);
|
||||
|
||||
const ctxtBeforeWithdrawal = await getContext(ctxtParams);
|
||||
|
||||
const amountToWithdraw = parseEther('2.0');
|
||||
|
||||
await waitForTx(
|
||||
await staticAWeth.withdrawDynamicAmount(
|
||||
user1Signer._address,
|
||||
amountToWithdraw,
|
||||
false,
|
||||
defaultTxParams
|
||||
)
|
||||
);
|
||||
|
||||
const ctxtAfterWithdrawal = await getContext(ctxtParams);
|
||||
const interestAccruedWithdrawal = getInterestAccrued(ctxtBeforeWithdrawal, ctxtAfterWithdrawal);
|
||||
expect(
|
||||
ctxtAfterWithdrawal.aTokenBalanceStaticAToken.toString(),
|
||||
'INVALID_ATOKEN_BALANCE_ON_STATICATOKEN_AFTER_WITHDRAW'
|
||||
).to.be.equal(
|
||||
ctxtBeforeWithdrawal.aTokenBalanceStaticAToken
|
||||
.minus(amountToWithdraw.toString())
|
||||
.plus(interestAccruedWithdrawal)
|
||||
.plus('1') // rounding issue
|
||||
.toString()
|
||||
);
|
||||
expect(
|
||||
ctxtAfterWithdrawal.underlyingBalanceUser.toString(),
|
||||
'INVALID_UNDERLYING_BALANCE_OF_USER_AFTER_WITHDRAWAL'
|
||||
).to.be.equal(ctxtBeforeWithdrawal.underlyingBalanceUser.toString());
|
||||
expect(
|
||||
ctxtAfterWithdrawal.userStaticATokenBalance.toString(),
|
||||
'INVALID_STATICATOKEN_BALANCE_OF_USER_AFTER_WITHDRAWAL'
|
||||
).to.be.equal(
|
||||
ctxtBeforeWithdrawal.userStaticATokenBalance
|
||||
.minus(rayDiv(amountToWithdraw.toString(), ctxtAfterWithdrawal.currentRate))
|
||||
.toString()
|
||||
);
|
||||
expect(
|
||||
ctxtAfterWithdrawal.underlyingBalanceStaticAToken.toString(),
|
||||
'INVALID_UNDERLYNG_BALANCE_OF_STATICATOKEN_AFTER_WITHDRAWAL'
|
||||
).to.be.equal(ctxtBeforeWithdrawal.underlyingBalanceStaticAToken.toString());
|
||||
expect(
|
||||
ctxtAfterWithdrawal.aTokenBalanceUser.toString(),
|
||||
'INVALID_ATOKEN_BALANCE_OF_USER_AFTER_WITHDRAWAL'
|
||||
).to.be.equal(
|
||||
ctxtBeforeWithdrawal.aTokenBalanceUser.plus(amountToWithdraw.toString()).toString()
|
||||
);
|
||||
});
|
||||
|
||||
// tslint:disable-next-line:max-line-length
|
||||
it('User 2 Deposits aWETH for user 1 and then withdraw some balance to second user in aToken', async () => {
|
||||
const amountToDeposit = utils.parseEther('4');
|
||||
|
||||
const ctxtParams: tContextParams = {
|
||||
staticAToken: <ERC20>staticAWeth,
|
||||
underlying: <ERC20>(<unknown>weth),
|
||||
aToken: <ERC20>aweth,
|
||||
user: user1Signer._address,
|
||||
lendingPool,
|
||||
};
|
||||
|
||||
const ctxtBeforeDeposit = await getContext(ctxtParams);
|
||||
|
||||
await waitForTx(
|
||||
await aweth.connect(controlledPkSigner).approve(staticAWeth.address, MAX_UINT_AMOUNT)
|
||||
);
|
||||
|
||||
await waitForTx(
|
||||
await staticAWeth
|
||||
.connect(controlledPkSigner)
|
||||
.deposit(user1Signer._address, amountToDeposit, 0, false, defaultTxParams)
|
||||
);
|
||||
|
||||
const ctxtAfterDeposit = await getContext(ctxtParams);
|
||||
const interestAccrued = getInterestAccrued(ctxtBeforeDeposit, ctxtAfterDeposit);
|
||||
const userInterestAccrued = getUserInterestAccrued(ctxtBeforeDeposit, ctxtAfterDeposit);
|
||||
expect(ctxtAfterDeposit.aTokenBalanceStaticAToken.toString()).to.be.equal(
|
||||
ctxtBeforeDeposit.aTokenBalanceStaticAToken
|
||||
.plus(new BigNumber(amountToDeposit.toString()))
|
||||
.plus(interestAccrued)
|
||||
.plus(1)
|
||||
.toString()
|
||||
);
|
||||
expect(ctxtAfterDeposit.underlyingBalanceUser.toString()).to.be.equal(
|
||||
ctxtBeforeDeposit.underlyingBalanceUser.toString()
|
||||
);
|
||||
expect(ctxtAfterDeposit.userDynamicStaticATokenBalance.toString()).to.be.equal(
|
||||
ctxtBeforeDeposit.userDynamicStaticATokenBalance
|
||||
.plus(new BigNumber(amountToDeposit.toString()))
|
||||
.plus(interestAccrued)
|
||||
.plus(1)
|
||||
.toString()
|
||||
);
|
||||
expect(ctxtAfterDeposit.underlyingBalanceStaticAToken.toString()).to.be.equal(
|
||||
ctxtBeforeDeposit.underlyingBalanceStaticAToken.toString()
|
||||
);
|
||||
expect(ctxtAfterDeposit.aTokenBalanceUser.toString()).to.be.equal(
|
||||
ctxtBeforeDeposit.aTokenBalanceUser.plus(userInterestAccrued).toString()
|
||||
);
|
||||
const ctxtBeforeWithdrawal = await getContext(ctxtParams);
|
||||
|
||||
const amountToWithdraw = parseEther('2.0');
|
||||
|
||||
await waitForTx(
|
||||
await staticAWeth.withdrawDynamicAmount(
|
||||
user1Signer._address,
|
||||
amountToWithdraw,
|
||||
false,
|
||||
defaultTxParams
|
||||
)
|
||||
);
|
||||
|
||||
const ctxtAfterWithdrawal = await getContext(ctxtParams);
|
||||
const interestAccruedWithdrawal = getInterestAccrued(ctxtBeforeWithdrawal, ctxtAfterWithdrawal);
|
||||
const userInterestAccruedWithdrawal = getUserInterestAccrued(
|
||||
ctxtBeforeWithdrawal,
|
||||
ctxtAfterWithdrawal
|
||||
);
|
||||
expect(
|
||||
ctxtAfterWithdrawal.aTokenBalanceStaticAToken.toString(),
|
||||
'INVALID_ATOKEN_BALANCE_ON_STATICATOKEN_AFTER_WITHDRAW'
|
||||
).to.be.equal(
|
||||
ctxtBeforeWithdrawal.aTokenBalanceStaticAToken
|
||||
.minus(amountToWithdraw.toString())
|
||||
.plus(interestAccruedWithdrawal)
|
||||
.plus('1') // rounding issue
|
||||
.toString()
|
||||
);
|
||||
expect(
|
||||
ctxtAfterWithdrawal.underlyingBalanceUser.toString(),
|
||||
'INVALID_UNDERLYING_BALANCE_OF_USER_AFTER_WITHDRAWAL'
|
||||
).to.be.equal(ctxtBeforeWithdrawal.underlyingBalanceUser.toString());
|
||||
expect(
|
||||
ctxtAfterWithdrawal.userStaticATokenBalance.toString(),
|
||||
'INVALID_STATICATOKEN_BALANCE_OF_USER_AFTER_WITHDRAWAL'
|
||||
).to.be.equal(
|
||||
ctxtBeforeWithdrawal.userStaticATokenBalance
|
||||
.minus(rayDiv(amountToWithdraw.toString(), ctxtAfterWithdrawal.currentRate))
|
||||
.toString()
|
||||
);
|
||||
expect(
|
||||
ctxtAfterWithdrawal.underlyingBalanceStaticAToken.toString(),
|
||||
'INVALID_UNDERLYNG_BALANCE_OF_STATICATOKEN_AFTER_WITHDRAWAL'
|
||||
).to.be.equal(ctxtBeforeWithdrawal.underlyingBalanceStaticAToken.toString());
|
||||
expect(
|
||||
ctxtAfterWithdrawal.aTokenBalanceUser.toString(),
|
||||
'INVALID_ATOKEN_BALANCE_OF_USER_AFTER_WITHDRAWAL'
|
||||
).to.be.equal(
|
||||
ctxtBeforeWithdrawal.aTokenBalanceUser
|
||||
.plus(userInterestAccruedWithdrawal)
|
||||
.plus(amountToWithdraw.toString())
|
||||
.toString()
|
||||
);
|
||||
});
|
||||
|
||||
it('Deposit using permit + metaDeposit()', async () => {
|
||||
const mockFactory = new StaticATokenMetaTransactionMockFactory(controlledPkSigner);
|
||||
const metaTransactionMock = await mockFactory.deploy();
|
||||
const chainId = DRE.network.config.chainId || 1;
|
||||
const userBalance = await aweth.balanceOf(controlledPkSigner.address);
|
||||
const amountToDeposit = new BigNumber(userBalance.div(2).toString());
|
||||
|
||||
const ctxtParams: tContextParams = {
|
||||
staticAToken: <ERC20>staticAWeth,
|
||||
underlying: <ERC20>(<unknown>weth),
|
||||
aToken: <ERC20>aweth,
|
||||
user: controlledPkSigner.address,
|
||||
lendingPool,
|
||||
};
|
||||
|
||||
const ctxtBeforeDeposit = await getContext(ctxtParams);
|
||||
|
||||
const permitParams = buildPermitParams(
|
||||
1, // mainnet fork
|
||||
aweth.address,
|
||||
'1',
|
||||
await aweth.name(),
|
||||
controlledPkSigner.address,
|
||||
staticAWeth.address,
|
||||
(await aweth._nonces(controlledPkSigner.address)).toNumber(),
|
||||
MAX_UINT_AMOUNT,
|
||||
userBalance.div(2).toString()
|
||||
);
|
||||
const depositParams = buildMetaDepositParams(
|
||||
1, // mainnet fork
|
||||
staticAWeth.address,
|
||||
'1',
|
||||
await staticAWeth.name(),
|
||||
controlledPkSigner.address,
|
||||
controlledPkSigner.address,
|
||||
(await staticAWeth._nonces(controlledPkSigner.address)).toNumber(),
|
||||
MAX_UINT_AMOUNT,
|
||||
false,
|
||||
0,
|
||||
userBalance.div(2).toString()
|
||||
);
|
||||
|
||||
const ownerPrivateKey = require('../../test-wallets.js').accounts[0].secretKey;
|
||||
if (!ownerPrivateKey) {
|
||||
throw new Error('INVALID_OWNER_PK');
|
||||
}
|
||||
expect(
|
||||
(await aweth.allowance(controlledPkSigner.address, metaTransactionMock.address)).toString()
|
||||
).to.be.equal('0', 'INVALID_ALLOWANCE_BEFORE_PERMIT');
|
||||
const { v: permitV, r: permitR, s: permitS } = getSignatureFromTypedData(
|
||||
ownerPrivateKey,
|
||||
permitParams
|
||||
);
|
||||
const { v: depositV, r: depositR, s: depositS } = getSignatureFromTypedData(
|
||||
ownerPrivateKey,
|
||||
depositParams
|
||||
);
|
||||
await metaTransactionMock.permitAndDeposit(
|
||||
staticAWeth.address,
|
||||
controlledPkSigner.address,
|
||||
userBalance.div(2),
|
||||
0,
|
||||
false,
|
||||
MAX_UINT_AMOUNT,
|
||||
{
|
||||
v: permitV,
|
||||
r: permitR,
|
||||
s: permitS,
|
||||
},
|
||||
{
|
||||
v: depositV,
|
||||
r: depositR,
|
||||
s: depositS,
|
||||
},
|
||||
1
|
||||
);
|
||||
const ctxtAfterDeposit = await getContext(ctxtParams);
|
||||
const interestAccrued = getInterestAccrued(ctxtBeforeDeposit, ctxtAfterDeposit);
|
||||
const userInterestAccrued = getUserInterestAccrued(ctxtBeforeDeposit, ctxtAfterDeposit);
|
||||
expect(ctxtAfterDeposit.aTokenBalanceStaticAToken.toString()).to.be.equal(
|
||||
ctxtBeforeDeposit.aTokenBalanceStaticAToken
|
||||
.plus(new BigNumber(amountToDeposit.toString()))
|
||||
.plus(interestAccrued)
|
||||
.minus(1)
|
||||
.toString()
|
||||
);
|
||||
expect(ctxtAfterDeposit.underlyingBalanceUser.toString()).to.be.equal(
|
||||
ctxtBeforeDeposit.underlyingBalanceUser.toString()
|
||||
);
|
||||
expect(ctxtAfterDeposit.userDynamicStaticATokenBalance.toString()).to.be.equal(
|
||||
ctxtBeforeDeposit.userDynamicStaticATokenBalance
|
||||
.plus(new BigNumber(amountToDeposit.toString()))
|
||||
.toString()
|
||||
);
|
||||
expect(ctxtAfterDeposit.underlyingBalanceStaticAToken.toString()).to.be.equal(
|
||||
ctxtBeforeDeposit.underlyingBalanceStaticAToken.toString()
|
||||
);
|
||||
expect(ctxtAfterDeposit.aTokenBalanceUser.toString()).to.be.equal(
|
||||
ctxtBeforeDeposit.aTokenBalanceUser
|
||||
.plus(userInterestAccrued)
|
||||
.minus(amountToDeposit)
|
||||
.toString()
|
||||
);
|
||||
});
|
||||
|
||||
it('Withdraw using metaWithdraw()', async () => {
|
||||
const ctxtParams: tContextParams = {
|
||||
staticAToken: <ERC20>staticAWeth,
|
||||
underlying: <ERC20>(<unknown>weth),
|
||||
aToken: <ERC20>aweth,
|
||||
user: controlledPkSigner.address,
|
||||
lendingPool,
|
||||
};
|
||||
const ctxtBeforeWithdrawal = await getContext(ctxtParams);
|
||||
const amountToWithdraw = parseEther('0.1');
|
||||
|
||||
const withdrawParams = buildMetaWithdrawParams(
|
||||
1, // mainnet fork
|
||||
staticAWeth.address,
|
||||
'1',
|
||||
await staticAWeth.name(),
|
||||
controlledPkSigner.address,
|
||||
controlledPkSigner.address,
|
||||
(await staticAWeth._nonces(controlledPkSigner.address)).toNumber(),
|
||||
MAX_UINT_AMOUNT,
|
||||
false,
|
||||
'0',
|
||||
amountToWithdraw.toString()
|
||||
);
|
||||
const ownerPrivateKey = require('../../test-wallets.js').accounts[0].secretKey;
|
||||
if (!ownerPrivateKey) {
|
||||
throw new Error('INVALID_OWNER_PK');
|
||||
}
|
||||
const { v, r, s } = getSignatureFromTypedData(ownerPrivateKey, withdrawParams);
|
||||
|
||||
await waitForTx(
|
||||
await staticAWeth.metaWithdraw(
|
||||
controlledPkSigner.address,
|
||||
controlledPkSigner.address,
|
||||
0,
|
||||
amountToWithdraw,
|
||||
false,
|
||||
MAX_UINT_AMOUNT,
|
||||
{
|
||||
v,
|
||||
r,
|
||||
s,
|
||||
},
|
||||
'1'
|
||||
)
|
||||
);
|
||||
|
||||
const ctxtAfterWithdrawal = await getContext(ctxtParams);
|
||||
const interestAccruedWithdrawal = getInterestAccrued(ctxtBeforeWithdrawal, ctxtAfterWithdrawal);
|
||||
const userInterestAccruedWithdrawal = getUserInterestAccrued(
|
||||
ctxtBeforeWithdrawal,
|
||||
ctxtAfterWithdrawal
|
||||
);
|
||||
expect(
|
||||
ctxtAfterWithdrawal.aTokenBalanceStaticAToken.toString(),
|
||||
'INVALID_ATOKEN_BALANCE_ON_STATICATOKEN_AFTER_WITHDRAW'
|
||||
).to.be.equal(
|
||||
ctxtBeforeWithdrawal.aTokenBalanceStaticAToken
|
||||
.minus(amountToWithdraw.toString())
|
||||
.plus(interestAccruedWithdrawal)
|
||||
.toString()
|
||||
);
|
||||
expect(
|
||||
ctxtAfterWithdrawal.underlyingBalanceUser.toString(),
|
||||
'INVALID_UNDERLYING_BALANCE_OF_USER_AFTER_WITHDRAWAL'
|
||||
).to.be.equal(ctxtBeforeWithdrawal.underlyingBalanceUser.toString());
|
||||
expect(
|
||||
ctxtAfterWithdrawal.userStaticATokenBalance.toString(),
|
||||
'INVALID_STATICATOKEN_BALANCE_OF_USER_AFTER_WITHDRAWAL'
|
||||
).to.be.equal(
|
||||
ctxtBeforeWithdrawal.userStaticATokenBalance
|
||||
.minus(rayDiv(amountToWithdraw.toString(), ctxtAfterWithdrawal.currentRate))
|
||||
.toString()
|
||||
);
|
||||
expect(
|
||||
ctxtAfterWithdrawal.underlyingBalanceStaticAToken.toString(),
|
||||
'INVALID_UNDERLYNG_BALANCE_OF_STATICATOKEN_AFTER_WITHDRAWAL'
|
||||
).to.be.equal(ctxtBeforeWithdrawal.underlyingBalanceStaticAToken.toString());
|
||||
expect(
|
||||
ctxtAfterWithdrawal.aTokenBalanceUser.toString(),
|
||||
'INVALID_ATOKEN_BALANCE_OF_USER_AFTER_WITHDRAWAL'
|
||||
).to.be.equal(
|
||||
ctxtBeforeWithdrawal.aTokenBalanceUser
|
||||
.plus(userInterestAccruedWithdrawal)
|
||||
.plus(amountToWithdraw.toString())
|
||||
.toString()
|
||||
);
|
||||
});
|
||||
});
|
||||
|
|
|
|||
Loading…
Reference in New Issue
Block a user