├── .nvmrc
├── .prettierignore
├── .gitattributes
├── .solhintignore
├── .yarnrc.yml
├── .prettierrc
├── scripts
├── package-version.sh
├── fork
│ ├── index.ts
│ ├── impersonateAccounts.ts
│ └── distributeEtherFromBinance.ts
├── helpers.ts
├── generateDeployment.ts
├── runLifecycle.ts
├── createAndRunPrizePoolPolygon.ts
└── createAndRunPrizePoolMumbai.ts
├── .solcover.js
├── aave
├── aaveMumbai.json
├── aavePolygon.json
├── aaveKovan.json
└── aaveMainnet.json
├── aave.config.ts
├── contracts
├── external
│ └── aave
│ │ ├── ATokenInterface.sol
│ │ ├── ILendingPoolAddressesProviderRegistry.sol
│ │ ├── IProtocolYieldSource.sol
│ │ ├── IProtocolDataProvider.sol
│ │ ├── ILendingPool.sol
│ │ ├── ILendingPoolAddressesProvider.sol
│ │ ├── IAToken.sol
│ │ └── IAaveIncentivesController.sol
├── test
│ ├── ATokenMintable.sol
│ ├── ERC20Mintable.sol
│ ├── AaveLendingPool.sol
│ ├── ATokenYieldSourceHarness.sol
│ └── SafeERC20Wrapper.sol
└── yield-source
│ └── ATokenYieldSource.sol
├── .gitignore
├── .envrc.example
├── tsconfig.json
├── .github
└── workflows
│ └── main.yml
├── .solhint.json
├── Constant.ts
├── hardhat.config.ts
├── hardhat.network.ts
├── package.json
├── README.md
└── test
└── ATokenYieldSource.test.ts
/.nvmrc:
--------------------------------------------------------------------------------
1 | 15.11.0
2 |
--------------------------------------------------------------------------------
/.prettierignore:
--------------------------------------------------------------------------------
1 | types
2 |
--------------------------------------------------------------------------------
/.gitattributes:
--------------------------------------------------------------------------------
1 | /.yarn/** linguist-vendored
2 |
--------------------------------------------------------------------------------
/.solhintignore:
--------------------------------------------------------------------------------
1 | contracts/hardhat-dependency-compiler
2 | contracts/external/aave
--------------------------------------------------------------------------------
/.yarnrc.yml:
--------------------------------------------------------------------------------
1 | yarnPath: ".yarn/releases/yarn-berry.cjs"
2 | nodeLinker: node-modules
3 | enableGlobalCache: true
4 |
--------------------------------------------------------------------------------
/.prettierrc:
--------------------------------------------------------------------------------
1 | {
2 | "printWidth": 100,
3 | "singleQuote": true,
4 | "trailingComma": "all",
5 | "tabWidth": 2
6 | }
7 |
--------------------------------------------------------------------------------
/scripts/package-version.sh:
--------------------------------------------------------------------------------
1 | #!/bin/sh
2 | echo $(cat package.json | grep \\\"version\\\" | head -1 | awk -F: '{ print $2 }' | sed 's/[\",]//g' | tr -d '[[:space:]]')
--------------------------------------------------------------------------------
/.solcover.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | skipFiles: [
3 | 'external/aave',
4 | 'test/ERC20Mintable.sol',
5 | 'test/SafeERC20Wrapper.sol'
6 | ],
7 | };
8 |
--------------------------------------------------------------------------------
/scripts/fork/index.ts:
--------------------------------------------------------------------------------
1 | export * as impersonateAccountsTask from './impersonateAccounts';
2 | export * as distributeEtherFromBinanceTask from './distributeEtherFromBinance';
3 |
--------------------------------------------------------------------------------
/scripts/helpers.ts:
--------------------------------------------------------------------------------
1 | import chalk from 'chalk';
2 |
3 | export const info = (message: string) => console.log(chalk.dim(message));
4 | export const success = (message: string) => console.log(chalk.green(message));
5 |
--------------------------------------------------------------------------------
/aave/aaveMumbai.json:
--------------------------------------------------------------------------------
1 | {"proto":[{"aTokenAddress":"0x7ec62b6fC19174255335C8f4346E0C2fcf870a6B","aTokenSymbol":"aAAVE","stableDebtTokenAddress":"",
2 | "variableDebtTokenAddress":"","symbol":"AAVE","address":"0x341d1f30e77D3FBfbD43D17183E2acb9dF25574E","decimals":18}]}
--------------------------------------------------------------------------------
/aave.config.ts:
--------------------------------------------------------------------------------
1 | export const yieldSourceTokenSymbols: { [chainId: number]: string[] } = {
2 | 1: ['sUSD', 'BUSD', 'GUSD', 'USDT'],
3 | 42: ['AAVE', 'BAT', 'BUSD', 'DAI', 'ENJ', 'KNC', 'LINK', 'MANA', 'MKR'],
4 | 137: ['AAVE', 'DAI', 'USDC', 'USDT', 'WBTC', 'WETH', 'WMATIC'],
5 | 80001: ['AAVE'],
6 | };
7 |
--------------------------------------------------------------------------------
/contracts/external/aave/ATokenInterface.sol:
--------------------------------------------------------------------------------
1 | // SPDX-License-Identifier: agpl-3.0
2 |
3 | pragma solidity 0.8.6;
4 |
5 | import "./IAToken.sol";
6 |
7 | interface ATokenInterface is IAToken {
8 | /**
9 | * @dev Returns the address of the underlying asset of this aToken (E.g. WETH for aWETH)
10 | **/
11 | /* solhint-disable-next-line func-name-mixedcase */
12 | function UNDERLYING_ASSET_ADDRESS() external view returns (address);
13 | }
14 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | /.yarn/*
2 | !/.yarn/releases
3 | !/.yarn/plugins
4 | !/.yarn/sdks
5 |
6 | # Swap the comments on the following lines if you don't wish to use zero-installs
7 | # Documentation here: https://yarnpkg.com/features/zero-installs
8 | #!/.yarn/cache
9 | /.pnp.*
10 |
11 | .envrc
12 |
13 | abis
14 | artifacts
15 | cache
16 | node_modules
17 | types
18 | deployments/localhost/
19 |
20 | # Solidity Coverage
21 | coverage
22 | coverage.json
23 |
24 | # VS Code
25 | .history
--------------------------------------------------------------------------------
/.envrc.example:
--------------------------------------------------------------------------------
1 | # Used to deploy to testnets
2 | export HDWALLET_MNEMONIC=''
3 |
4 | # Used to deploy to testnets
5 | export INFURA_API_KEY=''
6 |
7 | # Used for verifying contracts on Etherscan
8 | export ETHERSCAN_API_KEY=''
9 |
10 | # Required for forking
11 | export ALCHEMY_URL=''
12 | export FORK_ENABLED=''
13 |
14 | # Used to hide deploy logs
15 | export HIDE_DEPLOY_LOG=''
16 |
17 | # Used to generate a gas usage report
18 | export REPORT_GAS=''
19 | export COINMARKETCAP_API_KEY=''
20 |
--------------------------------------------------------------------------------
/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "target": "es5",
4 | "module": "commonjs",
5 | "strict": true,
6 | "esModuleInterop": true,
7 | "moduleResolution": "node",
8 | "forceConsistentCasingInFileNames": true,
9 | "resolveJsonModule": true,
10 | },
11 | "include": [
12 | "hardhat.config.ts",
13 | "hardhat.network.ts",
14 | "./deploy",
15 | "./scripts",
16 | "./test",
17 | "types/**/*",
18 | "Constant.ts"
19 | ]
20 | }
21 |
--------------------------------------------------------------------------------
/.github/workflows/main.yml:
--------------------------------------------------------------------------------
1 | name: Coveralls
2 |
3 | on: ["push", "pull_request"]
4 |
5 | jobs:
6 | build:
7 | runs-on: ubuntu-latest
8 | steps:
9 | - uses: actions/checkout@v2
10 | - name: Use Node.js 15.x
11 | uses: actions/setup-node@v2
12 | with:
13 | node-version: 15.x
14 | - name: yarn, compile, hint, coverage
15 | run: |
16 | yarn
17 | yarn compile
18 | yarn hint
19 | yarn coverage
20 | - name: Coveralls
21 | uses: coverallsapp/github-action@master
22 | with:
23 | github-token: ${{ secrets.GITHUB_TOKEN }}
24 |
--------------------------------------------------------------------------------
/contracts/test/ATokenMintable.sol:
--------------------------------------------------------------------------------
1 | // SPDX-License-Identifier: GPL-3.0
2 |
3 | pragma solidity 0.8.6;
4 |
5 | import "./ERC20Mintable.sol";
6 |
7 | contract ATokenMintable is ERC20Mintable {
8 | address public immutable underlyingAssetAddress;
9 |
10 | constructor(
11 | address _underlyingAssetAddress,
12 | string memory _name,
13 | string memory _symbol,
14 | uint8 decimals_
15 | ) ERC20Mintable(_name, _symbol, decimals_) {
16 | underlyingAssetAddress = _underlyingAssetAddress;
17 | }
18 |
19 | /* solhint-disable func-name-mixedcase */
20 | function UNDERLYING_ASSET_ADDRESS() external view returns (address) {
21 | return underlyingAssetAddress;
22 | }
23 | }
24 |
--------------------------------------------------------------------------------
/.solhint.json:
--------------------------------------------------------------------------------
1 | {
2 | "extends": "solhint:recommended",
3 | "plugins": [],
4 | "rules": {
5 | "func-order": "off",
6 | "mark-callable-contracts": "off",
7 | "no-empty-blocks": "off",
8 | "compiler-version": ["error", "0.8.6"],
9 | "private-vars-leading-underscore": "off",
10 | "code-complexity": "warn",
11 | "const-name-snakecase": "warn",
12 | "function-max-lines": "warn",
13 | "func-visibility": ["warn",{"ignoreConstructors":true}],
14 | "max-line-length": ["warn", 160],
15 | "avoid-suicide": "error",
16 | "avoid-sha3": "warn",
17 | "not-rely-on-time": "off",
18 | "reason-string": ["warn", {"maxLength": 64}]
19 | }
20 | }
21 |
--------------------------------------------------------------------------------
/contracts/test/ERC20Mintable.sol:
--------------------------------------------------------------------------------
1 | // SPDX-License-Identifier: GPL-3.0
2 |
3 | pragma solidity 0.8.6;
4 |
5 | import "@openzeppelin/contracts/token/ERC20/ERC20.sol";
6 |
7 | contract ERC20Mintable is ERC20 {
8 | uint8 internal __decimals;
9 |
10 | constructor(string memory _name, string memory _symbol, uint8 _decimals) ERC20(_name, _symbol) {
11 | __decimals = _decimals;
12 | }
13 |
14 | function decimals() public override view returns (uint8) {
15 | return __decimals;
16 | }
17 |
18 | function mint(address account, uint256 amount) public returns (bool) {
19 | _mint(account, amount);
20 | return true;
21 | }
22 |
23 | function burn(address account, uint256 amount) public returns (bool) {
24 | _burn(account, amount);
25 | return true;
26 | }
27 | }
28 |
--------------------------------------------------------------------------------
/contracts/test/AaveLendingPool.sol:
--------------------------------------------------------------------------------
1 | // SPDX-License-Identifier: GPL-3.0
2 |
3 | pragma solidity 0.8.6;
4 |
5 | import { IERC20 } from "@openzeppelin/contracts/token/ERC20/IERC20.sol";
6 |
7 | import { ATokenMintable } from "./ATokenMintable.sol";
8 |
9 | contract AaveLendingPool {
10 | IERC20 public immutable token;
11 | ATokenMintable public immutable aToken;
12 |
13 | constructor(
14 | IERC20 _token,
15 | ATokenMintable _aToken
16 | ) {
17 | token = _token;
18 | aToken = _aToken;
19 | }
20 |
21 | /* solhint-disable no-unused-vars */
22 | function deposit(
23 | address asset,
24 | uint256 amount,
25 | address onBehalfOf,
26 | uint16 referralCode
27 | ) external {
28 | IERC20(asset).transferFrom(msg.sender, address(this), amount);
29 | aToken.mint(onBehalfOf, amount);
30 | }
31 |
32 | function withdraw(
33 | address asset,
34 | uint256 amount,
35 | address to
36 | ) external returns (uint256) {
37 | aToken.burn(msg.sender, amount);
38 | IERC20(asset).transfer(to, amount);
39 | return amount;
40 | }
41 | }
42 |
--------------------------------------------------------------------------------
/contracts/external/aave/ILendingPoolAddressesProviderRegistry.sol:
--------------------------------------------------------------------------------
1 | // SPDX-License-Identifier: agpl-3.0
2 | pragma solidity 0.8.6;
3 |
4 | /**
5 | * @title LendingPoolAddressesProviderRegistry contract
6 | * @dev Main registry of LendingPoolAddressesProvider of multiple Aave protocol's markets
7 | * - Used for indexing purposes of Aave protocol's markets
8 | * - The id assigned to a LendingPoolAddressesProvider refers to the market it is connected with,
9 | * for example with `0` for the Aave main market and `1` for the next created
10 | * @author Aave
11 | **/
12 | interface ILendingPoolAddressesProviderRegistry {
13 | event AddressesProviderRegistered(address indexed newAddress);
14 | event AddressesProviderUnregistered(address indexed newAddress);
15 |
16 | function getAddressesProvidersList() external view returns (address[] memory);
17 |
18 | function getAddressesProviderIdByAddress(address addressesProvider)
19 | external
20 | view
21 | returns (uint256);
22 |
23 | function registerAddressesProvider(address provider, uint256 id) external;
24 |
25 | function unregisterAddressesProvider(address provider) external;
26 | }
27 |
--------------------------------------------------------------------------------
/contracts/external/aave/IProtocolYieldSource.sol:
--------------------------------------------------------------------------------
1 | // SPDX-License-Identifier: GPL-3.0
2 |
3 | pragma solidity 0.8.6;
4 |
5 | import "@pooltogether/yield-source-interface/contracts/IYieldSource.sol";
6 | import "@openzeppelin/contracts/token/ERC20/IERC20.sol";
7 |
8 | /// @title The interface used for all Yield Sources for the PoolTogether protocol
9 | /// @dev There are two privileged roles: the owner and the asset manager. The owner can configure the asset managers.
10 | interface IProtocolYieldSource is IYieldSource {
11 | /// @notice Allows the owner to transfer ERC20 tokens held by this contract to the target address.
12 | /// @dev This function is callable by the owner or asset manager.
13 | /// This function should not be able to transfer any tokens that represent user deposits.
14 | /// @param token The ERC20 token to transfer
15 | /// @param to The recipient of the tokens
16 | /// @param amount The amount of tokens to transfer
17 | function transferERC20(IERC20 token, address to, uint256 amount) external;
18 |
19 | /// @notice Allows someone to deposit into the yield source without receiving any shares. The deposited token will be the same as token()
20 | /// This allows anyone to distribute tokens among the share holders.
21 | function sponsor(uint256 amount) external;
22 | }
23 |
--------------------------------------------------------------------------------
/scripts/generateDeployment.ts:
--------------------------------------------------------------------------------
1 | interface ProxyDeployment {
2 | address?: string;
3 | abi?: any;
4 | transactionHash?: string;
5 | receipt?: any;
6 | args?: any;
7 | bytecode?: string;
8 | }
9 |
10 | import { Contract } from 'ethers';
11 | import { writeFileSync } from 'fs';
12 | import { ethers, getNamedAccounts } from 'hardhat';
13 |
14 | async function createDeployment() {
15 | const hash = '0x04e348a719e5e68b858ef78a9140af1e9440a4d48febc5c40a879cfd184757b5';
16 | const receipt = await ethers.provider.getTransactionReceipt(hash);
17 |
18 | console.log('receipt is ', receipt);
19 |
20 | const { genericProxyFactory } = await getNamedAccounts();
21 | let proxyFactoryContract: Contract = await ethers.getContractAt(
22 | 'GenericProxyFactory',
23 | genericProxyFactory,
24 | );
25 |
26 | const createdEvent = proxyFactoryContract.interface.parseLog(receipt.logs[0]);
27 |
28 | let jsonObj: ProxyDeployment = {
29 | address: createdEvent.args.created,
30 | transactionHash: receipt.transactionHash,
31 | receipt: receipt,
32 | args: ``,
33 | bytecode: `${await ethers.provider.getCode(createdEvent.args.created)}`,
34 | };
35 |
36 | writeFileSync(`./deployments/mainnet/aGUSD.json`, JSON.stringify(jsonObj), {
37 | encoding: 'utf8',
38 | flag: 'w',
39 | });
40 |
41 | console.log('finished');
42 | }
43 |
44 | createDeployment();
45 |
--------------------------------------------------------------------------------
/Constant.ts:
--------------------------------------------------------------------------------
1 | export const ADAI_ADDRESS_KOVAN = '0xdCf0aF9e59C002FA3AA091a46196b37530FD48a8';
2 | export const LENDING_POOL_ADDRESSES_PROVIDER_REGISTRY_ADDRESS_KOVAN =
3 | '0x1E40B561EC587036f9789aF83236f057D1ed2A90';
4 |
5 | export const DAI_ADDRESS_MAINNET = '0x6B175474E89094C44Da98b954EedeAC495271d0F';
6 | export const ADAI_ADDRESS_MAINNET = '0x028171bCA77440897B824Ca71D1c56caC55b68A3';
7 | export const LENDING_POOL_ADDRESSES_PROVIDER_REGISTRY_ADDRESS_MAINNET =
8 | '0x52D306e36E3B6B02c153d0266ff0f85d18BCD413';
9 |
10 | export const INCENTIVES_CONTROLLER_ADDRESS_MAINNET = '0xd784927Ff2f95ba542BfC824c8a8a98F3495f6b5';
11 |
12 | export const INCENTIVES_CONTROLLER_ADDRESS_POLYGON = '0x357D51124f59836DeD84c8a1730D72B749d8BC23';
13 |
14 | export const BINANCE_ADDRESS = '0x564286362092D8e7936f0549571a803B203aAceD';
15 | export const BINANCE7_ADDRESS = '0xBE0eB53F46cd790Cd13851d5EFf43D12404d33E8';
16 | export const DAI_RICH_ADDRESS = '0xF977814e90dA44bFA03b6295A0616a897441aceC';
17 |
18 | export const LARGE_GUSD_ADDRESS = '0xd2E801f22e3E85A12Dce545A9a6064cb04DE9C43';
19 | export const GUSD_ADDRESS = '0x056Fd409E1d7A124BD7017459dFEa2F387b6d5Cd';
20 |
21 | export const LARGE_SUSD_ADDRESS = '0x3f5CE5FBFe3E9af3971dD833D26bA9b5C936f0bE';
22 | export const SUSD_ADDRESS = '0x57Ab1ec28D129707052df4dF418D58a2D46d5f51';
23 |
24 | export const BUSD_ADDRESS = '0x4Fabb145d64652a948d72533023f6E7A623C7C53';
25 | export const LARGE_BUSD_ADDRESS = '0xF977814e90dA44bFA03b6295A0616a897441aceC';
26 |
--------------------------------------------------------------------------------
/hardhat.config.ts:
--------------------------------------------------------------------------------
1 | import '@nomiclabs/hardhat-etherscan';
2 | import '@nomiclabs/hardhat-ethers';
3 | import '@nomiclabs/hardhat-waffle';
4 | import '@typechain/hardhat';
5 | import 'hardhat-abi-exporter';
6 | import 'hardhat-deploy';
7 | import 'hardhat-deploy-ethers';
8 | import 'hardhat-gas-reporter';
9 | import 'solidity-coverage';
10 | import 'hardhat-dependency-compiler';
11 |
12 | import { HardhatUserConfig } from 'hardhat/config';
13 |
14 | import * as forkTasks from './scripts/fork';
15 | import networks from './hardhat.network';
16 |
17 | const optimizerEnabled = !process.env.OPTIMIZER_DISABLED;
18 |
19 | const config: HardhatUserConfig = {
20 | abiExporter: {
21 | path: './abis',
22 | clear: true,
23 | flat: true,
24 | },
25 | etherscan: {
26 | apiKey: process.env.ETHERSCAN_API_KEY,
27 | },
28 | gasReporter: {
29 | currency: 'USD',
30 | gasPrice: 100,
31 | enabled: process.env.REPORT_GAS ? true : false,
32 | coinmarketcap: process.env.COINMARKETCAP_API_KEY,
33 | maxMethodDiff: 10,
34 | },
35 | mocha: {
36 | timeout: 30000,
37 | },
38 | networks,
39 | solidity: {
40 | compilers: [
41 | {
42 | version: '0.8.6',
43 | settings: {
44 | optimizer: {
45 | enabled: optimizerEnabled,
46 | runs: 200,
47 | },
48 | evmVersion: 'istanbul',
49 | },
50 | },
51 | ],
52 | },
53 | typechain: {
54 | outDir: 'types',
55 | target: 'ethers-v5',
56 | },
57 | };
58 |
59 | forkTasks;
60 |
61 | export default config;
62 |
--------------------------------------------------------------------------------
/scripts/fork/impersonateAccounts.ts:
--------------------------------------------------------------------------------
1 | import { task } from 'hardhat/config';
2 |
3 | import {
4 | BINANCE_ADDRESS,
5 | BINANCE7_ADDRESS,
6 | DAI_RICH_ADDRESS,
7 | LARGE_GUSD_ADDRESS,
8 | LARGE_BUSD_ADDRESS,
9 | LARGE_SUSD_ADDRESS,
10 | } from '../../Constant';
11 | import { info, success } from '../helpers';
12 |
13 | export default task('fork:impersonate-accounts', 'Impersonate accounts').setAction(
14 | async (taskArguments, hre) => {
15 | info('Impersonate accounts...');
16 |
17 | await hre.network.provider.request({
18 | method: 'hardhat_impersonateAccount',
19 | params: ['0x364d6D0333432C3Ac016Ca832fb8594A8cE43Ca6'],
20 | });
21 |
22 | await hre.network.provider.request({
23 | method: 'hardhat_impersonateAccount',
24 | params: [BINANCE_ADDRESS],
25 | });
26 |
27 | await hre.network.provider.request({
28 | method: 'hardhat_impersonateAccount',
29 | params: [BINANCE7_ADDRESS],
30 | });
31 |
32 | await hre.network.provider.request({
33 | method: 'hardhat_impersonateAccount',
34 | params: [DAI_RICH_ADDRESS],
35 | });
36 |
37 | await hre.network.provider.request({
38 | method: 'hardhat_impersonateAccount',
39 | params: [LARGE_GUSD_ADDRESS],
40 | });
41 |
42 | await hre.network.provider.request({
43 | method: 'hardhat_impersonateAccount',
44 | params: [LARGE_BUSD_ADDRESS],
45 | });
46 |
47 | await hre.network.provider.request({
48 | method: 'hardhat_impersonateAccount',
49 | params: [LARGE_SUSD_ADDRESS],
50 | });
51 |
52 | success('Done!');
53 | },
54 | );
55 |
--------------------------------------------------------------------------------
/contracts/test/ATokenYieldSourceHarness.sol:
--------------------------------------------------------------------------------
1 | // SPDX-License-Identifier: GPL-3.0
2 |
3 | pragma solidity 0.8.6;
4 |
5 | import "../yield-source/ATokenYieldSource.sol";
6 | import "../external/aave/ATokenInterface.sol";
7 | import "../external/aave/ILendingPoolAddressesProviderRegistry.sol";
8 |
9 | /* solium-disable security/no-block-members */
10 | contract ATokenYieldSourceHarness is ATokenYieldSource {
11 |
12 | constructor (
13 | ATokenInterface _aToken,
14 | IAaveIncentivesController _incentivesController,
15 | ILendingPoolAddressesProviderRegistry _lendingPoolAddressesProviderRegistry,
16 | uint8 _decimals,
17 | string memory _symbol,
18 | string memory _name,
19 | address _owner
20 | ) ATokenYieldSource(_aToken, _incentivesController, _lendingPoolAddressesProviderRegistry, _decimals, _symbol, _name, _owner) {
21 |
22 | }
23 |
24 | function approveLendingPool(uint256 amount) external {
25 | IERC20(aToken.UNDERLYING_ASSET_ADDRESS()).approve(address(_lendingPool()), amount);
26 | }
27 |
28 | function mint(address account, uint256 amount) public returns (bool) {
29 | _mint(account, amount);
30 | return true;
31 | }
32 |
33 | function tokenToShares(uint256 tokens) external view returns (uint256) {
34 | return _tokenToShares(tokens);
35 | }
36 |
37 | function sharesToToken(uint256 shares) external view returns (uint256) {
38 | return _sharesToToken(shares);
39 | }
40 |
41 | function tokenAddress() external view returns (address) {
42 | return aToken.UNDERLYING_ASSET_ADDRESS();
43 | }
44 |
45 | function lendingPool() external view returns (ILendingPool) {
46 | return _lendingPool();
47 | }
48 | }
49 |
--------------------------------------------------------------------------------
/contracts/external/aave/IProtocolDataProvider.sol:
--------------------------------------------------------------------------------
1 | // SPDX-License-Identifier: agpl-3.0
2 | pragma solidity 0.8.6;
3 | pragma experimental ABIEncoderV2;
4 |
5 | import {ILendingPoolAddressesProvider} from "./ILendingPoolAddressesProvider.sol";
6 |
7 | interface IProtocolDataProvider {
8 | struct TokenData {
9 | string symbol;
10 | address tokenAddress;
11 | }
12 |
13 | function ADDRESSES_PROVIDER() external view returns (ILendingPoolAddressesProvider);
14 | function getAllReservesTokens() external view returns (TokenData[] memory);
15 | function getAllATokens() external view returns (TokenData[] memory);
16 | function getReserveConfigurationData(address asset) external view returns (uint256 decimals, uint256 ltv, uint256 liquidationThreshold, uint256 liquidationBonus, uint256 reserveFactor, bool usageAsCollateralEnabled, bool borrowingEnabled, bool stableBorrowRateEnabled, bool isActive, bool isFrozen);
17 | function getReserveData(address asset) external view returns (uint256 availableLiquidity, uint256 totalStableDebt, uint256 totalVariableDebt, uint256 liquidityRate, uint256 variableBorrowRate, uint256 stableBorrowRate, uint256 averageStableBorrowRate, uint256 liquidityIndex, uint256 variableBorrowIndex, uint40 lastUpdateTimestamp);
18 | function getUserReserveData(address asset, address user) external view returns (uint256 currentATokenBalance, uint256 currentStableDebt, uint256 currentVariableDebt, uint256 principalStableDebt, uint256 scaledVariableDebt, uint256 stableBorrowRate, uint256 liquidityRate, uint40 stableRateLastUpdated, bool usageAsCollateralEnabled);
19 | function getReserveTokensAddresses(address asset) external view returns (address aTokenAddress, address stableDebtTokenAddress, address variableDebtTokenAddress);
20 | }
21 |
--------------------------------------------------------------------------------
/contracts/external/aave/ILendingPool.sol:
--------------------------------------------------------------------------------
1 | // SPDX-License-Identifier: agpl-3.0
2 | pragma solidity 0.8.6;
3 |
4 | interface ILendingPool {
5 |
6 | /**
7 | * @dev Deposits an `amount` of underlying asset into the reserve, receiving in return overlying aTokens.
8 | * - E.g. User deposits 100 USDC and gets in return 100 aUSDC
9 | * @param asset The address of the underlying asset to deposit
10 | * @param amount The amount to be deposited
11 | * @param onBehalfOf The address that will receive the aTokens, same as msg.sender if the user
12 | * wants to receive them on his own wallet, or a different address if the beneficiary of aTokens
13 | * is a different wallet
14 | * @param referralCode Code used to register the integrator originating the operation, for potential rewards.
15 | * 0 if the action is executed directly by the user, without any middle-man
16 | **/
17 | function deposit(
18 | address asset,
19 | uint256 amount,
20 | address onBehalfOf,
21 | uint16 referralCode
22 | ) external;
23 |
24 | /**
25 | * @dev Withdraws an `amount` of underlying asset from the reserve, burning the equivalent aTokens owned
26 | * E.g. User has 100 aUSDC, calls withdraw() and receives 100 USDC, burning the 100 aUSDC
27 | * @param asset The address of the underlying asset to withdraw
28 | * @param amount The underlying amount to be withdrawn
29 | * - Send the value type(uint256).max in order to withdraw the whole aToken balance
30 | * @param to Address that will receive the underlying, same as msg.sender if the user
31 | * wants to receive it on his own wallet, or a different address if the beneficiary is a
32 | * different wallet
33 | * @return The final amount withdrawn
34 | **/
35 | function withdraw(
36 | address asset,
37 | uint256 amount,
38 | address to
39 | ) external returns (uint256);
40 |
41 | }
42 |
--------------------------------------------------------------------------------
/aave/aavePolygon.json:
--------------------------------------------------------------------------------
1 | {"proto":[
2 | {
3 | "aTokenAddress":"0x27F8D03b3a2196956ED754baDc28D73be8830A6e",
4 | "aTokenSymbol":"amDAI",
5 | "stableDebtTokenAddress":"",
6 | "variableDebtTokenAddress":"",
7 | "symbol":"DAI",
8 | "address":"0x8f3Cf7ad23Cd3CaDbD9735AFf958023239c6A063",
9 | "decimals":18
10 | },
11 | {
12 | "aTokenAddress":"0x1a13F4Ca1d028320A707D99520AbFefca3998b7F",
13 | "aTokenSymbol":"amUSDC",
14 | "stableDebtTokenAddress":"",
15 | "variableDebtTokenAddress":"",
16 | "symbol":"USDC",
17 | "address":"0x2791Bca1f2de4661ED88A30C99A7a9449Aa84174",
18 | "decimals":6
19 | },
20 | {
21 | "aTokenAddress":"0x60D55F02A771d515e077c9C2403a1ef324885CeC",
22 | "aTokenSymbol":"amUSDT",
23 | "stableDebtTokenAddress":"",
24 | "variableDebtTokenAddress":"",
25 | "symbol":"USDT",
26 | "address":"0xc2132D05D31c914a87C6611C10748AEb04B58e8F",
27 | "decimals":6
28 | },
29 | {
30 | "aTokenAddress":"0x5c2ed810328349100A66B82b78a1791B101C9D61",
31 | "aTokenSymbol":"amWBTC",
32 | "stableDebtTokenAddress":"",
33 | "variableDebtTokenAddress":"",
34 | "symbol":"WBTC",
35 | "address":"0x1BFD67037B42Cf73acF2047067bd4F2C47D9BfD6",
36 | "decimals":8
37 | },
38 | {
39 | "aTokenAddress":"0x28424507fefb6f7f8E9D3860F56504E4e5f5f390",
40 | "aTokenSymbol":"amWETH",
41 | "stableDebtTokenAddress":"",
42 | "variableDebtTokenAddress":"",
43 | "symbol":"WETH",
44 | "address":"0x7ceB23fD6bC0adD59E62ac25578270cFf1b9f619",
45 | "decimals":18
46 | },
47 | {
48 | "aTokenAddress":"0x8dF3aad3a84da6b69A4DA8aeC3eA40d9091B2Ac4",
49 | "aTokenSymbol":"amWMATIC",
50 | "stableDebtTokenAddress":"",
51 | "variableDebtTokenAddress":"",
52 | "symbol":"WMATIC",
53 | "address":"0x0d500B1d8E8eF31E21C99d1Db9A6444d3ADf1270",
54 | "decimals":18
55 | },
56 | {
57 | "aTokenAddress":"0x1d2a0E5EC8E5bBDCA5CB219e649B565d8e5c3360",
58 | "aTokenSymbol":"amAAVE",
59 | "stableDebtTokenAddress":"",
60 | "variableDebtTokenAddress":"",
61 | "symbol":"AAVE",
62 | "address":"0xD6DF932A45C0f255f85145f286eA0b292B21C90B",
63 | "decimals":18
64 | }
65 | ]}
--------------------------------------------------------------------------------
/hardhat.network.ts:
--------------------------------------------------------------------------------
1 | import { HardhatUserConfig } from 'hardhat/config';
2 |
3 | const alchemyUrl = process.env.ALCHEMY_URL;
4 | const infuraApiKey = process.env.INFURA_API_KEY;
5 | const mnemonic = process.env.HDWALLET_MNEMONIC;
6 |
7 | const networks: HardhatUserConfig['networks'] = {
8 | coverage: {
9 | url: 'http://127.0.0.1:8555',
10 | blockGasLimit: 200000000,
11 | allowUnlimitedContractSize: true,
12 | },
13 | localhost: {
14 | chainId: 1,
15 | url: 'http://127.0.0.1:8545',
16 | allowUnlimitedContractSize: true,
17 | },
18 | };
19 |
20 | if (alchemyUrl && process.env.FORK_ENABLED && mnemonic) {
21 | networks.hardhat = {
22 | chainId: 1,
23 | forking: {
24 | url: alchemyUrl,
25 | blockNumber: 12226812,
26 | },
27 | accounts: {
28 | mnemonic,
29 | },
30 | };
31 | } else {
32 | networks.hardhat = {
33 | allowUnlimitedContractSize: true,
34 | };
35 | }
36 |
37 | if (mnemonic) {
38 | networks.xdai = {
39 | chainId: 100,
40 | url: 'https://rpc.xdaichain.com/',
41 | accounts: {
42 | mnemonic,
43 | },
44 | };
45 | networks.poaSokol = {
46 | chainId: 77,
47 | url: 'https://sokol.poa.network',
48 | accounts: {
49 | mnemonic,
50 | },
51 | };
52 | networks.matic = {
53 | chainId: 137,
54 | url: 'https://rpc-mainnet.maticvigil.com',
55 | accounts: {
56 | mnemonic,
57 | },
58 | };
59 | networks.mumbai = {
60 | chainId: 80001,
61 | url: 'https://rpc-mumbai.matic.today',
62 | accounts: {
63 | mnemonic,
64 | },
65 | loggingEnabled: true,
66 | };
67 | }
68 |
69 | if (infuraApiKey && mnemonic) {
70 | networks.kovan = {
71 | url: `https://kovan.infura.io/v3/${infuraApiKey}`,
72 | accounts: {
73 | mnemonic,
74 | },
75 | };
76 |
77 | networks.ropsten = {
78 | url: `https://ropsten.infura.io/v3/${infuraApiKey}`,
79 | accounts: {
80 | mnemonic,
81 | },
82 | };
83 |
84 | networks.rinkeby = {
85 | url: `https://rinkeby.infura.io/v3/${infuraApiKey}`,
86 | accounts: {
87 | mnemonic,
88 | },
89 | };
90 |
91 | networks.mainnet = {
92 | url: alchemyUrl,
93 | accounts: {
94 | mnemonic,
95 | },
96 | };
97 | } else {
98 | console.warn('No infura or hdwallet available for testnets');
99 | }
100 |
101 | export default networks;
102 |
--------------------------------------------------------------------------------
/contracts/external/aave/ILendingPoolAddressesProvider.sol:
--------------------------------------------------------------------------------
1 | // SPDX-License-Identifier: agpl-3.0
2 | pragma solidity 0.8.6;
3 |
4 | /**
5 | * @title LendingPoolAddressesProvider contract
6 | * @dev Main registry of addresses part of or connected to the protocol, including permissioned roles
7 | * - Acting also as factory of proxies and admin of those, so with right to change its implementations
8 | * - Owned by the Aave Governance
9 | * @author Aave
10 | **/
11 | interface ILendingPoolAddressesProvider {
12 | event MarketIdSet(string newMarketId);
13 | event LendingPoolUpdated(address indexed newAddress);
14 | event ConfigurationAdminUpdated(address indexed newAddress);
15 | event EmergencyAdminUpdated(address indexed newAddress);
16 | event LendingPoolConfiguratorUpdated(address indexed newAddress);
17 | event LendingPoolCollateralManagerUpdated(address indexed newAddress);
18 | event PriceOracleUpdated(address indexed newAddress);
19 | event LendingRateOracleUpdated(address indexed newAddress);
20 | event ProxyCreated(bytes32 id, address indexed newAddress);
21 | event AddressSet(bytes32 id, address indexed newAddress, bool hasProxy);
22 |
23 | function getMarketId() external view returns (string memory);
24 |
25 | function setMarketId(string calldata marketId) external;
26 |
27 | function setAddress(bytes32 id, address newAddress) external;
28 |
29 | function setAddressAsProxy(bytes32 id, address impl) external;
30 |
31 | function getAddress(bytes32 id) external view returns (address);
32 |
33 | function getLendingPool() external view returns (address);
34 |
35 | function setLendingPoolImpl(address pool) external;
36 |
37 | function getLendingPoolConfigurator() external view returns (address);
38 |
39 | function setLendingPoolConfiguratorImpl(address configurator) external;
40 |
41 | function getLendingPoolCollateralManager() external view returns (address);
42 |
43 | function setLendingPoolCollateralManager(address manager) external;
44 |
45 | function getPoolAdmin() external view returns (address);
46 |
47 | function setPoolAdmin(address admin) external;
48 |
49 | function getEmergencyAdmin() external view returns (address);
50 |
51 | function setEmergencyAdmin(address admin) external;
52 |
53 | function getPriceOracle() external view returns (address);
54 |
55 | function setPriceOracle(address priceOracle) external;
56 |
57 | function getLendingRateOracle() external view returns (address);
58 |
59 | function setLendingRateOracle(address lendingRateOracle) external;
60 | }
61 |
--------------------------------------------------------------------------------
/scripts/fork/distributeEtherFromBinance.ts:
--------------------------------------------------------------------------------
1 | import { dai, usdc } from '@studydefi/money-legos/erc20';
2 |
3 | import { task } from 'hardhat/config';
4 |
5 | import {
6 | BINANCE_ADDRESS,
7 | BINANCE7_ADDRESS,
8 | DAI_RICH_ADDRESS,
9 | LARGE_GUSD_ADDRESS,
10 | GUSD_ADDRESS,
11 | LARGE_BUSD_ADDRESS,
12 | BUSD_ADDRESS,
13 | SUSD_ADDRESS,
14 | LARGE_SUSD_ADDRESS,
15 | } from '../../Constant';
16 | import { info, success } from '../helpers';
17 |
18 | export default task(
19 | 'fork:distribute-ether-from-binance',
20 | 'Distribute Ether from Binance',
21 | ).setAction(async (taskArguments, hre) => {
22 | info('Gathering funds from Binance...');
23 |
24 | const { getNamedAccounts, ethers } = hre;
25 | const { provider, getContractAt, getSigners } = ethers;
26 | const { deployer } = await getNamedAccounts();
27 |
28 | const [contractsOwner, yieldSourceOwner] = await getSigners();
29 |
30 | const binance = provider.getUncheckedSigner(BINANCE_ADDRESS);
31 | const binance7 = provider.getUncheckedSigner(BINANCE7_ADDRESS);
32 | const gusdHolder = provider.getUncheckedSigner(LARGE_GUSD_ADDRESS);
33 | const busdHolder = provider.getUncheckedSigner(LARGE_BUSD_ADDRESS);
34 | const susdHolder = provider.getUncheckedSigner(LARGE_SUSD_ADDRESS);
35 |
36 | const daiContract = await getContractAt(dai.abi, dai.address, binance);
37 | const usdcContract = await getContractAt(usdc.abi, usdc.address, binance7);
38 | const gusdContract = await getContractAt(dai.abi, GUSD_ADDRESS, gusdHolder);
39 | const busdContract = await getContractAt(dai.abi, BUSD_ADDRESS, busdHolder);
40 | const susdContract = await getContractAt(dai.abi, SUSD_ADDRESS, susdHolder);
41 |
42 | const recipients: { [key: string]: string } = {
43 | ['Deployer']: deployer,
44 | ['Dai Rich Signer']: DAI_RICH_ADDRESS,
45 | ['contractsOwner']: contractsOwner.address,
46 | ['yieldSourceOwner']: yieldSourceOwner.address,
47 | };
48 |
49 | await binance.sendTransaction({ to: gusdHolder._address, value: ethers.utils.parseEther('10') });
50 |
51 | const keys = Object.keys(recipients);
52 |
53 | for (var i = 0; i < keys.length; i++) {
54 | const name = keys[i];
55 | const address = recipients[name];
56 |
57 | info(`Sending 1000 Ether to ${name}...`);
58 | await binance.sendTransaction({ to: address, value: ethers.utils.parseEther('1000') });
59 |
60 | info(`Sending 1000 Dai to ${name}...`);
61 | await daiContract.transfer(address, ethers.utils.parseEther('1000'));
62 |
63 | info(`Sending 1000 GUSD to ${name}...`);
64 | await gusdContract.transfer(address, ethers.utils.parseUnits('1000', 2));
65 |
66 | info(`Sending 1000 BUSD to ${name}...`);
67 | await busdContract.transfer(address, ethers.utils.parseUnits('1000', 18));
68 |
69 | info(`Sending 1000 SUSD to ${name}...`);
70 | await susdContract.transfer(address, ethers.utils.parseUnits('1000', 18));
71 | }
72 |
73 | success('Done!');
74 | });
75 |
--------------------------------------------------------------------------------
/contracts/test/SafeERC20Wrapper.sol:
--------------------------------------------------------------------------------
1 | // SPDX-License-Identifier: GPL-3.0
2 |
3 | pragma solidity 0.8.6;
4 |
5 | import "@openzeppelin/contracts/token/ERC20/ERC20.sol";
6 | import "@openzeppelin/contracts/token/ERC20/IERC20.sol";
7 | import "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol";
8 |
9 | // Mock implementation from OpenZeppelin modified for our usage in tests
10 | // https://github.com/OpenZeppelin/openzeppelin-contracts-/blob/master/contracts/mocks/SafeERC20Helper.sol
11 | contract ERC20ReturnTrueMock is ERC20 {
12 |
13 | mapping (address => uint256) private _allowances;
14 |
15 | // IERC20's functions are not pure, but these mock implementations are: to prevent Solidity from issuing warnings,
16 | // we write to a dummy state variable.
17 | uint256 private _dummy;
18 |
19 | constructor (string memory _name, string memory _symbol) ERC20(_name, _symbol) {}
20 |
21 | function transfer(address, uint256) public override returns (bool) {
22 | _dummy = 0;
23 | return true;
24 | }
25 |
26 | function transferFrom(address, address, uint256) public override returns (bool) {
27 | _dummy = 0;
28 | return true;
29 | }
30 |
31 | function approve(address, uint256) public override returns (bool) {
32 | _dummy = 0;
33 | return true;
34 | }
35 |
36 | function setAllowance(uint256 allowance_) public {
37 | _allowances[_msgSender()] = allowance_;
38 | }
39 |
40 | function allowance(address owner, address) public view override returns (uint256) {
41 | return _allowances[owner];
42 | }
43 | uint256[48] private __gap;
44 | }
45 |
46 | contract SafeERC20Wrapper {
47 | using SafeERC20 for IERC20;
48 |
49 | IERC20 private _token;
50 |
51 | // solhint-disable func-name-mixedcase
52 | constructor (IERC20 token) public {
53 | _token = token;
54 | }
55 |
56 | function balanceOf(address account) public view returns (uint256) {
57 | _token.balanceOf(account);
58 | }
59 |
60 | function transfer(address recipient, uint256 amount) public returns (bool) {
61 | _token.safeTransfer(recipient, amount);
62 | }
63 |
64 | function transferFrom(address sender, address recipient, uint256 amount) public returns (bool) {
65 | _token.safeTransferFrom(sender, recipient, amount);
66 | }
67 |
68 | function approve(address spender, uint256 amount) public returns (bool) {
69 | _token.safeApprove(spender, amount);
70 | }
71 |
72 | function increaseAllowance(uint256 amount) public {
73 | _token.safeIncreaseAllowance(address(0), amount);
74 | }
75 |
76 | function decreaseAllowance(uint256 amount) public {
77 | _token.safeDecreaseAllowance(address(0), amount);
78 | }
79 |
80 | function setAllowance(uint256 allowance_) public {
81 | ERC20ReturnTrueMock(address(_token)).setAllowance(allowance_);
82 | }
83 |
84 | function allowance(address owner, address spender) public view returns (uint256) {
85 | return _token.allowance(owner, spender);
86 | }
87 | uint256[49] private __gap;
88 | }
89 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "@pooltogether/aave-yield-source",
3 | "version": "1.2.1",
4 | "description": "PoolTogether Aave Yield Source",
5 | "main": "index.js",
6 | "license": "GPL-3.0",
7 | "scripts": {
8 | "clean": "rm -rf cache/ artifacts/",
9 | "compile": "hardhat --show-stack-traces --max-memory 8192 compile",
10 | "coverage": "HIDE_DEPLOY_LOG=true OPTIMIZER_DISABLED=true hardhat coverage",
11 | "deploy": "hardhat deploy --write true --network",
12 | "etherscan-verify": "hardhat etherscan:verify --network",
13 | "format": "prettier --config .prettierrc --write **/*.ts",
14 | "format:file": "prettier --config .prettierrc --write",
15 | "hint": "solhint \"contracts/**/*.sol\"",
16 | "start-fork": "FORK_ENABLED=true hardhat node --no-reset --no-deploy",
17 | "impersonate-accounts": "hardhat --network localhost fork:impersonate-accounts ",
18 | "distribute-ether": "hardhat --network localhost fork:distribute-ether-from-binance",
19 | "create-prize-pool": "hardhat --network localhost fork:create-aave-prize-pool",
20 | "deploy-fork": "FORK_ENABLED=true; rm -rf deployments/localhost && yarn deploy localhost",
21 | "run-fork": "FORK_ENABLED=true; yarn impersonate-accounts && yarn distribute-ether && yarn create-prize-pool",
22 | "test": "yarn clean && hardhat test",
23 | "typechain": "typechain --target=ethers-v5 \"./abis/*.json\" --out-dir \"./types\"",
24 | "verify": "hardhat etherscan-verify --license GPL-3.0 --solc-input --network",
25 | "integration-test-mumbai": "yarn deploy mumbai && hardhat run --network mumbai ./scripts/createAndRunPrizePoolMumbai.ts ",
26 | "prepack": "rm -rf build cache && yarn compile",
27 | "postpublish": "PACKAGE_VERSION=$(./scripts/package-version.sh) && git tag -ae v$PACKAGE_VERSION && git push --tags"
28 | },
29 | "dependencies": {
30 | "@openzeppelin/contracts": "4.3.2",
31 | "@pooltogether/fixed-point": "1.0.0",
32 | "@pooltogether/owner-manager-contracts": "1.1.0",
33 | "@pooltogether/yield-source-interface": "1.3.0"
34 | },
35 | "devDependencies": {
36 | "@nomiclabs/hardhat-ethers": "2.0.2",
37 | "@nomiclabs/hardhat-etherscan": "2.1.1",
38 | "@nomiclabs/hardhat-waffle": "2.0.1",
39 | "@openzeppelin/hardhat-upgrades": "1.6.0",
40 | "@studydefi/money-legos": "2.4.1",
41 | "@typechain/ethers-v5": "7.0.1",
42 | "@typechain/hardhat": "2.3.0",
43 | "@types/chai": "4.2.15",
44 | "@types/debug": "4.1.5",
45 | "@types/mocha": "8.2.1",
46 | "@types/node": "14.14.32",
47 | "chai": "4.3.4",
48 | "chalk": "4.1.0",
49 | "debug": "4.3.1",
50 | "ethereum-waffle": "3.3.0",
51 | "ethers": "5.4.6",
52 | "evm-chains": "0.2.0",
53 | "hardhat": "2.1.1",
54 | "hardhat-abi-exporter": "2.1.2",
55 | "hardhat-dependency-compiler": "1.1.1",
56 | "hardhat-deploy": "0.9.0",
57 | "hardhat-deploy-ethers": "0.3.0-beta.7",
58 | "hardhat-gas-reporter": "1.0.4",
59 | "prettier": "2.2.1",
60 | "solhint": "3.3.3",
61 | "solidity-coverage": "0.7.17",
62 | "ts-generator": "0.1.1",
63 | "ts-node": "9.1.1",
64 | "typechain": "5.1.2",
65 | "typescript": "4.2.3"
66 | },
67 | "files": [
68 | "LICENSE",
69 | "abis/**",
70 | "aave/",
71 | "artifacts/**",
72 | "contracts/**",
73 | "types/**"
74 | ]
75 | }
76 |
--------------------------------------------------------------------------------
/contracts/external/aave/IAToken.sol:
--------------------------------------------------------------------------------
1 | // SPDX-License-Identifier: agpl-3.0
2 | pragma solidity 0.8.6;
3 |
4 | import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol";
5 |
6 | interface IAToken is IERC20 {
7 | /**
8 | * @dev Emitted after the mint action
9 | * @param from The address performing the mint
10 | * @param value The amount being
11 | * @param index The new liquidity index of the reserve
12 | **/
13 | event Mint(address indexed from, uint256 value, uint256 index);
14 |
15 | /**
16 | * @dev Mints `amount` aTokens to `user`
17 | * @param user The address receiving the minted tokens
18 | * @param amount The amount of tokens getting minted
19 | * @param index The new liquidity index of the reserve
20 | * @return `true` if the the previous balance of the user was 0
21 | */
22 | function mint(
23 | address user,
24 | uint256 amount,
25 | uint256 index
26 | ) external returns (bool);
27 |
28 | /**
29 | * @dev Emitted after aTokens are burned
30 | * @param from The owner of the aTokens, getting them burned
31 | * @param target The address that will receive the underlying
32 | * @param value The amount being burned
33 | * @param index The new liquidity index of the reserve
34 | **/
35 | event Burn(address indexed from, address indexed target, uint256 value, uint256 index);
36 |
37 | /**
38 | * @dev Emitted during the transfer action
39 | * @param from The user whose tokens are being transferred
40 | * @param to The recipient
41 | * @param value The amount being transferred
42 | * @param index The new liquidity index of the reserve
43 | **/
44 | event BalanceTransfer(address indexed from, address indexed to, uint256 value, uint256 index);
45 |
46 | /**
47 | * @dev Burns aTokens from `user` and sends the equivalent amount of underlying to `receiverOfUnderlying`
48 | * @param user The owner of the aTokens, getting them burned
49 | * @param receiverOfUnderlying The address that will receive the underlying
50 | * @param amount The amount being burned
51 | * @param index The new liquidity index of the reserve
52 | **/
53 | function burn(
54 | address user,
55 | address receiverOfUnderlying,
56 | uint256 amount,
57 | uint256 index
58 | ) external;
59 |
60 | /**
61 | * @dev Mints aTokens to the reserve treasury
62 | * @param amount The amount of tokens getting minted
63 | * @param index The new liquidity index of the reserve
64 | */
65 | function mintToTreasury(uint256 amount, uint256 index) external;
66 |
67 | /**
68 | * @dev Transfers aTokens in the event of a borrow being liquidated, in case the liquidators reclaims the aToken
69 | * @param from The address getting liquidated, current owner of the aTokens
70 | * @param to The recipient
71 | * @param value The amount of tokens getting transferred
72 | **/
73 | function transferOnLiquidation(
74 | address from,
75 | address to,
76 | uint256 value
77 | ) external;
78 |
79 | /**
80 | * @dev Transfers the underlying asset to `target`. Used by the LendingPool to transfer
81 | * assets in borrow(), withdraw() and flashLoan()
82 | * @param user The recipient of the aTokens
83 | * @param amount The amount getting transferred
84 | * @return The amount transferred
85 | **/
86 | function transferUnderlyingTo(address user, uint256 amount) external returns (uint256);
87 | }
88 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 | # PoolTogether Aave Yield Source 👻
10 |
11 | 
12 | [](https://coveralls.io/github/pooltogether/aave-yield-source?branch=main)
13 | [](https://docs.openzeppelin.com/)
14 |
15 | PoolTogether Yield Source that uses [Aave](https://aave.com/) V2 to generate yield by lending any ERC20 token supported by Aave and deposited into the Aave Yield Source.
16 |
17 | # Usage
18 |
19 | ## Adding a new Aave Lending Pool
20 |
21 | First make sure the Aave json config (`aave/aaveMainnet.json`, etc) includes the lending pool.
22 |
23 | Then, add the token symbol to the list in `aave.config.ts`.
24 |
25 | ## Deployment
26 |
27 | Follow Installation instructions.
28 |
29 | Aave provides a json blob per network of the files in the [docs](https://docs.aave.com/developers/deployed-contracts/deployed-contracts)
30 | The deploy script parses this and deploys a proxy contract if the aToken file does exist in the deployments directory.
31 |
32 | Ensure the `lendingPoolAddressesProviderRegistry` is up to date in the namedAccounts field of `hardhat.config.ts` .
33 |
34 | To add a new network, add a json file in the `./aave` directory then run:
35 |
36 | `yarn deploy `
37 |
38 | To add a new lending market, update the appropriate network json at `./aave` and run:
39 |
40 | `yarn deploy `
41 |
42 | The deployment script can be found in `deploy/deploy.ts`.
43 |
44 | ## Development
45 |
46 | Clone this repository and enter the directory.
47 |
48 | ### Installation
49 |
50 | Install dependencies:
51 |
52 | ```
53 | yarn
54 | ```
55 |
56 | This project uses [Yarn 2](https://yarnpkg.com), dependencies should get installed pretty quickly.
57 |
58 | ### Env
59 |
60 | We use [direnv](https://direnv.net) to manage environment variables. You'll likely need to install it.
61 |
62 | Copy `.envrc.example` and write down the env variables needed to run this project.
63 | ```
64 | cp .envrc.example .envrc
65 | ```
66 |
67 | Once your env variables are setup, load them with:
68 | ```
69 | direnv allow
70 | ```
71 |
72 | ### Test
73 |
74 | We use the [Hardhat](https://hardhat.org) ecosystem to test and deploy our contracts.
75 |
76 | To run unit tests:
77 |
78 | ```
79 | yarn test
80 | ```
81 |
82 | To run [solhint](https://protofire.github.io/solhint/) and tests:
83 |
84 | ```
85 | yarn verify
86 | ```
87 |
88 | To run coverage:
89 |
90 | ```
91 | yarn coverage
92 | ```
93 |
94 | ### Mainnet fork
95 |
96 | Before deploying, you can make sure your implementation works by deploying a Yield Source Prize Pool on a fork of Mainnet.
97 |
98 | Start Mainnet fork in a terminal window with the command:
99 |
100 | ```
101 | yarn start-fork
102 | ```
103 |
104 | In another window, start the scripts to deploy and create a Aave Yield Source Prize Pool, deposit Dai into it, award the prize and withdraw.
105 |
106 | ```
107 | yarn deploy-fork && yarn run-fork
108 | ```
109 |
110 | ### Contract Verification
111 |
112 | Once deployment is done, you can verify your contracts on [Etherscan](https://etherscan.io) by typing:
113 |
114 | ```
115 | yarn verify
116 | ```
117 |
118 | ### Code quality
119 |
120 | [Prettier](https://prettier.io) is used to format TypeScript code. Use it by running:
121 |
122 | ```
123 | yarn format
124 | ```
125 |
126 | [Solhint](https://protofire.github.io/solhint/) is used to lint Solidity files. Run it with:
127 | ```
128 | yarn hint
129 | ```
130 |
131 | [TypeChain](https://github.com/ethereum-ts/Typechain) is used to generates types for scripts and tests. Generate types by running:
132 | ```
133 | yarn typechain
134 | ```
135 |
--------------------------------------------------------------------------------
/scripts/runLifecycle.ts:
--------------------------------------------------------------------------------
1 | import { ethers, deployments, getNamedAccounts, getChainId } from 'hardhat';
2 | import YieldSourcePrizePool from '@pooltogether/pooltogether-contracts/abis/YieldSourcePrizePool.json';
3 | import PeriodicPrizeStrategy from '@pooltogether/pooltogether-contracts/abis/PeriodicPrizeStrategy.json';
4 | import MultipleWinners from '@pooltogether/pooltogether-contracts/abis/MultipleWinners.json';
5 | import { dai } from '@studydefi/money-legos/erc20';
6 |
7 | const daiERC20Address = '0x8f3Cf7ad23Cd3CaDbD9735AFf958023239c6A063';
8 | const daiYieldSourcePrizePool = '0xA4579F6C63858a28eA88ee4c1bdaD628888C4aDB';
9 |
10 | async function runLifeCycle() {
11 | depositTo();
12 | await new Promise((r) => setTimeout(r, 5000));
13 | startAward();
14 | await new Promise((r) => setTimeout(r, 5000));
15 | completeAward();
16 | await new Promise((r) => setTimeout(r, 5000));
17 | instantWithdraw();
18 | }
19 | runLifeCycle();
20 |
21 | async function depositTo() {
22 | const { deployer } = await getNamedAccounts();
23 |
24 | console.log('deployer is ', deployer);
25 | const signer = await ethers.provider.getSigner(deployer);
26 |
27 | const daiERC20Contract = await ethers.getContractAt(dai.abi, daiERC20Address);
28 |
29 | console.log('balanceOf deployer ', await daiERC20Contract.balanceOf(deployer));
30 |
31 | const prizePool = await ethers.getContractAt(
32 | YieldSourcePrizePool,
33 | daiYieldSourcePrizePool,
34 | signer,
35 | );
36 |
37 | console.log('controlledToken is ', await prizePool.token());
38 |
39 | const depositAmount = ethers.utils.parseEther('2');
40 | const approveResult = await daiERC20Contract.approve(daiYieldSourcePrizePool, depositAmount);
41 | console.log('approveResult ', approveResult.toString());
42 |
43 | const prizeStrategy = await ethers.getContractAt(
44 | MultipleWinners,
45 | await prizePool.prizeStrategy(),
46 | signer,
47 | );
48 | const ticketAddress = await prizeStrategy.ticket();
49 |
50 | const depositToResult = await prizePool.depositTo(
51 | deployer,
52 | depositAmount,
53 | ticketAddress,
54 | ethers.constants.AddressZero,
55 | );
56 | console.log('depositTo is ', depositToResult);
57 | }
58 | // depositTo()
59 |
60 | async function instantWithdraw() {
61 | const { deployer } = await getNamedAccounts();
62 | const signer = await ethers.provider.getSigner(deployer);
63 |
64 | const prizePool = await ethers.getContractAt(
65 | YieldSourcePrizePool,
66 | daiYieldSourcePrizePool,
67 | signer,
68 | );
69 | const withdrawAmount = ethers.utils.parseEther('0.5');
70 | const prizeStrategy = await ethers.getContractAt(
71 | MultipleWinners,
72 | await prizePool.prizeStrategy(),
73 | signer,
74 | );
75 | const ticketAddress = await prizeStrategy.ticket();
76 |
77 | const exitFee = await ethers.utils.parseEther('2');
78 |
79 | const withdrawResult = await prizePool.withdrawInstantlyFrom(
80 | deployer,
81 | withdrawAmount,
82 | ticketAddress,
83 | exitFee,
84 | );
85 | console.log(withdrawResult);
86 | }
87 | // instantWithdraw()
88 |
89 | async function balanceOf() {
90 | const { deployer } = await getNamedAccounts();
91 | const signer = await ethers.provider.getSigner(deployer);
92 |
93 | const prizePool = await ethers.getContractAt(
94 | YieldSourcePrizePool,
95 | daiYieldSourcePrizePool,
96 | signer,
97 | );
98 | console.log(await prizePool.balance());
99 | }
100 | // balanceOf()
101 |
102 | async function startAward() {
103 | const { deployer } = await getNamedAccounts();
104 | const signer = await ethers.provider.getSigner(deployer);
105 |
106 | const prizePool = await ethers.getContractAt(
107 | YieldSourcePrizePool,
108 | daiYieldSourcePrizePool,
109 | signer,
110 | );
111 | const prizeStrategy = await ethers.getContractAt(
112 | PeriodicPrizeStrategy,
113 | await prizePool.prizeStrategy(),
114 | signer,
115 | );
116 | console.log(await prizeStrategy.startAward());
117 | }
118 | // startAward()
119 |
120 | async function completeAward() {
121 | const { deployer } = await getNamedAccounts();
122 | const signer = await ethers.provider.getSigner(deployer);
123 |
124 | const prizePool = await ethers.getContractAt(
125 | YieldSourcePrizePool,
126 | daiYieldSourcePrizePool,
127 | signer,
128 | );
129 | const prizeStrategy = await ethers.getContractAt(
130 | PeriodicPrizeStrategy,
131 | await prizePool.prizeStrategy(),
132 | signer,
133 | );
134 | console.log(await prizeStrategy.completeAward());
135 | }
136 | // completeAward()
137 |
--------------------------------------------------------------------------------
/contracts/external/aave/IAaveIncentivesController.sol:
--------------------------------------------------------------------------------
1 | // SPDX-License-Identifier: agpl-3.0
2 |
3 | pragma solidity 0.8.6;
4 |
5 | pragma experimental ABIEncoderV2;
6 |
7 | interface IAaveIncentivesController {
8 | event RewardsAccrued(address indexed user, uint256 amount);
9 |
10 | event RewardsClaimed(address indexed user, address indexed to, uint256 amount);
11 |
12 | // Commented out to avoid displaying warnings about duplicate definition
13 | // event RewardsClaimed(
14 | // address indexed user,
15 | // address indexed to,
16 | // address indexed claimer,
17 | // uint256 amount
18 | // );
19 |
20 | event ClaimerSet(address indexed user, address indexed claimer);
21 |
22 | /*
23 | * @dev Returns the configuration of the distribution for a certain asset
24 | * @param asset The address of the reference asset of the distribution
25 | * @return The asset index, the emission per second and the last updated timestamp
26 | **/
27 | function getAssetData(address asset)
28 | external
29 | view
30 | returns (
31 | uint256,
32 | uint256,
33 | uint256
34 | );
35 |
36 | /**
37 | * @dev Whitelists an address to claim the rewards on behalf of another address
38 | * @param user The address of the user
39 | * @param claimer The address of the claimer
40 | */
41 | function setClaimer(address user, address claimer) external;
42 |
43 | /**
44 | * @dev Returns the whitelisted claimer for a certain address (0x0 if not set)
45 | * @param user The address of the user
46 | * @return The claimer address
47 | */
48 | function getClaimer(address user) external view returns (address);
49 |
50 | /**
51 | * @dev Configure assets for a certain rewards emission
52 | * @param assets The assets to incentivize
53 | * @param emissionsPerSecond The emission for each asset
54 | */
55 | function configureAssets(address[] calldata assets, uint256[] calldata emissionsPerSecond)
56 | external;
57 |
58 | /**
59 | * @dev Called by the corresponding asset on any update that affects the rewards distribution
60 | * @param asset The address of the user
61 | * @param userBalance The balance of the user of the asset in the lending pool
62 | * @param totalSupply The total supply of the asset in the lending pool
63 | **/
64 | function handleAction(
65 | address asset,
66 | uint256 userBalance,
67 | uint256 totalSupply
68 | ) external;
69 |
70 | /**
71 | * @dev Returns the total of rewards of an user, already accrued + not yet accrued
72 | * @param user The address of the user
73 | * @return The rewards
74 | **/
75 | function getRewardsBalance(address[] calldata assets, address user)
76 | external
77 | view
78 | returns (uint256);
79 |
80 | /**
81 | * @dev Claims reward for an user, on all the assets of the lending pool, accumulating the pending rewards
82 | * @param amount Amount of rewards to claim
83 | * @param to Address that will be receiving the rewards
84 | * @return Rewards claimed
85 | **/
86 | function claimRewards(
87 | address[] calldata assets,
88 | uint256 amount,
89 | address to
90 | ) external returns (uint256);
91 |
92 | /**
93 | * @dev Claims reward for an user on behalf, on all the assets of the lending pool, accumulating the pending rewards. The caller must
94 | * be whitelisted via "allowClaimOnBehalf" function by the RewardsAdmin role manager
95 | * @param amount Amount of rewards to claim
96 | * @param user Address to check and claim rewards
97 | * @param to Address that will be receiving the rewards
98 | * @return Rewards claimed
99 | **/
100 | function claimRewardsOnBehalf(
101 | address[] calldata assets,
102 | uint256 amount,
103 | address user,
104 | address to
105 | ) external returns (uint256);
106 |
107 | /**
108 | * @dev returns the unclaimed rewards of the user
109 | * @param user the address of the user
110 | * @return the unclaimed user rewards
111 | */
112 | function getUserUnclaimedRewards(address user) external view returns (uint256);
113 |
114 | /**
115 | * @dev returns the unclaimed rewards of the user
116 | * @param user the address of the user
117 | * @param asset The asset to incentivize
118 | * @return the user index for the asset
119 | */
120 | function getUserAssetData(address user, address asset) external view returns (uint256);
121 |
122 | /**
123 | * @dev for backward compatibility with previous implementation of the Incentives controller
124 | */
125 | function REWARD_TOKEN() external view returns (address);
126 |
127 | /**
128 | * @dev for backward compatibility with previous implementation of the Incentives controller
129 | */
130 | function PRECISION() external view returns (uint8);
131 |
132 | /**
133 | * @dev Gets the distribution end timestamp of the emissions
134 | */
135 | function DISTRIBUTION_END() external view returns (uint256);
136 | }
137 |
--------------------------------------------------------------------------------
/aave/aaveKovan.json:
--------------------------------------------------------------------------------
1 | {"proto":[{"aTokenAddress":"0x6d93ef8093F067f19d33C2360cE17b20a8c45CD7","aTokenSymbol":"aAAVE","stableDebtTokenAddress":"0x72d2Aea8aCcD3277D90093a974eFf3e1945871D7","variableDebtTokenAddress":"0x5aF7bAC415D9c249176ea233E92646E5c9288b92","symbol":"AAVE","address":"0xB597cd8D3217ea6477232F9217fa70837ff667Af","decimals":18},{"aTokenAddress":"0x28f92b4c8Bdab37AF6C4422927158560b4bB446e","aTokenSymbol":"aBAT","stableDebtTokenAddress":"0x07a0B32983ab8203E8C3493F0AbE5bFe784fAa15","variableDebtTokenAddress":"0xcE271C229576605bdabD0A3D664685cbC383f3a6","symbol":"BAT","address":"0x2d12186Fbb9f9a8C28B3FfdD4c42920f8539D738","decimals":18},{"aTokenAddress":"0xfe3E41Db9071458e39104711eF1Fa668bae44e85","aTokenSymbol":"aBUSD","stableDebtTokenAddress":"0x597c5d0390E7e995d36F2e49F9eD979697723bE9","variableDebtTokenAddress":"0xB85eCAd7a9C9F09749CeCF84122189A7908eC934","symbol":"BUSD","address":"0x4c6E1EFC12FDfD568186b7BAEc0A43fFfb4bCcCf","decimals":18},{"aTokenAddress":"0xdCf0aF9e59C002FA3AA091a46196b37530FD48a8","aTokenSymbol":"aDAI","stableDebtTokenAddress":"0x3B91257Fe5CA63b4114ac41A0d467D25E2F747F3","variableDebtTokenAddress":"0xEAbBDBe7aaD7d5A278da40967E62C8c8Fe5fAec8","symbol":"DAI","address":"0xFf795577d9AC8bD7D90Ee22b6C1703490b6512FD","decimals":18},{"aTokenAddress":"0x1d1F2Cb9ED46A8d5bf0254E5CE400514D62d55F0","aTokenSymbol":"aENJ","stableDebtTokenAddress":"0x8af08B5874380E1F1816e30bE12d773f4EB70e67","variableDebtTokenAddress":"0xc11e09B03634144a1862E14ef7569DbEb4b7F3a2","symbol":"ENJ","address":"0xC64f90Cd7B564D3ab580eb20a102A8238E218be2","decimals":18},{"aTokenAddress":"0xdDdEC78e29f3b579402C42ca1fd633DE00D23940","aTokenSymbol":"aKNC","stableDebtTokenAddress":"0x7f4E5bA1eE5dCAa4440371ec521cBc130De12E5e","variableDebtTokenAddress":"0x196d717b2D8a5694572C2742343C333EA27B8288","symbol":"KNC","address":"0x3F80c39c0b96A0945f9F0E9f55d8A8891c5671A8","decimals":18},{"aTokenAddress":"0xeD9044cA8F7caCe8eACcD40367cF2bee39eD1b04","aTokenSymbol":"aLINK","stableDebtTokenAddress":"0x0DBEE55AB73e3C14421d3f437a218ea99A520556","variableDebtTokenAddress":"0xcCead10A3BA54b1FA6D107b63B7D5e5e2f9888D8","symbol":"LINK","address":"0xAD5ce863aE3E4E9394Ab43d4ba0D80f419F61789","decimals":18},{"aTokenAddress":"0xA288B1767C91Aa9d8A14a65dC6B2E7ce68c02DFd","aTokenSymbol":"aMANA","stableDebtTokenAddress":"0xd4aEcF57cbcfeA373565DE75537aAc911EAF1759","variableDebtTokenAddress":"0xaEE5AA094B55b6538388A4E8CBAe9E81Bfe815e6","symbol":"MANA","address":"0x738Dc6380157429e957d223e6333Dc385c85Fec7","decimals":18},{"aTokenAddress":"0x9d9DaBEae6BcBa881404A9e499B13B2B3C1F329E","aTokenSymbol":"aMKR","stableDebtTokenAddress":"0xC37AadA7758e10a49bdECb9078753d5D096A4649","variableDebtTokenAddress":"0xB86a93aA1325e4F58E3dbA7CE9DA251D83374fA2","symbol":"MKR","address":"0x61e4CAE3DA7FD189e52a4879C7B8067D7C2Cc0FA","decimals":18},{"aTokenAddress":"0x01875ee883B32f5f961A92eC597DcEe2dB7589c1","aTokenSymbol":"aREN","stableDebtTokenAddress":"0xc66a5fd3Bd3D0329895ceE5755e161FD89c2EecD","variableDebtTokenAddress":"0x75f318b9B40c5bEb0EEAdab5294C4108A376a22d","symbol":"REN","address":"0x5eebf65A6746eed38042353Ba84c8e37eD58Ac6f","decimals":18},{"aTokenAddress":"0xAA74AdA92dE4AbC0371b75eeA7b1bd790a69C9e1","aTokenSymbol":"aSNX","stableDebtTokenAddress":"0x14B7a7Ab57190aEc3210303ef1cF29088535B329","variableDebtTokenAddress":"0x7dF2a710751cb9f1FD392107187e4Aed0Ae867b0","symbol":"SNX","address":"0x7FDb81B0b8a010dd4FFc57C3fecbf145BA8Bd947","decimals":18},{"aTokenAddress":"0x9488fF6F29ff75bfdF8cd5a95C6aa679bc7Cd65c","aTokenSymbol":"aSUSD","stableDebtTokenAddress":"0xB155258d3c18dd5D41e8838c8b45CaE1B17a11D9","variableDebtTokenAddress":"0xf3B942441Bd9d335E64413BeA6b76a49A5853C54","symbol":"sUSD","address":"0x99b267b9D96616f906D53c26dECf3C5672401282","decimals":18},{"aTokenAddress":"0x39914AdBe5fDbC2b9ADeedE8Bcd444b20B039204","aTokenSymbol":"aTUSD","stableDebtTokenAddress":"0x082576C4CfC2eE1e0b8088B84d50CEb97CD84E49","variableDebtTokenAddress":"0xC0cFab5E4A9D8DA2Bc98D0a2b3f9dc20f7eec19C","symbol":"TUSD","address":"0x016750AC630F711882812f24Dba6c95b9D35856d","decimals":18},{"aTokenAddress":"0xe12AFeC5aa12Cf614678f9bFeeB98cA9Bb95b5B0","aTokenSymbol":"aUSDC","stableDebtTokenAddress":"0x252C017036b144A812b53BC122d0E67cBB451aD4","variableDebtTokenAddress":"0xBE9B058a0f2840130372a81eBb3181dcE02BE957","symbol":"USDC","address":"0xe22da380ee6B445bb8273C81944ADEB6E8450422","decimals":6},{"aTokenAddress":"0xFF3c8bc103682FA918c954E84F5056aB4DD5189d","aTokenSymbol":"aUSDT","stableDebtTokenAddress":"0xf3DCeaDf668607bFCF565E84d9644c42eea518cd","variableDebtTokenAddress":"0xa6EfAF3B1C6c8E2be44818dB64E4DEC7416983a1","symbol":"USDT","address":"0x13512979ADE267AB5100878E2e0f485B568328a4","decimals":6},{"aTokenAddress":"0x62538022242513971478fcC7Fb27ae304AB5C29F","aTokenSymbol":"aWBTC","stableDebtTokenAddress":"0x45b85733E2609B9Eb18DbF1315765ddB8431e0B6","variableDebtTokenAddress":"0x9b8107B86A3cD6c8d766B30d3aDD046348bf8dB4","symbol":"WBTC","address":"0xD1B98B6607330172f1D991521145A22BCe793277","decimals":8},{"aTokenAddress":"0x87b1f4cf9BD63f7BBD3eE1aD04E8F52540349347","aTokenSymbol":"aWETH","stableDebtTokenAddress":"0x1F85D0dc45332D00aead98D26db0735350F80D18","variableDebtTokenAddress":"0xDD13CE9DE795E7faCB6fEC90E346C7F3abe342E2","symbol":"WETH","address":"0xd0A1E359811322d97991E03f863a0C30C2cF029C","decimals":18},{"aTokenAddress":"0xF6c7282943Beac96f6C70252EF35501a6c1148Fe","aTokenSymbol":"aYFI","stableDebtTokenAddress":"0x7417855ed88C62e610e612Be52AeE510703Dff04","variableDebtTokenAddress":"0xfF682fF79FEb2C057eC3Ff1e083eFdC66f9b37FB","symbol":"YFI","address":"0xb7c325266ec274fEb1354021D27FA3E3379D840d","decimals":18},{"aTokenAddress":"0xf02D7C23948c9178C68f5294748EB778Ab5e5D9c","aTokenSymbol":"aZRX","stableDebtTokenAddress":"0x7488Eb7fce7e31b91eB9cA4158d54D92e4BB03D7","variableDebtTokenAddress":"0x7a1C28e06bcb4b1fF4768BC2CB9cd33b7622cD62","symbol":"ZRX","address":"0xD0d76886cF8D952ca26177EB7CfDf83bad08C00C","decimals":18},{"aTokenAddress":"0x601FFc9b7309bdb0132a02a569FBd57d6D1740f2","aTokenSymbol":"aUNI","stableDebtTokenAddress":"0x7A43B2653FF42BDE048e3b14fB42028956a7B6b1","variableDebtTokenAddress":"0x10339d6562e8867bB93506572fF8Aea94B2fF656","symbol":"UNI","address":"0x075A36BA8846C6B6F53644fDd3bf17E5151789DC","decimals":18}]}
--------------------------------------------------------------------------------
/scripts/createAndRunPrizePoolPolygon.ts:
--------------------------------------------------------------------------------
1 | import { HardhatRuntimeEnvironment } from 'hardhat/types';
2 | import { ethers, deployments, getNamedAccounts, getChainId } from 'hardhat';
3 | import Erc20Abi from '../abis/ERC20.json';
4 |
5 | import PoolWithMultipleWinnersBuilder from '@pooltogether/pooltogether-contracts/deployments/matic/PoolWithMultipleWinnersBuilder.json';
6 | import RNGBlockhash from '@pooltogether/pooltogether-rng-contracts/deployments/matic_137/RNGBlockhash.json';
7 | import ControlledToken from '@pooltogether/pooltogether-contracts/abis/ControlledToken.json';
8 | import MultipleWinners from '@pooltogether/pooltogether-contracts/abis/MultipleWinners.json';
9 | import YieldSourcePrizePool from '@pooltogether/pooltogether-contracts/abis/YieldSourcePrizePool.json';
10 |
11 | import { BigNumber } from 'ethers';
12 | import { dai } from '@studydefi/money-legos/erc20';
13 | import { info, success } from './helpers';
14 |
15 | interface DepositAsset {
16 | depositAssetName: string;
17 | depositAssetAddress: string;
18 | depositAmount: BigNumber;
19 | depositAssetAbi: any;
20 | }
21 |
22 | async function createPrizePools() {
23 | console.log('running create prize pool script with chainId ', await getChainId());
24 |
25 | const { deployer } = await getNamedAccounts();
26 |
27 | console.log('deployer is ', deployer);
28 | const signer = await ethers.provider.getSigner(deployer);
29 |
30 | const allDeployments = await deployments.all();
31 |
32 | const aaveTokenAddress = '0x341d1f30e77D3FBfbD43D17183E2acb9dF25574E';
33 |
34 | console.log('balance of deployer ', await ethers.provider.getBalance(deployer));
35 |
36 | // minting mumbai AAVE
37 | const aaveTokenContract = await ethers.getContractAt(
38 | Erc20Abi,
39 | aaveTokenAddress,
40 | signer,
41 | );
42 | // const mintResult = await aaveTokenContract.transfer(deployer, "100")
43 |
44 | // console.log(mintResult)
45 |
46 | // call for each deployed yield source
47 | console.log('running lifecycle for aAAVE');
48 |
49 | await poolLifecycle(signer, allDeployments.aAAVE.address, {
50 | depositAssetName: 'AAVE',
51 | depositAssetAddress: aaveTokenAddress,
52 | depositAssetAbi: dai.abi,
53 | depositAmount: BigNumber.from(50),
54 | });
55 | }
56 | createPrizePools();
57 |
58 | async function poolLifecycle(
59 | contractsOwner: any,
60 | aTokenYieldSourceAddress: string,
61 | depositArgs: DepositAsset,
62 | ) {
63 | const { depositAssetAddress, depositAssetName, depositAmount, depositAssetAbi } = depositArgs;
64 |
65 | const { constants, provider, getContractAt, utils } = ethers;
66 | const { getBlock, getBlockNumber, getTransactionReceipt, send } = provider;
67 |
68 | const { AddressZero } = constants;
69 | const { parseEther: toWei } = utils;
70 |
71 | async function increaseTime(time: number) {
72 | await send('evm_increaseTime', [time]);
73 | await send('evm_mine', []);
74 | }
75 |
76 | const aTokenYieldSource = await getContractAt(
77 | 'ATokenYieldSource',
78 | aTokenYieldSourceAddress,
79 | contractsOwner,
80 | );
81 |
82 | info('Deploying ATokenYieldSourcePrizePool...');
83 |
84 | const poolBuilder = await getContractAt(
85 | PoolWithMultipleWinnersBuilder.abi,
86 | PoolWithMultipleWinnersBuilder.address,
87 | contractsOwner,
88 | );
89 |
90 | const aaveYieldSourcePrizePoolConfig = {
91 | yieldSource: aTokenYieldSource.address,
92 | maxExitFeeMantissa: toWei('0.5'),
93 | maxTimelockDuration: 1000,
94 | };
95 |
96 | const block = await getBlock(await getBlockNumber());
97 |
98 | info(`Block number ${block.number}`);
99 |
100 | const multipleWinnersConfig = {
101 | rngService: RNGBlockhash.address,
102 | prizePeriodStart: block.timestamp,
103 | prizePeriodSeconds: 60,
104 | ticketName: 'Ticket',
105 | ticketSymbol: 'TICK',
106 | sponsorshipName: 'Sponsorship',
107 | sponsorshipSymbol: 'SPON',
108 | ticketCreditLimitMantissa: toWei('0.1'),
109 | ticketCreditRateMantissa: toWei('0.001'),
110 | numberOfWinners: 1,
111 | };
112 |
113 | const yieldSourceMultipleWinnersTx = await poolBuilder.createYieldSourceMultipleWinners(
114 | aaveYieldSourcePrizePoolConfig,
115 | multipleWinnersConfig,
116 | 18,
117 | );
118 |
119 | console.info(yieldSourceMultipleWinnersTx);
120 |
121 | await new Promise((r) => setTimeout(r, 120000)); // sleep so that rpc provider has it
122 |
123 | console.log('calling getTranasctionReceipt with ', yieldSourceMultipleWinnersTx.hash);
124 |
125 | const yieldSourceMultipleWinnersReceipt = await getTransactionReceipt(
126 | yieldSourceMultipleWinnersTx.hash,
127 | );
128 |
129 | console.log('yieldSourceMultipleWinnersReceipt ', yieldSourceMultipleWinnersReceipt);
130 |
131 | const yieldSourcePrizePoolInitializedEvents = yieldSourceMultipleWinnersReceipt.logs.map(
132 | (log: any) => {
133 | try {
134 | return poolBuilder.interface.parseLog(log);
135 | } catch (e) {
136 | return null;
137 | }
138 | },
139 | );
140 |
141 | console.log('yieldSourcePrizePoolInitializedEvent ', yieldSourcePrizePoolInitializedEvents);
142 |
143 | const yieldSourcePrizePoolInitializedEvent = yieldSourcePrizePoolInitializedEvents.find(
144 | (event: any) => event && event.name === 'YieldSourcePrizePoolWithMultipleWinnersCreated',
145 | );
146 |
147 | console.log('yieldSourcePrizePoolInitializedEvent ', yieldSourcePrizePoolInitializedEvent);
148 |
149 | console.log(
150 | 'yieldSourcePrizePoolInitializedEvent.args.prizePool ',
151 | yieldSourcePrizePoolInitializedEvent?.args.prizePool,
152 | );
153 | const prizePool = await getContractAt(
154 | YieldSourcePrizePool,
155 | yieldSourcePrizePoolInitializedEvent?.args.prizePool,
156 | contractsOwner,
157 | );
158 |
159 | success(`Deployed ATokenYieldSourcePrizePool! ${prizePool.address}`);
160 |
161 | const prizeStrategy = await getContractAt(
162 | MultipleWinners,
163 | await prizePool.prizeStrategy(),
164 | contractsOwner,
165 | );
166 |
167 | const depositAssetContract = await getContractAt(
168 | depositAssetAbi,
169 | depositAssetAddress,
170 | contractsOwner,
171 | );
172 | await depositAssetContract.approve(prizePool.address, depositAmount);
173 |
174 | info(
175 | `Depositing ${depositAmount} ${depositAssetName} for ${
176 | contractsOwner._address
177 | }, ticket ${await prizeStrategy.ticket()}`,
178 | );
179 |
180 | await prizePool.depositTo(
181 | contractsOwner._address,
182 | depositAmount,
183 | await prizeStrategy.ticket(),
184 | AddressZero,
185 | );
186 |
187 | success('Deposit Successful!');
188 |
189 | info(`Prize strategy owner: ${await prizeStrategy.owner()}`);
190 |
191 | info('Starting award...');
192 | await prizeStrategy.startAward();
193 |
194 | await new Promise((r) => setTimeout(r, 60000)); // sleep so can completeAward
195 |
196 | info('Completing award...');
197 | const awardTx = await prizeStrategy.completeAward();
198 |
199 | await new Promise((r) => setTimeout(r, 220000));
200 | const awardReceipt = await getTransactionReceipt(awardTx.hash);
201 |
202 | console.log('awardReceipt ', awardReceipt);
203 |
204 | const awardLogs = awardReceipt.logs.map((log: any) => {
205 | try {
206 | return prizePool.interface.parseLog(log);
207 | } catch (e) {
208 | return null;
209 | }
210 | });
211 |
212 | const completeAwardLogs = awardReceipt.logs.map((log: any) => {
213 | try {
214 | return prizeStrategy.interface.parseLog(log);
215 | } catch (e) {
216 | return null;
217 | }
218 | });
219 |
220 | console.log('completeAwardLogs ', completeAwardLogs);
221 |
222 | const awarded = awardLogs.find((event: any) => event && event.name === 'Awarded');
223 | // console.log("awarded event", awarded)
224 |
225 | if (awarded) {
226 | success(`Awarded ${awarded?.args?.amount} ${depositAssetName}!`);
227 | }
228 |
229 | info('Withdrawing...');
230 | const ticketAddress = await prizeStrategy.ticket();
231 | const ticket = await getContractAt(ControlledToken, ticketAddress, contractsOwner);
232 |
233 | const withdrawalAmount = depositAmount.div(2); // withdraw half the amount deposited
234 |
235 | const earlyExitFee = await prizePool.callStatic.calculateEarlyExitFee(
236 | contractsOwner.address,
237 | ticket.address,
238 | withdrawalAmount,
239 | );
240 |
241 | const withdrawTx = await prizePool.withdrawInstantlyFrom(
242 | contractsOwner._address,
243 | withdrawalAmount,
244 | ticket.address,
245 | earlyExitFee.exitFee,
246 | );
247 |
248 | await new Promise((r) => setTimeout(r, 220000));
249 | const withdrawReceipt = await getTransactionReceipt(withdrawTx.hash);
250 | const withdrawLogs = withdrawReceipt.logs.map((log: any) => {
251 | try {
252 | return prizePool.interface.parseLog(log);
253 | } catch (e) {
254 | return null;
255 | }
256 | });
257 |
258 | const withdrawn = withdrawLogs.find((event: any) => event && event.name === 'InstantWithdrawal');
259 | success(`Withdrawn ${withdrawn?.args?.redeemed} ${depositAssetName}!`);
260 | success(`Exit fee was ${withdrawn?.args?.exitFee} ${depositAssetName}`);
261 |
262 | await prizePool.captureAwardBalance();
263 | const awardBalance = await prizePool.callStatic.awardBalance();
264 | success(`Current awardable balance is ${awardBalance} ${depositAssetName}`);
265 | }
266 |
--------------------------------------------------------------------------------
/scripts/createAndRunPrizePoolMumbai.ts:
--------------------------------------------------------------------------------
1 | import { HardhatRuntimeEnvironment } from 'hardhat/types';
2 | import { ethers, deployments, getNamedAccounts, getChainId } from 'hardhat';
3 | import Erc20MintableAbi from '../abis/ERC20Mintable.json';
4 |
5 | import PoolWithMultipleWinnersBuilder from '@pooltogether/pooltogether-contracts/deployments/mumbai/PoolWithMultipleWinnersBuilder.json';
6 | import RNGBlockhash from '@pooltogether/pooltogether-rng-contracts/deployments/mumbai_80001/RNGBlockhash.json';
7 | import ControlledToken from '@pooltogether/pooltogether-contracts/abis/ControlledToken.json';
8 | import MultipleWinners from '@pooltogether/pooltogether-contracts/abis/MultipleWinners.json';
9 | import YieldSourcePrizePool from '@pooltogether/pooltogether-contracts/abis/YieldSourcePrizePool.json';
10 |
11 | import { BigNumber, utils } from 'ethers';
12 | import { dai } from '@studydefi/money-legos/erc20';
13 | import { info, success } from './helpers';
14 |
15 | interface DepositAsset {
16 | depositAssetName: string;
17 | depositAssetAddress: string;
18 | depositAmount: BigNumber;
19 | depositAssetAbi: any;
20 | }
21 |
22 | const toWei = utils.parseEther;
23 |
24 | async function createPrizePools() {
25 | console.log('running create prize pool script with chainId ', await getChainId());
26 |
27 | const { deployer } = await getNamedAccounts();
28 |
29 | console.log('deployer is ', deployer);
30 | const signer = ethers.provider.getSigner(deployer);
31 |
32 | const allDeployments = await deployments.all();
33 |
34 | const aaveTokenAddress = '0x341d1f30e77D3FBfbD43D17183E2acb9dF25574E';
35 |
36 | console.log(
37 | 'balance of deployer ',
38 | ethers.utils.formatEther(await ethers.provider.getBalance(deployer)),
39 | );
40 |
41 | // minting mumbai AAVE
42 | // const aaveTokenContract = await ethers.getContractAt(Erc20MintableAbi, aaveTokenAddress, signer)
43 |
44 | // await aaveTokenContract.transfer(deployer, toWei('50'))
45 |
46 | // call for each deployed yield source
47 | console.log('running lifecycle for aAAVE');
48 |
49 | await poolLifecycle(signer, allDeployments.AaveAAVEYieldSource.address, {
50 | depositAssetName: 'AAVE',
51 | depositAssetAddress: aaveTokenAddress,
52 | depositAssetAbi: dai.abi,
53 | depositAmount: BigNumber.from(50),
54 | });
55 | }
56 |
57 | createPrizePools();
58 |
59 | async function poolLifecycle(
60 | contractsOwner: any,
61 | aTokenYieldSourceAddress: string,
62 | depositArgs: DepositAsset,
63 | ) {
64 | const { depositAssetAddress, depositAssetName, depositAmount, depositAssetAbi } = depositArgs;
65 |
66 | const { constants, provider, getContractAt, utils } = ethers;
67 | const { getBlock, getBlockNumber, getTransactionReceipt, send } = provider;
68 |
69 | const { AddressZero } = constants;
70 | const { formatEther, parseEther: toWei } = utils;
71 |
72 | async function increaseTime(time: number) {
73 | await send('evm_increaseTime', [time]);
74 | await send('evm_mine', []);
75 | }
76 |
77 | const aTokenYieldSource = await getContractAt(
78 | 'ATokenYieldSource',
79 | aTokenYieldSourceAddress,
80 | contractsOwner,
81 | );
82 |
83 | info('Deploying ATokenYieldSourcePrizePool...');
84 |
85 | const poolBuilder = await getContractAt(
86 | PoolWithMultipleWinnersBuilder.abi,
87 | PoolWithMultipleWinnersBuilder.address,
88 | contractsOwner,
89 | );
90 |
91 | const aaveYieldSourcePrizePoolConfig = {
92 | yieldSource: aTokenYieldSource.address,
93 | maxExitFeeMantissa: toWei('0.5'),
94 | maxTimelockDuration: 1000,
95 | };
96 |
97 | const block = await getBlock(await getBlockNumber());
98 |
99 | info(`Block number ${block.number}`);
100 |
101 | const multipleWinnersConfig = {
102 | rngService: RNGBlockhash.address,
103 | prizePeriodStart: block.timestamp,
104 | prizePeriodSeconds: 60,
105 | ticketName: 'Ticket',
106 | ticketSymbol: 'TICK',
107 | sponsorshipName: 'Sponsorship',
108 | sponsorshipSymbol: 'SPON',
109 | ticketCreditLimitMantissa: toWei('0.1'),
110 | ticketCreditRateMantissa: toWei('0.001'),
111 | numberOfWinners: 1,
112 | };
113 |
114 | console.log('poolBuilder', poolBuilder);
115 |
116 | const yieldSourceMultipleWinnersTx = await poolBuilder.createYieldSourceMultipleWinners(
117 | aaveYieldSourcePrizePoolConfig,
118 | multipleWinnersConfig,
119 | 18,
120 | );
121 |
122 | console.info(yieldSourceMultipleWinnersTx);
123 |
124 | await new Promise((r) => setTimeout(r, 120000)); // sleep so that rpc provider has it
125 |
126 | console.log('calling getTranasctionReceipt with ', yieldSourceMultipleWinnersTx.hash);
127 |
128 | const yieldSourceMultipleWinnersReceipt = await getTransactionReceipt(
129 | yieldSourceMultipleWinnersTx.hash,
130 | );
131 |
132 | console.log('yieldSourceMultipleWinnersReceipt ', yieldSourceMultipleWinnersReceipt);
133 |
134 | const yieldSourcePrizePoolInitializedEvents = yieldSourceMultipleWinnersReceipt.logs.map(
135 | (log: any) => {
136 | try {
137 | return poolBuilder.interface.parseLog(log);
138 | } catch (e) {
139 | return null;
140 | }
141 | },
142 | );
143 |
144 | console.log('yieldSourcePrizePoolInitializedEvent ', yieldSourcePrizePoolInitializedEvents);
145 |
146 | const yieldSourcePrizePoolInitializedEvent = yieldSourcePrizePoolInitializedEvents.find(
147 | (event: any) => event && event.name === 'YieldSourcePrizePoolWithMultipleWinnersCreated',
148 | );
149 |
150 | console.log('yieldSourcePrizePoolInitializedEvent ', yieldSourcePrizePoolInitializedEvent);
151 |
152 | console.log(
153 | 'yieldSourcePrizePoolInitializedEvent.args.prizePool ',
154 | yieldSourcePrizePoolInitializedEvent?.args.prizePool,
155 | );
156 | const prizePool = await getContractAt(
157 | YieldSourcePrizePool,
158 | yieldSourcePrizePoolInitializedEvent?.args.prizePool,
159 | contractsOwner,
160 | );
161 |
162 | success(`Deployed ATokenYieldSourcePrizePool! ${prizePool.address}`);
163 |
164 | const prizeStrategy = await getContractAt(
165 | MultipleWinners,
166 | await prizePool.prizeStrategy(),
167 | contractsOwner,
168 | );
169 |
170 | const depositAssetContract = await getContractAt(
171 | depositAssetAbi,
172 | depositAssetAddress,
173 | contractsOwner,
174 | );
175 | await depositAssetContract.approve(prizePool.address, depositAmount);
176 |
177 | info(
178 | `Depositing ${depositAmount} ${depositAssetName} for ${
179 | contractsOwner._address
180 | }, ticket ${await prizeStrategy.ticket()}`,
181 | );
182 |
183 | await prizePool.depositTo(
184 | contractsOwner._address,
185 | depositAmount,
186 | await prizeStrategy.ticket(),
187 | AddressZero,
188 | );
189 |
190 | success('Deposited To!');
191 |
192 | info(`Prize strategy owner: ${await prizeStrategy.owner()}`);
193 |
194 | info('Starting award...');
195 | await prizeStrategy.startAward();
196 |
197 | await new Promise((r) => setTimeout(r, 60000)); // sleep so can completeAward
198 |
199 | info('Completing award...');
200 | const awardTx = await prizeStrategy.completeAward();
201 |
202 | await new Promise((r) => setTimeout(r, 220000));
203 | const awardReceipt = await getTransactionReceipt(awardTx.hash);
204 |
205 | console.log('awardReceipt ', awardReceipt);
206 |
207 | const awardLogs = awardReceipt.logs.map((log: any) => {
208 | try {
209 | return prizePool.interface.parseLog(log);
210 | } catch (e) {
211 | return null;
212 | }
213 | });
214 |
215 | const completeAwardLogs = awardReceipt.logs.map((log: any) => {
216 | try {
217 | return prizeStrategy.interface.parseLog(log);
218 | } catch (e) {
219 | return null;
220 | }
221 | });
222 |
223 | console.log('completeAwardLogs ', completeAwardLogs);
224 |
225 | const awarded = awardLogs.find((event: any) => event && event.name === 'Awarded');
226 | // console.log("awarded event", awarded)
227 |
228 | if (awarded) {
229 | success(`Awarded ${awarded?.args?.amount} ${depositAssetName}!`);
230 | }
231 |
232 | info('Withdrawing...');
233 | const ticketAddress = await prizeStrategy.ticket();
234 | const ticket = await getContractAt(ControlledToken, ticketAddress, contractsOwner);
235 |
236 | const withdrawalAmount = depositAmount.div(2); // withdraw half the amount deposited
237 |
238 | const earlyExitFee = await prizePool.callStatic.calculateEarlyExitFee(
239 | contractsOwner.address,
240 | ticket.address,
241 | withdrawalAmount,
242 | );
243 |
244 | const withdrawTx = await prizePool.withdrawInstantlyFrom(
245 | contractsOwner._address,
246 | withdrawalAmount,
247 | ticket.address,
248 | earlyExitFee.exitFee,
249 | );
250 |
251 | await new Promise((r) => setTimeout(r, 220000));
252 | const withdrawReceipt = await getTransactionReceipt(withdrawTx.hash);
253 | const withdrawLogs = withdrawReceipt.logs.map((log: any) => {
254 | try {
255 | return prizePool.interface.parseLog(log);
256 | } catch (e) {
257 | return null;
258 | }
259 | });
260 |
261 | const withdrawn = withdrawLogs.find((event: any) => event && event.name === 'InstantWithdrawal');
262 | success(`Withdrawn ${withdrawn?.args?.redeemed} ${depositAssetName}!`);
263 | success(`Exit fee was ${withdrawn?.args?.exitFee} ${depositAssetName}`);
264 |
265 | await prizePool.captureAwardBalance();
266 | const awardBalance = await prizePool.callStatic.awardBalance();
267 | success(`Current awardable balance is ${awardBalance} ${depositAssetName}`);
268 | }
269 |
--------------------------------------------------------------------------------
/contracts/yield-source/ATokenYieldSource.sol:
--------------------------------------------------------------------------------
1 | // SPDX-License-Identifier: GPL-3.0
2 |
3 | pragma solidity 0.8.6;
4 |
5 | import "@openzeppelin/contracts/utils/math/SafeMath.sol";
6 | import "@openzeppelin/contracts/token/ERC20/ERC20.sol";
7 | import "@openzeppelin/contracts/token/ERC20/IERC20.sol";
8 | import "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol";
9 | import "@openzeppelin/contracts/security/ReentrancyGuard.sol";
10 | import "@pooltogether/fixed-point/contracts/FixedPoint.sol";
11 | import "@pooltogether/owner-manager-contracts/contracts/Manageable.sol";
12 |
13 | import "../external/aave/ILendingPool.sol";
14 | import "../external/aave/ILendingPoolAddressesProvider.sol";
15 | import "../external/aave/ILendingPoolAddressesProviderRegistry.sol";
16 | import "../external/aave/ATokenInterface.sol";
17 | import "../external/aave/IAaveIncentivesController.sol";
18 | import "../external/aave/IProtocolYieldSource.sol";
19 |
20 | /// @title Aave Yield Source integration contract, implementing PoolTogether's generic yield source interface
21 | /// @dev This contract inherits from the ERC20 implementation to keep track of users deposits
22 | /// @dev This contract inherits AssetManager which extends OwnableUpgradable
23 | /// @notice Yield source for a PoolTogether prize pool that generates yield by depositing into Aave V2
24 | contract ATokenYieldSource is ERC20, IProtocolYieldSource, Manageable, ReentrancyGuard {
25 | using SafeMath for uint256;
26 | using SafeERC20 for IERC20;
27 |
28 | /// @notice Emitted when the yield source is initialized
29 | event ATokenYieldSourceInitialized(
30 | IAToken indexed aToken,
31 | ILendingPoolAddressesProviderRegistry lendingPoolAddressesProviderRegistry,
32 | uint8 decimals,
33 | string name,
34 | string symbol,
35 | address owner
36 | );
37 |
38 | /// @notice Emitted when asset tokens are redeemed from the yield source
39 | event RedeemedToken(
40 | address indexed from,
41 | uint256 shares,
42 | uint256 amount
43 | );
44 |
45 | /// @notice Emitted when Aave rewards have been claimed
46 | event Claimed(
47 | address indexed user,
48 | address indexed to,
49 | uint256 amount
50 | );
51 |
52 | /// @notice Emitted when asset tokens are supplied to the yield source
53 | event SuppliedTokenTo(
54 | address indexed from,
55 | uint256 shares,
56 | uint256 amount,
57 | address indexed to
58 | );
59 |
60 | /// @notice Emitted when asset tokens are supplied to sponsor the yield source
61 | event Sponsored(
62 | address indexed from,
63 | uint256 amount
64 | );
65 |
66 | /// @notice Emitted when ERC20 tokens other than yield source's aToken are withdrawn from the yield source
67 | event TransferredERC20(
68 | address indexed from,
69 | address indexed to,
70 | uint256 amount,
71 | IERC20 indexed token
72 | );
73 |
74 | /// @notice Interface for the yield-bearing Aave aToken
75 | ATokenInterface public immutable aToken;
76 |
77 | /// @notice Interface for Aave incentivesController
78 | IAaveIncentivesController public immutable incentivesController;
79 |
80 | /// @notice Interface for Aave lendingPoolAddressesProviderRegistry
81 | ILendingPoolAddressesProviderRegistry public lendingPoolAddressesProviderRegistry;
82 |
83 | /// @notice Underlying asset token address.
84 | address private immutable _tokenAddress;
85 |
86 | /// @notice ERC20 token decimals.
87 | uint8 private immutable __decimals;
88 |
89 | /// @dev Aave genesis market LendingPoolAddressesProvider's ID
90 | /// @dev This variable could evolve in the future if we decide to support other markets
91 | uint256 private constant ADDRESSES_PROVIDER_ID = uint256(0);
92 |
93 | /// @dev PoolTogether's Aave Referral Code
94 | uint16 private constant REFERRAL_CODE = uint16(188);
95 |
96 | /// @notice Initializes the yield source with Aave aToken
97 | /// @param _aToken Aave aToken address
98 | /// @param _incentivesController Aave incentivesController address
99 | /// @param _lendingPoolAddressesProviderRegistry Aave lendingPoolAddressesProviderRegistry address
100 | /// @param _decimals Number of decimals the shares (inhereted ERC20) will have. Set as same as underlying asset to ensure sane ExchangeRates
101 | /// @param _symbol Token symbol for the underlying shares ERC20
102 | /// @param _name Token name for the underlying shares ERC20
103 | constructor (
104 | ATokenInterface _aToken,
105 | IAaveIncentivesController _incentivesController,
106 | ILendingPoolAddressesProviderRegistry _lendingPoolAddressesProviderRegistry,
107 | uint8 _decimals,
108 | string memory _symbol,
109 | string memory _name,
110 | address _owner
111 | ) Ownable(_owner) ERC20(_name, _symbol) ReentrancyGuard()
112 | {
113 | require(address(_aToken) != address(0), "ATokenYieldSource/aToken-not-zero-address");
114 | require(address(_incentivesController) != address(0), "ATokenYieldSource/incentivesController-not-zero-address");
115 | require(address(_lendingPoolAddressesProviderRegistry) != address(0), "ATokenYieldSource/lendingPoolRegistry-not-zero-address");
116 | require(_owner != address(0), "ATokenYieldSource/owner-not-zero-address");
117 | require(_decimals > 0, "ATokenYieldSource/decimals-gt-zero");
118 |
119 | aToken = _aToken;
120 | incentivesController = _incentivesController;
121 | lendingPoolAddressesProviderRegistry = _lendingPoolAddressesProviderRegistry;
122 | __decimals = _decimals;
123 |
124 | address tokenAddress = address(_aToken.UNDERLYING_ASSET_ADDRESS());
125 | _tokenAddress = tokenAddress;
126 |
127 | // Approve once for max amount
128 | IERC20(tokenAddress).safeApprove(address(_lendingPool()), type(uint256).max);
129 |
130 | emit ATokenYieldSourceInitialized (
131 | _aToken,
132 | _lendingPoolAddressesProviderRegistry,
133 | _decimals,
134 | _name,
135 | _symbol,
136 | _owner
137 | );
138 | }
139 |
140 | /// @notice Returns the number of decimals that the token repesenting yield source shares has
141 | /// @return The number of decimals
142 | function decimals() public override view returns (uint8) {
143 | return __decimals;
144 | }
145 |
146 | /// @notice Approve lending pool contract to spend max uint256 amount
147 | /// @dev Emergency function to re-approve max amount if approval amount dropped too low
148 | /// @return true if operation is successful
149 | function approveMaxAmount() external onlyOwner returns (bool) {
150 | address _lendingPoolAddress = address(_lendingPool());
151 | IERC20 _underlyingAsset = IERC20(_tokenAddress);
152 |
153 | _underlyingAsset.safeIncreaseAllowance(
154 | _lendingPoolAddress,
155 | type(uint256).max.sub(_underlyingAsset.allowance(address(this), _lendingPoolAddress))
156 | );
157 |
158 | return true;
159 | }
160 |
161 | /// @notice Returns the ERC20 asset token used for deposits
162 | /// @return The ERC20 asset token address
163 | function depositToken() public view override returns (address) {
164 | return _tokenAddress;
165 | }
166 |
167 | /// @notice Returns user total balance (in asset tokens). This includes the deposits and interest.
168 | /// @param addr User address
169 | /// @return The underlying balance of asset tokens
170 | function balanceOfToken(address addr) external override view returns (uint256) {
171 | return _sharesToToken(balanceOf(addr));
172 | }
173 |
174 | /// @notice Calculates the number of shares that should be mint or burned when a user deposit or withdraw
175 | /// @param _tokens Amount of tokens
176 | /// @return Number of shares
177 | function _tokenToShares(uint256 _tokens) internal view returns (uint256) {
178 | uint256 _shares;
179 | uint256 _totalSupply = totalSupply();
180 |
181 | if (_totalSupply == 0) {
182 | _shares = _tokens;
183 | } else {
184 | // rate = tokens / shares
185 | // shares = tokens * (totalShares / yieldSourceTotalSupply)
186 | uint256 _exchangeMantissa = FixedPoint.calculateMantissa(_totalSupply, aToken.balanceOf(address(this)));
187 | _shares = FixedPoint.multiplyUintByMantissa(_tokens, _exchangeMantissa);
188 | }
189 |
190 | return _shares;
191 | }
192 |
193 | /// @notice Calculates the number of tokens a user has in the yield source
194 | /// @param _shares Amount of shares
195 | /// @return Number of tokens
196 | function _sharesToToken(uint256 _shares) internal view returns (uint256) {
197 | uint256 _tokens;
198 | uint256 _totalSupply = totalSupply();
199 |
200 | if (_totalSupply == 0) {
201 | _tokens = _shares;
202 | } else {
203 | // tokens = (shares * yieldSourceTotalSupply) / totalShares
204 | _tokens = _shares.mul(aToken.balanceOf(address(this))).div(_totalSupply);
205 | }
206 |
207 | return _tokens;
208 | }
209 |
210 | /// @notice Checks that the amount of shares is greater than zero.
211 | /// @param _shares Amount of shares to check
212 | function _requireSharesGTZero(uint256 _shares) internal pure {
213 | require(_shares > 0, "ATokenYieldSource/shares-gt-zero");
214 | }
215 |
216 | /// @notice Deposit asset tokens to Aave
217 | /// @param mintAmount The amount of asset tokens to be deposited
218 | function _depositToAave(uint256 mintAmount) internal {
219 | IERC20(_tokenAddress).safeTransferFrom(msg.sender, address(this), mintAmount);
220 | _lendingPool().deposit(_tokenAddress, mintAmount, address(this), REFERRAL_CODE);
221 | }
222 |
223 | /// @notice Supplies asset tokens to the yield source
224 | /// @dev Shares corresponding to the number of tokens supplied are mint to the user's balance
225 | /// @dev Asset tokens are supplied to the yield source, then deposited into Aave
226 | /// @param mintAmount The amount of asset tokens to be supplied
227 | /// @param to The user whose balance will receive the tokens
228 | function supplyTokenTo(uint256 mintAmount, address to) external override nonReentrant {
229 | uint256 shares = _tokenToShares(mintAmount);
230 | _requireSharesGTZero(shares);
231 |
232 | _depositToAave(mintAmount);
233 | _mint(to, shares);
234 |
235 | emit SuppliedTokenTo(msg.sender, shares, mintAmount, to);
236 | }
237 |
238 | /// @notice Redeems asset tokens from the yield source
239 | /// @dev Shares corresponding to the number of tokens withdrawn are burnt from the user's balance
240 | /// @dev Asset tokens are withdrawn from Aave, then transferred from the yield source to the user's wallet
241 | /// @param redeemAmount The amount of asset tokens to be redeemed
242 | /// @return The actual amount of asset tokens that were redeemed
243 | function redeemToken(uint256 redeemAmount) external override nonReentrant returns (uint256) {
244 | uint256 shares = _tokenToShares(redeemAmount);
245 | _requireSharesGTZero(shares);
246 |
247 | _burn(msg.sender, shares);
248 |
249 | IERC20 _depositToken = IERC20(_tokenAddress);
250 | uint256 beforeBalance = _depositToken.balanceOf(address(this));
251 | _lendingPool().withdraw(_tokenAddress, redeemAmount, address(this));
252 | uint256 afterBalance = _depositToken.balanceOf(address(this));
253 |
254 | uint256 balanceDiff = afterBalance.sub(beforeBalance);
255 | _depositToken.safeTransfer(msg.sender, balanceDiff);
256 |
257 | emit RedeemedToken(msg.sender, shares, redeemAmount);
258 | return balanceDiff;
259 | }
260 |
261 | /// @notice Transfer ERC20 tokens other than the aTokens held by this contract to the recipient address
262 | /// @dev This function is only callable by the owner or asset manager
263 | /// @param erc20Token The ERC20 token to transfer
264 | /// @param to The recipient of the tokens
265 | /// @param amount The amount of tokens to transfer
266 | function transferERC20(IERC20 erc20Token, address to, uint256 amount) external override onlyManagerOrOwner {
267 | require(address(erc20Token) != address(aToken), "ATokenYieldSource/aToken-transfer-not-allowed");
268 | erc20Token.safeTransfer(to, amount);
269 | emit TransferredERC20(msg.sender, to, amount, erc20Token);
270 | }
271 |
272 | /// @notice Allows someone to deposit into the yield source without receiving any shares
273 | /// @dev This allows anyone to distribute tokens among the share holders
274 | /// @param amount The amount of tokens to deposit
275 | function sponsor(uint256 amount) external override nonReentrant {
276 | _depositToAave(amount);
277 | emit Sponsored(msg.sender, amount);
278 | }
279 |
280 | /// @notice Claims the accrued rewards for the aToken, accumulating any pending rewards.
281 | /// @param to Address where the claimed rewards will be sent.
282 | /// @return True if operation was successful.
283 | function claimRewards(address to) external onlyManagerOrOwner returns (bool) {
284 | require(to != address(0), "ATokenYieldSource/recipient-not-zero-address");
285 |
286 | IAaveIncentivesController _incentivesController = incentivesController;
287 |
288 | address[] memory _assets = new address[](1);
289 | _assets[0] = address(aToken);
290 |
291 | uint256 _amount = _incentivesController.getRewardsBalance(_assets, address(this));
292 | uint256 _amountClaimed = _incentivesController.claimRewards(_assets, _amount, to);
293 |
294 | emit Claimed(msg.sender, to, _amountClaimed);
295 | return true;
296 | }
297 |
298 | /// @notice Retrieves Aave LendingPool address
299 | /// @return A reference to LendingPool interface
300 | function _lendingPool() internal view returns (ILendingPool) {
301 | return ILendingPool(
302 | ILendingPoolAddressesProvider(
303 | lendingPoolAddressesProviderRegistry.getAddressesProvidersList()[ADDRESSES_PROVIDER_ID]
304 | ).getLendingPool()
305 | );
306 | }
307 | }
308 |
--------------------------------------------------------------------------------
/aave/aaveMainnet.json:
--------------------------------------------------------------------------------
1 | {"proto":[{"aTokenAddress":"0x3Ed3B47Dd13EC9a98b44e6204A523E766B225811","aTokenSymbol":"aUSDT","stableDebtTokenAddress":"0xe91D55AB2240594855aBd11b3faAE801Fd4c4687","variableDebtTokenAddress":"0x531842cEbbdD378f8ee36D171d6cC9C4fcf475Ec","symbol":"USDT","address":"0xdAC17F958D2ee523a2206206994597C13D831ec7","decimals":6},{"aTokenAddress":"0x9ff58f4fFB29fA2266Ab25e75e2A8b3503311656","aTokenSymbol":"aWBTC","stableDebtTokenAddress":"0x51B039b9AFE64B78758f8Ef091211b5387eA717c","variableDebtTokenAddress":"0x9c39809Dec7F95F5e0713634a4D0701329B3b4d2","symbol":"WBTC","address":"0x2260FAC5E5542a773Aa44fBCfeDf7C193bc2C599","decimals":8},{"aTokenAddress":"0x030bA81f1c18d280636F32af80b9AAd02Cf0854e","aTokenSymbol":"aWETH","stableDebtTokenAddress":"0x4e977830ba4bd783C0BB7F15d3e243f73FF57121","variableDebtTokenAddress":"0xF63B34710400CAd3e044cFfDcAb00a0f32E33eCf","symbol":"WETH","address":"0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2","decimals":18},{"aTokenAddress":"0x5165d24277cD063F5ac44Efd447B27025e888f37","aTokenSymbol":"aYFI","stableDebtTokenAddress":"0xca823F78C2Dd38993284bb42Ba9b14152082F7BD","variableDebtTokenAddress":"0x7EbD09022Be45AD993BAA1CEc61166Fcc8644d97","symbol":"YFI","address":"0x0bc529c00C6401aEF6D220BE8C6Ea1667F6Ad93e","decimals":18},{"aTokenAddress":"0xDf7FF54aAcAcbFf42dfe29DD6144A69b629f8C9e","aTokenSymbol":"aZRX","stableDebtTokenAddress":"0x071B4323a24E73A5afeEbe34118Cd21B8FAAF7C3","variableDebtTokenAddress":"0x85791D117A392097590bDeD3bD5abB8d5A20491A","symbol":"ZRX","address":"0xE41d2489571d322189246DaFA5ebDe1F4699F498","decimals":18},{"aTokenAddress":"0xB9D7CB55f463405CDfBe4E90a6D2Df01C2B92BF1","aTokenSymbol":"aUNI","stableDebtTokenAddress":"0xD939F7430dC8D5a427f156dE1012A56C18AcB6Aa","variableDebtTokenAddress":"0x5BdB050A92CADcCfCDcCCBFC17204a1C9cC0Ab73","symbol":"UNI","address":"0x1f9840a85d5aF5bf1D1762F925BDADdC4201F984","decimals":18},{"aTokenAddress":"0xFFC97d72E13E01096502Cb8Eb52dEe56f74DAD7B","aTokenSymbol":"aAAVE","stableDebtTokenAddress":"0x079D6a3E844BcECf5720478A718Edb6575362C5f","variableDebtTokenAddress":"0xF7DBA49d571745D9d7fcb56225B05BEA803EBf3C","symbol":"AAVE","address":"0x7Fc66500c84A76Ad7e9c93437bFc5Ac33E2DDaE9","decimals":18},{"aTokenAddress":"0x05Ec93c0365baAeAbF7AefFb0972ea7ECdD39CF1","aTokenSymbol":"aBAT","stableDebtTokenAddress":"0x277f8676FAcf4dAA5a6EA38ba511B7F65AA02f9F","variableDebtTokenAddress":"0xfc218A6Dfe6901CB34B1a5281FC6f1b8e7E56877","symbol":"BAT","address":"0x0D8775F648430679A709E98d2b0Cb6250d2887EF","decimals":18},{"aTokenAddress":"0xA361718326c15715591c299427c62086F69923D9","aTokenSymbol":"aBUSD","stableDebtTokenAddress":"0x4A7A63909A72D268b1D8a93a9395d098688e0e5C","variableDebtTokenAddress":"0xbA429f7011c9fa04cDd46a2Da24dc0FF0aC6099c","symbol":"BUSD","address":"0x4Fabb145d64652a948d72533023f6E7A623C7C53","decimals":18},{"aTokenAddress":"0x028171bCA77440897B824Ca71D1c56caC55b68A3","aTokenSymbol":"aDAI","stableDebtTokenAddress":"0x778A13D3eeb110A4f7bb6529F99c000119a08E92","variableDebtTokenAddress":"0x6C3c78838c761c6Ac7bE9F59fe808ea2A6E4379d","symbol":"DAI","address":"0x6B175474E89094C44Da98b954EedeAC495271d0F","decimals":18},{"aTokenAddress":"0xaC6Df26a590F08dcC95D5a4705ae8abbc88509Ef","aTokenSymbol":"aENJ","stableDebtTokenAddress":"0x943DcCA156b5312Aa24c1a08769D67FEce4ac14C","variableDebtTokenAddress":"0x38995F292a6E31b78203254fE1cdd5Ca1010A446","symbol":"ENJ","address":"0xF629cBd94d3791C9250152BD8dfBDF380E2a3B9c","decimals":18},{"aTokenAddress":"0x39C6b3e42d6A679d7D776778Fe880BC9487C2EDA","aTokenSymbol":"aKNC","stableDebtTokenAddress":"0x9915dfb872778B2890a117DA1F35F335eb06B54f","variableDebtTokenAddress":"0x6B05D1c608015Ccb8e205A690cB86773A96F39f1","symbol":"KNC","address":"0xdd974D5C2e2928deA5F71b9825b8b646686BD200","decimals":18},{"aTokenAddress":"0xa06bC25B5805d5F8d82847D191Cb4Af5A3e873E0","aTokenSymbol":"aLINK","stableDebtTokenAddress":"0xFB4AEc4Cc858F2539EBd3D37f2a43eAe5b15b98a","variableDebtTokenAddress":"0x0b8f12b1788BFdE65Aa1ca52E3e9F3Ba401be16D","symbol":"LINK","address":"0x514910771AF9Ca656af840dff83E8264EcF986CA","decimals":18},{"aTokenAddress":"0xa685a61171bb30d4072B338c80Cb7b2c865c873E","aTokenSymbol":"aMANA","stableDebtTokenAddress":"0xD86C74eA2224f4B8591560652b50035E4e5c0a3b","variableDebtTokenAddress":"0x0A68976301e46Ca6Ce7410DB28883E309EA0D352","symbol":"MANA","address":"0x0F5D2fB29fb7d3CFeE444a200298f468908cC942","decimals":18},{"aTokenAddress":"0xc713e5E149D5D0715DcD1c156a020976e7E56B88","aTokenSymbol":"aMKR","stableDebtTokenAddress":"0xC01C8E4b12a89456a9fD4e4e75B72546Bf53f0B5","variableDebtTokenAddress":"0xba728eAd5e496BE00DCF66F650b6d7758eCB50f8","symbol":"MKR","address":"0x9f8F72aA9304c8B593d555F12eF6589cC3A579A2","decimals":18},{"aTokenAddress":"0xCC12AbE4ff81c9378D670De1b57F8e0Dd228D77a","aTokenSymbol":"aREN","stableDebtTokenAddress":"0x3356Ec1eFA75d9D150Da1EC7d944D9EDf73703B7","variableDebtTokenAddress":"0xcd9D82d33bd737De215cDac57FE2F7f04DF77FE0","symbol":"REN","address":"0x408e41876cCCDC0F92210600ef50372656052a38","decimals":18},{"aTokenAddress":"0x35f6B052C598d933D69A4EEC4D04c73A191fE6c2","aTokenSymbol":"aSNX","stableDebtTokenAddress":"0x8575c8ae70bDB71606A53AeA1c6789cB0fBF3166","variableDebtTokenAddress":"0x267EB8Cf715455517F9BD5834AeAE3CeA1EBdbD8","symbol":"SNX","address":"0xC011a73ee8576Fb46F5E1c5751cA3B9Fe0af2a6F","decimals":18},{"aTokenAddress":"0x6C5024Cd4F8A59110119C56f8933403A539555EB","aTokenSymbol":"aSUSD","stableDebtTokenAddress":"0x30B0f7324feDF89d8eff397275F8983397eFe4af","variableDebtTokenAddress":"0xdC6a3Ab17299D9C2A412B0e0a4C1f55446AE0817","symbol":"sUSD","address":"0x57Ab1ec28D129707052df4dF418D58a2D46d5f51","decimals":18},{"aTokenAddress":"0x101cc05f4A51C0319f570d5E146a8C625198e636","aTokenSymbol":"aTUSD","stableDebtTokenAddress":"0x7f38d60D94652072b2C44a18c0e14A481EC3C0dd","variableDebtTokenAddress":"0x01C0eb1f8c6F1C1bF74ae028697ce7AA2a8b0E92","symbol":"TUSD","address":"0x0000000000085d4780B73119b644AE5ecd22b376","decimals":18},{"aTokenAddress":"0xBcca60bB61934080951369a648Fb03DF4F96263C","aTokenSymbol":"aUSDC","stableDebtTokenAddress":"0xE4922afAB0BbaDd8ab2a88E0C79d884Ad337fcA6","variableDebtTokenAddress":"0x619beb58998eD2278e08620f97007e1116D5D25b","symbol":"USDC","address":"0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48","decimals":6},{"aTokenAddress":"0x8dAE6Cb04688C62d939ed9B68d32Bc62e49970b1","aTokenSymbol":"aCRV","stableDebtTokenAddress":"0x9288059a74f589C919c7Cf1Db433251CdFEB874B","variableDebtTokenAddress":"0x00ad8eBF64F141f1C81e9f8f792d3d1631c6c684","symbol":"CRV","address":"0xD533a949740bb3306d119CC777fa900bA034cd52","decimals":18},{"aTokenAddress":"0xD37EE7e4f452C6638c96536e68090De8cBcdb583","aTokenSymbol":"aGUSD","stableDebtTokenAddress":"0xf8aC64ec6Ff8E0028b37EB89772d21865321bCe0","variableDebtTokenAddress":"0x279AF5b99540c1A3A7E3CDd326e19659401eF99e","symbol":"GUSD","address":"0x056Fd409E1d7A124BD7017459dFEa2F387b6d5Cd","decimals":2},{"aTokenAddress":"0x272F97b7a56a387aE942350bBC7Df5700f8a4576","aTokenSymbol":"aBAL","stableDebtTokenAddress":"0xe569d31590307d05DA3812964F1eDd551D665a0b","variableDebtTokenAddress":"0x13210D4Fe0d5402bd7Ecbc4B5bC5cFcA3b71adB0","symbol":"BAL","address":"0xba100000625a3754423978a60c9317c58a424e3D","decimals":18},{"aTokenAddress":"0xF256CC7847E919FAc9B808cC216cAc87CCF2f47a","aTokenSymbol":"aXSUSHI","stableDebtTokenAddress":"0x73Bfb81D7dbA75C904f430eA8BAe82DB0D41187B","variableDebtTokenAddress":"0xfAFEDF95E21184E3d880bd56D4806c4b8d31c69A","symbol":"xSUSHI","address":"0x8798249c2E607446EfB7Ad49eC89dD1865Ff4272","decimals":18}],"amm":[{"aTokenAddress":"0xf9Fb4AD91812b704Ba883B11d2B576E890a6730A","aTokenSymbol":"aAmmWETH","stableDebtTokenAddress":"0x118Ee405c6be8f9BA7cC7a98064EB5DA462235CF","variableDebtTokenAddress":"0xA4C273d9A0C1fe2674F0E845160d6232768a3064","symbol":"WETH","address":"0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2","decimals":18},{"aTokenAddress":"0x79bE75FFC64DD58e66787E4Eae470c8a1FD08ba4","aTokenSymbol":"aAmmDAI","stableDebtTokenAddress":"0x8da51a5a3129343468a63A96ccae1ff1352a3dfE","variableDebtTokenAddress":"0x3F4fA4937E72991367DC32687BC3278f095E7EAa","symbol":"DAI","address":"0x6B175474E89094C44Da98b954EedeAC495271d0F","decimals":18},{"aTokenAddress":"0xd24946147829DEaA935bE2aD85A3291dbf109c80","aTokenSymbol":"aAmmUSDC","stableDebtTokenAddress":"0xE5971a8a741892F3b3ac3E9c94d02588190cE220","variableDebtTokenAddress":"0xCFDC74b97b69319683fec2A4Ef95c4Ab739F1B12","symbol":"USDC","address":"0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48","decimals":6},{"aTokenAddress":"0x17a79792Fe6fE5C95dFE95Fe3fCEE3CAf4fE4Cb7","aTokenSymbol":"aAmmUSDT","stableDebtTokenAddress":"0x04A0577a89E1b9E8f6c87ee26cCe6a168fFfC5b5","variableDebtTokenAddress":"0xDcFE9BfC246b02Da384de757464a35eFCa402797","symbol":"USDT","address":"0xdAC17F958D2ee523a2206206994597C13D831ec7","decimals":6},{"aTokenAddress":"0x13B2f6928D7204328b0E8E4BCd0379aA06EA21FA","aTokenSymbol":"aAmmWBTC","stableDebtTokenAddress":"0x55E575d092c934503D7635A837584E2900e01d2b","variableDebtTokenAddress":"0x3b99fdaFdfE70d65101a4ba8cDC35dAFbD26375f","symbol":"WBTC","address":"0x2260FAC5E5542a773Aa44fBCfeDf7C193bc2C599","decimals":8},{"aTokenAddress":"0x9303EabC860a743aABcc3A1629014CaBcc3F8D36","aTokenSymbol":"aAmmUniDAIWETH","stableDebtTokenAddress":"0xE9562bf0A11315A1e39f9182F446eA58002f010E","variableDebtTokenAddress":"0x23bcc861b989762275165d08B127911F09c71628","symbol":"UNI-V2","address":"0xA478c2975Ab1Ea89e8196811F51A7B7Ade33eB11","decimals":18},{"aTokenAddress":"0xc58F53A8adff2fB4eb16ED56635772075E2EE123","aTokenSymbol":"aAmmUniWBTCWETH","stableDebtTokenAddress":"0xeef7d082D9bE2F5eC73C072228706286dea1f492","variableDebtTokenAddress":"0x02aAeB4C7736177242Ee0f71f6f6A0F057Aba87d","symbol":"UNI-V2","address":"0xBb2b8038a1640196FbE3e38816F3e67Cba72D940","decimals":18},{"aTokenAddress":"0xe59d2FF6995a926A574390824a657eEd36801E55","aTokenSymbol":"aAmmUniAAVEWETH","stableDebtTokenAddress":"0x997b26eFf106f138e71160022CaAb0AFC5814643","variableDebtTokenAddress":"0x859ED7D9E92d1fe42fF95C3BC3a62F7cB59C373E","symbol":"UNI-V2","address":"0xDFC14d2Af169B0D36C4EFF567Ada9b2E0CAE044f","decimals":18},{"aTokenAddress":"0xA1B0edF4460CC4d8bFAA18Ed871bFF15E5b57Eb4","aTokenSymbol":"aAmmUniBATWETH","stableDebtTokenAddress":"0x27c67541a4ea26a436e311b2E6fFeC82083a6983","variableDebtTokenAddress":"0x3Fbef89A21Dc836275bC912849627b33c61b09b4","symbol":"UNI-V2","address":"0xB6909B960DbbE7392D405429eB2b3649752b4838","decimals":18},{"aTokenAddress":"0xE340B25fE32B1011616bb8EC495A4d503e322177","aTokenSymbol":"aAmmUniDAIUSDC","stableDebtTokenAddress":"0x6Bb2BdD21920FcB2Ad855AB5d523222F31709d1f","variableDebtTokenAddress":"0x925E3FDd927E20e33C3177C4ff6fb72aD1133C87","symbol":"UNI-V2","address":"0xAE461cA67B15dc8dc81CE7615e0320dA1A9aB8D5","decimals":18},{"aTokenAddress":"0x0ea20e7fFB006d4Cfe84df2F72d8c7bD89247DB0","aTokenSymbol":"aAmmUniCRVWETH","stableDebtTokenAddress":"0xd6035f8803eE9f173b1D3EBc3BDE0Ea6B5165636","variableDebtTokenAddress":"0xF3f1a76cA6356a908CdCdE6b2AC2eaace3739Cd0","symbol":"UNI-V2","address":"0x3dA1313aE46132A397D90d95B1424A9A7e3e0fCE","decimals":18},{"aTokenAddress":"0xb8db81B84d30E2387de0FF330420A4AAA6688134","aTokenSymbol":"aAmmUniLINKWETH","stableDebtTokenAddress":"0xeb32b3A1De9a1915D2b452B673C53883b9Fa6a97","variableDebtTokenAddress":"0xeDe4052ed8e1F422F4E5062c679f6B18693fEcdc","symbol":"UNI-V2","address":"0xa2107FA5B38d9bbd2C461D6EDf11B11A50F6b974","decimals":18},{"aTokenAddress":"0x370adc71f67f581158Dc56f539dF5F399128Ddf9","aTokenSymbol":"aAmmUniMKRWETH","stableDebtTokenAddress":"0x6E7E38bB73E19b62AB5567940Caaa514e9d85982","variableDebtTokenAddress":"0xf36C394775285F89bBBDF09533421E3e81e8447c","symbol":"UNI-V2","address":"0xC2aDdA861F89bBB333c90c492cB837741916A225","decimals":18},{"aTokenAddress":"0xA9e201A4e269d6cd5E9F0FcbcB78520cf815878B","aTokenSymbol":"aAmmUniRENWETH","stableDebtTokenAddress":"0x312edeADf68E69A0f53518bF27EAcD1AbcC2897e","variableDebtTokenAddress":"0x2A8d5B1c1de15bfcd5EC41368C0295c60D8Da83c","symbol":"UNI-V2","address":"0x8Bd1661Da98EBDd3BD080F0bE4e6d9bE8cE9858c","decimals":18},{"aTokenAddress":"0x38E491A71291CD43E8DE63b7253E482622184894","aTokenSymbol":"aAmmUniSNXWETH","stableDebtTokenAddress":"0xef62A0C391D89381ddf8A8C90Ba772081107D287","variableDebtTokenAddress":"0xfd15008efA339A2390B48d2E0Ca8Abd523b406d3","symbol":"UNI-V2","address":"0x43AE24960e5534731Fc831386c07755A2dc33D47","decimals":18},{"aTokenAddress":"0x3D26dcd840fCC8e4B2193AcE8A092e4a65832F9f","aTokenSymbol":"aAmmUniUNIWETH","stableDebtTokenAddress":"0x6febCE732191Dc915D6fB7Dc5FE3AEFDDb85Bd1B","variableDebtTokenAddress":"0x0D878FbB01fbEEa7ddEFb896d56f1D3167af919F","symbol":"UNI-V2","address":"0xd3d2E2692501A5c9Ca623199D38826e513033a17","decimals":18},{"aTokenAddress":"0x391E86e2C002C70dEe155eAceB88F7A3c38f5976","aTokenSymbol":"aAmmUniUSDCWETH","stableDebtTokenAddress":"0xfAB4C9775A4316Ec67a8223ecD0F70F87fF532Fc","variableDebtTokenAddress":"0x26625d1dDf520fC8D975cc68eC6E0391D9d3Df61","symbol":"UNI-V2","address":"0xB4e16d0168e52d35CaCD2c6185b44281Ec28C9Dc","decimals":18},{"aTokenAddress":"0x2365a4890eD8965E564B7E2D27C38Ba67Fec4C6F","aTokenSymbol":"aAmmUniWBTCUSDC","stableDebtTokenAddress":"0xc66bfA05cCe646f05F71DeE333e3229cE24Bbb7e","variableDebtTokenAddress":"0x36dA0C5dC23397CBf9D13BbD74E93C04f99633Af","symbol":"UNI-V2","address":"0x004375Dff511095CC5A197A54140a24eFEF3A416","decimals":18},{"aTokenAddress":"0x5394794Be8b6eD5572FCd6b27103F46b5F390E8f","aTokenSymbol":"aAmmUniYFIWETH","stableDebtTokenAddress":"0x9B054B76d6DE1c4892ba025456A9c4F9be5B1766","variableDebtTokenAddress":"0xDf70Bdf01a3eBcd0D918FF97390852A914a92Df7","symbol":"UNI-V2","address":"0x2fDbAdf3C4D5A8666Bc06645B8358ab803996E28","decimals":18},{"aTokenAddress":"0x358bD0d980E031E23ebA9AA793926857703783BD","aTokenSymbol":"aAmmBptWBTCWETH","stableDebtTokenAddress":"0x46406eCd20FDE1DF4d80F15F07c434fa95CB6b33","variableDebtTokenAddress":"0xF655DF3832859cfB0AcfD88eDff3452b9Aa6Db24","symbol":"BPT","address":"0x1efF8aF5D577060BA4ac8A29A13525bb0Ee2A3D5","decimals":18},{"aTokenAddress":"0xd109b2A304587569c84308c55465cd9fF0317bFB","aTokenSymbol":"aAmmBptBALWETH","stableDebtTokenAddress":"0x6474d116476b8eDa1B21472a599Ff76A829AbCbb","variableDebtTokenAddress":"0xF41A5Cc7a61519B08056176d7B4b87AB34dF55AD","symbol":"BPT","address":"0x59A19D8c652FA0284f44113D0ff9aBa70bd46fB4","decimals":18}]}
--------------------------------------------------------------------------------
/test/ATokenYieldSource.test.ts:
--------------------------------------------------------------------------------
1 | import debug from 'debug';
2 |
3 | import { Signer } from '@ethersproject/abstract-signer';
4 | import { SignerWithAddress } from '@nomiclabs/hardhat-ethers/signers';
5 |
6 | import { expect } from 'chai';
7 | import { BigNumber } from 'ethers';
8 | import { MockContract } from 'ethereum-waffle';
9 | import { ethers, waffle } from 'hardhat';
10 |
11 | import {
12 | ATokenYieldSourceHarness,
13 | ATokenYieldSourceHarness__factory,
14 | AaveLendingPool,
15 | ATokenMintable,
16 | ERC20Mintable,
17 | } from '../types';
18 |
19 | import IAaveIncentivesController from '../abis/IAaveIncentivesController.json';
20 | import ILendingPoolAddressesProvider from '../abis/ILendingPoolAddressesProvider.json';
21 | import ILendingPoolAddressesProviderRegistry from '../abis/ILendingPoolAddressesProviderRegistry.json';
22 | import SafeERC20Wrapper from '../abis/SafeERC20Wrapper.json';
23 |
24 | const { constants, getContractFactory, getSigners, utils } = ethers;
25 | const { AddressZero, MaxUint256, Zero } = constants;
26 | const { parseUnits, formatEther } = utils;
27 |
28 | const DECIMALS = 6;
29 |
30 | const toWei = (amount: string) => parseUnits(amount, DECIMALS);
31 |
32 | describe('ATokenYieldSource', () => {
33 | let contractsOwner: Signer;
34 | let yieldSourceOwner: SignerWithAddress;
35 | let wallet2: SignerWithAddress;
36 | let attacker: SignerWithAddress;
37 |
38 | let aToken: ATokenMintable;
39 | let incentivesController: MockContract;
40 | let lendingPool: AaveLendingPool;
41 | let lendingPoolAddressesProvider: MockContract;
42 | let lendingPoolAddressesProviderRegistry: MockContract;
43 |
44 | let aTokenYieldSource: ATokenYieldSourceHarness;
45 |
46 | let erc20Token: MockContract;
47 | let usdcToken: ERC20Mintable;
48 |
49 | let constructorTest = false;
50 |
51 | const deployATokenYieldSource = async (
52 | aTokenAddress: string,
53 | incentivesControllerAddress: string,
54 | lendingPoolAddressesProviderRegistryAddress: string,
55 | decimals: number,
56 | owner: string,
57 | ) => {
58 | const ATokenYieldSource = (await ethers.getContractFactory(
59 | 'ATokenYieldSourceHarness',
60 | )) as ATokenYieldSourceHarness__factory;
61 |
62 | return await ATokenYieldSource.deploy(
63 | aTokenAddress,
64 | incentivesControllerAddress,
65 | lendingPoolAddressesProviderRegistryAddress,
66 | decimals,
67 | 'PoolTogether aUSDC Yield',
68 | 'PTaUSDC',
69 | owner,
70 | );
71 | };
72 |
73 | const supplyTokenTo = async (user: SignerWithAddress, userAmount: BigNumber) => {
74 | const userAddress = user.address;
75 |
76 | await usdcToken.mint(userAddress, userAmount);
77 | await usdcToken.connect(user).approve(aTokenYieldSource.address, MaxUint256);
78 |
79 | await aTokenYieldSource.connect(user).supplyTokenTo(userAmount, userAddress);
80 | };
81 |
82 | const sharesToToken = async (shares: BigNumber, yieldSourceTotalSupply: BigNumber) => {
83 | const totalShares = await aTokenYieldSource.totalSupply();
84 |
85 | // tokens = (shares * yieldSourceTotalSupply) / totalShares
86 | return shares.mul(yieldSourceTotalSupply).div(totalShares);
87 | };
88 |
89 | const tokenToShares = async (token: BigNumber, yieldSourceTotalSupply: BigNumber) => {
90 | const totalShares = await aTokenYieldSource.totalSupply();
91 |
92 | // shares = (tokens * totalSupply) / yieldSourceBalanceOfAToken
93 | return token.mul(totalShares).div(yieldSourceTotalSupply);
94 | };
95 |
96 | beforeEach(async () => {
97 | const { deployMockContract } = waffle;
98 |
99 | [contractsOwner, yieldSourceOwner, wallet2, attacker] = await getSigners();
100 |
101 | const ERC20MintableContract = await getContractFactory('ERC20Mintable', contractsOwner);
102 |
103 | debug('Mocking tokens...');
104 | erc20Token = await deployMockContract(contractsOwner, SafeERC20Wrapper);
105 | usdcToken = await ERC20MintableContract.deploy('USDC Stablecoin', 'USDC', DECIMALS);
106 |
107 | const ATokenMintableContract = await getContractFactory('ATokenMintable', contractsOwner);
108 |
109 | aToken = (await ATokenMintableContract.deploy(
110 | usdcToken.address,
111 | 'Aave interest bearing USDC',
112 | 'aUSDC',
113 | DECIMALS,
114 | )) as ATokenMintable;
115 |
116 | debug('Mocking contracts...');
117 |
118 | const AavePoolContract = await getContractFactory('AaveLendingPool', contractsOwner);
119 |
120 | lendingPool = (await AavePoolContract.deploy(
121 | usdcToken.address,
122 | aToken.address,
123 | )) as AaveLendingPool;
124 |
125 | incentivesController = await deployMockContract(contractsOwner, IAaveIncentivesController);
126 |
127 | lendingPoolAddressesProvider = await deployMockContract(
128 | contractsOwner,
129 | ILendingPoolAddressesProvider,
130 | );
131 |
132 | lendingPoolAddressesProviderRegistry = await deployMockContract(
133 | contractsOwner,
134 | ILendingPoolAddressesProviderRegistry,
135 | );
136 |
137 | await lendingPoolAddressesProvider.mock.getLendingPool.returns(lendingPool.address);
138 | await lendingPoolAddressesProviderRegistry.mock.getAddressesProvidersList.returns([
139 | lendingPoolAddressesProvider.address,
140 | '0x67FB118A780fD740C8936511947cC4bE7bb7730c',
141 | ]);
142 |
143 | debug('Deploying ATokenYieldSource...');
144 |
145 | if (!constructorTest) {
146 | aTokenYieldSource = await deployATokenYieldSource(
147 | aToken.address,
148 | incentivesController.address,
149 | lendingPoolAddressesProviderRegistry.address,
150 | DECIMALS,
151 | yieldSourceOwner.address,
152 | );
153 | }
154 | });
155 |
156 | describe('constructor()', () => {
157 | beforeEach(() => {
158 | constructorTest = true;
159 | });
160 |
161 | afterEach(() => {
162 | constructorTest = false;
163 | });
164 |
165 | it('should fail if aToken is address zero', async () => {
166 | await expect(
167 | deployATokenYieldSource(
168 | AddressZero,
169 | incentivesController.address,
170 | lendingPoolAddressesProviderRegistry.address,
171 | DECIMALS,
172 | yieldSourceOwner.address,
173 | ),
174 | ).to.be.revertedWith('ATokenYieldSource/aToken-not-zero-address');
175 | });
176 |
177 | it('should fail if incentivesController is address zero', async () => {
178 | await expect(
179 | deployATokenYieldSource(
180 | aToken.address,
181 | AddressZero,
182 | lendingPoolAddressesProviderRegistry.address,
183 | DECIMALS,
184 | yieldSourceOwner.address,
185 | ),
186 | ).to.be.revertedWith('ATokenYieldSource/incentivesController-not-zero-address');
187 | });
188 |
189 | it('should fail if lendingPoolAddressesProviderRegistry is address zero', async () => {
190 | await expect(
191 | deployATokenYieldSource(
192 | aToken.address,
193 | incentivesController.address,
194 | AddressZero,
195 | DECIMALS,
196 | yieldSourceOwner.address,
197 | ),
198 | ).to.be.revertedWith('ATokenYieldSource/lendingPoolRegistry-not-zero-address');
199 | });
200 |
201 | it('should fail if owner is address zero', async () => {
202 | await expect(
203 | deployATokenYieldSource(
204 | aToken.address,
205 | incentivesController.address,
206 | lendingPoolAddressesProviderRegistry.address,
207 | DECIMALS,
208 | AddressZero,
209 | ),
210 | ).to.be.revertedWith('ATokenYieldSource/owner-not-zero-address');
211 | });
212 |
213 | it('should fail if token decimal is not greater than 0', async () => {
214 | await expect(
215 | deployATokenYieldSource(
216 | aToken.address,
217 | incentivesController.address,
218 | lendingPoolAddressesProviderRegistry.address,
219 | 0,
220 | yieldSourceOwner.address,
221 | ),
222 | ).to.be.revertedWith('ATokenYieldSource/decimals-gt-zero');
223 | });
224 | });
225 |
226 | describe('create()', () => {
227 | it('should create ATokenYieldSource', async () => {
228 | expect(await aTokenYieldSource.decimals()).to.equal(DECIMALS);
229 | expect(await aTokenYieldSource.aToken()).to.equal(aToken.address);
230 | expect(await aTokenYieldSource.lendingPoolAddressesProviderRegistry()).to.equal(
231 | lendingPoolAddressesProviderRegistry.address,
232 | );
233 |
234 | expect(await aTokenYieldSource.owner()).to.equal(yieldSourceOwner.address);
235 | });
236 | });
237 |
238 | describe('approveMaxAmount()', () => {
239 | it('should approve lending pool to spend max uint256 amount', async () => {
240 | await aTokenYieldSource.approveLendingPool(Zero);
241 | await aTokenYieldSource.connect(yieldSourceOwner).approveMaxAmount();
242 |
243 | expect(await usdcToken.allowance(aTokenYieldSource.address, lendingPool.address)).to.equal(
244 | MaxUint256,
245 | );
246 | });
247 |
248 | it('should fail if not owner', async () => {
249 | await expect(aTokenYieldSource.connect(wallet2).approveMaxAmount()).to.be.revertedWith(
250 | 'Ownable/caller-not-owner',
251 | );
252 | });
253 | });
254 |
255 | describe('depositToken()', () => {
256 | it('should return the underlying token', async () => {
257 | expect(await aTokenYieldSource.depositToken()).to.equal(usdcToken.address);
258 | });
259 | });
260 |
261 | describe('balanceOfToken()', () => {
262 | it('should return user balance', async () => {
263 | const firstAmount = toWei('100');
264 | const yieldSourceTotalSupply = firstAmount.mul(2);
265 |
266 | await supplyTokenTo(yieldSourceOwner, firstAmount);
267 | await supplyTokenTo(yieldSourceOwner, firstAmount);
268 |
269 | const shares = await aTokenYieldSource.balanceOf(yieldSourceOwner.address);
270 | const tokens = await sharesToToken(shares, yieldSourceTotalSupply);
271 |
272 | expect(await aTokenYieldSource.balanceOfToken(yieldSourceOwner.address)).to.equal(tokens);
273 | });
274 | });
275 |
276 | describe('_tokenToShares()', () => {
277 | it('should return shares amount', async () => {
278 | const amount = toWei('100');
279 |
280 | await supplyTokenTo(yieldSourceOwner, amount);
281 | await supplyTokenTo(wallet2, amount);
282 |
283 | const tokens = toWei('10');
284 | const shares = await tokenToShares(tokens, amount.mul(2));
285 |
286 | expect(await aTokenYieldSource.tokenToShares(toWei('10'))).to.equal(shares);
287 | });
288 |
289 | it('should return 0 if tokens param is 0', async () => {
290 | expect(await aTokenYieldSource.tokenToShares('0')).to.equal('0');
291 | });
292 |
293 | it('should return tokens if totalSupply is 0', async () => {
294 | expect(await aTokenYieldSource.tokenToShares(toWei('100'))).to.equal(toWei('100'));
295 | });
296 |
297 | it('should return shares even if aToken total supply has a lot of decimals', async () => {
298 | const tokens = toWei('0.000005');
299 | const shares = toWei('1');
300 |
301 | await aTokenYieldSource.mint(yieldSourceOwner.address, shares);
302 | await aToken.mint(aTokenYieldSource.address, tokens);
303 |
304 | expect(await aTokenYieldSource.tokenToShares(tokens)).to.equal(shares);
305 | });
306 |
307 | it('should return shares even if aToken total supply increases', async () => {
308 | const amount = toWei('100');
309 | const tokens = toWei('1');
310 |
311 | await aTokenYieldSource.mint(yieldSourceOwner.address, amount);
312 | await aTokenYieldSource.mint(wallet2.address, amount);
313 | await aToken.mint(aTokenYieldSource.address, amount);
314 |
315 | expect(await aTokenYieldSource.tokenToShares(tokens)).to.equal(toWei('2'));
316 |
317 | await aToken.mint(aTokenYieldSource.address, parseUnits('100', 12).sub(amount));
318 |
319 | expect(await aTokenYieldSource.tokenToShares(tokens)).to.equal(2);
320 | });
321 |
322 | it('should fail to return shares if aToken total supply increases too much', async () => {
323 | const amount = toWei('100');
324 | const tokens = toWei('1');
325 |
326 | await aTokenYieldSource.mint(yieldSourceOwner.address, amount);
327 | await aTokenYieldSource.mint(wallet2.address, amount);
328 | await aToken.mint(aTokenYieldSource.address, amount);
329 |
330 | expect(await aTokenYieldSource.tokenToShares(tokens)).to.equal(toWei('2'));
331 |
332 | await aToken.mint(aTokenYieldSource.address, parseUnits('100', 13).sub(amount));
333 |
334 | await expect(aTokenYieldSource.supplyTokenTo(tokens, wallet2.address)).to.be.revertedWith(
335 | 'ATokenYieldSource/shares-gt-zero',
336 | );
337 | });
338 | });
339 |
340 | describe('_sharesToToken()', () => {
341 | it('should return tokens amount', async () => {
342 | const amount = toWei('100');
343 |
344 | await aTokenYieldSource.mint(yieldSourceOwner.address, amount);
345 | await aTokenYieldSource.mint(wallet2.address, amount);
346 | await aToken.mint(aTokenYieldSource.address, toWei('1000'));
347 |
348 | expect(await aTokenYieldSource.sharesToToken(toWei('2'))).to.equal(toWei('10'));
349 | });
350 |
351 | it('should return shares if totalSupply is 0', async () => {
352 | const shares = toWei('100');
353 | expect(await aTokenYieldSource.sharesToToken(shares)).to.equal(shares);
354 | });
355 |
356 | it('should return tokens even if shares are very small', async () => {
357 | const shares = toWei('0.000005');
358 | const tokens = toWei('100');
359 |
360 | await aTokenYieldSource.mint(yieldSourceOwner.address, shares);
361 | await aToken.mint(aTokenYieldSource.address, tokens);
362 |
363 | expect(await aTokenYieldSource.sharesToToken(shares)).to.equal(tokens);
364 | });
365 |
366 | it('should return tokens even if aToken total supply increases', async () => {
367 | const amount = toWei('100');
368 | const tokens = toWei('1');
369 |
370 | await aTokenYieldSource.mint(yieldSourceOwner.address, amount);
371 | await aTokenYieldSource.mint(wallet2.address, amount);
372 | await aToken.mint(aTokenYieldSource.address, amount);
373 |
374 | expect(await aTokenYieldSource.sharesToToken(toWei('2'))).to.equal(tokens);
375 |
376 | await aToken.mint(aTokenYieldSource.address, parseUnits('100', 12).sub(amount));
377 |
378 | expect(await aTokenYieldSource.sharesToToken(2)).to.equal(tokens);
379 | });
380 | });
381 |
382 | describe('supplyTokenTo()', () => {
383 | let amount: BigNumber;
384 | let tokenAddress: any;
385 |
386 | beforeEach(async () => {
387 | amount = toWei('100');
388 | tokenAddress = await aTokenYieldSource.tokenAddress();
389 | });
390 |
391 | it('should supply assets if totalSupply is 0', async () => {
392 | await supplyTokenTo(yieldSourceOwner, amount);
393 | expect(await aTokenYieldSource.totalSupply()).to.equal(amount);
394 | });
395 |
396 | it('should supply assets if totalSupply is not 0', async () => {
397 | await supplyTokenTo(yieldSourceOwner, amount);
398 | await supplyTokenTo(wallet2, amount);
399 | });
400 |
401 | it('should fail to manipulate share price significantly', async () => {
402 | const attackAmount = toWei('10');
403 | const aTokenAmount = toWei('1000');
404 | const attackerBalance = attackAmount.add(aTokenAmount);
405 |
406 | await supplyTokenTo(attacker, attackAmount);
407 |
408 | // Attacker sends 1000 aTokens directly to the contract to manipulate share price
409 | await aToken.mint(attacker.address, aTokenAmount);
410 | await aToken.connect(attacker).approve(aTokenYieldSource.address, aTokenAmount);
411 | await aToken.connect(attacker).transfer(aTokenYieldSource.address, aTokenAmount);
412 |
413 | await supplyTokenTo(wallet2, amount);
414 |
415 | expect(await aTokenYieldSource.balanceOfToken(attacker.address)).to.equal(attackerBalance);
416 |
417 | // We account for a small loss in precision due to the attack
418 | expect(await aTokenYieldSource.balanceOfToken(wallet2.address)).to.be.gte(
419 | amount.sub(toWei('0.0001')),
420 | );
421 | });
422 |
423 | it('should succeed to manipulate share price significantly but users should not be able to deposit smaller amounts', async () => {
424 | const attackAmount = BigNumber.from(1);
425 | const aTokenAmount = toWei('1000');
426 |
427 | await supplyTokenTo(attacker, attackAmount);
428 |
429 | // Attacker sends 1000 aTokens directly to the contract to manipulate share price
430 | await aToken.mint(attacker.address, aTokenAmount);
431 | await aToken.connect(attacker).approve(aTokenYieldSource.address, aTokenAmount);
432 | await aToken.connect(attacker).transfer(aTokenYieldSource.address, aTokenAmount);
433 |
434 | await expect(supplyTokenTo(wallet2, amount)).to.be.revertedWith(
435 | 'ATokenYieldSource/shares-gt-zero',
436 | );
437 | });
438 | });
439 |
440 | describe('redeemToken()', () => {
441 | let yieldSourceOwnerBalance: BigNumber;
442 | let redeemAmount: BigNumber;
443 |
444 | beforeEach(() => {
445 | yieldSourceOwnerBalance = toWei('300');
446 | redeemAmount = toWei('100');
447 | });
448 |
449 | it('should redeem assets', async () => {
450 | await supplyTokenTo(yieldSourceOwner, yieldSourceOwnerBalance);
451 |
452 | await aTokenYieldSource.connect(yieldSourceOwner).redeemToken(redeemAmount);
453 |
454 | expect(await aTokenYieldSource.balanceOf(yieldSourceOwner.address)).to.equal(
455 | yieldSourceOwnerBalance.sub(redeemAmount),
456 | );
457 | });
458 |
459 | it('should not be able to redeem assets if balance is 0', async () => {
460 | await expect(
461 | aTokenYieldSource.connect(yieldSourceOwner).redeemToken(redeemAmount),
462 | ).to.be.revertedWith('ERC20: burn amount exceeds balance');
463 | });
464 |
465 | it('should fail to redeem if amount is greater than balance', async () => {
466 | const yieldSourceOwnerLowBalance = toWei('10');
467 |
468 | await supplyTokenTo(yieldSourceOwner, yieldSourceOwnerLowBalance);
469 |
470 | await expect(
471 | aTokenYieldSource.connect(yieldSourceOwner).redeemToken(redeemAmount),
472 | ).to.be.revertedWith('ERC20: burn amount exceeds balance');
473 | });
474 |
475 | it('should succeed to manipulate share price but fail to redeem without burning any shares', async () => {
476 | const amount = toWei('100000');
477 | const attackAmount = BigNumber.from(1);
478 | const aTokenAmount = toWei('10000');
479 |
480 | await supplyTokenTo(attacker, attackAmount);
481 |
482 | // Attacker sends 10000 aTokens directly to the contract to manipulate share price
483 | await aToken.mint(attacker.address, aTokenAmount);
484 | await aToken.connect(attacker).approve(aTokenYieldSource.address, aTokenAmount);
485 | await aToken.connect(attacker).transfer(aTokenYieldSource.address, aTokenAmount);
486 |
487 | await supplyTokenTo(wallet2, amount);
488 |
489 | const sharePrice = await aTokenYieldSource.sharesToToken(BigNumber.from(1));
490 |
491 | // Redeem 1 wei less than the full amount of a share to burn 0 share instead of 1 because of rounding error
492 | // The actual amount of shares to be burnt should be 0.99 but since Solidity truncates down, it will be 0
493 | const attackerRedeemAmount = sharePrice.sub(1);
494 |
495 | await expect(
496 | aTokenYieldSource.connect(attacker).redeemToken(attackerRedeemAmount),
497 | ).to.be.revertedWith('ATokenYieldSource/shares-gt-zero');
498 | });
499 | });
500 |
501 | describe('transferERC20()', () => {
502 | it('should transferERC20 if yieldSourceOwner', async () => {
503 | const transferAmount = toWei('10');
504 |
505 | await erc20Token.mock.transfer.withArgs(wallet2.address, transferAmount).returns(true);
506 |
507 | await aTokenYieldSource
508 | .connect(yieldSourceOwner)
509 | .transferERC20(erc20Token.address, wallet2.address, transferAmount);
510 | });
511 |
512 | it('should transferERC20 if assetManager', async () => {
513 | const transferAmount = toWei('10');
514 |
515 | await erc20Token.mock.transfer
516 | .withArgs(yieldSourceOwner.address, transferAmount)
517 | .returns(true);
518 |
519 | await aTokenYieldSource.connect(yieldSourceOwner).setManager(wallet2.address);
520 |
521 | await aTokenYieldSource
522 | .connect(wallet2)
523 | .transferERC20(erc20Token.address, yieldSourceOwner.address, transferAmount);
524 | });
525 |
526 | it('should not allow to transfer aToken', async () => {
527 | await expect(
528 | aTokenYieldSource
529 | .connect(yieldSourceOwner)
530 | .transferERC20(aToken.address, wallet2.address, toWei('10')),
531 | ).to.be.revertedWith('ATokenYieldSource/aToken-transfer-not-allowed');
532 | });
533 |
534 | it('should fail to transferERC20 if not yieldSourceOwner or assetManager', async () => {
535 | await expect(
536 | aTokenYieldSource
537 | .connect(wallet2)
538 | .transferERC20(erc20Token.address, yieldSourceOwner.address, toWei('10')),
539 | ).to.be.revertedWith('Manageable/caller-not-manager-or-owner');
540 | });
541 | });
542 |
543 | describe('sponsor()', () => {
544 | let amount: BigNumber;
545 | let tokenAddress: any;
546 |
547 | beforeEach(async () => {
548 | amount = toWei('500');
549 | tokenAddress = await aTokenYieldSource.tokenAddress();
550 | });
551 |
552 | it('should sponsor Yield Source', async () => {
553 | const wallet2Amount = toWei('100');
554 |
555 | await supplyTokenTo(wallet2, wallet2Amount);
556 |
557 | await usdcToken.mint(yieldSourceOwner.address, amount);
558 | await usdcToken.connect(yieldSourceOwner).approve(aTokenYieldSource.address, MaxUint256);
559 |
560 | await aTokenYieldSource.connect(yieldSourceOwner).sponsor(amount);
561 |
562 | expect(await aTokenYieldSource.balanceOfToken(wallet2.address)).to.equal(
563 | amount.add(wallet2Amount),
564 | );
565 | });
566 | });
567 |
568 | describe('claimRewards()', () => {
569 | const claimAmount = toWei('100');
570 |
571 | beforeEach(async () => {
572 | await incentivesController.mock.getRewardsBalance
573 | .withArgs([aToken.address], aTokenYieldSource.address)
574 | .returns(claimAmount);
575 |
576 | await incentivesController.mock.claimRewards
577 | .withArgs([aToken.address], claimAmount, wallet2.address)
578 | .returns(claimAmount);
579 | });
580 |
581 | it('should claimRewards if yieldSourceOwner', async () => {
582 | await expect(aTokenYieldSource.connect(yieldSourceOwner).claimRewards(wallet2.address))
583 | .to.emit(aTokenYieldSource, 'Claimed')
584 | .withArgs(yieldSourceOwner.address, wallet2.address, claimAmount);
585 | });
586 |
587 | it('should claimRewards if assetManager', async () => {
588 | await aTokenYieldSource.connect(yieldSourceOwner).setManager(wallet2.address);
589 |
590 | await expect(aTokenYieldSource.connect(wallet2).claimRewards(wallet2.address))
591 | .to.emit(aTokenYieldSource, 'Claimed')
592 | .withArgs(wallet2.address, wallet2.address, claimAmount);
593 | });
594 |
595 | it('should fail to claimRewards if recipient is address zero', async () => {
596 | await expect(
597 | aTokenYieldSource.connect(yieldSourceOwner).claimRewards(AddressZero),
598 | ).to.be.revertedWith('ATokenYieldSource/recipient-not-zero-address');
599 | });
600 |
601 | it('should fail to claimRewards if not yieldSourceOwner or assetManager', async () => {
602 | await expect(
603 | aTokenYieldSource.connect(wallet2).claimRewards(wallet2.address),
604 | ).to.be.revertedWith('Manageable/caller-not-manager-or-owner');
605 | });
606 | });
607 |
608 | describe('_lendingPool()', () => {
609 | it('should return Aave LendingPool address', async () => {
610 | expect(await aTokenYieldSource.lendingPool()).to.equal(lendingPool.address);
611 | });
612 | });
613 | });
614 |
--------------------------------------------------------------------------------