fix: Reject deposits directly to implementation

This commit is contained in:
Lasse Herskind 2021-10-14 13:58:41 +01:00
parent 123415be61
commit 02b9641276
6 changed files with 149 additions and 13 deletions

View File

@ -9,4 +9,5 @@ library StaticATokenErrors {
string public constant INVALID_RECIPIENT = '5'; string public constant INVALID_RECIPIENT = '5';
string public constant INVALID_CLAIMER = '6'; string public constant INVALID_CLAIMER = '6';
string public constant ONLY_ONE_AMOUNT_FORMAT_ALLOWED = '7'; string public constant ONLY_ONE_AMOUNT_FORMAT_ALLOWED = '7';
string public constant ONLY_PROXY_MAY_CALL = '8';
} }

View File

@ -70,6 +70,17 @@ contract StaticATokenLM is
// user => unclaimedRewards (in RAYs) // user => unclaimedRewards (in RAYs)
mapping(address => uint256) private _unclaimedRewards; mapping(address => uint256) private _unclaimedRewards;
bool public isImplementation;
modifier onlyProxy() {
require(isImplementation == false, StaticATokenErrors.ONLY_PROXY_MAY_CALL);
_;
}
constructor() public {
isImplementation = true;
}
///@inheritdoc VersionedInitializable ///@inheritdoc VersionedInitializable
function getRevision() internal pure virtual override returns (uint256) { function getRevision() internal pure virtual override returns (uint256) {
return STATIC_ATOKEN_LM_REVISION; return STATIC_ATOKEN_LM_REVISION;
@ -294,7 +305,7 @@ contract StaticATokenLM is
uint256 amount, uint256 amount,
uint16 referralCode, uint16 referralCode,
bool fromUnderlying bool fromUnderlying
) internal returns (uint256) { ) internal onlyProxy returns (uint256) {
require(recipient != address(0), StaticATokenErrors.INVALID_RECIPIENT); require(recipient != address(0), StaticATokenErrors.INVALID_RECIPIENT);
_updateRewards(); _updateRewards();

View File

@ -10,6 +10,8 @@ import {
WETH9, WETH9,
AToken, AToken,
StaticATokenLM, StaticATokenLM,
InitializableAdminUpgradeabilityProxyFactory,
StaticAToken,
} from '../../../../types'; } from '../../../../types';
import { import {
impersonateAccountsHardhat, impersonateAccountsHardhat,
@ -27,6 +29,7 @@ import { AbiCoder, formatEther, verifyTypedData } from 'ethers/lib/utils';
import { _TypedDataEncoder } from 'ethers/lib/utils'; import { _TypedDataEncoder } from 'ethers/lib/utils';
import { expect, use } from 'chai'; import { expect, use } from 'chai';
import { zeroAddress } from 'hardhat/node_modules/ethereumjs-util';
//use(solidity); //use(solidity);
@ -80,6 +83,7 @@ describe('StaticATokenLM: aToken wrapper with static balances and NO liquidity m
let enj: ERC20; let enj: ERC20;
let aenj: AToken; let aenj: AToken;
let staticATokenImplementation: StaticATokenLM;
let staticAToken: StaticATokenLM; let staticAToken: StaticATokenLM;
let snap: string; let snap: string;
@ -99,8 +103,25 @@ describe('StaticATokenLM: aToken wrapper with static balances and NO liquidity m
aenj = ATokenFactory.connect(AENJ, userSigner); aenj = ATokenFactory.connect(AENJ, userSigner);
stkAave = ERC20Factory.connect(STKAAVE, userSigner); stkAave = ERC20Factory.connect(STKAAVE, userSigner);
staticAToken = await new StaticATokenLMFactory(userSigner).deploy(); staticATokenImplementation = await new StaticATokenLMFactory(userSigner).deploy();
await staticAToken.initialize(LENDING_POOL, AENJ, 'Wrapped aENJ', 'waaenj'); await staticATokenImplementation.initialize(LENDING_POOL, AENJ, 'Wrapped aENJ', 'waaenj');
const proxy = await new InitializableAdminUpgradeabilityProxyFactory(userSigner).deploy();
const encodedInitializedParams = staticATokenImplementation.interface.encodeFunctionData(
'initialize',
[LENDING_POOL, AENJ, 'Wrapped aENJ', 'waaenj']
);
await proxy['initialize(address,address,bytes)'](
staticATokenImplementation.address,
zeroAddress(),
encodedInitializedParams
);
staticAToken = StaticATokenLMFactory.connect(proxy.address, userSigner);
expect(await staticATokenImplementation.isImplementation()).to.be.eq(true);
expect(await staticAToken.isImplementation()).to.be.eq(false);
expect(await staticAToken.decimals()).to.be.eq(18); expect(await staticAToken.decimals()).to.be.eq(18);
expect(await staticAToken.name()).to.be.eq('Wrapped aENJ'); expect(await staticAToken.name()).to.be.eq('Wrapped aENJ');
@ -264,7 +285,7 @@ describe('StaticATokenLM: aToken wrapper with static balances and NO liquidity m
const bGas = BigNumber.from(bReceipt['gasUsed']); const bGas = BigNumber.from(bReceipt['gasUsed']);
expect(aGas).to.be.gt(250000); expect(aGas).to.be.gt(250000);
expect(bGas).to.be.lt(200000); expect(bGas).to.be.lt(210000);
await DRE.network.provider.send('evm_setAutomine', [true]); await DRE.network.provider.send('evm_setAutomine', [true]);
await DRE.network.provider.send('evm_mine', []); await DRE.network.provider.send('evm_mine', []);
@ -296,8 +317,8 @@ describe('StaticATokenLM: aToken wrapper with static balances and NO liquidity m
const aGas = BigNumber.from(aReceipt['gasUsed']); const aGas = BigNumber.from(aReceipt['gasUsed']);
const bGas = BigNumber.from(bReceipt['gasUsed']); const bGas = BigNumber.from(bReceipt['gasUsed']);
expect(aGas).to.be.lt(25000); expect(aGas).to.be.lt(35000);
expect(bGas).to.be.lt(25000); expect(bGas).to.be.lt(35000);
await DRE.network.provider.send('evm_setAutomine', [true]); await DRE.network.provider.send('evm_setAutomine', [true]);
}); });

View File

@ -14,6 +14,7 @@ import {
AToken, AToken,
StaticAToken, StaticAToken,
StaticATokenLM, StaticATokenLM,
InitializableAdminUpgradeabilityProxyFactory,
} from '../../../../types'; } from '../../../../types';
import { IAaveIncentivesControllerFactory } from '../../../../types/IAaveIncentivesControllerFactory'; import { IAaveIncentivesControllerFactory } from '../../../../types/IAaveIncentivesControllerFactory';
import { import {
@ -68,6 +69,7 @@ const LM_ERRORS = {
INVALID_RECIPIENT: '5', INVALID_RECIPIENT: '5',
INVALID_CLAIMER: '6', INVALID_CLAIMER: '6',
ONLY_ONE_AMOUNT_FORMAT_ALLOWED: '7', ONLY_ONE_AMOUNT_FORMAT_ALLOWED: '7',
ONLY_PROXY_MAY_CALL: '8',
}; };
type tBalancesInvolved = { type tBalancesInvolved = {
@ -147,6 +149,7 @@ describe('StaticATokenLM: aToken wrapper with static balances and NO liquidity m
let enjWhale: providers.JsonRpcSigner; let enjWhale: providers.JsonRpcSigner;
let staticATokenImplementation: StaticATokenLM;
let staticAToken: StaticATokenLM; let staticAToken: StaticATokenLM;
let snap: string; let snap: string;
@ -167,8 +170,25 @@ describe('StaticATokenLM: aToken wrapper with static balances and NO liquidity m
aenj = ATokenFactory.connect(AENJ, userSigner); aenj = ATokenFactory.connect(AENJ, userSigner);
stkAave = ERC20Factory.connect(STKAAVE, userSigner); stkAave = ERC20Factory.connect(STKAAVE, userSigner);
staticAToken = await new StaticATokenLMFactory(userSigner).deploy(); staticATokenImplementation = await new StaticATokenLMFactory(userSigner).deploy();
await staticAToken.initialize(LENDING_POOL, AENJ, 'Wrapped aENJ', 'waaenj'); await staticATokenImplementation.initialize(LENDING_POOL, AENJ, 'Wrapped aENJ', 'waaenj');
const proxy = await new InitializableAdminUpgradeabilityProxyFactory(userSigner).deploy();
const encodedInitializedParams = staticATokenImplementation.interface.encodeFunctionData(
'initialize',
[LENDING_POOL, AENJ, 'Wrapped aENJ', 'waaenj']
);
await proxy['initialize(address,address,bytes)'](
staticATokenImplementation.address,
zeroAddress(),
encodedInitializedParams
);
staticAToken = StaticATokenLMFactory.connect(proxy.address, userSigner);
expect(await staticATokenImplementation.isImplementation()).to.be.eq(true);
expect(await staticAToken.isImplementation()).to.be.eq(false);
expect(await staticAToken.getIncentivesController()).to.be.eq(zeroAddress()); expect(await staticAToken.getIncentivesController()).to.be.eq(zeroAddress());
expect(await staticAToken.ASSET()).to.be.eq(await staticAToken.UNDERLYING_ASSET_ADDRESS()); expect(await staticAToken.ASSET()).to.be.eq(await staticAToken.UNDERLYING_ASSET_ADDRESS());
@ -199,6 +219,27 @@ describe('StaticATokenLM: aToken wrapper with static balances and NO liquidity m
await evmRevert(snap); await evmRevert(snap);
}); });
it('Deposit ENJ directly to implementation (expect revert)', async () => {
const amountToDeposit = utils.parseEther('5');
const amountToWithdraw = MAX_UINT_AMOUNT;
// Just preparation
await waitForTx(
await enj.approve(staticATokenImplementation.address, amountToDeposit, defaultTxParams)
);
// Depositing
await expect(
staticATokenImplementation.deposit(
userSigner._address,
amountToDeposit,
0,
true,
defaultTxParams
)
).to.be.revertedWith(LM_ERRORS.ONLY_PROXY_MAY_CALL);
});
it('Deposit ENJ on waaenj, then withdraw of the whole balance in underlying', async () => { it('Deposit ENJ on waaenj, then withdraw of the whole balance in underlying', async () => {
const amountToDeposit = utils.parseEther('5'); const amountToDeposit = utils.parseEther('5');
const amountToWithdraw = MAX_UINT_AMOUNT; const amountToWithdraw = MAX_UINT_AMOUNT;

View File

@ -10,6 +10,7 @@ import {
WETH9, WETH9,
AToken, AToken,
StaticATokenLM, StaticATokenLM,
InitializableAdminUpgradeabilityProxyFactory,
} from '../../../../types'; } from '../../../../types';
import { import {
impersonateAccountsHardhat, impersonateAccountsHardhat,
@ -27,6 +28,7 @@ import { AbiCoder, formatEther, verifyTypedData } from 'ethers/lib/utils';
import { _TypedDataEncoder } from 'ethers/lib/utils'; import { _TypedDataEncoder } from 'ethers/lib/utils';
import { expect, use } from 'chai'; import { expect, use } from 'chai';
import { zeroAddress } from 'ethereumjs-util';
//use(solidity); //use(solidity);
@ -81,6 +83,7 @@ describe('StaticATokenLM: aToken wrapper with static balances and liquidity mini
let aweth: AToken; let aweth: AToken;
let stkAave: ERC20; let stkAave: ERC20;
let staticATokenImplementation: StaticATokenLM;
let staticAToken: StaticATokenLM; let staticAToken: StaticATokenLM;
let snap: string; let snap: string;
@ -97,14 +100,31 @@ describe('StaticATokenLM: aToken wrapper with static balances and liquidity mini
aweth = ATokenFactory.connect(AWETH, userSigner); aweth = ATokenFactory.connect(AWETH, userSigner);
stkAave = ERC20Factory.connect(STKAAVE, userSigner); stkAave = ERC20Factory.connect(STKAAVE, userSigner);
staticAToken = await new StaticATokenLMFactory(userSigner).deploy(); staticATokenImplementation = await new StaticATokenLMFactory(userSigner).deploy();
await staticAToken.initialize( await staticATokenImplementation.initialize(
LENDING_POOL, LENDING_POOL,
AWETH, AWETH,
'Static Aave Interest Bearing WETH', 'Static Aave Interest Bearing WETH',
'stataWETH' 'stataWETH'
); );
const proxy = await new InitializableAdminUpgradeabilityProxyFactory(userSigner).deploy();
const encodedInitializedParams = staticATokenImplementation.interface.encodeFunctionData(
'initialize',
[LENDING_POOL, AWETH, 'Static Aave Interest Bearing WETH', 'stataWETH']
);
await proxy['initialize(address,address,bytes)'](
staticATokenImplementation.address,
zeroAddress(),
encodedInitializedParams
);
staticAToken = StaticATokenLMFactory.connect(proxy.address, userSigner);
expect(await staticATokenImplementation.isImplementation()).to.be.eq(true);
expect(await staticAToken.isImplementation()).to.be.eq(false);
expect(await staticAToken.decimals()).to.be.eq(18); expect(await staticAToken.decimals()).to.be.eq(18);
expect(await staticAToken.name()).to.be.eq('Static Aave Interest Bearing WETH'); expect(await staticAToken.name()).to.be.eq('Static Aave Interest Bearing WETH');
expect(await staticAToken.symbol()).to.be.eq('stataWETH'); expect(await staticAToken.symbol()).to.be.eq('stataWETH');

View File

@ -14,6 +14,7 @@ import {
AToken, AToken,
StaticAToken, StaticAToken,
StaticATokenLM, StaticATokenLM,
InitializableAdminUpgradeabilityProxyFactory,
} from '../../../../types'; } from '../../../../types';
import { IAaveIncentivesControllerFactory } from '../../../../types/IAaveIncentivesControllerFactory'; import { IAaveIncentivesControllerFactory } from '../../../../types/IAaveIncentivesControllerFactory';
import { import {
@ -39,6 +40,7 @@ import {
} from '../../../../helpers/contracts-helpers'; } from '../../../../helpers/contracts-helpers';
import { IAaveIncentivesController } from '../../../../types/IAaveIncentivesController'; import { IAaveIncentivesController } from '../../../../types/IAaveIncentivesController';
import { deploySelfdestructTransferMock } from '../../../../helpers/contracts-deployments'; import { deploySelfdestructTransferMock } from '../../../../helpers/contracts-deployments';
import { zeroAddress } from 'ethereumjs-util';
const { expect, use } = require('chai'); const { expect, use } = require('chai');
@ -66,6 +68,7 @@ const LM_ERRORS = {
INVALID_RECIPIENT: '5', INVALID_RECIPIENT: '5',
INVALID_CLAIMER: '6', INVALID_CLAIMER: '6',
ONLY_ONE_AMOUNT_FORMAT_ALLOWED: '7', ONLY_ONE_AMOUNT_FORMAT_ALLOWED: '7',
ONLY_PROXY_MAY_CALL: '8',
}; };
type tBalancesInvolved = { type tBalancesInvolved = {
@ -143,6 +146,7 @@ describe('StaticATokenLM: aToken wrapper with static balances and liquidity mini
let aweth: AToken; let aweth: AToken;
let stkAave: ERC20; let stkAave: ERC20;
let staticATokenImplementation: StaticATokenLM;
let staticAToken: StaticATokenLM; let staticAToken: StaticATokenLM;
let snap: string; let snap: string;
@ -162,15 +166,31 @@ describe('StaticATokenLM: aToken wrapper with static balances and liquidity mini
aweth = ATokenFactory.connect(AWETH, userSigner); aweth = ATokenFactory.connect(AWETH, userSigner);
stkAave = ERC20Factory.connect(STKAAVE, userSigner); stkAave = ERC20Factory.connect(STKAAVE, userSigner);
staticAToken = await new StaticATokenLMFactory(userSigner).deploy(); staticATokenImplementation = await new StaticATokenLMFactory(userSigner).deploy();
await staticAToken.initialize( await staticATokenImplementation.initialize(
LENDING_POOL, LENDING_POOL,
AWETH, AWETH,
'Static Aave Interest Bearing WETH', 'Static Aave Interest Bearing WETH',
'stataAAVE' 'stataWETH'
); );
const proxy = await new InitializableAdminUpgradeabilityProxyFactory(userSigner).deploy();
const encodedInitializedParams = staticATokenImplementation.interface.encodeFunctionData(
'initialize',
[LENDING_POOL, AWETH, 'Static Aave Interest Bearing WETH', 'stataWETH']
);
await proxy['initialize(address,address,bytes)'](
staticATokenImplementation.address,
zeroAddress(),
encodedInitializedParams
);
staticAToken = StaticATokenLMFactory.connect(proxy.address, userSigner);
expect(await staticAToken.getIncentivesController()).to.be.eq(INCENTIVES_CONTROLLER); expect(await staticAToken.getIncentivesController()).to.be.eq(INCENTIVES_CONTROLLER);
expect(await staticATokenImplementation.isImplementation()).to.be.eq(true);
expect(await staticAToken.isImplementation()).to.be.eq(false);
ctxtParams = { ctxtParams = {
staticAToken: <StaticATokenLM>staticAToken, staticAToken: <StaticATokenLM>staticAToken,
@ -194,6 +214,28 @@ describe('StaticATokenLM: aToken wrapper with static balances and liquidity mini
await evmRevert(snap); await evmRevert(snap);
}); });
it('Deposit WETH directly to implementation (expect revert)', async () => {
const amountToDeposit = utils.parseEther('5');
const amountToWithdraw = MAX_UINT_AMOUNT;
// Just preparation
await waitForTx(await weth.deposit({ value: amountToDeposit }));
await waitForTx(
await weth.approve(staticATokenImplementation.address, amountToDeposit, defaultTxParams)
);
// Depositing
await expect(
staticATokenImplementation.deposit(
userSigner._address,
amountToDeposit,
0,
true,
defaultTxParams
)
).to.be.revertedWith(LM_ERRORS.ONLY_PROXY_MAY_CALL);
});
it('Deposit WETH on stataWETH, then withdraw of the whole balance in underlying', async () => { it('Deposit WETH on stataWETH, then withdraw of the whole balance in underlying', async () => {
const amountToDeposit = utils.parseEther('5'); const amountToDeposit = utils.parseEther('5');
const amountToWithdraw = MAX_UINT_AMOUNT; const amountToWithdraw = MAX_UINT_AMOUNT;