Skip to content

Instantly share code, notes, and snippets.

@rotcivegaf
Created September 19, 2024 23:34
Show Gist options
  • Save rotcivegaf/90096bb3d4da9cb355059e7bd388489d to your computer and use it in GitHub Desktop.
Save rotcivegaf/90096bb3d4da9cb355059e7bd388489d to your computer and use it in GitHub Desktop.

Solve Guide

  1. Install mxpy: installing-mxpy
  2. Create wallet: mxpy wallet new --format keystore-mnemonic --outfile test_wallet.json
  3. Faucet:
    • Go to: devnet-wallet.multiversx
    • Click on "Advanced Options/keystore"
    • Go to faucet and request tokens
    • The wallet should have 5 xEGLD, you can see it on the website itself or we can use the multiversx devnet explorer

Challenges

Bump

This one is quite simple, you just have to call the bump function.

To solve it, I make a bash script that calls this bump function over and over again.

Call the bump function with .json wallet:

mxpy --verbose \
    contract call erd1qqqqqqqqqqqqqpgq23j27f6w0r75hfyc5td753f9ahvfpp5x4wzq65czqw \
    --recall-nonce --keyfile test_wallet.json --gas-limit 6000000 \
    --proxy https://devnet-gateway.multiversx.com --function="bump" --send

To avoid write the wallet password every time, we can transform this wallet to a .pem file:

mxpy wallet convert \
    --infile test_wallet.json \
    --in-format keystore-mnemonic \
    --outfile test_wallet.pem \
    --out-format pem

And now call with:

mxpy --verbose \
    contract call erd1qqqqqqqqqqqqqpgq23j27f6w0r75hfyc5td753f9ahvfpp5x4wzq65czqw \
    --pem=../test_wallet.pem \
    --recall-nonce --gas-limit 6000000 \
    --proxy https://devnet-gateway.multiversx.com --function="bump" --send

Finally with bash:

#!/bin/bash

while :; do
    mxpy --verbose \
    contract call erd1qqqqqqqqqqqqqpgq23j27f6w0r75hfyc5td753f9ahvfpp5x4wzq65czqw \
    --pem=../test_wallet.pem \
    --recall-nonce --gas-limit 6000000 \
    --proxy https://devnet-gateway.multiversx.com --function="bump" --send
done

Coin Flip

In this challenge, we must flip a coin and as long as it comes out true we will increase our bump, but if at some point it comes out false our score will go to 0.

At the time I didn't know how to code and deploy contracts so what I did was to call the coinflip function:

mxpy --verbose \
    contract call erd1qqqqqqqqqqqqqpgq0jtfsyk7rfgu50v6wh5wwtk2mjgseca54wzqyntf2v \
    --pem=../test_wallet.pem \
    --recall-nonce --gas-limit 6000000 \
    --proxy https://devnet-gateway.multiversx.com --function="coinflip" --send --wait

And read the variable bumps:

mxpy contract \
    query erd1qqqqqqqqqqqqqpgq0jtfsyk7rfgu50v6wh5wwtk2mjgseca54wzqyntf2v \
    --function="bumps" \
    --arguments erd1luqg7unslc0ku7rvmhtfgsrqxvfwl7lv7s6ld680de5ftxfmkc3sze3pdh \
    --proxy=https://devnet-gateway.multiversx.com

I repeated this until my bumps increased once and stopped calling the function so that my score would not reset.

Gaspass

Ah... what a headache, the solution is simple, call the function gaspass with a certain amount of --gas-limit such that it satisfies gas_left == the_key, the problem is that I can't figure out what value gas_left has.

I tried estimating a certain maximum amount of gas and then with a bash script calling the gaspass function sending it 1 wei less gas per cycle, but I couldn't get it to work.

Try to solve in a pro mode(to much caffeine)

Although I won the CTF I was not happy, so I started to research rust and MultiversX.

After learning some rust and with the help of my teacher GPT, I was able to put together a contract that worked:

//File: ./src/ctf_bump_solve.rs

#[multiversx_sc::proxy]
pub trait CtfInterfaces {
    #[endpoint(bump)]
    fn bump(&self);

    #[endpoint(coinflip)]
    fn coinflip(&self) -> bool;

    #[endpoint(gaspass)]
    fn gaspass(&self) -> bool;
}
//File: ./src/ctf_solve.rs

#![no_std]

use multiversx_sc::imports::*;
multiversx_sc::derive_imports!();

mod ctf_interfaces;

const KEY_BASELINE: u64 = 3_000_000;

#[multiversx_sc::contract]
pub trait CtfSolve {
    #[init]
    fn init(&self) {
       // self.owners().insert(self.blockchain().get_caller());
    }

    #[endpoint]
    fn withdraw(&self, recipient: ManagedAddress, amount: BigUint) {
        // TODO: finish this
        // self.require_owner();
        // self.send().direct_egld(&recipient, &amount).transfer_execute();
    }

    #[proxy]
    fn get_ctfs_proxy(&self, to: ManagedAddress) -> ctf_interfaces::Proxy<Self::Api>;

    // First challenge   

    #[endpoint(callBump)]
    fn call_bump(&self, to: ManagedAddress) {
        self.get_ctfs_proxy(to).bump().async_call_and_exit();
    }

    // Second challenge

    #[endpoint(tryCoinFlip)]
    fn try_coin_flip(&self, to: ManagedAddress) {
        if self.flip_coin() {
            self.get_ctfs_proxy(to).coinflip().async_call_and_exit();
        }
    }

    fn flip_coin(&self) -> bool {
        let block_nonce = self.blockchain().get_block_nonce();
        block_nonce & 1 == block_nonce & 2
    }

    // Third challenge

