mirror of
https://github.com/Instadapp/aave-protocol-v2.git
synced 2024-07-29 21:47:30 +00:00
test: Add tests for static aToken with liquidity mining
This commit is contained in:
parent
053b186777
commit
2a1ba6ab3d
|
@ -250,6 +250,102 @@ export const buildPermitParams = (
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
|
export const buildMetaDepositParams = (
|
||||||
|
chainId: number,
|
||||||
|
token: tEthereumAddress,
|
||||||
|
revision: string,
|
||||||
|
tokenName: string,
|
||||||
|
depositor: tEthereumAddress,
|
||||||
|
recipient: tEthereumAddress,
|
||||||
|
referralCode: number,
|
||||||
|
fromUnderlying: boolean,
|
||||||
|
nonce: number,
|
||||||
|
deadline: string,
|
||||||
|
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,
|
||||||
|
recipient,
|
||||||
|
value,
|
||||||
|
referralCode,
|
||||||
|
fromUnderlying,
|
||||||
|
nonce,
|
||||||
|
deadline,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
export const buildMetaWithdrawParams = (
|
||||||
|
chainId: number,
|
||||||
|
token: tEthereumAddress,
|
||||||
|
revision: string,
|
||||||
|
tokenName: string,
|
||||||
|
owner: tEthereumAddress,
|
||||||
|
recipient: tEthereumAddress,
|
||||||
|
staticAmount: string,
|
||||||
|
dynamicAmount: string,
|
||||||
|
toUnderlying: boolean,
|
||||||
|
nonce: number,
|
||||||
|
deadline: string
|
||||||
|
) => ({
|
||||||
|
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,
|
||||||
|
recipient,
|
||||||
|
staticAmount,
|
||||||
|
dynamicAmount,
|
||||||
|
toUnderlying,
|
||||||
|
nonce,
|
||||||
|
deadline,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
export const getSignatureFromTypedData = (
|
export const getSignatureFromTypedData = (
|
||||||
privateKey: string,
|
privateKey: string,
|
||||||
typedData: any // TODO: should be TypedData, from eth-sig-utils, but TS doesn't accept it
|
typedData: any // TODO: should be TypedData, from eth-sig-utils, but TS doesn't accept it
|
||||||
|
|
|
@ -0,0 +1,597 @@
|
||||||
|
import rawDRE, { ethers } from 'hardhat';
|
||||||
|
import bnjs from 'bignumber.js';
|
||||||
|
import { solidity } from 'ethereum-waffle';
|
||||||
|
import {
|
||||||
|
LendingPoolFactory,
|
||||||
|
WETH9Factory,
|
||||||
|
ATokenFactory,
|
||||||
|
ERC20,
|
||||||
|
LendingPool,
|
||||||
|
StaticATokenLMFactory,
|
||||||
|
ERC20Factory,
|
||||||
|
WETH9,
|
||||||
|
AToken,
|
||||||
|
StaticATokenLM,
|
||||||
|
} from '../../../types';
|
||||||
|
import {
|
||||||
|
impersonateAccountsHardhat,
|
||||||
|
DRE,
|
||||||
|
waitForTx,
|
||||||
|
evmRevert,
|
||||||
|
evmSnapshot,
|
||||||
|
timeLatest,
|
||||||
|
advanceTimeAndBlock,
|
||||||
|
} from '../../../helpers/misc-utils';
|
||||||
|
import { BigNumber, providers, Signer, utils } from 'ethers';
|
||||||
|
import { MAX_UINT_AMOUNT } from '../../../helpers/constants';
|
||||||
|
import { tEthereumAddress } from '../../../helpers/types';
|
||||||
|
import { AbiCoder, formatEther, verifyTypedData } from 'ethers/lib/utils';
|
||||||
|
|
||||||
|
import { _TypedDataEncoder } from 'ethers/lib/utils';
|
||||||
|
|
||||||
|
import { expect, use } from 'chai';
|
||||||
|
import { stat } from 'fs';
|
||||||
|
import { getCurrentBlock } from '../../../helpers/contracts-helpers';
|
||||||
|
|
||||||
|
//use(solidity);
|
||||||
|
|
||||||
|
const DEFAULT_GAS_LIMIT = 10000000;
|
||||||
|
const DEFAULT_GAS_PRICE = utils.parseUnits('100', 'gwei');
|
||||||
|
|
||||||
|
const defaultTxParams = { gasLimit: DEFAULT_GAS_LIMIT, gasPrice: DEFAULT_GAS_PRICE };
|
||||||
|
|
||||||
|
const LENDING_POOL = '0x7d2768dE32b0b80b7a3454c06BdAc94A69DDc7A9';
|
||||||
|
|
||||||
|
const WETH = '0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2';
|
||||||
|
const STKAAVE = '0x4da27a545c0c5B758a6BA100e3a049001de870f5';
|
||||||
|
const AWETH = '0x030bA81f1c18d280636F32af80b9AAd02Cf0854e';
|
||||||
|
|
||||||
|
const getUserData = async (_users: Signer[], _debug = false, { staticAToken, stkAave }) => {
|
||||||
|
let usersData: {
|
||||||
|
pendingRewards: BigNumber;
|
||||||
|
stkAaveBalance: BigNumber;
|
||||||
|
staticBalance: BigNumber;
|
||||||
|
}[] = [];
|
||||||
|
if (_debug) {
|
||||||
|
console.log(`Printing user data:`);
|
||||||
|
}
|
||||||
|
for (let i = 0; i < _users.length; i++) {
|
||||||
|
const userAddress = await _users[i].getAddress();
|
||||||
|
usersData.push({
|
||||||
|
pendingRewards: await staticAToken.getClaimableRewards(userAddress),
|
||||||
|
stkAaveBalance: await stkAave.balanceOf(userAddress),
|
||||||
|
staticBalance: await staticAToken.balanceOf(userAddress),
|
||||||
|
});
|
||||||
|
if (_debug) {
|
||||||
|
console.log(
|
||||||
|
`\tUser ${i} pendingRewards: ${formatEther(
|
||||||
|
usersData[i].pendingRewards
|
||||||
|
)}, stkAave balance: ${formatEther(usersData[i].stkAaveBalance)}, static bal: ${formatEther(
|
||||||
|
usersData[i].staticBalance
|
||||||
|
)} `
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return usersData;
|
||||||
|
};
|
||||||
|
|
||||||
|
const DUST = 100;
|
||||||
|
|
||||||
|
describe('StaticATokenLM: aToken wrapper with static balances and liquidity mining', () => {
|
||||||
|
let userSigner: providers.JsonRpcSigner;
|
||||||
|
let user2Signer: providers.JsonRpcSigner;
|
||||||
|
let lendingPool: LendingPool;
|
||||||
|
let weth: WETH9;
|
||||||
|
let aweth: AToken;
|
||||||
|
let stkAave: ERC20;
|
||||||
|
|
||||||
|
let staticAToken: StaticATokenLM;
|
||||||
|
|
||||||
|
let snap: string;
|
||||||
|
|
||||||
|
before(async () => {
|
||||||
|
await rawDRE.run('set-DRE');
|
||||||
|
|
||||||
|
console.log(`Initial block number: ${await getCurrentBlock()}`);
|
||||||
|
|
||||||
|
const [user1, user2] = await DRE.ethers.getSigners();
|
||||||
|
userSigner = DRE.ethers.provider.getSigner(await user1.getAddress());
|
||||||
|
user2Signer = DRE.ethers.provider.getSigner(await user2.getAddress());
|
||||||
|
lendingPool = LendingPoolFactory.connect(LENDING_POOL, userSigner);
|
||||||
|
|
||||||
|
weth = WETH9Factory.connect(WETH, userSigner);
|
||||||
|
aweth = ATokenFactory.connect(AWETH, userSigner);
|
||||||
|
stkAave = ERC20Factory.connect(STKAAVE, userSigner);
|
||||||
|
|
||||||
|
staticAToken = await new StaticATokenLMFactory(userSigner).deploy(
|
||||||
|
LENDING_POOL,
|
||||||
|
AWETH,
|
||||||
|
'Static Aave Interest Bearing WETH',
|
||||||
|
'stataAAVE'
|
||||||
|
);
|
||||||
|
|
||||||
|
snap = await evmSnapshot();
|
||||||
|
});
|
||||||
|
|
||||||
|
beforeEach(async () => {
|
||||||
|
await evmRevert(snap);
|
||||||
|
snap = await evmSnapshot();
|
||||||
|
});
|
||||||
|
|
||||||
|
after(async () => {
|
||||||
|
await evmRevert(snap);
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('Small checks', async () => {
|
||||||
|
it('Rewards increase at deposit, update and withdraw and set to 0 at claim', async () => {
|
||||||
|
const amountToDeposit = utils.parseEther('5');
|
||||||
|
const amountToWithdraw = MAX_UINT_AMOUNT; // Still need to figure out why this works :eyes:
|
||||||
|
|
||||||
|
// Just preparation
|
||||||
|
await waitForTx(await weth.deposit({ value: amountToDeposit.mul(2) }));
|
||||||
|
await waitForTx(
|
||||||
|
await weth.approve(staticAToken.address, amountToDeposit.mul(2), defaultTxParams)
|
||||||
|
);
|
||||||
|
|
||||||
|
// Depositing
|
||||||
|
await waitForTx(
|
||||||
|
await staticAToken.deposit(userSigner._address, amountToDeposit, 0, true, defaultTxParams)
|
||||||
|
);
|
||||||
|
|
||||||
|
const pendingRewards1 = await staticAToken.getClaimableRewards(userSigner._address);
|
||||||
|
|
||||||
|
// Depositing
|
||||||
|
await waitForTx(
|
||||||
|
await staticAToken.deposit(userSigner._address, amountToDeposit, 0, true, defaultTxParams)
|
||||||
|
);
|
||||||
|
|
||||||
|
const pendingRewards2 = await staticAToken.getClaimableRewards(userSigner._address);
|
||||||
|
|
||||||
|
await waitForTx(await staticAToken.updateRewards());
|
||||||
|
|
||||||
|
const pendingRewards3 = await staticAToken.getClaimableRewards(userSigner._address);
|
||||||
|
|
||||||
|
// Withdrawing all.
|
||||||
|
await waitForTx(
|
||||||
|
await staticAToken.withdraw(userSigner._address, amountToWithdraw, true, defaultTxParams)
|
||||||
|
);
|
||||||
|
|
||||||
|
const pendingRewards4 = await staticAToken.getClaimableRewards(userSigner._address);
|
||||||
|
const claimedRewards4 = await stkAave.balanceOf(userSigner._address);
|
||||||
|
|
||||||
|
await waitForTx(await staticAToken.claimRewards(userSigner._address));
|
||||||
|
|
||||||
|
const pendingRewards5 = await staticAToken.getClaimableRewards(userSigner._address);
|
||||||
|
|
||||||
|
expect(pendingRewards2).to.be.gt(pendingRewards1);
|
||||||
|
expect(pendingRewards3).to.be.gt(pendingRewards2);
|
||||||
|
expect(pendingRewards4).to.be.gt(pendingRewards3);
|
||||||
|
expect(pendingRewards5).to.be.eq(0);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('Check getters', async () => {
|
||||||
|
const amountToDeposit = utils.parseEther('5');
|
||||||
|
const amountToWithdraw = MAX_UINT_AMOUNT; // Still need to figure out why this works :eyes:
|
||||||
|
|
||||||
|
// Just preparation
|
||||||
|
await waitForTx(await weth.deposit({ value: amountToDeposit.mul(2) }));
|
||||||
|
await waitForTx(
|
||||||
|
await weth.approve(staticAToken.address, amountToDeposit.mul(2), defaultTxParams)
|
||||||
|
);
|
||||||
|
|
||||||
|
// Depositing
|
||||||
|
await waitForTx(
|
||||||
|
await staticAToken.deposit(userSigner._address, amountToDeposit, 0, true, defaultTxParams)
|
||||||
|
);
|
||||||
|
|
||||||
|
const staticBalance = await staticAToken.balanceOf(userSigner._address);
|
||||||
|
const dynamicBalance = await staticAToken.dynamicBalanceOf(userSigner._address);
|
||||||
|
|
||||||
|
const dynamicBalanceFromStatic = await staticAToken.staticToDynamicAmount(staticBalance);
|
||||||
|
const staticBalanceFromDynamic = await staticAToken.dynamicToStaticAmount(dynamicBalance);
|
||||||
|
|
||||||
|
expect(staticBalance).to.be.eq(staticBalanceFromDynamic);
|
||||||
|
expect(dynamicBalance).to.be.eq(dynamicBalanceFromStatic);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('Update and claim', async () => {
|
||||||
|
const amountToDeposit = utils.parseEther('5');
|
||||||
|
const amountToWithdraw = MAX_UINT_AMOUNT; // Still need to figure out why this works :eyes:
|
||||||
|
|
||||||
|
// Just preparation
|
||||||
|
await waitForTx(await weth.deposit({ value: amountToDeposit.mul(2) }));
|
||||||
|
await waitForTx(
|
||||||
|
await weth.approve(staticAToken.address, amountToDeposit.mul(2), defaultTxParams)
|
||||||
|
);
|
||||||
|
|
||||||
|
// Depositing
|
||||||
|
await waitForTx(
|
||||||
|
await staticAToken.deposit(userSigner._address, amountToDeposit, 0, true, defaultTxParams)
|
||||||
|
);
|
||||||
|
|
||||||
|
const pendingRewards1 = await staticAToken.getClaimableRewards(userSigner._address);
|
||||||
|
|
||||||
|
// Depositing
|
||||||
|
await waitForTx(
|
||||||
|
await staticAToken.deposit(userSigner._address, amountToDeposit, 0, true, defaultTxParams)
|
||||||
|
);
|
||||||
|
|
||||||
|
const pendingRewards2 = await staticAToken.getClaimableRewards(userSigner._address);
|
||||||
|
|
||||||
|
await waitForTx(await staticAToken.updateRewards());
|
||||||
|
|
||||||
|
const pendingRewards3 = await staticAToken.getClaimableRewards(userSigner._address);
|
||||||
|
const claimedRewards3 = await stkAave.balanceOf(userSigner._address);
|
||||||
|
|
||||||
|
await waitForTx(await staticAToken.updateAndClaimRewards(userSigner._address));
|
||||||
|
|
||||||
|
const pendingRewards4 = await staticAToken.getClaimableRewards(userSigner._address);
|
||||||
|
const claimedRewards4 = await stkAave.balanceOf(userSigner._address);
|
||||||
|
|
||||||
|
expect(pendingRewards1).to.be.eq(0);
|
||||||
|
expect(pendingRewards2).to.be.gt(pendingRewards1);
|
||||||
|
expect(pendingRewards3).to.be.gt(pendingRewards2);
|
||||||
|
expect(pendingRewards4).to.be.eq(0);
|
||||||
|
|
||||||
|
expect(claimedRewards3).to.be.eq(0);
|
||||||
|
expect(claimedRewards4).to.be.gt(pendingRewards3);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('Withdraw to other user', async () => {
|
||||||
|
const amountToDeposit = utils.parseEther('5');
|
||||||
|
const amountToWithdraw = MAX_UINT_AMOUNT;
|
||||||
|
|
||||||
|
const recipient = user2Signer._address;
|
||||||
|
|
||||||
|
// Just preparation
|
||||||
|
await waitForTx(await weth.deposit({ value: amountToDeposit.mul(2) }));
|
||||||
|
await waitForTx(
|
||||||
|
await weth.approve(staticAToken.address, amountToDeposit.mul(2), defaultTxParams)
|
||||||
|
);
|
||||||
|
|
||||||
|
// Depositing
|
||||||
|
await waitForTx(
|
||||||
|
await staticAToken.deposit(userSigner._address, amountToDeposit, 0, true, defaultTxParams)
|
||||||
|
);
|
||||||
|
|
||||||
|
const userPendingRewards1 = await staticAToken.getClaimableRewards(userSigner._address);
|
||||||
|
const recipientPendingRewards1 = await staticAToken.getClaimableRewards(recipient);
|
||||||
|
|
||||||
|
// Withdrawing all
|
||||||
|
await waitForTx(
|
||||||
|
await staticAToken.withdraw(recipient, amountToWithdraw, true, defaultTxParams)
|
||||||
|
);
|
||||||
|
|
||||||
|
const userPendingRewards2 = await staticAToken.getClaimableRewards(userSigner._address);
|
||||||
|
const recipientPendingRewards2 = await staticAToken.getClaimableRewards(recipient);
|
||||||
|
|
||||||
|
// Check that the recipient have gotten the rewards
|
||||||
|
expect(userPendingRewards2).to.be.gt(userPendingRewards1);
|
||||||
|
expect(recipientPendingRewards1).to.be.eq(0);
|
||||||
|
expect(recipientPendingRewards2).to.be.eq(0);
|
||||||
|
});
|
||||||
|
|
||||||
|
// Those that checks that subs could not be wrong or something other
|
||||||
|
});
|
||||||
|
|
||||||
|
it('Multiple users deposit WETH on stataWETH, wait 1 hour, update rewards, one user transfer, then claim and update rewards.', async () => {
|
||||||
|
// In this case, the recipient should have approx twice the rewards.
|
||||||
|
// Note that he has not held the 2x balance for this entire time, but only for one block.
|
||||||
|
// He have gotten this extra reward from the sender, because there was not a update prior.
|
||||||
|
|
||||||
|
// Only diff here is if we wait, transfer, wait
|
||||||
|
|
||||||
|
// 1. Deposit
|
||||||
|
// 2. Wait 3600 seconds
|
||||||
|
// 2-5. Update rewards
|
||||||
|
// 3. Transfer
|
||||||
|
// 4. Wait 3600 seconds
|
||||||
|
// 5. Claim rewards
|
||||||
|
// 6. Update rewards
|
||||||
|
|
||||||
|
// When doing so, it should be clear that the recipient also gets the 'uncollected' rewards to the protocol that the value has accrued since last update.
|
||||||
|
// The thought is that since it is expensive to retrieve these rewards, a small holder may rather want to give away the extra rewards (if rewards < gas).
|
||||||
|
|
||||||
|
const amountToDeposit = utils.parseEther('5');
|
||||||
|
const allusers = await DRE.ethers.getSigners();
|
||||||
|
const users = [allusers[0], allusers[1], allusers[2], allusers[3], allusers[4]];
|
||||||
|
|
||||||
|
const _debugUserData = false;
|
||||||
|
|
||||||
|
for (let i = 0; i < 5; i++) {
|
||||||
|
let currentUser = users[i];
|
||||||
|
// Preparation
|
||||||
|
await waitForTx(await weth.connect(currentUser).deposit({ value: amountToDeposit }));
|
||||||
|
await waitForTx(
|
||||||
|
await weth
|
||||||
|
.connect(currentUser)
|
||||||
|
.approve(staticAToken.address, amountToDeposit, defaultTxParams)
|
||||||
|
);
|
||||||
|
|
||||||
|
// Deposit
|
||||||
|
await waitForTx(
|
||||||
|
await staticAToken
|
||||||
|
.connect(currentUser)
|
||||||
|
.deposit(await currentUser.getAddress(), amountToDeposit, 0, true, defaultTxParams)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Advance time to accrue significant rewards.
|
||||||
|
await advanceTimeAndBlock(60 * 60);
|
||||||
|
await staticAToken.updateRewards();
|
||||||
|
|
||||||
|
let staticATokenStkAaveBalInitial = await stkAave.balanceOf(staticAToken.address);
|
||||||
|
let usersDataInitial = await getUserData(users, _debugUserData, { staticAToken, stkAave });
|
||||||
|
|
||||||
|
await waitForTx(
|
||||||
|
await staticAToken
|
||||||
|
.connect(users[0])
|
||||||
|
.transfer(
|
||||||
|
await users[1].getAddress(),
|
||||||
|
await staticAToken.balanceOf(await users[0].getAddress())
|
||||||
|
)
|
||||||
|
);
|
||||||
|
|
||||||
|
await advanceTimeAndBlock(60 * 60);
|
||||||
|
|
||||||
|
for (let i = 0; i < 5; i++) {
|
||||||
|
await waitForTx(await staticAToken.claimRewards(await users[i].getAddress()));
|
||||||
|
}
|
||||||
|
|
||||||
|
let staticATokenStkAaveBalAfterTransferAndClaim = await stkAave.balanceOf(staticAToken.address);
|
||||||
|
let usersDataAfterTransferAndClaim = await getUserData(users, _debugUserData, {
|
||||||
|
staticAToken,
|
||||||
|
stkAave,
|
||||||
|
});
|
||||||
|
|
||||||
|
await waitForTx(await staticAToken.updateRewards());
|
||||||
|
|
||||||
|
let staticATokenStkAaveBalFinal = await stkAave.balanceOf(staticAToken.address);
|
||||||
|
let usersDataFinal = await getUserData(users, _debugUserData, { staticAToken, stkAave });
|
||||||
|
|
||||||
|
// Time for checks
|
||||||
|
let pendingRewardsSumInitial = BigNumber.from(0);
|
||||||
|
let pendingRewardsSumAfter = BigNumber.from(0);
|
||||||
|
let pendingRewardsSumFinal = BigNumber.from(0);
|
||||||
|
for (let i = 0; i < 5; i++) {
|
||||||
|
expect(usersDataInitial[i].stkAaveBalance).to.be.eq(0);
|
||||||
|
// Everyone else than i == 1, should have no change in pending rewards.
|
||||||
|
// i == 1, will get additional rewards that have accrue
|
||||||
|
expect(usersDataAfterTransferAndClaim[i].stkAaveBalance).to.be.eq(
|
||||||
|
usersDataInitial[i].pendingRewards
|
||||||
|
);
|
||||||
|
if (i > 1) {
|
||||||
|
// Expect initial static balance == after transfer == after claiming
|
||||||
|
expect(usersDataInitial[i].staticBalance).to.be.eq(
|
||||||
|
usersDataAfterTransferAndClaim[i].staticBalance
|
||||||
|
);
|
||||||
|
expect(usersDataInitial[i].staticBalance).to.be.eq(usersDataFinal[i].staticBalance);
|
||||||
|
}
|
||||||
|
|
||||||
|
pendingRewardsSumInitial = pendingRewardsSumInitial.add(usersDataInitial[i].pendingRewards);
|
||||||
|
pendingRewardsSumAfter = pendingRewardsSumAfter.add(
|
||||||
|
usersDataAfterTransferAndClaim[i].pendingRewards
|
||||||
|
);
|
||||||
|
pendingRewardsSumFinal = pendingRewardsSumFinal.add(usersDataFinal[i].pendingRewards);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Expect user 0 to accrue zero fees after the transfer
|
||||||
|
expect(usersDataFinal[0].pendingRewards).to.be.eq(0);
|
||||||
|
expect(usersDataAfterTransferAndClaim[0].staticBalance).to.be.eq(0);
|
||||||
|
expect(usersDataFinal[0].staticBalance).to.be.eq(0);
|
||||||
|
|
||||||
|
// Expect user 1 to have received funds
|
||||||
|
expect(usersDataAfterTransferAndClaim[1].staticBalance).to.be.eq(
|
||||||
|
usersDataInitial[1].staticBalance.add(usersDataInitial[0].staticBalance)
|
||||||
|
);
|
||||||
|
/*
|
||||||
|
* Expect user 1 to have accrued more than twice in pending rewards.
|
||||||
|
* note that we get very little rewards in the transfer, because of the fresh update.
|
||||||
|
*/
|
||||||
|
// Expect the pending of user to be a lot
|
||||||
|
expect(usersDataFinal[1].pendingRewards).to.be.gt(usersDataFinal[2].pendingRewards.mul(2));
|
||||||
|
// Expect his total fees to be almost 1.5 as large. Because of the small initial diff
|
||||||
|
expect(usersDataFinal[1].pendingRewards.add(usersDataFinal[1].stkAaveBalance)).to.be.gt(
|
||||||
|
usersDataFinal[2].pendingRewards.add(usersDataFinal[2].stkAaveBalance).mul(145).div(100)
|
||||||
|
);
|
||||||
|
expect(usersDataFinal[1].pendingRewards.add(usersDataFinal[1].stkAaveBalance)).to.be.lt(
|
||||||
|
usersDataFinal[2].pendingRewards.add(usersDataFinal[2].stkAaveBalance).mul(155).div(100)
|
||||||
|
);
|
||||||
|
|
||||||
|
// Expect there to be excess stkAave in the contract. Expect it to be dust. This ensure that everyone can claim full amount of rewards.
|
||||||
|
expect(pendingRewardsSumInitial).to.be.lte(staticATokenStkAaveBalInitial);
|
||||||
|
expect(staticATokenStkAaveBalInitial.sub(pendingRewardsSumInitial)).to.be.lte(DUST);
|
||||||
|
|
||||||
|
expect(pendingRewardsSumAfter).to.be.lte(staticATokenStkAaveBalAfterTransferAndClaim);
|
||||||
|
expect(staticATokenStkAaveBalAfterTransferAndClaim.sub(pendingRewardsSumAfter)).to.be.lte(DUST);
|
||||||
|
|
||||||
|
expect(pendingRewardsSumFinal).to.be.lte(staticATokenStkAaveBalFinal);
|
||||||
|
expect(staticATokenStkAaveBalFinal.sub(pendingRewardsSumFinal)).to.be.lte(DUST);
|
||||||
|
|
||||||
|
expect(usersDataInitial[0].pendingRewards).to.be.eq(
|
||||||
|
usersDataAfterTransferAndClaim[0].stkAaveBalance
|
||||||
|
);
|
||||||
|
expect(usersDataAfterTransferAndClaim[0].pendingRewards).to.be.eq(0);
|
||||||
|
expect(usersDataAfterTransferAndClaim[1].staticBalance).to.be.eq(
|
||||||
|
usersDataInitial[1].staticBalance.add(usersDataInitial[0].staticBalance)
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('Multiple users deposit WETH on stataWETH, wait 1 hour, one user transfer, then claim and update rewards.', async () => {
|
||||||
|
// In this case, the recipient should have approx twice the rewards.
|
||||||
|
// Note that he has not held the 2x balance for this entire time, but only for one block.
|
||||||
|
// He have gotten this extra reward from the sender, because there was not a update prior.
|
||||||
|
|
||||||
|
// Only diff here is if we wait, transfer, wait
|
||||||
|
|
||||||
|
// 1. Deposit
|
||||||
|
// 2. Wait 3600 seconds
|
||||||
|
// 3. Transfer
|
||||||
|
// 4. Wait 3600 seconds
|
||||||
|
// 5. Claim rewards
|
||||||
|
// 6. Update rewards
|
||||||
|
|
||||||
|
// When doing so, it should be clear that the recipient also gets the 'uncollected' rewards to the protocol that the value has accrued since last update.
|
||||||
|
// The thought is that since it is expensive to retrieve these rewards, a small holder may rather want to give away the extra rewards (if rewards < gas).
|
||||||
|
|
||||||
|
const amountToDeposit = utils.parseEther('5'); //'5');
|
||||||
|
const allusers = await DRE.ethers.getSigners();
|
||||||
|
const users = [allusers[0], allusers[1], allusers[2], allusers[3], allusers[4]];
|
||||||
|
|
||||||
|
const _debugUserData = false;
|
||||||
|
|
||||||
|
for (let i = 0; i < 5; i++) {
|
||||||
|
let currentUser = users[i];
|
||||||
|
// Preparation
|
||||||
|
await waitForTx(await weth.connect(currentUser).deposit({ value: amountToDeposit }));
|
||||||
|
await waitForTx(
|
||||||
|
await weth
|
||||||
|
.connect(currentUser)
|
||||||
|
.approve(staticAToken.address, amountToDeposit, defaultTxParams)
|
||||||
|
);
|
||||||
|
|
||||||
|
// Deposit
|
||||||
|
await waitForTx(
|
||||||
|
await staticAToken
|
||||||
|
.connect(currentUser)
|
||||||
|
.deposit(await currentUser.getAddress(), amountToDeposit, 0, true, defaultTxParams)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Advance time to accrue significant rewards.
|
||||||
|
await advanceTimeAndBlock(60 * 60);
|
||||||
|
|
||||||
|
let staticATokenStkAaveBalInitial = await stkAave.balanceOf(staticAToken.address);
|
||||||
|
let usersDataInitial = await getUserData(users, _debugUserData, { staticAToken, stkAave });
|
||||||
|
|
||||||
|
// User 0 transfer full balance of staticATokens to user 1. This will also transfer the rewards since last update as well.
|
||||||
|
await waitForTx(
|
||||||
|
await staticAToken
|
||||||
|
.connect(users[0])
|
||||||
|
.transfer(
|
||||||
|
await users[1].getAddress(),
|
||||||
|
await staticAToken.balanceOf(await users[0].getAddress())
|
||||||
|
)
|
||||||
|
);
|
||||||
|
|
||||||
|
await advanceTimeAndBlock(60 * 60);
|
||||||
|
|
||||||
|
for (let i = 0; i < 5; i++) {
|
||||||
|
await waitForTx(await staticAToken.claimRewards(await users[i].getAddress()));
|
||||||
|
}
|
||||||
|
|
||||||
|
let staticATokenStkAaveBalAfterTransfer = await stkAave.balanceOf(staticAToken.address);
|
||||||
|
let usersDataAfterTransfer = await getUserData(users, _debugUserData, {
|
||||||
|
staticAToken,
|
||||||
|
stkAave,
|
||||||
|
});
|
||||||
|
|
||||||
|
await waitForTx(await staticAToken.updateRewards());
|
||||||
|
|
||||||
|
let staticATokenStkAaveBalFinal = await stkAave.balanceOf(staticAToken.address);
|
||||||
|
let usersDataFinal = await getUserData(users, _debugUserData, { staticAToken, stkAave });
|
||||||
|
|
||||||
|
// Time for checks
|
||||||
|
let pendingRewardsSumInitial = BigNumber.from(0);
|
||||||
|
let pendingRewardsSumAfter = BigNumber.from(0);
|
||||||
|
let pendingRewardsSumFinal = BigNumber.from(0);
|
||||||
|
for (let i = 0; i < 5; i++) {
|
||||||
|
expect(usersDataInitial[i].stkAaveBalance).to.be.eq(0);
|
||||||
|
// Everyone else than i == 1, should have no change in pending rewards.
|
||||||
|
// i == 1, will get additional rewards that have accrue
|
||||||
|
expect(usersDataAfterTransfer[i].stkAaveBalance).to.be.eq(usersDataInitial[i].pendingRewards);
|
||||||
|
if (i > 1) {
|
||||||
|
// Expect initial static balance == after transfer == after claiming
|
||||||
|
expect(usersDataInitial[i].staticBalance).to.be.eq(usersDataAfterTransfer[i].staticBalance);
|
||||||
|
expect(usersDataInitial[i].staticBalance).to.be.eq(usersDataFinal[i].staticBalance);
|
||||||
|
}
|
||||||
|
|
||||||
|
pendingRewardsSumInitial = pendingRewardsSumInitial.add(usersDataInitial[i].pendingRewards);
|
||||||
|
pendingRewardsSumAfter = pendingRewardsSumAfter.add(usersDataAfterTransfer[i].pendingRewards);
|
||||||
|
pendingRewardsSumFinal = pendingRewardsSumFinal.add(usersDataFinal[i].pendingRewards);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Expect user 0 to accrue zero fees after the transfer
|
||||||
|
expect(usersDataFinal[0].pendingRewards).to.be.eq(0);
|
||||||
|
expect(usersDataAfterTransfer[0].staticBalance).to.be.eq(0);
|
||||||
|
expect(usersDataFinal[0].staticBalance).to.be.eq(0);
|
||||||
|
|
||||||
|
// Expect user 1 to have received funds
|
||||||
|
expect(usersDataAfterTransfer[1].staticBalance).to.be.eq(
|
||||||
|
usersDataInitial[1].staticBalance.add(usersDataInitial[0].staticBalance)
|
||||||
|
);
|
||||||
|
/*
|
||||||
|
* Expect user 1 to have pending more than twice the rewards as the last user.
|
||||||
|
* Note that he should have accrued this, even though he did not have 2x bal for the full time,
|
||||||
|
* as he also received the "uncollected" rewards from user1 at the transfer.
|
||||||
|
*/
|
||||||
|
expect(usersDataFinal[1].pendingRewards).to.be.gt(usersDataFinal[2].pendingRewards.mul(2));
|
||||||
|
// Expect his total fees to be almost twice as large. Because of the small initial diff
|
||||||
|
expect(usersDataFinal[1].pendingRewards.add(usersDataFinal[1].stkAaveBalance)).to.be.gt(
|
||||||
|
usersDataFinal[2].pendingRewards.add(usersDataFinal[2].stkAaveBalance).mul(195).div(100)
|
||||||
|
);
|
||||||
|
expect(usersDataFinal[1].pendingRewards.add(usersDataFinal[1].stkAaveBalance)).to.be.lt(
|
||||||
|
usersDataFinal[2].pendingRewards.add(usersDataFinal[2].stkAaveBalance).mul(205).div(100)
|
||||||
|
);
|
||||||
|
|
||||||
|
// Expect there to be excess stkAave in the contract.
|
||||||
|
// Expect it to be dust. This ensure that everyone can claim full amount of rewards.
|
||||||
|
expect(pendingRewardsSumInitial).to.be.lte(staticATokenStkAaveBalInitial);
|
||||||
|
expect(staticATokenStkAaveBalInitial.sub(pendingRewardsSumInitial)).to.be.lte(DUST);
|
||||||
|
|
||||||
|
expect(pendingRewardsSumAfter).to.be.lte(staticATokenStkAaveBalAfterTransfer);
|
||||||
|
expect(staticATokenStkAaveBalAfterTransfer.sub(pendingRewardsSumAfter)).to.be.lte(DUST);
|
||||||
|
|
||||||
|
// We got an error here, pendingRewardsSumFinal = actual + 1
|
||||||
|
expect(pendingRewardsSumFinal).to.be.lte(staticATokenStkAaveBalFinal);
|
||||||
|
expect(staticATokenStkAaveBalFinal.sub(pendingRewardsSumFinal)).to.be.lte(DUST); // How small should we say dust is?
|
||||||
|
|
||||||
|
// Expect zero rewards after all is claimed. But there is some dust left.
|
||||||
|
expect(usersDataInitial[0].pendingRewards).to.be.eq(usersDataAfterTransfer[0].stkAaveBalance);
|
||||||
|
expect(usersDataAfterTransfer[0].pendingRewards).to.be.eq(0);
|
||||||
|
expect(usersDataAfterTransfer[1].staticBalance).to.be.eq(
|
||||||
|
usersDataInitial[1].staticBalance.add(usersDataInitial[0].staticBalance)
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('Mass deposit, then mass claim', async () => {
|
||||||
|
const amountToDeposit = utils.parseEther('1.1'); // 18 decimals should be the worst here //1.135359735917531199
|
||||||
|
const users = await DRE.ethers.getSigners();
|
||||||
|
|
||||||
|
const depositCount = 50;
|
||||||
|
|
||||||
|
for (let i = 0; i < depositCount; i++) {
|
||||||
|
if (i % 50 == 0 && i > 0) {
|
||||||
|
console.log('50 deposits');
|
||||||
|
}
|
||||||
|
let currentUser = users[i % users.length];
|
||||||
|
// Preparation
|
||||||
|
await waitForTx(await weth.connect(currentUser).deposit({ value: amountToDeposit }));
|
||||||
|
await waitForTx(
|
||||||
|
await weth
|
||||||
|
.connect(currentUser)
|
||||||
|
.approve(staticAToken.address, amountToDeposit, defaultTxParams)
|
||||||
|
);
|
||||||
|
|
||||||
|
// Deposit
|
||||||
|
await waitForTx(
|
||||||
|
await staticAToken
|
||||||
|
.connect(currentUser)
|
||||||
|
.deposit(await currentUser.getAddress(), amountToDeposit, 0, true, defaultTxParams)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Advance time to accrue significant rewards.
|
||||||
|
await advanceTimeAndBlock(60 * 60);
|
||||||
|
await waitForTx(await staticAToken.updateRewards());
|
||||||
|
|
||||||
|
for (let i = 0; i < users.length; i++) {
|
||||||
|
const pendingReward = await staticAToken.getClaimableRewards(await users[i].getAddress());
|
||||||
|
await waitForTx(await staticAToken.claimRewards(await users[i].getAddress()));
|
||||||
|
// We have a mistake here. Rounding of the pendingReward seems to be the issue
|
||||||
|
// console.log(`What user: ${i}, with pending reward: ${pendingReward}`);
|
||||||
|
expect(await stkAave.balanceOf(await users[i].getAddress())).to.be.eq(pendingReward);
|
||||||
|
}
|
||||||
|
expect(await stkAave.balanceOf(staticAToken.address)).to.be.lt(DUST);
|
||||||
|
});
|
||||||
|
});
|
|
@ -0,0 +1,948 @@
|
||||||
|
import rawDRE, { ethers } from 'hardhat';
|
||||||
|
import bnjs from 'bignumber.js';
|
||||||
|
import { solidity } from 'ethereum-waffle';
|
||||||
|
import {
|
||||||
|
LendingPoolFactory,
|
||||||
|
WETH9Factory,
|
||||||
|
StaticATokenFactory,
|
||||||
|
ATokenFactory,
|
||||||
|
ERC20,
|
||||||
|
LendingPool,
|
||||||
|
StaticATokenLMFactory,
|
||||||
|
ERC20Factory,
|
||||||
|
WETH9,
|
||||||
|
AToken,
|
||||||
|
StaticAToken,
|
||||||
|
StaticATokenLM,
|
||||||
|
} from '../../../types';
|
||||||
|
import {
|
||||||
|
impersonateAccountsHardhat,
|
||||||
|
DRE,
|
||||||
|
waitForTx,
|
||||||
|
evmRevert,
|
||||||
|
evmSnapshot,
|
||||||
|
timeLatest,
|
||||||
|
advanceTimeAndBlock,
|
||||||
|
} from '../../../helpers/misc-utils';
|
||||||
|
import { BigNumber, providers, Signer, utils } from 'ethers';
|
||||||
|
import { rayMul } from '../../../helpers/ray-math';
|
||||||
|
import { MAX_UINT_AMOUNT, ZERO_ADDRESS } from '../../../helpers/constants';
|
||||||
|
import { tEthereumAddress } from '../../../helpers/types';
|
||||||
|
import { AbiCoder, formatEther, verifyTypedData } from 'ethers/lib/utils';
|
||||||
|
import { stat } from 'fs';
|
||||||
|
|
||||||
|
import { _TypedDataEncoder } from 'ethers/lib/utils';
|
||||||
|
import {
|
||||||
|
buildMetaDepositParams,
|
||||||
|
buildMetaWithdrawParams,
|
||||||
|
buildPermitParams,
|
||||||
|
getSignatureFromTypedData,
|
||||||
|
} from '../../../helpers/contracts-helpers';
|
||||||
|
import { TypedDataUtils, typedSignatureHash, TYPED_MESSAGE_SCHEMA } from 'eth-sig-util';
|
||||||
|
import { zeroAddress } from 'ethereumjs-util';
|
||||||
|
|
||||||
|
const { expect, use } = require('chai');
|
||||||
|
|
||||||
|
use(solidity);
|
||||||
|
|
||||||
|
const DEFAULT_GAS_LIMIT = 10000000;
|
||||||
|
const DEFAULT_GAS_PRICE = utils.parseUnits('100', 'gwei');
|
||||||
|
|
||||||
|
const defaultTxParams = { gasLimit: DEFAULT_GAS_LIMIT, gasPrice: DEFAULT_GAS_PRICE };
|
||||||
|
|
||||||
|
const ETHER_BANK = '0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2';
|
||||||
|
const LENDING_POOL = '0x7d2768dE32b0b80b7a3454c06BdAc94A69DDc7A9';
|
||||||
|
|
||||||
|
const WETH = '0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2';
|
||||||
|
const STKAAVE = '0x4da27a545c0c5B758a6BA100e3a049001de870f5';
|
||||||
|
const AWETH = '0x030bA81f1c18d280636F32af80b9AAd02Cf0854e';
|
||||||
|
|
||||||
|
const TEST_USERS = [
|
||||||
|
'0x0F4ee9631f4be0a63756515141281A3E2B293Bbe',
|
||||||
|
'0x8BffC896D42F07776561A5814D6E4240950d6D3a',
|
||||||
|
];
|
||||||
|
|
||||||
|
type tBalancesInvolved = {
|
||||||
|
staticATokenATokenBalance: BigNumber;
|
||||||
|
staticATokenStkAaveBalance: BigNumber;
|
||||||
|
staticATokenUnderlyingBalance: BigNumber;
|
||||||
|
userStkAaveBalance: BigNumber;
|
||||||
|
userATokenBalance: BigNumber;
|
||||||
|
userUnderlyingBalance: BigNumber;
|
||||||
|
userStaticATokenBalance: BigNumber;
|
||||||
|
userDynamicStaticATokenBalance: BigNumber;
|
||||||
|
userPendingRewards: BigNumber;
|
||||||
|
user2StkAaveBalance: BigNumber;
|
||||||
|
user2ATokenBalance: BigNumber;
|
||||||
|
user2UnderlyingBalance: BigNumber;
|
||||||
|
user2StaticATokenBalance: BigNumber;
|
||||||
|
user2DynamicStaticATokenBalance: BigNumber;
|
||||||
|
user2PendingRewards: BigNumber;
|
||||||
|
currentRate: BigNumber;
|
||||||
|
staticATokenSupply: BigNumber;
|
||||||
|
};
|
||||||
|
|
||||||
|
type tContextParams = {
|
||||||
|
staticAToken: StaticATokenLM;
|
||||||
|
underlying: ERC20;
|
||||||
|
aToken: ERC20;
|
||||||
|
stkAave: ERC20;
|
||||||
|
user: tEthereumAddress;
|
||||||
|
user2: tEthereumAddress;
|
||||||
|
lendingPool: LendingPool;
|
||||||
|
};
|
||||||
|
|
||||||
|
const getContext = async ({
|
||||||
|
staticAToken,
|
||||||
|
underlying,
|
||||||
|
aToken,
|
||||||
|
stkAave,
|
||||||
|
user,
|
||||||
|
user2,
|
||||||
|
lendingPool,
|
||||||
|
}: tContextParams): Promise<tBalancesInvolved> => ({
|
||||||
|
staticATokenATokenBalance: await aToken.balanceOf(staticAToken.address),
|
||||||
|
staticATokenStkAaveBalance: await stkAave.balanceOf(staticAToken.address),
|
||||||
|
staticATokenUnderlyingBalance: await underlying.balanceOf(staticAToken.address),
|
||||||
|
userStaticATokenBalance: await staticAToken.balanceOf(user),
|
||||||
|
userStkAaveBalance: await stkAave.balanceOf(user),
|
||||||
|
userATokenBalance: await aToken.balanceOf(user),
|
||||||
|
userUnderlyingBalance: await underlying.balanceOf(user),
|
||||||
|
userDynamicStaticATokenBalance: await staticAToken.dynamicBalanceOf(user),
|
||||||
|
userPendingRewards: await staticAToken.getClaimableRewards(user),
|
||||||
|
user2StkAaveBalance: await stkAave.balanceOf(user2),
|
||||||
|
user2ATokenBalance: await aToken.balanceOf(user2),
|
||||||
|
user2UnderlyingBalance: await underlying.balanceOf(user2),
|
||||||
|
user2StaticATokenBalance: await staticAToken.balanceOf(user2),
|
||||||
|
user2DynamicStaticATokenBalance: await staticAToken.dynamicBalanceOf(user2),
|
||||||
|
user2PendingRewards: await staticAToken.getClaimableRewards(user2),
|
||||||
|
currentRate: await lendingPool.getReserveNormalizedIncome(WETH),
|
||||||
|
staticATokenSupply: await staticAToken.totalSupply(),
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('StaticATokenLM: aToken wrapper with static balances and liquidity mining', () => {
|
||||||
|
let userSigner: providers.JsonRpcSigner;
|
||||||
|
let user2Signer: providers.JsonRpcSigner;
|
||||||
|
let lendingPool: LendingPool;
|
||||||
|
let weth: WETH9;
|
||||||
|
let aweth: AToken;
|
||||||
|
let stkAave: ERC20;
|
||||||
|
|
||||||
|
let staticAToken: StaticATokenLM;
|
||||||
|
|
||||||
|
let snap: string;
|
||||||
|
|
||||||
|
let ctxtParams: tContextParams;
|
||||||
|
|
||||||
|
before(async () => {
|
||||||
|
await rawDRE.run('set-DRE');
|
||||||
|
|
||||||
|
const [user1, user2] = await DRE.ethers.getSigners();
|
||||||
|
userSigner = DRE.ethers.provider.getSigner(await user1.getAddress());
|
||||||
|
user2Signer = DRE.ethers.provider.getSigner(await user2.getAddress());
|
||||||
|
lendingPool = LendingPoolFactory.connect(LENDING_POOL, userSigner);
|
||||||
|
|
||||||
|
weth = WETH9Factory.connect(WETH, userSigner);
|
||||||
|
aweth = ATokenFactory.connect(AWETH, userSigner);
|
||||||
|
stkAave = ERC20Factory.connect(STKAAVE, userSigner);
|
||||||
|
|
||||||
|
staticAToken = await new StaticATokenLMFactory(userSigner).deploy(
|
||||||
|
LENDING_POOL,
|
||||||
|
AWETH,
|
||||||
|
'Static Aave Interest Bearing WETH',
|
||||||
|
'stataAAVE'
|
||||||
|
);
|
||||||
|
|
||||||
|
ctxtParams = {
|
||||||
|
staticAToken: <StaticATokenLM>staticAToken,
|
||||||
|
underlying: <ERC20>(<unknown>weth),
|
||||||
|
aToken: <ERC20>aweth,
|
||||||
|
stkAave: <ERC20>stkAave,
|
||||||
|
user: userSigner._address,
|
||||||
|
user2: user2Signer._address,
|
||||||
|
lendingPool,
|
||||||
|
};
|
||||||
|
|
||||||
|
snap = await evmSnapshot();
|
||||||
|
});
|
||||||
|
|
||||||
|
beforeEach(async () => {
|
||||||
|
await evmRevert(snap);
|
||||||
|
snap = await evmSnapshot();
|
||||||
|
});
|
||||||
|
|
||||||
|
after(async () => {
|
||||||
|
await evmRevert(snap);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('Deposit WETH on stataWETH, then withdraw of the whole balance in underlying', async () => {
|
||||||
|
const amountToDeposit = utils.parseEther('5');
|
||||||
|
const amountToWithdraw = MAX_UINT_AMOUNT; // Still need to figure out why this works :eyes:
|
||||||
|
|
||||||
|
// Just preparation
|
||||||
|
await waitForTx(await weth.deposit({ value: amountToDeposit }));
|
||||||
|
await waitForTx(await weth.approve(staticAToken.address, amountToDeposit, defaultTxParams));
|
||||||
|
|
||||||
|
const ctxtInitial = await getContext(ctxtParams);
|
||||||
|
|
||||||
|
await expect(
|
||||||
|
staticAToken.deposit(ZERO_ADDRESS, amountToDeposit, 0, true, defaultTxParams)
|
||||||
|
).to.be.revertedWith('INVALID_RECIPIENT');
|
||||||
|
|
||||||
|
// Depositing
|
||||||
|
await waitForTx(
|
||||||
|
await staticAToken.deposit(userSigner._address, amountToDeposit, 0, true, defaultTxParams)
|
||||||
|
);
|
||||||
|
|
||||||
|
const ctxtAfterDeposit = await getContext(ctxtParams);
|
||||||
|
|
||||||
|
/* console.log(
|
||||||
|
`ScaledBalanceOf ${formatEther(
|
||||||
|
await aweth.scaledBalanceOf(staticAToken.address)
|
||||||
|
)}... Static supply: ${formatEther(await staticAToken.totalSupply())}... ${formatEther(
|
||||||
|
await staticAToken.balanceOf(userSigner._address)
|
||||||
|
)} `
|
||||||
|
);*/
|
||||||
|
|
||||||
|
await expect(
|
||||||
|
staticAToken.withdraw(ZERO_ADDRESS, amountToWithdraw, true, defaultTxParams)
|
||||||
|
).to.be.revertedWith('INVALID_RECIPIENT');
|
||||||
|
|
||||||
|
// Withdrawing all
|
||||||
|
await waitForTx(
|
||||||
|
await staticAToken.withdraw(userSigner._address, amountToWithdraw, true, defaultTxParams)
|
||||||
|
);
|
||||||
|
|
||||||
|
const ctxtAfterWithdrawal = await getContext(ctxtParams);
|
||||||
|
|
||||||
|
// Claiming the rewards
|
||||||
|
await waitForTx(await staticAToken.claimRewards(userSigner._address));
|
||||||
|
|
||||||
|
const ctxtAfterClaim = await getContext(ctxtParams);
|
||||||
|
|
||||||
|
// Check values throughout
|
||||||
|
|
||||||
|
// Check that aWETH balance of staticAToken contract is increased as expected
|
||||||
|
expect(ctxtAfterDeposit.staticATokenATokenBalance).to.be.eq(
|
||||||
|
ctxtInitial.staticATokenATokenBalance.add(amountToDeposit)
|
||||||
|
);
|
||||||
|
|
||||||
|
// Check user WETH balance of user is decreased as expected
|
||||||
|
expect(ctxtAfterDeposit.userUnderlyingBalance).to.be.eq(
|
||||||
|
ctxtInitial.userUnderlyingBalance.sub(amountToDeposit)
|
||||||
|
);
|
||||||
|
/*console.log(
|
||||||
|
`Deposit amount ${formatEther(amountToDeposit)}. Dynamic balance: ${formatEther(
|
||||||
|
ctxtAfterDeposit.userDynamicStaticATokenBalance
|
||||||
|
)}. aWEth bal: ${formatEther(ctxtAfterDeposit.staticATokenATokenBalance)}`
|
||||||
|
);*/
|
||||||
|
expect(ctxtAfterDeposit.userDynamicStaticATokenBalance).to.be.eq(
|
||||||
|
ctxtInitial.userDynamicStaticATokenBalance.add(amountToDeposit)
|
||||||
|
);
|
||||||
|
expect(ctxtAfterDeposit.userDynamicStaticATokenBalance).to.be.eq(
|
||||||
|
ctxtAfterDeposit.staticATokenATokenBalance
|
||||||
|
);
|
||||||
|
expect(ctxtAfterDeposit.staticATokenUnderlyingBalance).to.be.eq(
|
||||||
|
ctxtInitial.staticATokenUnderlyingBalance
|
||||||
|
);
|
||||||
|
expect(ctxtAfterDeposit.userATokenBalance).to.be.eq(ctxtInitial.userATokenBalance);
|
||||||
|
expect(ctxtAfterDeposit.userStkAaveBalance).to.be.eq(0);
|
||||||
|
expect(ctxtAfterDeposit.staticATokenStkAaveBalance).to.be.eq(0);
|
||||||
|
|
||||||
|
expect(
|
||||||
|
ctxtAfterWithdrawal.staticATokenATokenBalance,
|
||||||
|
'INVALID_ATOKEN_BALANCE_ON_STATICATOKEN_AFTER_WITHDRAW'
|
||||||
|
).to.be.eq(
|
||||||
|
BigNumber.from(
|
||||||
|
rayMul(
|
||||||
|
new bnjs(
|
||||||
|
ctxtAfterWithdrawal.staticATokenSupply
|
||||||
|
.add(ctxtAfterDeposit.userStaticATokenBalance)
|
||||||
|
.toString()
|
||||||
|
),
|
||||||
|
new bnjs(ctxtAfterWithdrawal.currentRate.toString())
|
||||||
|
)
|
||||||
|
.minus(
|
||||||
|
rayMul(
|
||||||
|
new bnjs(ctxtAfterDeposit.userStaticATokenBalance.toString()),
|
||||||
|
new bnjs(ctxtAfterWithdrawal.currentRate.toString())
|
||||||
|
)
|
||||||
|
)
|
||||||
|
.toString()
|
||||||
|
)
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(ctxtAfterWithdrawal.userStaticATokenBalance).to.be.eq(0);
|
||||||
|
expect(ctxtAfterWithdrawal.staticATokenSupply).to.be.eq(0);
|
||||||
|
expect(ctxtAfterWithdrawal.staticATokenUnderlyingBalance).to.be.eq(0);
|
||||||
|
|
||||||
|
// Check with possible rounding error. Sometimes we have an issue with it being 0 lower as well.
|
||||||
|
expect(ctxtAfterWithdrawal.staticATokenStkAaveBalance).to.be.gte(
|
||||||
|
ctxtAfterWithdrawal.userPendingRewards
|
||||||
|
);
|
||||||
|
expect(ctxtAfterWithdrawal.staticATokenStkAaveBalance).to.be.lte(
|
||||||
|
ctxtAfterWithdrawal.userPendingRewards.add(1)
|
||||||
|
);
|
||||||
|
expect(ctxtAfterWithdrawal.userStkAaveBalance).to.be.eq(0);
|
||||||
|
|
||||||
|
expect(ctxtAfterClaim.userStkAaveBalance).to.be.eq(ctxtAfterWithdrawal.userPendingRewards);
|
||||||
|
expect(ctxtAfterClaim.staticATokenStkAaveBalance).to.be.lte(1);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('Deposit WETH on stataWETH and then withdraw some balance in underlying', async () => {
|
||||||
|
const amountToDeposit = utils.parseEther('5');
|
||||||
|
const amountToWithdraw = utils.parseEther('2.5');
|
||||||
|
|
||||||
|
// Preparation
|
||||||
|
await waitForTx(await weth.deposit({ value: amountToDeposit }));
|
||||||
|
await waitForTx(await weth.approve(staticAToken.address, amountToDeposit, defaultTxParams));
|
||||||
|
|
||||||
|
const ctxtInitial = await getContext(ctxtParams);
|
||||||
|
|
||||||
|
// Deposit
|
||||||
|
await waitForTx(
|
||||||
|
await staticAToken.deposit(userSigner._address, amountToDeposit, 0, true, defaultTxParams)
|
||||||
|
);
|
||||||
|
|
||||||
|
const ctxtAfterDeposit = await getContext(ctxtParams);
|
||||||
|
|
||||||
|
// Withdraw
|
||||||
|
await waitForTx(
|
||||||
|
await staticAToken.withdraw(userSigner._address, amountToWithdraw, true, defaultTxParams)
|
||||||
|
);
|
||||||
|
const ctxtAfterWithdrawal = await getContext(ctxtParams);
|
||||||
|
|
||||||
|
// Claim
|
||||||
|
await waitForTx(await staticAToken.claimRewards(userSigner._address));
|
||||||
|
const ctxtAfterClaim = await getContext(ctxtParams);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('Deposit WETH on stataWETH and then withdraw all the balance in aToken', async () => {
|
||||||
|
const amountToDeposit = utils.parseEther('5');
|
||||||
|
const amountToWithdraw = MAX_UINT_AMOUNT; // Still need to figure out why this works :eyes:
|
||||||
|
|
||||||
|
// Preparation
|
||||||
|
await waitForTx(await weth.deposit({ value: amountToDeposit }));
|
||||||
|
await waitForTx(await weth.approve(staticAToken.address, amountToDeposit, defaultTxParams));
|
||||||
|
|
||||||
|
const ctxtInitial = await getContext(ctxtParams);
|
||||||
|
|
||||||
|
// Deposit
|
||||||
|
await waitForTx(
|
||||||
|
await staticAToken.deposit(userSigner._address, amountToDeposit, 0, true, defaultTxParams)
|
||||||
|
);
|
||||||
|
|
||||||
|
const ctxtAfterDeposit = await getContext(ctxtParams);
|
||||||
|
|
||||||
|
// Withdraw
|
||||||
|
await waitForTx(
|
||||||
|
await staticAToken.withdraw(userSigner._address, amountToWithdraw, false, defaultTxParams)
|
||||||
|
);
|
||||||
|
|
||||||
|
const ctxtAfterWithdrawal = await getContext(ctxtParams);
|
||||||
|
|
||||||
|
// Claim
|
||||||
|
await waitForTx(await staticAToken.claimRewards(userSigner._address));
|
||||||
|
const ctxtAfterClaim = await getContext(ctxtParams);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('Deposit aWETH on stataWETH and then withdraw some balance in aToken', async () => {
|
||||||
|
const amountToDeposit = utils.parseEther('5');
|
||||||
|
const amountToWithdraw = utils.parseEther('2.5');
|
||||||
|
|
||||||
|
// Preparation
|
||||||
|
await waitForTx(await weth.deposit({ value: amountToDeposit }));
|
||||||
|
await waitForTx(await weth.approve(lendingPool.address, amountToDeposit, defaultTxParams));
|
||||||
|
await waitForTx(
|
||||||
|
await lendingPool.deposit(
|
||||||
|
weth.address,
|
||||||
|
amountToDeposit,
|
||||||
|
userSigner._address,
|
||||||
|
0,
|
||||||
|
defaultTxParams
|
||||||
|
)
|
||||||
|
);
|
||||||
|
await waitForTx(await aweth.approve(staticAToken.address, amountToDeposit, defaultTxParams));
|
||||||
|
|
||||||
|
const ctxtInitial = await getContext(ctxtParams);
|
||||||
|
|
||||||
|
// Deposit
|
||||||
|
await waitForTx(
|
||||||
|
await staticAToken.deposit(userSigner._address, amountToDeposit, 0, false, defaultTxParams)
|
||||||
|
);
|
||||||
|
|
||||||
|
const ctxtAfterDeposit = await getContext(ctxtParams);
|
||||||
|
|
||||||
|
// Withdraw
|
||||||
|
await waitForTx(
|
||||||
|
await staticAToken.withdraw(userSigner._address, amountToWithdraw, false, defaultTxParams)
|
||||||
|
);
|
||||||
|
|
||||||
|
const ctxtAfterWithdrawal = await getContext(ctxtParams);
|
||||||
|
|
||||||
|
// Claim
|
||||||
|
await waitForTx(await staticAToken.claimRewards(userSigner._address));
|
||||||
|
const ctxtAfterClaim = await getContext(ctxtParams);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('Transfer with permit() (expect fail)', async () => {
|
||||||
|
const amountToDeposit = utils.parseEther('5');
|
||||||
|
const amountToWithdraw = MAX_UINT_AMOUNT; // Still need to figure out why this works :eyes:
|
||||||
|
|
||||||
|
// Just preparation
|
||||||
|
await waitForTx(await weth.deposit({ value: amountToDeposit }));
|
||||||
|
await waitForTx(await weth.approve(staticAToken.address, amountToDeposit, defaultTxParams));
|
||||||
|
|
||||||
|
// Depositing
|
||||||
|
await waitForTx(
|
||||||
|
await staticAToken.deposit(userSigner._address, amountToDeposit, 0, true, defaultTxParams)
|
||||||
|
);
|
||||||
|
|
||||||
|
const ownerPrivateKey = require('../../../test-wallets.js').accounts[0].secretKey;
|
||||||
|
if (!ownerPrivateKey) {
|
||||||
|
throw new Error('INVALID_OWNER_PK');
|
||||||
|
}
|
||||||
|
|
||||||
|
const owner = userSigner;
|
||||||
|
const spender = user2Signer;
|
||||||
|
|
||||||
|
const tokenName = await staticAToken.name();
|
||||||
|
|
||||||
|
const chainId = DRE.network.config.chainId || 1;
|
||||||
|
const expiration = 0;
|
||||||
|
const nonce = (await staticAToken._nonces(owner._address)).toNumber();
|
||||||
|
const permitAmount = ethers.utils.parseEther('2').toString();
|
||||||
|
const msgParams = buildPermitParams(
|
||||||
|
chainId,
|
||||||
|
staticAToken.address,
|
||||||
|
'1',
|
||||||
|
tokenName,
|
||||||
|
owner._address,
|
||||||
|
spender._address,
|
||||||
|
nonce,
|
||||||
|
expiration.toFixed(),
|
||||||
|
permitAmount
|
||||||
|
);
|
||||||
|
|
||||||
|
expect((await staticAToken.allowance(owner._address, spender._address)).toString()).to.be.equal(
|
||||||
|
'0',
|
||||||
|
'INVALID_ALLOWANCE_BEFORE_PERMIT'
|
||||||
|
);
|
||||||
|
|
||||||
|
const { v, r, s } = getSignatureFromTypedData(ownerPrivateKey, msgParams);
|
||||||
|
|
||||||
|
await expect(
|
||||||
|
staticAToken
|
||||||
|
.connect(spender)
|
||||||
|
.permit(ZERO_ADDRESS, spender._address, permitAmount, expiration, v, r, s, chainId)
|
||||||
|
).to.be.revertedWith('INVALID_OWNER');
|
||||||
|
|
||||||
|
await expect(
|
||||||
|
staticAToken
|
||||||
|
.connect(spender)
|
||||||
|
.permit(owner._address, spender._address, permitAmount, expiration, v, r, s, chainId)
|
||||||
|
).to.be.revertedWith('INVALID_EXPIRATION');
|
||||||
|
|
||||||
|
expect((await staticAToken.allowance(owner._address, spender._address)).toString()).to.be.equal(
|
||||||
|
'0',
|
||||||
|
'INVALID_ALLOWANCE_AFTER_PERMIT'
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('Transfer with permit()', async () => {
|
||||||
|
const amountToDeposit = utils.parseEther('5');
|
||||||
|
const amountToWithdraw = MAX_UINT_AMOUNT; // Still need to figure out why this works :eyes:
|
||||||
|
|
||||||
|
// Just preparation
|
||||||
|
await waitForTx(await weth.deposit({ value: amountToDeposit }));
|
||||||
|
await waitForTx(await weth.approve(staticAToken.address, amountToDeposit, defaultTxParams));
|
||||||
|
|
||||||
|
// Depositing
|
||||||
|
await waitForTx(
|
||||||
|
await staticAToken.deposit(userSigner._address, amountToDeposit, 0, true, defaultTxParams)
|
||||||
|
);
|
||||||
|
|
||||||
|
const ownerPrivateKey = require('../../../test-wallets.js').accounts[0].secretKey;
|
||||||
|
if (!ownerPrivateKey) {
|
||||||
|
throw new Error('INVALID_OWNER_PK');
|
||||||
|
}
|
||||||
|
|
||||||
|
const owner = userSigner;
|
||||||
|
const spender = user2Signer;
|
||||||
|
|
||||||
|
const tokenName = await staticAToken.name();
|
||||||
|
|
||||||
|
const chainId = DRE.network.config.chainId || 1;
|
||||||
|
const expiration = MAX_UINT_AMOUNT;
|
||||||
|
const nonce = (await staticAToken._nonces(owner._address)).toNumber();
|
||||||
|
const permitAmount = ethers.utils.parseEther('2').toString();
|
||||||
|
const msgParams = buildPermitParams(
|
||||||
|
chainId,
|
||||||
|
staticAToken.address,
|
||||||
|
'1',
|
||||||
|
tokenName,
|
||||||
|
owner._address,
|
||||||
|
spender._address,
|
||||||
|
nonce,
|
||||||
|
expiration,
|
||||||
|
permitAmount
|
||||||
|
);
|
||||||
|
|
||||||
|
expect((await staticAToken.allowance(owner._address, spender._address)).toString()).to.be.equal(
|
||||||
|
'0',
|
||||||
|
'INVALID_ALLOWANCE_BEFORE_PERMIT'
|
||||||
|
);
|
||||||
|
|
||||||
|
const { v, r, s } = getSignatureFromTypedData(ownerPrivateKey, msgParams);
|
||||||
|
|
||||||
|
await expect(
|
||||||
|
staticAToken
|
||||||
|
.connect(spender)
|
||||||
|
.permit(spender._address, spender._address, permitAmount, expiration, v, r, s, chainId)
|
||||||
|
).to.be.revertedWith('INVALID_SIGNATURE');
|
||||||
|
|
||||||
|
await waitForTx(
|
||||||
|
await staticAToken
|
||||||
|
.connect(spender)
|
||||||
|
.permit(owner._address, spender._address, permitAmount, expiration, v, r, s, chainId)
|
||||||
|
);
|
||||||
|
|
||||||
|
expect((await staticAToken.allowance(owner._address, spender._address)).toString()).to.be.equal(
|
||||||
|
permitAmount,
|
||||||
|
'INVALID_ALLOWANCE_AFTER_PERMIT'
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('Deposit using metaDeposit()', async () => {
|
||||||
|
// What is a metadeposit
|
||||||
|
const amountToDeposit = utils.parseEther('5');
|
||||||
|
const chainId = DRE.network.config.chainId ? DRE.network.config.chainId : 1;
|
||||||
|
|
||||||
|
const domain = {
|
||||||
|
name: await staticAToken.name(),
|
||||||
|
version: '1',
|
||||||
|
chainId: chainId,
|
||||||
|
verifyingContract: staticAToken.address,
|
||||||
|
};
|
||||||
|
const domainSeperator = _TypedDataEncoder.hashDomain(domain);
|
||||||
|
const seperator = await staticAToken.getDomainSeparator(chainId);
|
||||||
|
expect(seperator).to.be.eq(domainSeperator);
|
||||||
|
|
||||||
|
const userPrivateKey = require('../../../test-wallets.js').accounts[0].secretKey;
|
||||||
|
if (!userPrivateKey) {
|
||||||
|
throw new Error('INVALID_OWNER_PK');
|
||||||
|
}
|
||||||
|
|
||||||
|
// Preparation
|
||||||
|
await waitForTx(await weth.deposit({ value: amountToDeposit }));
|
||||||
|
await waitForTx(await weth.approve(staticAToken.address, amountToDeposit, defaultTxParams));
|
||||||
|
|
||||||
|
// Here it begins
|
||||||
|
const tokenName = await staticAToken.name();
|
||||||
|
const nonce = (await staticAToken._nonces(userSigner._address)).toNumber();
|
||||||
|
const value = amountToDeposit.toString();
|
||||||
|
const referralCode = 0;
|
||||||
|
const depositor = userSigner._address;
|
||||||
|
const recipient = userSigner._address;
|
||||||
|
const fromUnderlying = true;
|
||||||
|
const deadline = MAX_UINT_AMOUNT; // (await timeLatest()).plus(60 * 60).toFixed();
|
||||||
|
|
||||||
|
const msgParams = buildMetaDepositParams(
|
||||||
|
chainId,
|
||||||
|
staticAToken.address,
|
||||||
|
'1',
|
||||||
|
tokenName,
|
||||||
|
depositor,
|
||||||
|
recipient,
|
||||||
|
referralCode,
|
||||||
|
fromUnderlying,
|
||||||
|
nonce,
|
||||||
|
deadline,
|
||||||
|
value
|
||||||
|
);
|
||||||
|
|
||||||
|
const { v, r, s } = getSignatureFromTypedData(userPrivateKey, msgParams);
|
||||||
|
|
||||||
|
const sigParams = {
|
||||||
|
v,
|
||||||
|
r,
|
||||||
|
s,
|
||||||
|
};
|
||||||
|
|
||||||
|
const ctxtInitial = await getContext(ctxtParams);
|
||||||
|
|
||||||
|
await expect(
|
||||||
|
staticAToken
|
||||||
|
.connect(user2Signer)
|
||||||
|
.metaDeposit(
|
||||||
|
ZERO_ADDRESS,
|
||||||
|
recipient,
|
||||||
|
value,
|
||||||
|
referralCode,
|
||||||
|
fromUnderlying,
|
||||||
|
deadline,
|
||||||
|
sigParams,
|
||||||
|
chainId
|
||||||
|
)
|
||||||
|
).to.be.revertedWith('INVALID_DEPOSITOR');
|
||||||
|
|
||||||
|
await expect(
|
||||||
|
staticAToken
|
||||||
|
.connect(user2Signer)
|
||||||
|
.metaDeposit(
|
||||||
|
depositor,
|
||||||
|
recipient,
|
||||||
|
value,
|
||||||
|
referralCode,
|
||||||
|
fromUnderlying,
|
||||||
|
0,
|
||||||
|
sigParams,
|
||||||
|
chainId
|
||||||
|
)
|
||||||
|
).to.be.revertedWith('INVALID_EXPIRATION');
|
||||||
|
|
||||||
|
await expect(
|
||||||
|
staticAToken
|
||||||
|
.connect(user2Signer)
|
||||||
|
.metaDeposit(
|
||||||
|
user2Signer._address,
|
||||||
|
recipient,
|
||||||
|
value,
|
||||||
|
referralCode,
|
||||||
|
fromUnderlying,
|
||||||
|
deadline,
|
||||||
|
sigParams,
|
||||||
|
chainId
|
||||||
|
)
|
||||||
|
).to.be.revertedWith('INVALID_SIGNATURE');
|
||||||
|
|
||||||
|
// Deposit
|
||||||
|
await waitForTx(
|
||||||
|
await staticAToken
|
||||||
|
.connect(user2Signer)
|
||||||
|
.metaDeposit(
|
||||||
|
depositor,
|
||||||
|
recipient,
|
||||||
|
value,
|
||||||
|
referralCode,
|
||||||
|
fromUnderlying,
|
||||||
|
deadline,
|
||||||
|
sigParams,
|
||||||
|
chainId
|
||||||
|
)
|
||||||
|
);
|
||||||
|
|
||||||
|
const ctxtAfterDeposit = await getContext(ctxtParams);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('Withdraw using withdrawDynamicAmount()', async () => {
|
||||||
|
const amountToDeposit = utils.parseEther('5');
|
||||||
|
const amountToWithdraw = utils.parseEther('1');
|
||||||
|
|
||||||
|
// Preparation
|
||||||
|
await waitForTx(await weth.deposit({ value: amountToDeposit }));
|
||||||
|
await waitForTx(await weth.approve(staticAToken.address, amountToDeposit, defaultTxParams));
|
||||||
|
|
||||||
|
const ctxtInitial = await getContext(ctxtParams);
|
||||||
|
|
||||||
|
// Deposit
|
||||||
|
await waitForTx(
|
||||||
|
await staticAToken.deposit(userSigner._address, amountToDeposit, 0, true, defaultTxParams)
|
||||||
|
);
|
||||||
|
|
||||||
|
const ctxtAfterDeposit = await getContext(ctxtParams);
|
||||||
|
|
||||||
|
// Withdraw dynamic amount
|
||||||
|
await waitForTx(
|
||||||
|
await staticAToken.withdrawDynamicAmount(
|
||||||
|
userSigner._address,
|
||||||
|
amountToWithdraw,
|
||||||
|
false,
|
||||||
|
defaultTxParams
|
||||||
|
)
|
||||||
|
);
|
||||||
|
|
||||||
|
const ctxtAfterWithdrawal = await getContext(ctxtParams);
|
||||||
|
|
||||||
|
// Claim
|
||||||
|
await waitForTx(await staticAToken.claimRewards(userSigner._address));
|
||||||
|
const ctxtAfterClaim = await getContext(ctxtParams);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('Withdraw using metaWithdraw()', async () => {
|
||||||
|
// What is a metadeposit
|
||||||
|
const amountToDeposit = utils.parseEther('5');
|
||||||
|
const chainId = DRE.network.config.chainId ? DRE.network.config.chainId : 1;
|
||||||
|
|
||||||
|
const domain = {
|
||||||
|
name: await staticAToken.name(),
|
||||||
|
version: '1',
|
||||||
|
chainId: chainId,
|
||||||
|
verifyingContract: staticAToken.address,
|
||||||
|
};
|
||||||
|
const domainSeperator = _TypedDataEncoder.hashDomain(domain);
|
||||||
|
const seperator = await staticAToken.getDomainSeparator(chainId);
|
||||||
|
expect(seperator).to.be.eq(domainSeperator);
|
||||||
|
|
||||||
|
const userPrivateKey = require('../../../test-wallets.js').accounts[0].secretKey;
|
||||||
|
if (!userPrivateKey) {
|
||||||
|
throw new Error('INVALID_OWNER_PK');
|
||||||
|
}
|
||||||
|
|
||||||
|
// Preparation
|
||||||
|
await waitForTx(await weth.deposit({ value: amountToDeposit }));
|
||||||
|
await waitForTx(await weth.approve(staticAToken.address, amountToDeposit, defaultTxParams));
|
||||||
|
|
||||||
|
// Deposit
|
||||||
|
await waitForTx(
|
||||||
|
await staticAToken.deposit(userSigner._address, amountToDeposit, 0, true, defaultTxParams)
|
||||||
|
);
|
||||||
|
|
||||||
|
// Meta withdraw
|
||||||
|
const tokenName = await staticAToken.name();
|
||||||
|
const nonce = (await staticAToken._nonces(userSigner._address)).toNumber();
|
||||||
|
const owner = userSigner._address;
|
||||||
|
const recipient = userSigner._address;
|
||||||
|
const staticAmount = (await staticAToken.balanceOf(userSigner._address)).toString();
|
||||||
|
const dynamicAmount = '0';
|
||||||
|
const toUnderlying = true;
|
||||||
|
const deadline = MAX_UINT_AMOUNT; // (await timeLatest()).plus(60 * 60).toFixed();
|
||||||
|
|
||||||
|
const msgParams = buildMetaWithdrawParams(
|
||||||
|
chainId,
|
||||||
|
staticAToken.address,
|
||||||
|
'1',
|
||||||
|
tokenName,
|
||||||
|
owner,
|
||||||
|
recipient,
|
||||||
|
staticAmount,
|
||||||
|
dynamicAmount,
|
||||||
|
toUnderlying,
|
||||||
|
nonce,
|
||||||
|
deadline
|
||||||
|
);
|
||||||
|
|
||||||
|
const { v, r, s } = getSignatureFromTypedData(userPrivateKey, msgParams);
|
||||||
|
|
||||||
|
const sigParams = {
|
||||||
|
v,
|
||||||
|
r,
|
||||||
|
s,
|
||||||
|
};
|
||||||
|
|
||||||
|
const ctxtInitial = await getContext(ctxtParams);
|
||||||
|
|
||||||
|
await expect(
|
||||||
|
staticAToken
|
||||||
|
.connect(user2Signer)
|
||||||
|
.metaWithdraw(
|
||||||
|
ZERO_ADDRESS,
|
||||||
|
recipient,
|
||||||
|
staticAmount,
|
||||||
|
dynamicAmount,
|
||||||
|
toUnderlying,
|
||||||
|
deadline,
|
||||||
|
sigParams,
|
||||||
|
chainId
|
||||||
|
)
|
||||||
|
).to.be.revertedWith('INVALID_OWNER');
|
||||||
|
|
||||||
|
await expect(
|
||||||
|
staticAToken
|
||||||
|
.connect(user2Signer)
|
||||||
|
.metaWithdraw(
|
||||||
|
owner,
|
||||||
|
recipient,
|
||||||
|
staticAmount,
|
||||||
|
dynamicAmount,
|
||||||
|
toUnderlying,
|
||||||
|
0,
|
||||||
|
sigParams,
|
||||||
|
chainId
|
||||||
|
)
|
||||||
|
).to.be.revertedWith('INVALID_EXPIRATION');
|
||||||
|
|
||||||
|
await expect(
|
||||||
|
staticAToken
|
||||||
|
.connect(user2Signer)
|
||||||
|
.metaWithdraw(
|
||||||
|
user2Signer._address,
|
||||||
|
recipient,
|
||||||
|
staticAmount,
|
||||||
|
dynamicAmount,
|
||||||
|
toUnderlying,
|
||||||
|
deadline,
|
||||||
|
sigParams,
|
||||||
|
chainId
|
||||||
|
)
|
||||||
|
).to.be.revertedWith('INVALID_SIGNATURE');
|
||||||
|
|
||||||
|
// Deposit
|
||||||
|
await waitForTx(
|
||||||
|
await staticAToken
|
||||||
|
.connect(user2Signer)
|
||||||
|
.metaWithdraw(
|
||||||
|
owner,
|
||||||
|
recipient,
|
||||||
|
staticAmount,
|
||||||
|
dynamicAmount,
|
||||||
|
toUnderlying,
|
||||||
|
deadline,
|
||||||
|
sigParams,
|
||||||
|
chainId
|
||||||
|
)
|
||||||
|
);
|
||||||
|
|
||||||
|
const ctxtAfterDeposit = await getContext(ctxtParams);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('Withdraw using metaWithdraw() (expect to fail)', async () => {
|
||||||
|
// What is a metadeposit
|
||||||
|
const amountToDeposit = utils.parseEther('5');
|
||||||
|
const chainId = DRE.network.config.chainId ? DRE.network.config.chainId : 1;
|
||||||
|
|
||||||
|
const domain = {
|
||||||
|
name: await staticAToken.name(),
|
||||||
|
version: '1',
|
||||||
|
chainId: chainId,
|
||||||
|
verifyingContract: staticAToken.address,
|
||||||
|
};
|
||||||
|
const domainSeperator = _TypedDataEncoder.hashDomain(domain);
|
||||||
|
const seperator = await staticAToken.getDomainSeparator(chainId);
|
||||||
|
expect(seperator).to.be.eq(domainSeperator);
|
||||||
|
|
||||||
|
const userPrivateKey = require('../../../test-wallets.js').accounts[0].secretKey;
|
||||||
|
if (!userPrivateKey) {
|
||||||
|
throw new Error('INVALID_OWNER_PK');
|
||||||
|
}
|
||||||
|
|
||||||
|
// Preparation
|
||||||
|
await waitForTx(await weth.deposit({ value: amountToDeposit }));
|
||||||
|
await waitForTx(await weth.approve(staticAToken.address, amountToDeposit, defaultTxParams));
|
||||||
|
|
||||||
|
// Deposit
|
||||||
|
await waitForTx(
|
||||||
|
await staticAToken.deposit(userSigner._address, amountToDeposit, 0, true, defaultTxParams)
|
||||||
|
);
|
||||||
|
|
||||||
|
// Meta withdraw
|
||||||
|
const tokenName = await staticAToken.name();
|
||||||
|
const nonce = (await staticAToken._nonces(userSigner._address)).toNumber();
|
||||||
|
const owner = userSigner._address;
|
||||||
|
const recipient = userSigner._address;
|
||||||
|
const staticAmount = (await staticAToken.balanceOf(userSigner._address)).toString();
|
||||||
|
const dynamicAmount = (
|
||||||
|
await await staticAToken.dynamicBalanceOf(userSigner._address)
|
||||||
|
).toString();
|
||||||
|
const toUnderlying = true;
|
||||||
|
const deadline = MAX_UINT_AMOUNT; // (await timeLatest()).plus(60 * 60).toFixed();
|
||||||
|
|
||||||
|
const msgParams = buildMetaWithdrawParams(
|
||||||
|
chainId,
|
||||||
|
staticAToken.address,
|
||||||
|
'1',
|
||||||
|
tokenName,
|
||||||
|
owner,
|
||||||
|
recipient,
|
||||||
|
staticAmount,
|
||||||
|
dynamicAmount,
|
||||||
|
toUnderlying,
|
||||||
|
nonce,
|
||||||
|
deadline
|
||||||
|
);
|
||||||
|
|
||||||
|
const { v, r, s } = getSignatureFromTypedData(userPrivateKey, msgParams);
|
||||||
|
|
||||||
|
const sigParams = {
|
||||||
|
v,
|
||||||
|
r,
|
||||||
|
s,
|
||||||
|
};
|
||||||
|
|
||||||
|
const ctxtInitial = await getContext(ctxtParams);
|
||||||
|
|
||||||
|
await expect(
|
||||||
|
staticAToken
|
||||||
|
.connect(user2Signer)
|
||||||
|
.metaWithdraw(
|
||||||
|
owner,
|
||||||
|
recipient,
|
||||||
|
staticAmount,
|
||||||
|
dynamicAmount,
|
||||||
|
toUnderlying,
|
||||||
|
deadline,
|
||||||
|
sigParams,
|
||||||
|
chainId
|
||||||
|
)
|
||||||
|
).to.be.revertedWith('ONLY_ONE_AMOUNT_FORMAT_ALLOWED');
|
||||||
|
|
||||||
|
const ctxtAfterDeposit = await getContext(ctxtParams);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('Deposit WETH on stataWETH, then transfer and withdraw of the whole balance in underlying, finally claim', async () => {
|
||||||
|
const amountToDeposit = utils.parseEther('5');
|
||||||
|
const amountToWithdraw = MAX_UINT_AMOUNT; // Still need to figure out why this works :eyes:
|
||||||
|
|
||||||
|
// Preparation
|
||||||
|
await waitForTx(await weth.deposit({ value: amountToDeposit }));
|
||||||
|
await waitForTx(await weth.approve(staticAToken.address, amountToDeposit, defaultTxParams));
|
||||||
|
|
||||||
|
const ctxtInitial = await getContext(ctxtParams);
|
||||||
|
|
||||||
|
// Deposit
|
||||||
|
await waitForTx(
|
||||||
|
await staticAToken.deposit(userSigner._address, amountToDeposit, 0, true, defaultTxParams)
|
||||||
|
);
|
||||||
|
|
||||||
|
const ctxtAfterDeposit = await getContext(ctxtParams);
|
||||||
|
// Transfer staticATokens to other user
|
||||||
|
await waitForTx(
|
||||||
|
await staticAToken.transfer(user2Signer._address, ctxtAfterDeposit.userStaticATokenBalance)
|
||||||
|
);
|
||||||
|
|
||||||
|
const ctxtAfterTransfer = await getContext(ctxtParams);
|
||||||
|
|
||||||
|
// Withdraw
|
||||||
|
await waitForTx(
|
||||||
|
await staticAToken
|
||||||
|
.connect(user2Signer)
|
||||||
|
.withdraw(user2Signer._address, amountToWithdraw, true, defaultTxParams)
|
||||||
|
);
|
||||||
|
|
||||||
|
const ctxtAfterWithdrawal = await getContext(ctxtParams);
|
||||||
|
|
||||||
|
// Claim
|
||||||
|
await waitForTx(await staticAToken.claimRewards(user2Signer._address));
|
||||||
|
const ctxtAfterClaim = await getContext(ctxtParams);
|
||||||
|
|
||||||
|
// TODO: Need to do some checks with the transferred (fresh rewards) as well.
|
||||||
|
// e.g., we need to show that the received is more than he have gained "by himself" in the same period.
|
||||||
|
|
||||||
|
// Checks
|
||||||
|
expect(ctxtAfterDeposit.staticATokenATokenBalance).to.be.eq(
|
||||||
|
ctxtInitial.staticATokenATokenBalance.add(amountToDeposit)
|
||||||
|
);
|
||||||
|
expect(ctxtAfterDeposit.userUnderlyingBalance).to.be.eq(
|
||||||
|
ctxtInitial.userUnderlyingBalance.sub(amountToDeposit)
|
||||||
|
);
|
||||||
|
expect(ctxtAfterTransfer.user2StaticATokenBalance).to.be.eq(
|
||||||
|
ctxtAfterDeposit.userStaticATokenBalance
|
||||||
|
);
|
||||||
|
expect(ctxtAfterTransfer.userStaticATokenBalance).to.be.eq(0);
|
||||||
|
expect(ctxtAfterTransfer.userPendingRewards).to.be.eq(0);
|
||||||
|
expect(ctxtAfterTransfer.user2PendingRewards).to.be.eq(0);
|
||||||
|
expect(ctxtAfterWithdrawal.staticATokenSupply).to.be.eq(0);
|
||||||
|
expect(ctxtAfterWithdrawal.staticATokenATokenBalance).to.be.eq(0);
|
||||||
|
expect(ctxtAfterWithdrawal.userPendingRewards).to.be.eq(0);
|
||||||
|
expect(ctxtAfterWithdrawal.user2PendingRewards).to.be.lte(
|
||||||
|
ctxtAfterWithdrawal.staticATokenStkAaveBalance
|
||||||
|
);
|
||||||
|
expect(ctxtAfterClaim.user2StkAaveBalance).to.be.eq(ctxtAfterWithdrawal.user2PendingRewards);
|
||||||
|
expect(ctxtAfterClaim.userStkAaveBalance).to.be.eq(0);
|
||||||
|
expect(ctxtAfterClaim.staticATokenStkAaveBalance).to.be.eq(
|
||||||
|
ctxtAfterWithdrawal.staticATokenStkAaveBalance.sub(ctxtAfterWithdrawal.user2PendingRewards)
|
||||||
|
);
|
||||||
|
// Expect dust to be left in the contract
|
||||||
|
expect(ctxtAfterClaim.staticATokenStkAaveBalance).to.be.lt(5);
|
||||||
|
});
|
||||||
|
});
|
Loading…
Reference in New Issue
Block a user