Skip to main content

Challenge 8: Flash!

In this challenge, you will explore a decentralized exchange (DEX) with a critical flaw you can exploit to capture the flag. This exchange operates with two tokens—CTFA and CTFB—and features a vault that allows users to take flash loans. Your objective is to manipulate the token balances effectively to obtain the flag by using the vulnerabilities in the DEX's flash loan mechanism. To solve this challenge, you will have to have a deep understanding of programmable transaction blocks (PTBs) and how to build them using the TS SDK or the CLI.

Deployed Contract Addresses:

MintA<0xa94921fd4516ec6b8479e08f5f181edf5029d6df327631cf7073603cc24cb207::ctfa::CTFA>: 0xf74d7452f4ed9ba695074813d6482a856d9b6ba7ed676d1c6d811836a2d1ef79
MintB<0xa94921fd4516ec6b8479e08f5f181edf5029d6df327631cf7073603cc24cb207::ctfb::CTFB>: 0x467ff8123c47e11a0c467fe8a5708234f53ae8ccfa126ee9ab3dedd333ee9419
Package: 0xa94921fd4516ec6b8479e08f5f181edf5029d6df327631cf7073603cc24cb207
CoinMetadata<0xa94921fd4516ec6b8479e08f5f181edf5029d6df327631cf7073603cc24cb207::ctfa::CTFA>: 0x3e8467af29646d8cb7f7238fad52b58531a303011ea6cd2c10c2d8f590161cae
CoinMetadata<0xa94921fd4516ec6b8479e08f5f181edf5029d6df327631cf7073603cc24cb207::ctfb::CTFB>: 0x475f4444beba1baba0454b137951b3b7ebec2ff703f49b50233db8d3777df1d0

Contracts

firstcoin.move

module ctf::ctfa {
use iota::coin::{Self, Coin, TreasuryCap};

public struct CTFA has drop {}

public struct MintA<phantom CTFA> has key, store {
id: UID,
cap: TreasuryCap<CTFA>
}

fun init(witness: CTFA, ctx: &mut TxContext) {
// Get a treasury cap for the coin and give it to the transaction sender
let (treasury_cap, metadata) = coin::create_currency<CTFA>(witness, 1, b"CTFA", b"CTF A Coin", b"Token for the CTF", option::none(), ctx);
let mint = MintA<CTFA> {
id: object::new(ctx),
cap:treasury_cap
};
transfer::share_object(mint);
transfer::public_freeze_object(metadata);
}

public(package) fun mint_for_vault<CTFA>(mut mint: MintA<CTFA>, ctx: &mut TxContext): Coin<CTFA> {
let coina = coin::mint<CTFA>(&mut mint.cap, 100, ctx);
coin::mint_and_transfer(&mut mint.cap, 10, tx_context::sender(ctx), ctx);
let MintA<CTFA> {
id: ida,
cap: capa
} = mint;
object::delete(ida);
transfer::public_freeze_object(capa);
coina
}

}

secondcoin.move

module ctf::ctfb {
use iota::coin::{Self, Coin, TreasuryCap};

public struct CTFB has drop {}

public struct MintB<phantom CTFB> has key, store {
id: UID,
cap: TreasuryCap<CTFB>
}

fun init(witness: CTFB, ctx: &mut TxContext) {
// Get a treasury cap for the coin and give it to the transaction sender
let (treasury_cap, metadata) = coin::create_currency<CTFB>(witness, 1, b"CTFB", b"CTF B Coin", b"Token for the CTF", option::none(), ctx);
let mint = MintB<CTFB> {
id: object::new(ctx),
cap:treasury_cap
};
transfer::share_object(mint);
transfer::public_freeze_object(metadata);
}

public(package) fun mint_for_vault<CTFB>(mut mint: MintB<CTFB>, ctx: &mut TxContext): Coin<CTFB> {
let coinb = coin::mint<CTFB>(&mut mint.cap, 100, ctx);
coin::mint_and_transfer(&mut mint.cap, 10, tx_context::sender(ctx), ctx);
let MintB<CTFB> {
id: ida,
cap: capa
} = mint;
object::delete(ida);
transfer::public_freeze_object(capa);
coinb
}

}

