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<0x817e64f33b784f0c4a3d2cd18f62cc4409bc869efa715f29e93eff52343fb7b2::airdrop::AIRDROP>: 0x75b11dbbf346488ac50bbd6a29754ebb8ba3bd82439e066d6964f69562273135
Vault: 0xd23dfa1f51d97073ce8a70738a731a41034e3261831254ab462a100e5b3fe6b3
Package: 0x817e64f33b784f0c4a3d2cd18f62cc4409bc869efa715f29e93eff52343fb7b2
TreasuryCap<0x817e64f33b784f0c4a3d2cd18f62cc4409bc869efa715f29e93eff52343fb7b2::airdrop::AIRDROP>: 0xeaa5652a1a516192b84daadc02e122b2eebb45fd24660accd8801f8b45f6f0d8
Counter: 0x981ce022aa5193b3460073484dd2f479093d276fff1d3e742ea506d5f87bc0bb

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, 0, b"HORSE", b"Horse Tokens", b"To The Moon", option::none(), ctx);
let coins_minted = coin::mint<AIRDROP>(&mut coincap, 10, 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 = 10;
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.