    #[endpoint(callGaspass)]
    fn call_gaspass(&self, to: ManagedAddress, personal_key: u64, delta: u64) {
        // TODO: finish this
        //let b = self.get_ctfs_proxy(to).gaspass().with_gas_limit(2).async_call();
    }

    #[event("resp")]
    fn resp_event(&self, #[indexed] passed: bool);

    #[view]
    fn personal_key(&self, caller: &ManagedAddress) -> u64 {
        let bytes = caller.to_byte_array();
        bytes.iter().map(|&b| b as u64).sum()
    }

    #[view]
    fn get_delta(&self) -> u64 {
        let gas_left = self.blockchain().get_gas_left();
        gas_left
    }
}
  1. Compile with: mxpy contract build
  2. Deploy with:
mxpy --verbose \
    contract deploy --bytecode=./output/ctf-bump-solve.wasm \
    --recall-nonce --gas-limit=50000000 \
    --pem=../test_wallet.pem \
    --proxy https://devnet-gateway.multiversx.com \
    --send
  1. Solve First challenge:
mxpy --verbose \
    contract call erd1qqqqqqqqqqqqqpgqxcdcrw9sdw4pnasluutz8umxxrgsgc8lkc3sr3md4r \
    --recall-nonce \
    --gas-limit 10000000 \
    --pem=../test_wallet.pem \
    --function="callBump" \
    --arguments erd1qqqqqqqqqqqqqpgq23j27f6w0r75hfyc5td753f9ahvfpp5x4wzq65czqw \
    --proxy https://devnet-gateway.multiversx.com \
    --send
  1. Solve Second challenge:
mxpy --verbose \
    contract call erd1qqqqqqqqqqqqqpgqxcdcrw9sdw4pnasluutz8umxxrgsgc8lkc3sr3md4r \
    --recall-nonce \
    --gas-limit 10000000 \
    --pem=../test_wallet.pem \
    --function="tryCoinFlip" \
    --arguments erd1qqqqqqqqqqqqqpgq0jtfsyk7rfgu50v6wh5wwtk2mjgseca54wzqyntf2v \
    --proxy https://devnet-gateway.multiversx.com \
    --send
  1. Solve Third challenge: TODO

Bump

Since the MultiversX network does not support multiple calls to the same contract within a single transaction, the solution is the same but this time it increases the bumps of the deployed contract.

It took me a long time to come to this conclusion and I still can't make sense of it...

Coin Flip

Now yes, the contract before calling the coin flip is going to make sure I win, if I don't win it doesn't call it and ends the execution, simple enough.

Gaspass

Unfortunately I couldn't solve it, I couldn't get the value of gas_left, I tried several ways but there are stones in all of them.

But what I could do is to modify this contract so that it emits an event when calling the gaspass function:

@@ -16,6 +16,7 @@ pub trait CtfGaspass: bump_common::BumpCommon {
     #[endpoint]
     fn gaspass(&self) -> bool {
         let gas_left = self.blockchain().get_gas_left();
+        self.gasleft_event(gas_left);
         let caller = self.blockchain().get_caller();
         let the_key = KEY_BASELINE + self.personal_key(&caller);
         let passed = gas_left == the_key;
@@ -36,4 +37,7 @@ pub trait CtfGaspass: bump_common::BumpCommon {
 
     #[event("gaspass")]
     fn gaspass_event(&self, #[indexed] caller: &ManagedAddress, #[indexed] passed: bool);
+
+    #[event("gasleft")]
+    fn gasleft_event(&self, #[indexed] gas: u64);
 }

Now with this modified ctf I can get the gas_left and I finally managed to solve it

  1. Send a transaction to my modified CtfGaspass:
mxpy --verbose \
    contract call erd1qqqqqqqqqqqqqpgqa27qgm0xfuyhh07n8c9ceuxx3x6sz4zlkc3svfa4sr \
    --recall-nonce \
    --gas-limit 10000000 \
    --pem=../test_wallet.pem \
    --function="gaspass" \
    --proxy https://devnet-gateway.multiversx.com \
    --send
  1. Then I look at the logs of my tx, inside the gasleft one I get 8738930 and since I sent it with gas limit 10000000, the delta is 1261070.
  2. I get my personal_key using the personal_key function of CtfSolve: 4591
mxpy contract \
    query erd1qqqqqqqqqqqqqpgqxcdcrw9sdw4pnasluutz8umxxrgsgc8lkc3sr3md4r \
    --function="personal_key" \
    --arguments erd1luqg7unslc0ku7rvmhtfgsrqxvfwl7lv7s6ld680de5ftxfmkc3sze3pdh \
    --proxy=https://devnet-gateway.multiversx.com
  1. With all this I can calculate the gas to be sent: gas_limit = KEY_BASELINE + personal_key + gas_left = 3000000 + 4591 + 1261070 = 4265661
mxpy --verbose \
    contract call erd1qqqqqqqqqqqqqpgqa27qgm0xfuyhh07n8c9ceuxx3x6sz4zlkc3svfa4sr \
    --recall-nonce \
    --gas-limit 4265661 \
    --pem=../test_wallet.pem \
    --function="gaspass" \
    --proxy https://devnet-gateway.multiversx.com \
    --send
  1. Finally the bumps is increase:
mxpy contract \
    query erd1qqqqqqqqqqqqqpgqa27qgm0xfuyhh07n8c9ceuxx3x6sz4zlkc3svfa4sr \
    --function="bumps" \
    --arguments erd1luqg7unslc0ku7rvmhtfgsrqxvfwl7lv7s6ld680de5ftxfmkc3sze3pdh \
    --proxy=https://devnet-gateway.multiversx.com

Unfortunately I didn't manage to make a call specifying the gas limit within the CtfSolve contract, but something worked!

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