mirror of
https://github.com/Instadapp/aave-protocol-v2.git
synced 2024-07-29 21:47:30 +00:00
Added emergency token and ether withdrawal. Follow code guidelines.
This commit is contained in:
parent
4893548d36
commit
a54f910928
|
@ -10,14 +10,16 @@ import {ReserveLogic} from '../libraries/logic/ReserveLogic.sol';
|
|||
import {ReserveConfiguration} from '../libraries/configuration/ReserveConfiguration.sol';
|
||||
import {UserConfiguration} from '../libraries/configuration/UserConfiguration.sol';
|
||||
import {Helpers} from '../libraries/helpers/Helpers.sol';
|
||||
import {Ownable} from '../dependencies/openzeppelin/contracts/Ownable.sol';
|
||||
import {IERC20} from '../dependencies/openzeppelin/contracts/IERC20.sol';
|
||||
|
||||
contract WETHGateway is IWETHGateway {
|
||||
contract WETHGateway is IWETHGateway, Ownable {
|
||||
using ReserveConfiguration for ReserveConfiguration.Map;
|
||||
using UserConfiguration for UserConfiguration.Map;
|
||||
|
||||
IWETH public immutable WETH;
|
||||
ILendingPool public immutable POOL;
|
||||
IAToken public immutable aWETH;
|
||||
IWETH internal immutable WETH;
|
||||
ILendingPool internal immutable POOL;
|
||||
IAToken internal immutable aWETH;
|
||||
|
||||
/**
|
||||
* @dev Sets the WETH address and the LendingPoolAddressesProvider address. Infinite approves lending pool.
|
||||
|
@ -59,7 +61,7 @@ contract WETHGateway is IWETHGateway {
|
|||
aWETH.transferFrom(msg.sender, address(this), amountToWithdraw);
|
||||
POOL.withdraw(address(WETH), amountToWithdraw, address(this));
|
||||
WETH.withdraw(amountToWithdraw);
|
||||
safeTransferETH(to, amountToWithdraw);
|
||||
_safeTransferETH(to, amountToWithdraw);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -91,7 +93,7 @@ contract WETHGateway is IWETHGateway {
|
|||
POOL.repay(address(WETH), msg.value, rateMode, onBehalfOf);
|
||||
|
||||
// refund remaining dust eth
|
||||
if (msg.value > paybackAmount) safeTransferETH(msg.sender, msg.value - paybackAmount);
|
||||
if (msg.value > paybackAmount) _safeTransferETH(msg.sender, msg.value - paybackAmount);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -99,11 +101,57 @@ contract WETHGateway is IWETHGateway {
|
|||
* @param to recipient of the transfer
|
||||
* @param value the amount to send
|
||||
*/
|
||||
function safeTransferETH(address to, uint256 value) internal {
|
||||
function _safeTransferETH(address to, uint256 value) internal {
|
||||
(bool success, ) = to.call{value: value}(new bytes(0));
|
||||
require(success, 'ETH_TRANSFER_FAILED');
|
||||
}
|
||||
|
||||
/**
|
||||
* @dev transfer ERC20 from the utility contract, for ERC20 recovery in case of stuck tokens due
|
||||
* direct transfers to the contract address.
|
||||
* @param token token to transfer
|
||||
* @param to recipient of the transfer
|
||||
* @param amount amount to send
|
||||
*/
|
||||
function emergencyTokenTransfer(
|
||||
address token,
|
||||
address to,
|
||||
uint256 amount
|
||||
) external onlyOwner {
|
||||
IERC20(token).transfer(to, amount);
|
||||
}
|
||||
|
||||
/**
|
||||
* @dev transfer native Ether from the utility contract, for native Ether recovery in case of stuck Ether
|
||||
* due selfdestructs, transfer ether to pre-computated contract address before deployment or bad contract behaviour.
|
||||
* @param to recipient of the transfer
|
||||
* @param amount amount to send
|
||||
*/
|
||||
function emergencyEtherTransfer(address to, uint256 amount) external onlyOwner {
|
||||
_safeTransferETH(to, amount);
|
||||
}
|
||||
|
||||
/**
|
||||
* @dev Get WETH address used by WETHGateway
|
||||
*/
|
||||
function getWETHAddress() external view returns (address) {
|
||||
return address(WETH);
|
||||
}
|
||||
|
||||
/**
|
||||
* @dev Get aWETH address used by WETHGateway
|
||||
*/
|
||||
function getAWETHAddress() external view returns (address) {
|
||||
return address(aWETH);
|
||||
}
|
||||
|
||||
/**
|
||||
* @dev Get LendingPool address used by WETHGateway
|
||||
*/
|
||||
function getLendingPoolAddress() external view returns (address) {
|
||||
return address(POOL);
|
||||
}
|
||||
|
||||
/**
|
||||
* @dev Only WETH contract is allowed to transfer ETH here. Prevent other addresses to send Ether to this contract.
|
||||
*/
|
||||
|
|
8
contracts/mocks/attacks/SefldestructTransfer.sol
Normal file
8
contracts/mocks/attacks/SefldestructTransfer.sol
Normal file
|
@ -0,0 +1,8 @@
|
|||
// SPDX-License-Identifier: agpl-3.0
|
||||
pragma solidity ^0.6.8;
|
||||
|
||||
contract SelfdestructTransfer {
|
||||
function destroyAndTransfer(address payable to) external payable {
|
||||
selfdestruct(to);
|
||||
}
|
||||
}
|
|
@ -171,7 +171,7 @@
|
|||
},
|
||||
"ReserveLogic": {
|
||||
"buidlerevm": {
|
||||
"address": "0x920d847fE49E54C19047ba8bc236C45A8068Bca7",
|
||||
"address": "0xFAe0fd738dAbc8a0426F47437322b6d026A9FD95",
|
||||
"deployer": "0xc783df8a850f42e7F7e57013759C285caa701eB6"
|
||||
},
|
||||
"kovan": {
|
||||
|
@ -181,20 +181,19 @@
|
|||
},
|
||||
"GenericLogic": {
|
||||
"buidlerevm": {
|
||||
"address": "0xA4765Ff72A9F3CfE73089bb2c3a41B838DF71574",
|
||||
"address": "0x6082731fdAba4761277Fb31299ebC782AD3bCf24",
|
||||
"deployer": "0xc783df8a850f42e7F7e57013759C285caa701eB6"
|
||||
}
|
||||
},
|
||||
"ValidationLogic": {
|
||||
"buidlerevm": {
|
||||
"address": "0x35c1419Da7cf0Ff885B8Ef8EA9242FEF6800c99b",
|
||||
"address": "0x8456161947DFc1fC159A0B26c025cD2b4bba0c3e",
|
||||
"deployer": "0xc783df8a850f42e7F7e57013759C285caa701eB6"
|
||||
}
|
||||
},
|
||||
"LendingPool": {
|
||||
"buidlerevm": {
|
||||
"address": "0xe2607EabC87fd0A4856840bF23da8458cDF0434F",
|
||||
"deployer": "0xc783df8a850f42e7F7e57013759C285caa701eB6"
|
||||
"address": "0xD9273d497eDBC967F39d419461CfcF382a0A822e"
|
||||
}
|
||||
},
|
||||
"LendingPoolConfigurator": {
|
||||
|
@ -210,7 +209,7 @@
|
|||
},
|
||||
"ATokensAndRatesHelper": {
|
||||
"buidlerevm": {
|
||||
"address": "0xA4765Ff72A9F3CfE73089bb2c3a41B838DF71574",
|
||||
"address": "0x06bA8d8af0dF898D0712DffFb0f862cC51AF45c2",
|
||||
"deployer": "0xc783df8a850f42e7F7e57013759C285caa701eB6"
|
||||
}
|
||||
},
|
||||
|
@ -301,5 +300,11 @@
|
|||
"address": "0x920d847fE49E54C19047ba8bc236C45A8068Bca7",
|
||||
"deployer": "0xc783df8a850f42e7F7e57013759C285caa701eB6"
|
||||
}
|
||||
},
|
||||
"SelfdestructTransferMock": {
|
||||
"buidlerevm": {
|
||||
"address": "0x920d847fE49E54C19047ba8bc236C45A8068Bca7",
|
||||
"deployer": "0xc783df8a850f42e7F7e57013759C285caa701eB6"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -41,6 +41,7 @@ import {
|
|||
MockVariableDebtTokenFactory,
|
||||
PriceOracleFactory,
|
||||
ReserveLogicFactory,
|
||||
SelfdestructTransferFactory,
|
||||
StableDebtTokenFactory,
|
||||
VariableDebtTokenFactory,
|
||||
WalletBalanceProviderFactory,
|
||||
|
@ -53,6 +54,7 @@ import {StableAndVariableTokensHelperFactory} from '../types/StableAndVariableTo
|
|||
import {MockStableDebtToken} from '../types/MockStableDebtToken';
|
||||
import {MockVariableDebtToken} from '../types/MockVariableDebtToken';
|
||||
import {MintableDelegationErc20} from '../types/MintableDelegationErc20';
|
||||
import {SelfdestructTransfer} from '../types/SelfdestructTransfer';
|
||||
|
||||
export const deployLendingPoolAddressesProvider = async (verify?: boolean) =>
|
||||
withSaveAndVerify(
|
||||
|
@ -472,3 +474,11 @@ export const deployMockAToken = async (
|
|||
args,
|
||||
verify
|
||||
);
|
||||
|
||||
export const deploySelfdestructTransferMock = async (verify?: boolean) =>
|
||||
withSaveAndVerify(
|
||||
await new SelfdestructTransferFactory(await getFirstSigner()).deploy(),
|
||||
eContractid.SelfdestructTransferMock,
|
||||
[],
|
||||
verify
|
||||
);
|
||||
|
|
|
@ -16,6 +16,7 @@ import {
|
|||
MockVariableDebtTokenFactory,
|
||||
PriceOracleFactory,
|
||||
ReserveLogicFactory,
|
||||
SelfdestructTransferFactory,
|
||||
StableAndVariableTokensHelperFactory,
|
||||
StableDebtTokenFactory,
|
||||
VariableDebtTokenFactory,
|
||||
|
@ -262,3 +263,11 @@ export const getMockStableDebtToken = async (address?: tEthereumAddress) =>
|
|||
(await getDb().get(`${eContractid.MockStableDebtToken}.${BRE.network.name}`).value()).address,
|
||||
await getFirstSigner()
|
||||
);
|
||||
|
||||
export const getSelfdestructTransferMock = async (address?: tEthereumAddress) =>
|
||||
await SelfdestructTransferFactory.connect(
|
||||
address ||
|
||||
(await getDb().get(`${eContractid.SelfdestructTransferMock}.${BRE.network.name}`).value())
|
||||
.address,
|
||||
await getFirstSigner()
|
||||
);
|
||||
|
|
|
@ -62,6 +62,7 @@ export enum eContractid {
|
|||
WETHGateway = 'WETHGateway',
|
||||
WETH = 'WETH',
|
||||
WETHMocked = 'WETHMocked',
|
||||
SelfdestructTransferMock = 'SelfdestructTransferMock',
|
||||
}
|
||||
|
||||
/*
|
||||
|
|
|
@ -1,11 +1,13 @@
|
|||
import {MAX_UINT_AMOUNT} from '../helpers/constants';
|
||||
import {convertToCurrencyDecimals} from '../helpers/contracts-helpers';
|
||||
import {makeSuite, TestEnv} from './helpers/make-suite';
|
||||
import {parseEther} from 'ethers/lib/utils';
|
||||
import {formatEther, parseEther, parseUnits} from 'ethers/lib/utils';
|
||||
import {BRE, waitForTx} from '../helpers/misc-utils';
|
||||
import {BigNumber} from 'ethers';
|
||||
import {getStableDebtToken, getVariableDebtToken} from '../helpers/contracts-getters';
|
||||
import {WethGateway} from '../types/WethGateway';
|
||||
import {use} from 'chai';
|
||||
import {deploySelfdestructTransferMock} from '../helpers/contracts-deployments';
|
||||
|
||||
const {expect} = require('chai');
|
||||
|
||||
|
@ -183,27 +185,123 @@ makeSuite('Use native ETH at LendingPool via WETHGateway', (testEnv: TestEnv) =>
|
|||
expect(debtBalanceAfterFullRepay).to.be.eq(zero);
|
||||
});
|
||||
|
||||
it('Should revert if receive or fallback functions receives Ether', async () => {
|
||||
it('Should revert if receiver function receives Ether if not WETH', async () => {
|
||||
const {users, wethGateway} = testEnv;
|
||||
const user = users[0];
|
||||
const amount = parseEther('1');
|
||||
|
||||
// Call receiver function (empty data + value)
|
||||
await expect(
|
||||
user.signer.sendTransaction({to: wethGateway.address, value: amount})
|
||||
user.signer.sendTransaction({
|
||||
to: wethGateway.address,
|
||||
value: amount,
|
||||
gasLimit: BRE.network.config.gas,
|
||||
})
|
||||
).to.be.revertedWith('Receive not allowed');
|
||||
});
|
||||
|
||||
it('Should revert if fallback functions is called with Ether', async () => {
|
||||
const {users, wethGateway} = testEnv;
|
||||
const user = users[0];
|
||||
const amount = parseEther('1');
|
||||
const fakeABI = ['function wantToCallFallback()'];
|
||||
const abiCoder = new BRE.ethers.utils.Interface(fakeABI);
|
||||
const fakeMethodEncoded = abiCoder.encodeFunctionData('wantToCallFallback', []);
|
||||
|
||||
// Call fallback function with value
|
||||
await expect(
|
||||
user.signer.sendTransaction({to: wethGateway.address, data: fakeMethodEncoded, value: amount})
|
||||
).to.be.reverted;
|
||||
user.signer.sendTransaction({
|
||||
to: wethGateway.address,
|
||||
data: fakeMethodEncoded,
|
||||
value: amount,
|
||||
gasLimit: BRE.network.config.gas,
|
||||
})
|
||||
).to.be.revertedWith('Fallback not allowed');
|
||||
});
|
||||
|
||||
it('Should revert if fallback functions is called', async () => {
|
||||
const {users, wethGateway} = testEnv;
|
||||
const user = users[0];
|
||||
|
||||
const fakeABI = ['function wantToCallFallback()'];
|
||||
const abiCoder = new BRE.ethers.utils.Interface(fakeABI);
|
||||
const fakeMethodEncoded = abiCoder.encodeFunctionData('wantToCallFallback', []);
|
||||
|
||||
// Call fallback function without value
|
||||
await expect(user.signer.sendTransaction({to: wethGateway.address, data: fakeMethodEncoded})).to
|
||||
.be.reverted;
|
||||
await expect(
|
||||
user.signer.sendTransaction({
|
||||
to: wethGateway.address,
|
||||
data: fakeMethodEncoded,
|
||||
gasLimit: BRE.network.config.gas,
|
||||
})
|
||||
).to.be.revertedWith('Fallback not allowed');
|
||||
});
|
||||
|
||||
it('Getters should retrieve correct state', async () => {
|
||||
const {aWETH, weth, pool, wethGateway} = testEnv;
|
||||
|
||||
const WETHAddress = await wethGateway.getWETHAddress();
|
||||
const aWETHAddress = await wethGateway.getAWETHAddress();
|
||||
const poolAddress = await wethGateway.getLendingPoolAddress();
|
||||
|
||||
expect(WETHAddress).to.be.equal(weth.address);
|
||||
expect(aWETHAddress).to.be.equal(aWETH.address);
|
||||
expect(poolAddress).to.be.equal(pool.address);
|
||||
});
|
||||
|
||||
it('Owner can do emergency token recovery', async () => {
|
||||
const {users, dai, wethGateway, deployer} = testEnv;
|
||||
const user = users[0];
|
||||
const amount = parseEther('1');
|
||||
|
||||
await dai.connect(user.signer).mint(amount);
|
||||
const daiBalanceAfterMint = await dai.balanceOf(user.address);
|
||||
|
||||
await dai.connect(user.signer).transfer(wethGateway.address, amount);
|
||||
const daiBalanceAfterBadTransfer = await dai.balanceOf(user.address);
|
||||
expect(daiBalanceAfterBadTransfer).to.be.eq(
|
||||
daiBalanceAfterMint.sub(amount),
|
||||
'User should have lost the funds here.'
|
||||
);
|
||||
|
||||
await wethGateway
|
||||
.connect(deployer.signer)
|
||||
.emergencyTokenTransfer(dai.address, user.address, amount);
|
||||
const daiBalanceAfterRecovery = await dai.balanceOf(user.address);
|
||||
|
||||
expect(daiBalanceAfterRecovery).to.be.eq(
|
||||
daiBalanceAfterMint,
|
||||
'User should recover the funds due emergency token transfer'
|
||||
);
|
||||
});
|
||||
|
||||
xit('Owner can do emergency native ETH recovery', async () => {
|
||||
const {users, wethGateway, deployer} = testEnv;
|
||||
const user = users[0];
|
||||
const amount = parseEther('1');
|
||||
|
||||
const selfdestructContract = await deploySelfdestructTransferMock();
|
||||
|
||||
const userBalancePriorCall = await user.signer.getBalance();
|
||||
const callTx = await selfdestructContract.destroyAndTransfer(wethGateway.address, {
|
||||
value: amount,
|
||||
});
|
||||
const {gasUsed} = await waitForTx(callTx);
|
||||
const gasFees = gasUsed.mul(callTx.gasPrice);
|
||||
const userBalanceAfterCall = await user.signer.getBalance();
|
||||
console.log(formatEther(userBalanceAfterCall));
|
||||
|
||||
expect(userBalanceAfterCall).to.be.eq(userBalancePriorCall.sub(amount).sub(gasFees), '');
|
||||
'User should have lost the funds';
|
||||
|
||||
await wethGateway.connect(deployer.signer).emergencyEtherTransfer(user.address, amount);
|
||||
|
||||
const userBalanceAfterRecovery = await user.signer.getBalance();
|
||||
|
||||
console.log(formatEther(userBalanceAfterCall));
|
||||
expect(userBalanceAfterRecovery).to.be.eq(
|
||||
userBalancePriorCall.sub(gasFees),
|
||||
'User should recover the funds due emergency eth transfer'
|
||||
);
|
||||
});
|
||||
});
|
||||
|
|
Loading…
Reference in New Issue
Block a user