- Install
mxpy
: installing-mxpy - Create wallet:
mxpy wallet new --format keystore-mnemonic --outfile test_wallet.json
- Faucet:
- Go to: devnet-wallet.multiversx
- Click on "Advanced Options/keystore"
- Go to
faucet
andrequest tokens
- The wallet should have 5 xEGLD, you can see it on the website itself or we can use the multiversx devnet explorer
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
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.
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.
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
}
}
- Compile with:
mxpy contract build
- 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
- 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
- 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
- Solve Third challenge: TODO
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...
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.
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);
}
- 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
- 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. - I get my
personal_key
using thepersonal_key
function ofCtfSolve
: 4591
mxpy contract \
query erd1qqqqqqqqqqqqqpgqxcdcrw9sdw4pnasluutz8umxxrgsgc8lkc3sr3md4r \
--function="personal_key" \
--arguments erd1luqg7unslc0ku7rvmhtfgsrqxvfwl7lv7s6ld680de5ftxfmkc3sze3pdh \
--proxy=https://devnet-gateway.multiversx.com
- 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
- 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!