Skip to main content

Challenge 4: Airdrop

Your mission is to participate in the "Horse Token" airdrop and capture the elusive flag. You'll need to mint some Horse Tokens and claim your share through the airdrop mechanism. But simply collecting tokens won’t be enough—securing the flag requires a bit more effort.

Use your command line expertise to interact with the system, track your progress, and perform key actions efficiently. Pay close attention to the airdrop logic, as understanding how the token distribution works will be crucial to successfully capturing the flag.

Deployed Contract Addresses:

CoinMetadata<0x7641ee891b657e349e7c34bcb0ad78bbd8ac9e41c7bcd627f822625e7b725f67::airdrop::AIRDROP>: 0xdcff4eb5d5aaf0fddb111c133eb060ad610b03458307588d408ff7f2734b3aff
Vault: 0x5c154e5199c1ebbbc839c7bb19d0f44f6e82b8aec0434e7c20c5bacaec246025
Package: 0x7641ee891b657e349e7c34bcb0ad78bbd8ac9e41c7bcd627f822625e7b725f67
TreasuryCap<0x7641ee891b657e349e7c34bcb0ad78bbd8ac9e41c7bcd627f822625e7b725f67::airdrop::AIRDROP>: 0x49e2b450e5bdea0442869338f19380972922d4bcf5db8ffa50b5c3d877f67c8e
Counter: 0x0bcae86c077ed58296e0e35e7459e3cd2722954850c8d2e6205fb415dc142bcf

Contracts

airdrop.move

module ctf::airdrop {
use iota::table::{Self, Table};
use iota::coin::{Self, Coin};
use iota::balance::{Self, Balance};
use ctf::counter::{Self, Counter};


public struct AirdroppedTo has store {
user: address,
}

public struct Vault has key {
id: UID,
balance: Balance<AIRDROP>,
userlist: Table<address, AirdroppedTo>,
}

public struct AIRDROP has drop {}

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

fun init (witness: AIRDROP, ctx: &mut TxContext) {
counter::create_counter(ctx);

let initializer = tx_context::sender(ctx);
let (mut coincap, coindata) = coin::create_currency(witness, 18, b"HORSE", b"Horse Tokens", b"To The Moon", option::none(), ctx);
let coins_minted = coin::mint<AIRDROP>(&mut coincap, 10000, ctx);
transfer::public_freeze_object(coindata);
transfer::public_transfer(coincap, initializer);
transfer::share_object(
Vault {
id: object::new(ctx),
balance: coin::into_balance<AIRDROP>(coins_minted),
userlist: table::new<address, AirdroppedTo>(ctx),
}
);
}

public entry fun airdrop(vault: &mut Vault, ctx: &mut TxContext) {
let sender = tx_context::sender(ctx);
assert!(!table::contains<address, AirdroppedTo>(&vault.userlist, sender) ,1);
let mut balance_drop = balance::split(&mut vault.balance, 1);
let coin_drop = coin::take(&mut balance_drop, 1, ctx);
transfer::public_transfer(coin_drop, sender);
balance::destroy_zero(balance_drop);
table::add<address, AirdroppedTo>(&mut vault.userlist, sender, AirdroppedTo {
user: sender,
});
}

public entry fun get_flag(user_counter: &mut Counter, coin_drop: &mut Coin<AIRDROP>, ctx: &mut TxContext) {

counter::increment(user_counter);
counter::is_within_limit(user_counter);

let expected_value = coin::value(coin_drop);
assert!(expected_value == 2, 2);

transfer::public_transfer(Flag {
id: object::new(ctx),
user: tx_context::sender(ctx)
}, tx_context::sender(ctx));
}
}

counter.move

module ctf::counter {

const MaxCounter: u64 = 1000;
const ENoAttemptLeft: u64 = 0;

/// A shared counter.
public struct Counter has key {
id: UID,
owner: address,
value: u64
}

/// Create and share a Counter object.
public(package) fun create_counter(ctx: &mut TxContext) {
transfer::share_object(Counter {
id: object::new(ctx),
owner: tx_context::sender(ctx),
value: 0
})
}

public fun owner(counter: &Counter): address {
counter.owner
}

public fun value(counter: &Counter): u64 {
counter.value
}

/// Increment a counter by 1.
public fun increment(counter: &mut Counter) {
counter.value = counter.value + 1;
}

/// Set value (only runnable by the Counter owner)
public entry fun set_value(counter: &mut Counter, value: u64, ctx: &TxContext) {
assert!(counter.owner == tx_context::sender(ctx), 0);
counter.value = value;
}

/// Check whether the counter has reached the limit.
public fun is_within_limit(counter: &mut Counter) {
assert!(counter.value <= MaxCounter, ENoAttemptLeft);
}
}

Challenges 1-3 have introduced you to the basics of interacting with Move contracts, the Object Model, and the Coin Standard. In this challenge, you'll need to apply your knowledge to a more complex scenario involving an airdrop mechanism. This challnege can be solved with IOTA PTBs, which will also help you in further challenges.

Good luck in capturing your fourth flag!

tip

Under Deployed Contract Addresses, you can find the addresses of the package as well as the Vault. Carefully check what the constraints are for the get_flag function to work, as it has some assertions that need to be met.