feat: Added Rewards Aware ATokens tests and scripts.

This commit is contained in:
David Racero 2021-05-12 17:09:49 +02:00
parent 55a9880c79
commit d6ede6c87e
23 changed files with 1070 additions and 207 deletions

View File

@ -70,4 +70,5 @@ export const MOCK_CHAINLINK_AGGREGATORS_PRICES = {
STAKE: oneEther.multipliedBy('0.003620948469').toFixed(),
xSUSHI: oneEther.multipliedBy('0.00913428586').toFixed(),
USD: '5848466240000000',
REW: oneEther.multipliedBy('0.00137893825230').toFixed(),
};

View File

@ -1,5 +1,5 @@
import { Contract } from 'ethers';
import { DRE } from './misc-utils';
import { DRE, notFalsyOrZeroAddress } from './misc-utils';
import {
tEthereumAddress,
eContractid,
@ -10,12 +10,12 @@ import {
IReserveParams,
PoolConfiguration,
eEthereumNetwork,
eNetwork,
} from './types';
import { MintableERC20 } from '../types/MintableERC20';
import { MockContract } from 'ethereum-waffle';
import { getReservesConfigByPool } from './configuration';
import { ConfigNames, getReservesConfigByPool, loadPoolConfig } from './configuration';
import { getFirstSigner } from './contracts-getters';
import { ZERO_ADDRESS } from './constants';
import {
AaveProtocolDataProviderFactory,
ATokenFactory,
@ -49,6 +49,8 @@ import {
WETH9MockedFactory,
WETHGatewayFactory,
FlashLiquidationAdapterFactory,
RewardsTokenFactory,
RewardsATokenMockFactory,
} from '../types';
import {
withSaveAndVerify,
@ -57,6 +59,8 @@ import {
insertContractAddressInDb,
deployContract,
verifyContract,
getParamPerNetwork,
getOptionalParamAddressPerNetwork,
} from './contracts-helpers';
import { StableAndVariableTokensHelperFactory } from '../types/StableAndVariableTokensHelperFactory';
import { MintableDelegationERC20 } from '../types/MintableDelegationERC20';
@ -347,20 +351,20 @@ export const deployVariableDebtToken = async (
return instance;
};
export const deployGenericStableDebtToken = async () =>
export const deployGenericStableDebtToken = async (verify?: boolean) =>
withSaveAndVerify(
await new StableDebtTokenFactory(await getFirstSigner()).deploy(),
eContractid.StableDebtToken,
[],
false
verify
);
export const deployGenericVariableDebtToken = async () =>
export const deployGenericVariableDebtToken = async (verify?: boolean) =>
withSaveAndVerify(
await new VariableDebtTokenFactory(await getFirstSigner()).deploy(),
eContractid.VariableDebtToken,
[],
false
verify
);
export const deployGenericAToken = async (
@ -395,7 +399,7 @@ export const deployGenericAToken = async (
return instance;
};
export const deployGenericATokenImpl = async (verify: boolean) =>
export const deployGenericATokenImpl = async (verify?: boolean) =>
withSaveAndVerify(
await new ATokenFactory(await getFirstSigner()).deploy(),
eContractid.AToken,
@ -435,7 +439,7 @@ export const deployDelegationAwareAToken = async (
return instance;
};
export const deployDelegationAwareATokenImpl = async (verify: boolean) =>
export const deployDelegationAwareATokenImpl = async (verify?: boolean) =>
withSaveAndVerify(
await new DelegationAwareATokenFactory(await getFirstSigner()).deploy(),
eContractid.DelegationAwareAToken,
@ -473,8 +477,7 @@ export const deployMockTokens = async (config: PoolConfiguration, verify?: boole
[
tokenSymbol,
tokenSymbol,
configData[tokenSymbol as keyof iMultiPoolsAssets<IReserveParams>].reserveDecimals ||
defaultDecimals.toString(),
configData[tokenSymbol]?.reserveDecimals || defaultDecimals.toString(),
],
verify
);
@ -633,3 +636,81 @@ export const deployFlashLiquidationAdapter = async (
args,
verify
);
export const deployRewardsMockedToken = async (verify?: boolean) =>
withSaveAndVerify(
await new RewardsTokenFactory(await getFirstSigner()).deploy(),
eContractid.RewardsToken,
[],
verify
);
export const deployRewardATokenMock = async (verify?: boolean) => {
return withSaveAndVerify(
await new RewardsATokenMockFactory(await getFirstSigner()).deploy(),
eContractid.RewardsATokenMock,
[],
verify
);
};
export const chooseATokenDeployment = (id: eContractid) => {
switch (id) {
case eContractid.AToken:
return deployGenericATokenImpl;
case eContractid.DelegationAwareAToken:
return deployDelegationAwareATokenImpl;
case eContractid.RewardsATokenMock:
return deployRewardATokenMock;
default:
throw Error(`Missing aToken implementation deployment script for: ${id}`);
}
};
export const deployATokenImplementations = async (
pool: ConfigNames,
reservesConfig: { [key: string]: IReserveParams },
verify?: boolean
) => {
const poolConfig = loadPoolConfig(pool);
const network = <eNetwork>DRE.network.name;
// Obtain the different AToken implementations of all reserves inside the Market config
const aTokenImplementations = [
...Object.entries(reservesConfig).reduce<Set<eContractid>>((acc, [, entry]) => {
acc.add(entry.aTokenImpl);
return acc;
}, new Set<eContractid>()),
];
console.log(aTokenImplementations);
for (let x = 0; x < aTokenImplementations.length; x++) {
const aTokenAddress = getOptionalParamAddressPerNetwork(
poolConfig[aTokenImplementations[x].toString()],
network
);
if (!notFalsyOrZeroAddress(aTokenAddress)) {
const deployImplementationMethod = chooseATokenDeployment(aTokenImplementations[x]);
console.log(`Deploying implementation`, aTokenImplementations[x]);
await deployImplementationMethod(verify);
}
}
// Debt tokens, for now all Market configs follows same implementations
const genericStableDebtTokenAddress = getOptionalParamAddressPerNetwork(
poolConfig.StableDebtTokenImplementation,
network
);
const geneticVariableDebtTokenAddress = getOptionalParamAddressPerNetwork(
poolConfig.VariableDebtTokenImplementation,
network
);
if (!notFalsyOrZeroAddress(genericStableDebtTokenAddress)) {
await deployGenericStableDebtToken(verify);
}
if (!notFalsyOrZeroAddress(geneticVariableDebtTokenAddress)) {
await deployGenericVariableDebtToken(verify);
}
};

View File

@ -30,6 +30,8 @@ import {
WETH9MockedFactory,
WETHGatewayFactory,
FlashLiquidationAdapterFactory,
RewardsATokenMockFactory,
RewardsTokenFactory,
} from '../types';
import { IERC20DetailedFactory } from '../types/IERC20DetailedFactory';
import { MockTokenMap } from './contracts-helpers';
@ -364,3 +366,20 @@ export const getFlashLiquidationAdapter = async (address?: tEthereumAddress) =>
.address,
await getFirstSigner()
);
export const getRewardsToken = async (address?: tEthereumAddress) =>
await RewardsTokenFactory.connect(
address ||
(await getDb().get(`${eContractid.RewardsToken}.${DRE.network.name}`).value()).address,
await getFirstSigner()
);
export const getRewardsATokenMock = async (address?: tEthereumAddress) =>
await RewardsATokenMockFactory.connect(
address ||
(await getDb().get(`${eContractid.RewardsATokenMock}.${DRE.network.name}`).value()).address,
await getFirstSigner()
);
export const getRewardsAToken = async (address: tEthereumAddress) =>
await RewardsATokenMockFactory.connect(address, await getFirstSigner());

View File

@ -2,7 +2,7 @@ import { Contract, Signer, utils, ethers, BigNumberish } from 'ethers';
import { signTypedData_v4 } from 'eth-sig-util';
import { fromRpcSig, ECDSASignature } from 'ethereumjs-util';
import BigNumber from 'bignumber.js';
import { getDb, DRE, waitForTx } from './misc-utils';
import { getDb, DRE, waitForTx, notFalsyOrZeroAddress } from './misc-utils';
import {
tEthereumAddress,
eContractid,
@ -23,9 +23,12 @@ import { MintableERC20 } from '../types/MintableERC20';
import { Artifact } from 'hardhat/types';
import { Artifact as BuidlerArtifact } from '@nomiclabs/buidler/types';
import { verifyEtherscanContract } from './etherscan-verification';
import { getIErc20Detailed } from './contracts-getters';
import { getFirstSigner, getIErc20Detailed } from './contracts-getters';
import { usingTenderly, verifyAtTenderly } from './tenderly-utils';
import { usingPolygon, verifyAtPolygon } from './polygon-utils';
import { ConfigNames, loadPoolConfig } from './configuration';
import { ZERO_ADDRESS } from './constants';
import { RewardsTokenFactory, RewardsATokenMockFactory } from '../types';
export type MockTokenMap = { [symbol: string]: MintableERC20 };
@ -173,6 +176,16 @@ export const getParamPerNetwork = <T>(param: iParamsPerNetwork<T>, network: eNet
}
};
export const getOptionalParamAddressPerNetwork = (
param: iParamsPerNetwork<tEthereumAddress> | undefined | null,
network: eNetwork
) => {
if (!param) {
return ZERO_ADDRESS;
}
return getParamPerNetwork(param, network);
};
export const getParamPerPool = <T>({ proto, amm, matic }: iParamsPerPool<T>, pool: AavePools) => {
switch (pool) {
case AavePools.proto:
@ -335,3 +348,23 @@ export const verifyContract = async (
}
return instance;
};
export const getContractAddressWithJsonFallback = async (
id: string,
pool: ConfigNames
): Promise<tEthereumAddress> => {
const poolConfig = loadPoolConfig(pool);
const network = <eNetwork>DRE.network.name;
const db = getDb();
const contractAtMarketConfig = getOptionalParamAddressPerNetwork(poolConfig[id], network);
if (notFalsyOrZeroAddress(contractAtMarketConfig)) {
return contractAtMarketConfig;
}
const contractAtDb = await getDb().get(`${id}.${DRE.network.name}`).value();
if (contractAtDb?.address) {
return contractAtDb.address as tEthereumAddress;
}
throw Error(`Missing contract address ${id} at Market config and JSON local db`);
};

View File

@ -1,48 +1,25 @@
import {
eContractid,
eEthereumNetwork,
eNetwork,
iMultiPoolsAssets,
IReserveParams,
tEthereumAddress,
} from './types';
import { AaveProtocolDataProvider } from '../types/AaveProtocolDataProvider';
import { chunk, DRE, getDb, waitForTx } from './misc-utils';
import { chunk, getDb, waitForTx } from './misc-utils';
import {
getAaveProtocolDataProvider,
getAToken,
getATokensAndRatesHelper,
getLendingPoolAddressesProvider,
getLendingPoolConfiguratorProxy,
getStableAndVariableTokensHelper,
} from './contracts-getters';
import { rawInsertContractAddressInDb } from './contracts-helpers';
import { BigNumber, BigNumberish, Signer } from 'ethers';
import {
deployDefaultReserveInterestRateStrategy,
deployDelegationAwareAToken,
deployDelegationAwareATokenImpl,
deployGenericAToken,
deployGenericATokenImpl,
deployGenericStableDebtToken,
deployGenericVariableDebtToken,
deployStableDebtToken,
deployVariableDebtToken,
} from './contracts-deployments';
import { ZERO_ADDRESS } from './constants';
import { isZeroAddress } from 'ethereumjs-util';
import { DefaultReserveInterestRateStrategy, DelegationAwareAToken } from '../types';
export const chooseATokenDeployment = (id: eContractid) => {
switch (id) {
case eContractid.AToken:
return deployGenericAToken;
case eContractid.DelegationAwareAToken:
return deployDelegationAwareAToken;
default:
throw Error(`Missing aToken deployment script for: ${id}`);
}
};
getContractAddressWithJsonFallback,
rawInsertContractAddressInDb,
} from './contracts-helpers';
import { BigNumber, BigNumberish, Signer } from 'ethers';
import { deployDefaultReserveInterestRateStrategy } from './contracts-deployments';
import { ConfigNames } from './configuration';
export const initReservesByHelper = async (
reservesParams: iMultiPoolsAssets<IReserveParams>,
@ -54,19 +31,16 @@ export const initReservesByHelper = async (
admin: tEthereumAddress,
treasuryAddress: tEthereumAddress,
incentivesController: tEthereumAddress,
poolName: ConfigNames,
verify: boolean
): Promise<BigNumber> => {
let gasUsage = BigNumber.from('0');
const stableAndVariableDeployer = await getStableAndVariableTokensHelper();
const addressProvider = await getLendingPoolAddressesProvider();
// CHUNK CONFIGURATION
const initChunks = 4;
// Initialize variables for future reserves initialization
let reserveTokens: string[] = [];
let reserveInitDecimals: string[] = [];
let reserveSymbols: string[] = [];
let initInputParams: {
@ -99,49 +73,8 @@ export const initReservesByHelper = async (
];
let rateStrategies: Record<string, typeof strategyRates> = {};
let strategyAddresses: Record<string, tEthereumAddress> = {};
let strategyAddressPerAsset: Record<string, string> = {};
let aTokenType: Record<string, string> = {};
let delegationAwareATokenImplementationAddress = '';
let aTokenImplementationAddress = '';
let stableDebtTokenImplementationAddress = '';
let variableDebtTokenImplementationAddress = '';
// NOT WORKING ON MATIC, DEPLOYING INDIVIDUAL IMPLs INSTEAD
// const tx1 = await waitForTx(
// await stableAndVariableDeployer.initDeployment([ZERO_ADDRESS], ["1"])
// );
// console.log(tx1.events);
// tx1.events?.forEach((event, index) => {
// stableDebtTokenImplementationAddress = event?.args?.stableToken;
// variableDebtTokenImplementationAddress = event?.args?.variableToken;
// rawInsertContractAddressInDb(`stableDebtTokenImpl`, stableDebtTokenImplementationAddress);
// rawInsertContractAddressInDb(`variableDebtTokenImpl`, variableDebtTokenImplementationAddress);
// });
//gasUsage = gasUsage.add(tx1.gasUsed);
stableDebtTokenImplementationAddress = await (await deployGenericStableDebtToken()).address;
variableDebtTokenImplementationAddress = await (await deployGenericVariableDebtToken()).address;
const aTokenImplementation = await deployGenericATokenImpl(verify);
aTokenImplementationAddress = aTokenImplementation.address;
rawInsertContractAddressInDb(`aTokenImpl`, aTokenImplementationAddress);
const delegatedAwareReserves = Object.entries(reservesParams).filter(
([_, { aTokenImpl }]) => aTokenImpl === eContractid.DelegationAwareAToken
) as [string, IReserveParams][];
if (delegatedAwareReserves.length > 0) {
const delegationAwareATokenImplementation = await deployDelegationAwareATokenImpl(verify);
delegationAwareATokenImplementationAddress = delegationAwareATokenImplementation.address;
rawInsertContractAddressInDb(
`delegationAwareATokenImpl`,
delegationAwareATokenImplementationAddress
);
}
const reserves = Object.entries(reservesParams).filter(
([_, { aTokenImpl }]) =>
aTokenImpl === eContractid.DelegationAwareAToken || aTokenImpl === eContractid.AToken
) as [string, IReserveParams][];
const reserves = Object.entries(reservesParams);
for (let [symbol, params] of reserves) {
const { strategy, aTokenImpl, reserveDecimals } = params;
@ -171,44 +104,30 @@ export const initReservesByHelper = async (
// and once under the actual `strategyASSET` key.
rawInsertContractAddressInDb(strategy.name, strategyAddresses[strategy.name]);
}
strategyAddressPerAsset[symbol] = strategyAddresses[strategy.name];
console.log('Strategy address for asset %s: %s', symbol, strategyAddressPerAsset[symbol]);
if (aTokenImpl === eContractid.AToken) {
aTokenType[symbol] = 'generic';
} else if (aTokenImpl === eContractid.DelegationAwareAToken) {
aTokenType[symbol] = 'delegation aware';
}
reserveInitDecimals.push(reserveDecimals);
reserveTokens.push(tokenAddresses[symbol]);
// Prepare input parameters
reserveSymbols.push(symbol);
}
for (let i = 0; i < reserveSymbols.length; i++) {
let aTokenToUse: string;
if (aTokenType[reserveSymbols[i]] === 'generic') {
aTokenToUse = aTokenImplementationAddress;
} else {
aTokenToUse = delegationAwareATokenImplementationAddress;
}
initInputParams.push({
aTokenImpl: aTokenToUse,
stableDebtTokenImpl: stableDebtTokenImplementationAddress,
variableDebtTokenImpl: variableDebtTokenImplementationAddress,
underlyingAssetDecimals: reserveInitDecimals[i],
interestRateStrategyAddress: strategyAddressPerAsset[reserveSymbols[i]],
underlyingAsset: reserveTokens[i],
aTokenImpl: await getContractAddressWithJsonFallback(aTokenImpl, poolName),
stableDebtTokenImpl: await getContractAddressWithJsonFallback(
eContractid.StableDebtToken,
poolName
),
variableDebtTokenImpl: await getContractAddressWithJsonFallback(
eContractid.VariableDebtToken,
poolName
),
underlyingAssetDecimals: reserveDecimals,
interestRateStrategyAddress: strategyAddresses[strategy.name],
underlyingAsset: tokenAddresses[symbol],
treasury: treasuryAddress,
incentivesController,
underlyingAssetName: reserveSymbols[i],
aTokenName: `${aTokenNamePrefix} ${reserveSymbols[i]}`,
aTokenSymbol: `a${symbolPrefix}${reserveSymbols[i]}`,
variableDebtTokenName: `${variableDebtTokenNamePrefix} ${symbolPrefix}${reserveSymbols[i]}`,
variableDebtTokenSymbol: `variableDebt${symbolPrefix}${reserveSymbols[i]}`,
stableDebtTokenName: `${stableDebtTokenNamePrefix} ${reserveSymbols[i]}`,
stableDebtTokenSymbol: `stableDebt${symbolPrefix}${reserveSymbols[i]}`,
incentivesController: incentivesController,
underlyingAssetName: symbol,
aTokenName: `${aTokenNamePrefix} ${symbol}`,
aTokenSymbol: `a${symbolPrefix}${symbol}`,
variableDebtTokenName: `${variableDebtTokenNamePrefix} ${symbolPrefix}${symbol}`,
variableDebtTokenSymbol: `variableDebt${symbolPrefix}${symbol}`,
stableDebtTokenName: `${stableDebtTokenNamePrefix} ${symbol}`,
stableDebtTokenSymbol: `stableDebt${symbolPrefix}${symbol}`,
params: '0x10',
});
}
@ -218,7 +137,6 @@ export const initReservesByHelper = async (
const chunkedInitInputParams = chunk(initInputParams, initChunks);
const configurator = await getLendingPoolConfiguratorProxy();
//await waitForTx(await addressProvider.setPoolAdmin(admin));
console.log(`- Reserves initialization in ${chunkedInitInputParams.length} txs`);
for (let chunkIndex = 0; chunkIndex < chunkedInitInputParams.length; chunkIndex++) {
@ -228,10 +146,9 @@ export const initReservesByHelper = async (
console.log(` - Reserve ready for: ${chunkedSymbols[chunkIndex].join(', ')}`);
console.log(' * gasUsed', tx3.gasUsed.toString());
//gasUsage = gasUsage.add(tx3.gasUsed);
}
return gasUsage; // Deprecated
return gasUsage;
};
export const getPairsTokenAggregator = (

View File

@ -87,6 +87,8 @@ export enum eContractid {
UniswapLiquiditySwapAdapter = 'UniswapLiquiditySwapAdapter',
UniswapRepayAdapter = 'UniswapRepayAdapter',
FlashLiquidationAdapter = 'FlashLiquidationAdapter',
RewardsATokenMock = 'RewardsATokenMock',
RewardsToken = 'RewardsToken',
}
/*
@ -237,8 +239,9 @@ export interface iAssetBase<T> {
BptWBTCWETH: T;
BptBALWETH: T;
WMATIC: T;
STAKE: T;
xSUSHI: T;
STAKE: T;
REW: T;
}
export type iAssetsWithoutETH<T> = Omit<iAssetBase<T>, 'ETH'>;
@ -352,6 +355,7 @@ export enum TokenContractId {
WMATIC = 'WMATIC',
STAKE = 'STAKE',
xSUSHI = 'xSUSHI',
REW = 'REW',
}
export interface IReserveParams extends IReserveBorrowParams, IReserveCollateralParams {
@ -494,6 +498,8 @@ export interface ICommonConfiguration {
WethGateway: iParamsPerNetwork<tEthereumAddress>;
ReserveFactorTreasuryAddress: iParamsPerNetwork<tEthereumAddress>;
IncentivesController: iParamsPerNetwork<tEthereumAddress>;
StableDebtTokenImplementation?: iParamsPerNetwork<tEthereumAddress>;
VariableDebtTokenImplementation?: iParamsPerNetwork<tEthereumAddress>;
}
export interface IAaveConfiguration extends ICommonConfiguration {

View File

@ -1,6 +1,6 @@
import { eContractid, IReserveParams } from '../../helpers/types';
import {
import {
rateStrategyStableOne,
rateStrategyStableTwo,
rateStrategyStableThree,
@ -21,7 +21,7 @@ export const strategyBUSD: IReserveParams = {
stableBorrowRateEnabled: false,
reserveDecimals: '18',
aTokenImpl: eContractid.AToken,
reserveFactor: '1000'
reserveFactor: '1000',
};
export const strategyDAI: IReserveParams = {
@ -33,7 +33,7 @@ export const strategyDAI: IReserveParams = {
stableBorrowRateEnabled: true,
reserveDecimals: '18',
aTokenImpl: eContractid.AToken,
reserveFactor: '1000'
reserveFactor: '1000',
};
export const strategySUSD: IReserveParams = {
@ -45,7 +45,7 @@ export const strategySUSD: IReserveParams = {
stableBorrowRateEnabled: false,
reserveDecimals: '18',
aTokenImpl: eContractid.AToken,
reserveFactor: '2000'
reserveFactor: '2000',
};
export const strategyTUSD: IReserveParams = {
@ -57,7 +57,7 @@ export const strategyTUSD: IReserveParams = {
stableBorrowRateEnabled: true,
reserveDecimals: '18',
aTokenImpl: eContractid.AToken,
reserveFactor: '1000'
reserveFactor: '1000',
};
export const strategyUSDC: IReserveParams = {
@ -69,7 +69,7 @@ export const strategyUSDC: IReserveParams = {
stableBorrowRateEnabled: true,
reserveDecimals: '6',
aTokenImpl: eContractid.AToken,
reserveFactor: '1000'
reserveFactor: '1000',
};
export const strategyUSDT: IReserveParams = {
@ -81,7 +81,7 @@ export const strategyUSDT: IReserveParams = {
stableBorrowRateEnabled: true,
reserveDecimals: '6',
aTokenImpl: eContractid.AToken,
reserveFactor: '1000'
reserveFactor: '1000',
};
export const strategyAAVE: IReserveParams = {
@ -93,7 +93,7 @@ export const strategyAAVE: IReserveParams = {
stableBorrowRateEnabled: false,
reserveDecimals: '18',
aTokenImpl: eContractid.AToken,
reserveFactor: '0'
reserveFactor: '0',
};
export const strategyBAT: IReserveParams = {
@ -105,7 +105,7 @@ export const strategyBAT: IReserveParams = {
stableBorrowRateEnabled: true,
reserveDecimals: '18',
aTokenImpl: eContractid.AToken,
reserveFactor: '2000'
reserveFactor: '2000',
};
export const strategyENJ: IReserveParams = {
@ -117,7 +117,7 @@ export const strategyENJ: IReserveParams = {
stableBorrowRateEnabled: true,
reserveDecimals: '18',
aTokenImpl: eContractid.AToken,
reserveFactor: '2000'
reserveFactor: '2000',
};
export const strategyWETH: IReserveParams = {
@ -129,7 +129,7 @@ export const strategyWETH: IReserveParams = {
stableBorrowRateEnabled: true,
reserveDecimals: '18',
aTokenImpl: eContractid.AToken,
reserveFactor: '1000'
reserveFactor: '1000',
};
export const strategyKNC: IReserveParams = {
@ -141,7 +141,7 @@ export const strategyKNC: IReserveParams = {
stableBorrowRateEnabled: true,
reserveDecimals: '18',
aTokenImpl: eContractid.AToken,
reserveFactor: '2000'
reserveFactor: '2000',
};
export const strategyLINK: IReserveParams = {
@ -153,7 +153,7 @@ export const strategyLINK: IReserveParams = {
stableBorrowRateEnabled: true,
reserveDecimals: '18',
aTokenImpl: eContractid.AToken,
reserveFactor: '2000'
reserveFactor: '2000',
};
export const strategyMANA: IReserveParams = {
@ -165,7 +165,7 @@ export const strategyMANA: IReserveParams = {
stableBorrowRateEnabled: true,
reserveDecimals: '18',
aTokenImpl: eContractid.AToken,
reserveFactor: '3500'
reserveFactor: '3500',
};
export const strategyMKR: IReserveParams = {
@ -177,7 +177,7 @@ export const strategyMKR: IReserveParams = {
stableBorrowRateEnabled: true,
reserveDecimals: '18',
aTokenImpl: eContractid.AToken,
reserveFactor: '2000'
reserveFactor: '2000',
};
export const strategyREN: IReserveParams = {
@ -189,7 +189,7 @@ export const strategyREN: IReserveParams = {
stableBorrowRateEnabled: true,
reserveDecimals: '18',
aTokenImpl: eContractid.AToken,
reserveFactor: '2000'
reserveFactor: '2000',
};
export const strategySNX: IReserveParams = {
@ -201,7 +201,7 @@ export const strategySNX: IReserveParams = {
stableBorrowRateEnabled: false,
reserveDecimals: '18',
aTokenImpl: eContractid.AToken,
reserveFactor: '3500'
reserveFactor: '3500',
};
// Invalid borrow rates in params currently, replaced with snx params
@ -214,7 +214,7 @@ export const strategyUNI: IReserveParams = {
stableBorrowRateEnabled: false,
reserveDecimals: '18',
aTokenImpl: eContractid.DelegationAwareAToken,
reserveFactor: '2000'
reserveFactor: '2000',
};
export const strategyWBTC: IReserveParams = {
@ -226,7 +226,7 @@ export const strategyWBTC: IReserveParams = {
stableBorrowRateEnabled: true,
reserveDecimals: '8',
aTokenImpl: eContractid.AToken,
reserveFactor: '2000'
reserveFactor: '2000',
};
export const strategyYFI: IReserveParams = {
@ -238,7 +238,7 @@ export const strategyYFI: IReserveParams = {
stableBorrowRateEnabled: true,
reserveDecimals: '18',
aTokenImpl: eContractid.AToken,
reserveFactor: '2000'
reserveFactor: '2000',
};
export const strategyZRX: IReserveParams = {
@ -250,7 +250,7 @@ export const strategyZRX: IReserveParams = {
stableBorrowRateEnabled: true,
reserveDecimals: '18',
aTokenImpl: eContractid.AToken,
reserveFactor: '2000'
reserveFactor: '2000',
};
export const strategyXSUSHI: IReserveParams = {
@ -263,4 +263,9 @@ export const strategyXSUSHI: IReserveParams = {
reserveDecimals: '18',
aTokenImpl: eContractid.AToken,
reserveFactor: '3500',
};
};
export const strategyMockedRewardAwareToken: IReserveParams = {
...strategyBAT,
aTokenImpl: eContractid.RewardsATokenMock,
};

View File

@ -1,5 +1,6 @@
import { task } from 'hardhat/config';
import {
deployATokenImplementations,
deployATokensAndRatesHelper,
deployLendingPool,
deployLendingPoolConfigurator,
@ -13,13 +14,15 @@ import {
getLendingPoolConfiguratorProxy,
} from '../../helpers/contracts-getters';
import { insertContractAddressInDb } from '../../helpers/contracts-helpers';
import { ConfigNames, loadPoolConfig } from '../../helpers/configuration';
task('dev:deploy-lending-pool', 'Deploy lending pool for dev enviroment')
.addFlag('verify', 'Verify contracts at Etherscan')
.setAction(async ({ verify }, localBRE) => {
.addParam('pool', `Pool name to retrieve configuration, supported: ${Object.values(ConfigNames)}`)
.setAction(async ({ verify, pool }, localBRE) => {
await localBRE.run('set-DRE');
const addressesProvider = await getLendingPoolAddressesProvider();
const poolConfig = loadPoolConfig(pool);
const lendingPoolImpl = await deployLendingPool(verify);
@ -55,4 +58,5 @@ task('dev:deploy-lending-pool', 'Deploy lending pool for dev enviroment')
[lendingPoolProxy.address, addressesProvider.address, lendingPoolConfiguratorProxy.address],
verify
);
await deployATokenImplementations(pool, poolConfig.ReservesConfig, verify);
});

View File

@ -41,6 +41,7 @@ task('dev:initialize-lending-pool', 'Initialize lending pool configuration.')
VariableDebtTokenNamePrefix,
SymbolPrefix,
WethGateway,
ReservesConfig,
} = poolConfig;
const mockTokens = await getAllMockedTokens();
const allTokenAddresses = getAllTokenAddresses(mockTokens);
@ -53,14 +54,12 @@ task('dev:initialize-lending-pool', 'Initialize lending pool configuration.')
const testHelpers = await deployAaveProtocolDataProvider(addressesProvider.address, verify);
const reservesParams = getReservesConfigByPool(AavePools.proto);
const admin = await addressesProvider.getPoolAdmin();
const treasuryAddress = await getTreasuryAddress(poolConfig);
await initReservesByHelper(
reservesParams,
ReservesConfig,
protoPoolReservesAddresses,
ATokenNamePrefix,
StableDebtTokenNamePrefix,
@ -69,9 +68,10 @@ task('dev:initialize-lending-pool', 'Initialize lending pool configuration.')
admin,
treasuryAddress,
ZERO_ADDRESS,
pool,
verify
);
await configureReservesByHelper(reservesParams, protoPoolReservesAddresses, testHelpers, admin);
await configureReservesByHelper(ReservesConfig, protoPoolReservesAddresses, testHelpers, admin);
const collateralManager = await deployLendingPoolCollateralManager(verify);
await waitForTx(

View File

@ -1,6 +1,7 @@
import { task } from 'hardhat/config';
import { getParamPerNetwork, insertContractAddressInDb } from '../../helpers/contracts-helpers';
import {
deployATokenImplementations,
deployATokensAndRatesHelper,
deployLendingPool,
deployLendingPoolConfigurator,
@ -78,6 +79,7 @@ task('full:deploy-lending-pool', 'Deploy lending pool for dev enviroment')
[lendingPoolProxy.address, addressesProvider.address, lendingPoolConfiguratorProxy.address],
verify
);
await deployATokenImplementations(pool, poolConfig.ReservesConfig, verify);
} catch (error) {
if (DRE.network.name.includes('tenderly')) {
const transactionLink = `https://dashboard.tenderly.co/${DRE.config.tenderly.username}/${

View File

@ -66,6 +66,7 @@ task('full:initialize-lending-pool', 'Initialize lending pool configuration.')
admin,
treasuryAddress,
incentivesController,
pool,
verify
);
await configureReservesByHelper(ReservesConfig, reserveAssets, testHelpers, admin);

View File

@ -3,9 +3,9 @@ import { eEthereumNetwork } from '../../helpers/types';
import { getTreasuryAddress } from '../../helpers/configuration';
import * as marketConfigs from '../../markets/aave';
import * as reserveConfigs from '../../markets/aave/reservesConfigs';
import { chooseATokenDeployment } from '../../helpers/init-helpers';
import { getLendingPoolAddressesProvider } from './../../helpers/contracts-getters';
import {
chooseATokenDeployment,
deployDefaultReserveInterestRateStrategy,
deployStableDebtToken,
deployVariableDebtToken,
@ -48,17 +48,7 @@ WRONG RESERVE ASSET SETUP:
);
const poolAddress = await addressProvider.getLendingPool();
const treasuryAddress = await getTreasuryAddress(marketConfigs.AaveConfig);
const aToken = await deployCustomAToken(
[
poolAddress,
reserveAssetAddress,
treasuryAddress,
ZERO_ADDRESS, // Incentives Controller
`Aave interest bearing ${symbol}`,
`a${symbol}`,
],
verify
);
const aToken = await deployCustomAToken(verify);
const stableDebt = await deployStableDebtToken(
[
poolAddress,

View File

@ -24,7 +24,7 @@ task('aave:dev', 'Deploy development enviroment')
await localBRE.run('dev:deploy-address-provider', { verify });
console.log('3. Deploy lending pool');
await localBRE.run('dev:deploy-lending-pool', { verify });
await localBRE.run('dev:deploy-lending-pool', { verify, pool: POOL_NAME });
console.log('4. Deploy oracles');
await localBRE.run('dev:deploy-oracles', { verify, pool: POOL_NAME });

View File

@ -27,8 +27,9 @@ import {
deployUniswapRepayAdapter,
deployFlashLiquidationAdapter,
authorizeWETHGateway,
deployATokenImplementations,
deployRewardsMockedToken,
} from '../../helpers/contracts-deployments';
import { eEthereumNetwork } from '../../helpers/types';
import { Signer } from 'ethers';
import { TokenContractId, eContractid, tEthereumAddress, AavePools } from '../../helpers/types';
import { MintableERC20 } from '../../types/MintableERC20';
@ -55,11 +56,12 @@ import {
getPairsTokenAggregator,
} from '../../helpers/contracts-getters';
import { WETH9Mocked } from '../../types/WETH9Mocked';
import { strategyMockedRewardAwareToken } from '../../markets/aave/reservesConfigs';
import { RewardsToken } from '../../types';
const MOCK_USD_PRICE_IN_WEI = AaveConfig.ProtocolGlobalParams.MockUsdPriceInWei;
const ALL_ASSETS_INITIAL_PRICES = AaveConfig.Mocks.AllAssetsInitialPrices;
const USD_ADDRESS = AaveConfig.ProtocolGlobalParams.UsdAddress;
const MOCK_CHAINLINK_AGGREGATORS_PRICES = AaveConfig.Mocks.AllAssetsInitialPrices;
const LENDING_RATE_ORACLE_RATES_COMMON = AaveConfig.LendingRateOracleRatesCommon;
const deployAllMockTokens = async (deployer: Signer) => {
@ -95,9 +97,14 @@ const deployAllMockTokens = async (deployer: Signer) => {
const buildTestEnv = async (deployer: Signer, secondaryWallet: Signer) => {
console.time('setup');
const aaveAdmin = await deployer.getAddress();
const config = loadPoolConfig(ConfigNames.Aave);
const mockTokens = await deployAllMockTokens(deployer);
console.log('Deployed mocks');
const mockTokens: {
[symbol: string]: MockContract | MintableERC20 | WETH9Mocked | RewardsToken;
} = {
...(await deployAllMockTokens(deployer)),
REW: await deployRewardsMockedToken(),
};
const addressesProvider = await deployLendingPoolAddressesProvider(AaveConfig.MarketId);
await waitForTx(await addressesProvider.setPoolAdmin(aaveAdmin));
@ -191,13 +198,13 @@ const buildTestEnv = async (deployer: Signer, secondaryWallet: Signer) => {
WMATIC: mockTokens.WMATIC.address,
USD: USD_ADDRESS,
STAKE: mockTokens.STAKE.address,
xSUSHI: mockTokens.xSUSHI.address
xSUSHI: mockTokens.xSUSHI.address,
REW: mockTokens.REW.address,
},
fallbackOracle
);
const mockAggregators = await deployAllMockAggregators(MOCK_CHAINLINK_AGGREGATORS_PRICES);
console.log('Mock aggs deployed');
const mockAggregators = await deployAllMockAggregators(ALL_ASSETS_INITIAL_PRICES);
const allTokenAddresses = Object.entries(mockTokens).reduce(
(accum: { [tokenSymbol: string]: tEthereumAddress }, [tokenSymbol, tokenContract]) => ({
...accum,
@ -232,17 +239,18 @@ const buildTestEnv = async (deployer: Signer, secondaryWallet: Signer) => {
aaveAdmin
);
const reservesParams = getReservesConfigByPool(AavePools.proto);
// Reserve params from AAVE pool + mocked tokens
const reservesParams = {
...config.ReservesConfig,
REW: strategyMockedRewardAwareToken,
};
const testHelpers = await deployAaveProtocolDataProvider(addressesProvider.address);
await insertContractAddressInDb(eContractid.AaveProtocolDataProvider, testHelpers.address);
await deployATokenImplementations(ConfigNames.Aave, reservesParams, false);
const admin = await deployer.getAddress();
console.log('Initialize configuration');
const config = loadPoolConfig(ConfigNames.Aave);
const {
ATokenNamePrefix,
StableDebtTokenNamePrefix,
@ -261,6 +269,7 @@ const buildTestEnv = async (deployer: Signer, secondaryWallet: Signer) => {
admin,
treasuryAddress,
ZERO_ADDRESS,
ConfigNames.Aave,
false
);

View File

@ -14,6 +14,8 @@ import {
getUniswapLiquiditySwapAdapter,
getUniswapRepayAdapter,
getFlashLiquidationAdapter,
getRewardsToken,
getRewardsATokenMock,
} from '../../../helpers/contracts-getters';
import { eEthereumNetwork, tEthereumAddress } from '../../../helpers/types';
import { LendingPool } from '../../../types/LendingPool';
@ -37,7 +39,7 @@ import { WETH9Mocked } from '../../../types/WETH9Mocked';
import { WETHGateway } from '../../../types/WETHGateway';
import { solidity } from 'ethereum-waffle';
import { AaveConfig } from '../../../markets/aave';
import { FlashLiquidationAdapter } from '../../../types';
import { FlashLiquidationAdapter, RewardsATokenMock, RewardsToken } from '../../../types';
import { HardhatRuntimeEnvironment } from 'hardhat/types';
import { usingTenderly } from '../../../helpers/tenderly-utils';
@ -62,6 +64,8 @@ export interface TestEnv {
aDai: AToken;
usdc: MintableERC20;
aave: MintableERC20;
rew: RewardsToken;
aRew: RewardsATokenMock;
addressesProvider: LendingPoolAddressesProvider;
uniswapLiquiditySwapAdapter: UniswapLiquiditySwapAdapter;
uniswapRepayAdapter: UniswapRepayAdapter;
@ -88,6 +92,8 @@ const testEnv: TestEnv = {
aDai: {} as AToken,
usdc: {} as MintableERC20,
aave: {} as MintableERC20,
rew: {} as RewardsToken,
aRew: {} as RewardsATokenMock,
addressesProvider: {} as LendingPoolAddressesProvider,
uniswapLiquiditySwapAdapter: {} as UniswapLiquiditySwapAdapter,
uniswapRepayAdapter: {} as UniswapRepayAdapter,
@ -129,7 +135,7 @@ export async function initializeMakeSuite() {
const allTokens = await testEnv.helpersContract.getAllATokens();
const aDaiAddress = allTokens.find((aToken) => aToken.symbol === 'aDAI')?.tokenAddress;
const aRewAddress = allTokens.find((aToken) => aToken.symbol === 'aREW')?.tokenAddress;
const aWEthAddress = allTokens.find((aToken) => aToken.symbol === 'aWETH')?.tokenAddress;
const reservesTokens = await testEnv.helpersContract.getAllReservesTokens();
@ -138,6 +144,7 @@ export async function initializeMakeSuite() {
const usdcAddress = reservesTokens.find((token) => token.symbol === 'USDC')?.tokenAddress;
const aaveAddress = reservesTokens.find((token) => token.symbol === 'AAVE')?.tokenAddress;
const wethAddress = reservesTokens.find((token) => token.symbol === 'WETH')?.tokenAddress;
const rewAddress = reservesTokens.find((token) => token.symbol === 'REW')?.tokenAddress;
if (!aDaiAddress || !aWEthAddress) {
process.exit(1);
@ -154,6 +161,8 @@ export async function initializeMakeSuite() {
testEnv.aave = await getMintableERC20(aaveAddress);
testEnv.weth = await getWETHMocked(wethAddress);
testEnv.wethGateway = await getWETHGateway();
testEnv.rew = await getRewardsToken(rewAddress);
testEnv.aRew = await getRewardsATokenMock(aRewAddress);
testEnv.uniswapLiquiditySwapAdapter = await getUniswapLiquiditySwapAdapter();
testEnv.uniswapRepayAdapter = await getUniswapRepayAdapter();

View File

@ -0,0 +1,21 @@
import { BigNumber } from 'ethers';
import {
BigNumberValue,
ethersValueToZDBigNumber,
valueToZDBigNumber,
} from '../utils/ray-math/bignumber';
export function getRewards(
balance: BigNumber,
assetIndex: BigNumber,
userIndex: BigNumber,
precision: number = 18
): BigNumber {
return BigNumber.from(
ethersValueToZDBigNumber(balance)
.multipliedBy(ethersValueToZDBigNumber(assetIndex).minus(ethersValueToZDBigNumber(userIndex)))
.dividedBy(valueToZDBigNumber(10).exponentiatedBy(precision))
.toString()
);
}

View File

@ -0,0 +1,112 @@
import { expect } from 'chai';
import { calcExpectedRewards } from '../utils/calculations';
import { SignerWithAddress } from '../make-suite';
import { ZERO_ADDRESS } from '../../../../helpers/constants';
import { tEthereumAddress } from '../../../../helpers/types';
import { IERC20Factory } from '../../../../types/IERC20Factory';
import { getRewardsAToken } from '../../../../helpers/contracts-getters';
import { BigNumber as EthersBigNumber } from '@ethersproject/bignumber';
import BigNumber from 'bignumber.js';
import '../utils/math';
export const checkRewards = async (
user: SignerWithAddress,
aToken: tEthereumAddress,
block: number,
shouldReward?: boolean,
claimedToken?: tEthereumAddress,
beforeBalanceClaimedToken?: EthersBigNumber
) => {
const rewardAwareToken = await getRewardsAToken(aToken);
const rewardsAvailable = await rewardAwareToken.getRewardsTokenAddressList();
const userBalance = await rewardAwareToken.balanceOf(user.address, { blockTag: block - 1 });
const totalRewardsBefore = new Array(rewardsAvailable.length);
const userRewardsBefore = new Array(rewardsAvailable.length);
const userIndexesBefore = new Array(rewardsAvailable.length);
const totalRewardsAfter = new Array(rewardsAvailable.length);
const userRewardsAfter = new Array(rewardsAvailable.length);
const userIndexesAfter = new Array(rewardsAvailable.length);
const userExpectedRewards = new Array(rewardsAvailable.length);
for (let i = 0; i < rewardsAvailable.length; i++) {
if (rewardsAvailable[i] == ZERO_ADDRESS) break;
// Before action
totalRewardsBefore[i] = await rewardAwareToken.getLifetimeRewards(rewardsAvailable[i], {
blockTag: block - 1,
});
userRewardsBefore[i] = await rewardAwareToken.getUserRewardsAccrued(
rewardsAvailable[i],
user.address,
{
blockTag: block - 1,
}
);
userIndexesBefore[i] = await rewardAwareToken.getUserIndex(rewardsAvailable[i], user.address, {
blockTag: block - 1,
});
// After action
totalRewardsAfter[i] = await rewardAwareToken.getLifetimeRewards(rewardsAvailable[i], {
blockTag: block,
});
userRewardsAfter[i] = await rewardAwareToken.getUserRewardsAccrued(
rewardsAvailable[i],
user.address,
{
blockTag: block,
}
);
userIndexesAfter[i] = await rewardAwareToken.getUserIndex(rewardsAvailable[i], user.address, {
blockTag: block,
});
userExpectedRewards[i] = calcExpectedRewards(
userBalance,
userIndexesAfter[i],
userIndexesBefore[i]
);
// Explicit check rewards when the test case expects rewards to the user
if (shouldReward) {
expect(userRewardsAfter[i]).to.be.gt('0');
expect(userRewardsAfter[i]).to.eq(
userRewardsBefore[i].add(userExpectedRewards[i]),
`User rewards for token ${rewardsAvailable[i]} does not match`
);
if (beforeBalanceClaimedToken && rewardsAvailable[i] === claimedToken) {
const reserveFactor = await rewardAwareToken.getRewardsReserveFactor();
const totalRewards = userRewardsBefore[i].add(userExpectedRewards[i]);
const priorClaimed = await rewardAwareToken.getUserClaimedRewards(
claimedToken,
user.address,
{
blockTag: block - 1,
}
);
const totalClaim = totalRewards.sub(priorClaimed);
const treasureRewards = EthersBigNumber.from(
new BigNumber(totalClaim.toString())
.percentMul(new BigNumber(reserveFactor.toString()))
.toString()
);
const userRewards = totalClaim.sub(treasureRewards);
const afterBalance = await IERC20Factory.connect(claimedToken, user.signer).balanceOf(
user.address
);
expect(afterBalance).to.be.eq(beforeBalanceClaimedToken.add(userRewards));
}
} else {
expect(userExpectedRewards[i]).to.be.eq('0', 'This action should not reward');
expect(userRewardsBefore[i]).to.be.eq(userRewardsAfter[i], 'Rewards should stay the same');
}
}
return;
};

View File

@ -1,14 +1,9 @@
import BigNumber from 'bignumber.js';
import { BigNumber as BigNumberEthers } from 'ethers';
import { ONE_YEAR, RAY, MAX_UINT_AMOUNT, PERCENTAGE_FACTOR } from '../../../../helpers/constants';
import {
IReserveParams,
iAavePoolAssets,
RateMode,
tEthereumAddress,
} from '../../../../helpers/types';
import { IReserveParams, iAavePoolAssets, RateMode } from '../../../../helpers/types';
import './math';
import { ReserveData, UserReserveData } from './interfaces';
import { expect } from 'chai';
export const strToBN = (amount: string): BigNumber => new BigNumber(amount);
@ -1244,7 +1239,9 @@ export const calcExpectedInterestRates = (
];
let stableBorrowRate: BigNumber = marketStableRate;
let variableBorrowRate: BigNumber = new BigNumber(reserveConfiguration.strategy.baseVariableBorrowRate);
let variableBorrowRate: BigNumber = new BigNumber(
reserveConfiguration.strategy.baseVariableBorrowRate
);
const optimalRate = new BigNumber(reserveConfiguration.strategy.optimalUtilizationRate);
const excessRate = new BigNumber(RAY).minus(optimalRate);
@ -1256,13 +1253,17 @@ export const calcExpectedInterestRates = (
stableBorrowRate = stableBorrowRate
.plus(reserveConfiguration.strategy.stableRateSlope1)
.plus(
new BigNumber(reserveConfiguration.strategy.stableRateSlope2).rayMul(excessUtilizationRateRatio)
new BigNumber(reserveConfiguration.strategy.stableRateSlope2).rayMul(
excessUtilizationRateRatio
)
);
variableBorrowRate = variableBorrowRate
.plus(reserveConfiguration.strategy.variableRateSlope1)
.plus(
new BigNumber(reserveConfiguration.strategy.variableRateSlope2).rayMul(excessUtilizationRateRatio)
new BigNumber(reserveConfiguration.strategy.variableRateSlope2).rayMul(
excessUtilizationRateRatio
)
);
} else {
stableBorrowRate = stableBorrowRate.plus(
@ -1433,3 +1434,14 @@ const calcExpectedTotalVariableDebt = (
) => {
return reserveData.scaledVariableDebt.rayMul(expectedVariableDebtIndex);
};
export function calcExpectedRewards(
balance: BigNumberEthers,
userIndexAfter: BigNumberEthers,
userIndexBefore: BigNumberEthers,
precision: number = 24
): BigNumberEthers {
return balance
.mul(userIndexAfter.sub(userIndexBefore))
.div(BigNumberEthers.from(10).pow(precision));
}

View File

@ -0,0 +1,20 @@
import BigNumber from 'bignumber.js';
import { BigNumber as BigNumberEthers, BigNumberish } from 'ethers';
export type BigNumberValue = string | number | BigNumber | BigNumberEthers | BigNumberish;
export const BigNumberZD = BigNumber.clone({
DECIMAL_PLACES: 0,
ROUNDING_MODE: BigNumber.ROUND_DOWN,
});
export function valueToBigNumber(amount: BigNumberValue): BigNumber {
return new BigNumber(amount.toString());
}
export function valueToZDBigNumber(amount: BigNumberValue): BigNumber {
return new BigNumberZD(amount.toString());
}
export function ethersValueToZDBigNumber(amount: BigNumberEthers): BigNumber {
return new BigNumberZD('0x' + amount.toHexString());
}

View File

@ -0,0 +1,40 @@
import BigNumber from 'bignumber.js';
import { BigNumberValue, valueToZDBigNumber } from './bignumber';
export function getLinearCumulatedRewards(
emissionPerSecond: BigNumberValue,
lastUpdateTimestamp: BigNumberValue,
currentTimestamp: BigNumberValue
): BigNumber {
const timeDelta = valueToZDBigNumber(currentTimestamp).minus(lastUpdateTimestamp.toString());
return timeDelta.multipliedBy(emissionPerSecond.toString());
}
export function getNormalizedDistribution(
balance: BigNumberValue,
oldIndex: BigNumberValue,
emissionPerSecond: BigNumberValue,
lastUpdateTimestamp: BigNumberValue,
currentTimestamp: BigNumberValue,
emissionEndTimestamp: BigNumberValue,
precision: number = 18
): BigNumber {
if (
balance.toString() === '0' ||
valueToZDBigNumber(lastUpdateTimestamp).gte(emissionEndTimestamp.toString())
) {
return valueToZDBigNumber(oldIndex);
}
const linearReward = getLinearCumulatedRewards(
emissionPerSecond,
lastUpdateTimestamp,
valueToZDBigNumber(currentTimestamp).gte(emissionEndTimestamp.toString())
? emissionEndTimestamp
: currentTimestamp
);
return linearReward
.multipliedBy(valueToZDBigNumber(10).exponentiatedBy(precision))
.div(balance.toString())
.plus(oldIndex.toString());
}

View File

@ -0,0 +1,59 @@
import BigNumber from 'bignumber.js';
import { BigNumberValue, valueToZDBigNumber } from './bignumber';
export const WAD = valueToZDBigNumber(10).pow(18);
export const HALF_WAD = WAD.dividedBy(2);
export const RAY = valueToZDBigNumber(10).pow(27);
export const HALF_RAY = RAY.dividedBy(2);
export const WAD_RAY_RATIO = valueToZDBigNumber(10).pow(9);
export function wadMul(a: BigNumberValue, b: BigNumberValue): BigNumber {
return HALF_WAD.plus(valueToZDBigNumber(a).multipliedBy(b.toString())).div(WAD);
}
export function wadDiv(a: BigNumberValue, b: BigNumberValue): BigNumber {
const halfB = valueToZDBigNumber(b).div(2);
return halfB.plus(valueToZDBigNumber(a).multipliedBy(WAD)).div(b.toString());
}
export function rayMul(a: BigNumberValue, b: BigNumberValue): BigNumber {
return HALF_RAY.plus(valueToZDBigNumber(a).multipliedBy(b.toString())).div(RAY);
}
export function rayDiv(a: BigNumberValue, b: BigNumberValue): BigNumber {
const halfB = valueToZDBigNumber(b).div(2);
return halfB.plus(valueToZDBigNumber(a).multipliedBy(RAY)).div(b.toString());
}
export function rayToWad(a: BigNumberValue): BigNumber {
const halfRatio = valueToZDBigNumber(WAD_RAY_RATIO).div(2);
return halfRatio.plus(a.toString()).div(WAD_RAY_RATIO);
}
export function wadToRay(a: BigNumberValue): BigNumber {
return valueToZDBigNumber(a).multipliedBy(WAD_RAY_RATIO).decimalPlaces(0);
}
export function rayPow(a: BigNumberValue, p: BigNumberValue): BigNumber {
let x = valueToZDBigNumber(a);
let n = valueToZDBigNumber(p);
let z = !n.modulo(2).eq(0) ? x : valueToZDBigNumber(RAY);
for (n = n.div(2); !n.eq(0); n = n.div(2)) {
x = rayMul(x, x);
if (!n.modulo(2).eq(0)) {
z = rayMul(z, x);
}
}
return z;
}
export function rayToDecimal(a: BigNumberValue): BigNumber {
return valueToZDBigNumber(a).dividedBy(RAY);
}

View File

@ -0,0 +1,521 @@
import BigNumberJs from 'bignumber.js';
import { BigNumber } from 'ethers';
import { parseEther } from 'ethers/lib/utils';
import { MAX_UINT_AMOUNT } from '../../helpers/constants';
import { evmRevert, evmSnapshot, increaseTime } from '../../helpers/misc-utils';
import { makeSuite, SignerWithAddress, TestEnv } from './helpers/make-suite';
import { checkRewards } from './helpers/rewards-distribution/verify';
const chai = require('chai');
const { expect } = chai;
/**
* @dev REW is a mocked mintable token named RewardsToken.sol with an emission rate of 1 REW per second that can be deposited using a RewardsAwareAToken implementation named RewardsATokenMock.sol
* The distribution of REW happens at `claim`, but there is also a `updateMintableEmission` functions that updates the state of the distribution of 1 user but does not claim.
*/
makeSuite('Reward Aware AToken', (testEnv: TestEnv) => {
let initTimestamp;
let evmSnapshotId;
before('Initializing configuration', async () => {
// Sets BigNumber for this suite, instead of globally
BigNumberJs.config({ DECIMAL_PLACES: 0, ROUNDING_MODE: BigNumberJs.ROUND_DOWN });
});
after('Reset', () => {
// Reset BigNumber
BigNumberJs.config({ DECIMAL_PLACES: 20, ROUNDING_MODE: BigNumberJs.ROUND_HALF_UP });
});
beforeEach(async () => {
initTimestamp = await testEnv.rew.INIT_TIMESTAMP();
evmSnapshotId = await evmSnapshot();
});
afterEach(async () => {
await evmRevert(evmSnapshotId);
});
const mintAndDeposit = async (
key: SignerWithAddress,
shouldReward?: boolean,
amountSize?: BigNumber
) => {
const { rew, aRew, pool } = testEnv;
const amount = amountSize || parseEther('1');
const userATokenBalanceBeforeDeposit = await aRew.balanceOf(key.address);
// Mint REW to user
await rew.connect(key.signer).mint(amount);
// Approve and Deposit REW to pool and mint
await rew.connect(key.signer).approve(pool.address, amount);
const txDeposit = await pool.connect(key.signer).deposit(rew.address, amount, key.address, '0');
expect(Promise.resolve(txDeposit)).emit(aRew, 'Mint');
const userBalanceAfterDeposit = await rew.balanceOf(key.address);
const userATokenBalanceAfterDeposit = await aRew.balanceOf(key.address);
expect(userATokenBalanceAfterDeposit)
.to.be.eq(
userATokenBalanceBeforeDeposit.add(amount),
'User aToken balance should be equal the amount deposited'
)
.and.gt('0', 'User aToken balance should be greater than zero');
expect(userBalanceAfterDeposit).to.be.eq('0', 'Token balance should be zero');
if (!txDeposit.blockNumber) {
throw 'missing block number';
}
// Check all token rewards
await checkRewards(key, aRew.address, txDeposit.blockNumber, shouldReward);
};
const claim = async (user: SignerWithAddress, skipRewardChecks?: boolean) => {
skipRewardChecks = true;
const { rew, aRew } = testEnv;
// Claim all the rewards from aToken
const txClaim = await aRew.connect(user.signer).claim(rew.address);
await expect(Promise.resolve(txClaim)).to.emit(aRew, 'Claim');
// Calculate expected rewards
if (!txClaim.blockNumber) {
throw 'Block number missing from tx';
}
if (!skipRewardChecks) {
// Check all token rewards
await checkRewards(user, aRew.address, txClaim.blockNumber, true);
}
};
describe('Deposits: mints', async () => {
it('User1 deposits Reward Aware Token to Lending Pool', async () => {
const {
users: [user1],
} = testEnv;
await mintAndDeposit(user1);
});
it('Other users deposits Reward Aware Token to Lending Pool', async () => {
const {
users: [, user2, user3, user4],
} = testEnv;
await mintAndDeposit(user2);
await mintAndDeposit(user3);
await mintAndDeposit(user4);
});
it('User1 deposits multiple time Reward Aware Token to Lending Pool', async () => {
const {
users: [user1],
} = testEnv;
await mintAndDeposit(user1, false, parseEther('2'));
await mintAndDeposit(user1, true, parseEther('12'));
});
});
describe('Withdrawals: burns', async () => {
it('User1 deposits Reward Aware Token to Lending Pool', async () => {
const {
users: [user1],
aRew,
rew,
pool,
} = testEnv;
// Deposits
await mintAndDeposit(user1);
// Burn/Withdraw
await aRew.approve(pool.address, MAX_UINT_AMOUNT);
await pool.connect(user1.signer).withdraw(rew.address, MAX_UINT_AMOUNT, user1.address);
});
});
describe('Claim rewards', async () => {
it('User1 claims 100% portion of REW via the aToken contract', async () => {
const {
users: [user1],
rew,
aRew,
} = testEnv;
// User1 deposits
await mintAndDeposit(user1);
// Pass time to generate rewards
await increaseTime(1000);
// Check rewards for aToken at rew
const token = await aRew.getRewardsTokenAddress('0');
const aTokenRewards = await rew.getClaimableRewards(aRew.address);
// Check rewards for user at aRew
const userRewards = await aRew.getClaimableRewards(rew.address, user1.address);
// Expect user rewards to be the same as aToken rewards due 100%
expect(aTokenRewards).to.be.eq(
userRewards,
'Rewards should be the same due user holds 100% of RewardsAware distribution'
);
// Claims and check rewards
await claim(user1);
});
it('Two users with half the portion of REW via the aToken contract', async () => {
const {
users: [user1, user2],
rew,
aRew,
} = testEnv;
// User1 and user2 deposits
await mintAndDeposit(user1);
await mintAndDeposit(user2);
// Pass time to generate rewards
await increaseTime(1000);
// Check rewards for aToken at rew
const aTokenRewards = await rew.getClaimableRewards(aRew.address);
// Check rewards for user1 at aRew
const user1Rewards = await aRew.getClaimableRewards(rew.address, user1.address);
// Check rewards for user2 at aRew
const user2Rewards = await aRew.getClaimableRewards(rew.address, user2.address);
// Expect user rewards to be the same as aToken rewards
expect(aTokenRewards).to.be.eq(
user1Rewards.add(user2Rewards),
'Rewards should be the same of all users of RewardsAware distribution'
);
// Claims and check rewards
await claim(user1);
await claim(user2);
});
it('Four users with different portions of REW via the aToken contract', async () => {
const {
users: [user1, user2, user3, user4],
rew,
aRew,
} = testEnv;
// Deposits
await mintAndDeposit(user1, false, parseEther('1'));
await mintAndDeposit(user2, false, parseEther('2.5'));
await mintAndDeposit(user3, false, parseEther('4.7'));
await mintAndDeposit(user4, false, parseEther('0.31'));
// Pass time to generate rewards
await increaseTime(1000);
// Claims and check rewards
await claim(user1);
await claim(user2);
await claim(user3);
await claim(user4);
// Pass time to generate rewards
await increaseTime(2713);
// Claims and check rewards
await claim(user1);
await claim(user2);
await claim(user3);
await claim(user4);
});
it('Two users with half the portion of REW via the aToken contract, one burns and them claims', async () => {
const {
users: [user1, user2],
rew,
aRew,
pool,
} = testEnv;
// User1 and user2 deposits
await mintAndDeposit(user1, false, parseEther('1000'));
await mintAndDeposit(user2, false, parseEther('1000'));
// Pass time to generate rewards
await increaseTime(1000);
// Claims and check rewards
await claim(user1);
await claim(user2);
// Pass time to generate rewards
await increaseTime(1000);
// Burn/Withdraw to update current lifetime rewards state
await aRew.approve(pool.address, MAX_UINT_AMOUNT);
await pool.connect(user1.signer).withdraw(rew.address, MAX_UINT_AMOUNT, user1.address);
// Claims
await claim(user1);
await claim(user2);
});
});
describe('Getters', () => {
describe('getClaimableRewards', () => {
it('Rewards should be zero if user with no balance', async () => {
const {
users: [user1],
aRew,
rew,
} = testEnv;
const rewards = await aRew.getClaimableRewards(rew.address, user1.address);
expect(rewards).eq('0', 'Rewards should be zero');
});
it('Rewards should be available after time travel', async () => {
const {
users: [user1],
aRew,
rew,
} = testEnv;
await mintAndDeposit(user1);
// Pass time to generate rewards
await increaseTime(1000);
const rewards = await aRew.getClaimableRewards(rew.address, user1.address);
expect(rewards).gt('0', 'Rewards should be greater than zero');
});
});
describe('getUserLifetimeRewardsAccrued', () => {
it('User lifetime rewards should be zero if no deposit', async () => {
const {
users: [user1],
aRew,
rew,
} = testEnv;
const rewards = await aRew.getUserRewardsAccrued(rew.address, user1.address);
expect(rewards).eq('0', 'Rewards should be zero');
});
it('User lifetime rewards should be zero if deposit due state is not updated', async () => {
const {
users: [user1],
aRew,
rew,
} = testEnv;
await mintAndDeposit(user1);
const rewards = await aRew.getUserRewardsAccrued(rew.address, user1.address);
expect(rewards).eq('0', 'Rewards should be zero');
});
it('User should have some lifetime rewards if deposit again due state is updated', async () => {
const {
users: [user1],
aRew,
rew,
} = testEnv;
await mintAndDeposit(user1);
// Pass time to generate rewards
await increaseTime(1000);
await mintAndDeposit(user1, true);
const rewards = await aRew.getUserRewardsAccrued(rew.address, user1.address);
expect(rewards).gt('0', 'Rewards should be greater than zero');
});
it('User should have some lifetime rewards if claims due state is updated', async () => {
const {
users: [user1],
aRew,
rew,
} = testEnv;
await mintAndDeposit(user1);
// Pass time to generate rewards
await increaseTime(1000);
await claim(user1);
const rewards = await aRew.getUserRewardsAccrued(rew.address, user1.address);
expect(rewards).gt('0', 'Rewards should be greater than zero');
});
});
describe('getUserIndex', () => {
it('User index should be zero if no deposit', async () => {
const {
users: [user1],
aRew,
rew,
} = testEnv;
const rewards = await aRew.getUserIndex(rew.address, user1.address);
expect(rewards).eq('0', 'Rewards should be zero');
});
it('User lifetime rewards should be zero if deposit due state is not updated', async () => {
const {
users: [user1],
aRew,
rew,
} = testEnv;
await mintAndDeposit(user1);
const rewards = await aRew.getUserIndex(rew.address, user1.address);
expect(rewards).eq('0', 'Rewards should be zero');
});
it('User should have some lifetime rewards if deposit again due state is updated', async () => {
const {
users: [user1],
aRew,
rew,
} = testEnv;
await mintAndDeposit(user1);
// Pass time to generate rewards
await increaseTime(1000);
await mintAndDeposit(user1, true);
const rewards = await aRew.getUserIndex(rew.address, user1.address);
expect(rewards).gt('0', 'Rewards should be greater than zero');
});
it('User should have some lifetime rewards if claims due state is updated', async () => {
const {
users: [user1],
aRew,
rew,
} = testEnv;
await mintAndDeposit(user1);
// Pass time to generate rewards
await increaseTime(1000);
await claim(user1);
const rewards = await aRew.getUserIndex(rew.address, user1.address);
expect(rewards).gt('0', 'Rewards should be greater than zero');
});
});
describe('getUserClaimedRewards', () => {
it('User should NOT have claimed rewards if didnt claim', async () => {
const {
users: [user1],
aRew,
rew,
} = testEnv;
await mintAndDeposit(user1);
// Pass time to generate rewards
await increaseTime(1000);
const rewards = await aRew.getUserIndex(rew.address, user1.address);
expect(rewards).eq('0', 'Rewards should be zero');
});
it('User should have claimed rewards if claims', async () => {
const {
users: [user1],
aRew,
rew,
} = testEnv;
await mintAndDeposit(user1);
// Pass time to generate rewards
await increaseTime(1000);
await claim(user1);
const rewards = await aRew.getUserIndex(rew.address, user1.address);
expect(rewards).gt('0', 'Rewards should be greater than zero');
});
});
describe('getLifetimeRewards', () => {
it('The aToken Tifetime rewards should be zero if there is no deposits', async () => {
const { aRew, rew } = testEnv;
const rewards = await aRew.getLifetimeRewards(rew.address);
expect(rewards).eq('0', 'Rewards should be zero');
});
it('The aToken lifetime rewards should be zero at init', async () => {
const { aRew, rew } = testEnv;
const rewards = await aRew.getLifetimeRewards(rew.address);
expect(rewards).eq('0', 'Rewards should be zero');
});
it('The aToken lifetime rewards should update if there is further actions: deposit', async () => {
const {
users: [user1],
aRew,
rew,
} = testEnv;
await mintAndDeposit(user1);
// Pass time to generate rewards
await increaseTime(1000);
// Deposit again to update current lifetime rewards state
await mintAndDeposit(user1, true);
const rewards = await aRew.getLifetimeRewards(rew.address);
expect(rewards).gte('0', 'Rewards should be greater than zero');
});
it('The aToken lifetime rewards should update if there is further actions: claim', async () => {
const {
users: [user1],
aRew,
rew,
} = testEnv;
await mintAndDeposit(user1);
// Pass time to generate rewards
await increaseTime(1000);
// Claim to update current lifetime rewards state
await claim(user1);
const rewards = await aRew.getLifetimeRewards(rew.address);
expect(rewards).gte('0', 'Rewards should be greater than zero');
});
it('The aToken lifetime rewards should update if there is further actions: burn', async () => {
const {
users: [user1],
aRew,
rew,
pool,
} = testEnv;
await mintAndDeposit(user1);
// Pass time to generate rewards
await increaseTime(1000);
// Burn/Withdraw to update current lifetime rewards state
await aRew.approve(pool.address, MAX_UINT_AMOUNT);
await pool.connect(user1.signer).withdraw(rew.address, MAX_UINT_AMOUNT, user1.address);
const rewards = await aRew.getLifetimeRewards(rew.address);
expect(rewards).gte('0', 'Rewards should be greater than zero');
});
});
describe('getRewardsToken', () => {
it('The getter should return the current token reward address', async () => {
const { aRew, rew } = testEnv;
const rewardToken = await aRew.getRewardsTokenAddress(0);
expect(rewardToken).to.be.equal(rew.address);
});
});
});
});

View File

@ -26,7 +26,8 @@ import {
deployUniswapLiquiditySwapAdapter,
deployUniswapRepayAdapter,
deployFlashLiquidationAdapter,
authorizeWETHGateway
authorizeWETHGateway,
deployATokenImplementations,
} from '../../helpers/contracts-deployments';
import { Signer } from 'ethers';
import { TokenContractId, eContractid, tEthereumAddress, AavePools } from '../../helpers/types';
@ -94,6 +95,14 @@ const deployAllMockTokens = async (deployer: Signer) => {
const buildTestEnv = async (deployer: Signer, secondaryWallet: Signer) => {
console.time('setup');
const aaveAdmin = await deployer.getAddress();
const config = loadPoolConfig(ConfigNames.Amm);
const {
ATokenNamePrefix,
StableDebtTokenNamePrefix,
VariableDebtTokenNamePrefix,
SymbolPrefix,
ReservesConfig,
} = config;
const mockTokens = await deployAllMockTokens(deployer);
@ -229,8 +238,7 @@ const buildTestEnv = async (deployer: Signer, secondaryWallet: Signer) => {
lendingRateOracle,
aaveAdmin
);
const reservesParams = getReservesConfigByPool(AavePools.amm);
await deployATokenImplementations(ConfigNames.Amm, ReservesConfig);
const testHelpers = await deployAaveProtocolDataProvider(addressesProvider.address);
@ -239,18 +247,10 @@ const buildTestEnv = async (deployer: Signer, secondaryWallet: Signer) => {
console.log('Initialize configuration');
const config = loadPoolConfig(ConfigNames.Amm);
const {
ATokenNamePrefix,
StableDebtTokenNamePrefix,
VariableDebtTokenNamePrefix,
SymbolPrefix,
} = config;
const treasuryAddress = await getTreasuryAddress(config);
await initReservesByHelper(
reservesParams,
ReservesConfig,
allReservesAddresses,
ATokenNamePrefix,
StableDebtTokenNamePrefix,
@ -259,9 +259,10 @@ const buildTestEnv = async (deployer: Signer, secondaryWallet: Signer) => {
admin,
treasuryAddress,
ZERO_ADDRESS,
ConfigNames.Amm,
false
);
await configureReservesByHelper(reservesParams, allReservesAddresses, testHelpers, admin);
await configureReservesByHelper(ReservesConfig, allReservesAddresses, testHelpers, admin);
const collateralManager = await deployLendingPoolCollateralManager();
await waitForTx(