vault.move

module ctf::vault{
use iota::coin::{Self, Coin};
use iota::balance::{Self, Balance};
use ctf::ctfa::{Self, MintA};
use ctf::ctfb::{Self, MintB};

public struct Vault<phantom A, phantom B> has key {
id: UID,
coin_a: Balance<A>,
coin_b: Balance<B>,
flashed: bool
}

public struct Flag has key, store {
id: UID,
user: address
}

public struct Receipt {
id: ID,
a_to_b: bool,
repay_amount: u64
}

public entry fun initialize<A,B>(capa: MintA<A>, capb: MintB<B>,ctx: &mut TxContext) {
let vault = Vault<A, B> {
id: object::new(ctx),
coin_a: coin::into_balance(ctfa::mint_for_vault(capa, ctx)),
coin_b: coin::into_balance(ctfb::mint_for_vault(capb, ctx)),
flashed: false
};
transfer::share_object(vault);
}

public fun flash<A,B>(vault: &mut Vault<A,B>, amount: u64, a_to_b: bool, ctx: &mut TxContext): (Coin<A>, Coin<B>, Receipt) {
assert!(!vault.flashed, 1);
let (coin_a, coin_b) = if (a_to_b) {
(coin::zero<A>(ctx), coin::from_balance(balance::split(&mut vault.coin_b, amount ), ctx))
}
else {
(coin::from_balance(balance::split(&mut vault.coin_a, amount ), ctx), coin::zero<B>(ctx))
};

let receipt = Receipt {
id: object::id(vault),
a_to_b,
repay_amount: amount
};
vault.flashed = true;

(coin_a, coin_b, receipt)

}

public fun repay_flash<A,B>(vault: &mut Vault<A,B>, coina: Coin<A>, coinb: Coin<B>, receipt: Receipt) {
let Receipt {
id: _,
a_to_b: a2b,
repay_amount: amount
} = receipt;
if (a2b) {
assert!(coin::value(&coinb) >= amount, 0);
} else {
assert!(coin::value(&coina) >= amount, 1);
};
balance::join(&mut vault.coin_a, coin::into_balance(coina));
balance::join(&mut vault.coin_b, coin::into_balance(coinb));
vault.flashed = false;
}

public fun swap_a_to_b<A,B>(vault: &mut Vault<A,B>, coina:Coin<A>, ctx: &mut TxContext): Coin<B> {
let amount_out_B = coin::value(&coina) * balance::value(&vault.coin_b) / balance::value(&vault.coin_a);
coin::put<A>(&mut vault.coin_a, coina);
coin::take(&mut vault.coin_b, amount_out_B, ctx)
}

public fun swap_b_to_a<A,B>(vault: &mut Vault<A,B>, coinb:Coin<B>, ctx: &mut TxContext): Coin<A> {
let amount_out_A = coin::value(&coinb) * balance::value(&vault.coin_a) / balance::value(&vault.coin_b);
coin::put<B>(&mut vault.coin_b, coinb);
coin::take(&mut vault.coin_a, amount_out_A, ctx)
}

#[allow(lint(self_transfer))]
public fun get_flag<A,B>(vault: &Vault<A,B>, ctx: &mut TxContext) {
assert!(
balance::value(&vault.coin_a) == 0 && balance::value(&vault.coin_b) == 0, 123
);
transfer::public_transfer(Flag {
id: object::new(ctx),
user: tx_context::sender(ctx)
}, tx_context::sender(ctx));
}
}

Good luck in capturing your eighth flag!

This challenge will test your understanding of the Object Model, the Coin Standard, and PTBs. You will need to use your knowledge of these concepts to exploit the DEX's flash loan mechanism and capture the flag.

tip

The DEX programmer pulled an all-nighter before writing the flash loan mechanism, making a critical mistake.