script (⏱,💰)

script (⏱,💰)

NG#8 - Contract Account

About the contract account, there are 3 subtasks that make up the bad account series. This article is the first task of the series, Stealing Souls.

After downloading the task, there are 2 contract accounts, tombkeeper_1.cairo and tombkeeper_2.cairo. When deployed, 100 $SOUL will be automatically minted to the contract. Each transaction will deduct 0.1 $SOUL, and the task requires stealing all $SOUL.

Contract 1#

In the validate_calls function, there is a comment stating that it will verify that the interacting contract address is not blacklisted (i.e., the Soul ERC20 contract address), meaning that direct Transfer Token transactions cannot be sent.

    fn validate_calls(mut calls: Array<Call>, blacklisted: ContractAddress) {
        match calls.pop_front() {
            Option::Some(call) => {
                // Trying to steal some soul? Nice try...
                assert(call.to != blacklisted, 'CANNOT_CALL_SOUL_TOKEN');
            },
            Option::None(_) => { return (); }
        }

        validate_calls(calls, blacklisted)
    }

However, the __execute__ function of this contract lacks validation for the caller, allowing it to bypass __validate__ and directly call __execute__.

In the snforge test, construct a call to transfer soul tokens without fees. After passing the local test, serialize the test calls and send them to __execute__ using sncast or a browser to complete the task.

Contract 2#

Contract 2 has a bug fix:

  • fn __execute__ adds assert(get_caller_address().is_zero(), 'INVALID_CALLER');, preventing direct function calls from other contract wallets.
  • If the recipient is the $SOUL contract, an extremely large value of IMPOSSIBLE_SOUL_FEE is required as gas. Since the type is u256, overflow is not possible.
if call.to == blacklisted {
    // Trying to steal some soul? Nice try...
    total_fee + IMPOSSIBLE_SOUL_FEE
} else {
    total_fee + SOUL_FEE
}

First, I tried passing 1000 calldata calls to __validate__, intending to increase the total_fee to 100 and then create a random call to execute and transfer the tokens.

After modifying the total_fee, it was found that the execute function initially set that it cannot be called by a contract, as it is to prevent attacks from other contracts. This blocked the method of directly calling using starkli and a browser.

After consulting with the mentor, it was suggested to sign with a local private key and use Tombkeeper as an account to call other contracts to complete the task. This requires the use of the SDK.

I redeployed the sand_devils contract from the Introduction to CTF and set the count to 1000, allowing any number to be subtracted from 1000 each time.

The SDK has versions in different languages, and I used starknet.js. The important steps are:

  1. Use getClassAt to get the ABI and create an instance of the sand_devil contract.
  2. Create an Account instance using the Tombkeeper2 account address and the private key used to deploy the contract.
  3. Use await devilContract.invoke("slay", [1],{ parseRequest: false} to send the transaction.

It was found that only 0.1 SOUL was deducted as a fee, and the previous 1000 calls to __validate__ did not increase the fee to 100 SOUL. It seems that there is a check at the lower level that if __execute__ is not called, a separate __validate__ will not cause a change in state.

So I tried to directly send a transaction with 1000 calls using starknet.js, which should complete both __validate__ and __execute__ at the same time. The account contract supports transaction bundling operations.

By sending a bundled transaction in starknet.js using contract.populate to convert the address, variables, and parameters into the built-in Call type, and using account.execute to send Call[], even with 1000 transactions, it can be confirmed quickly.

By checking the hash in the browser, it was successful in embedding 1000 slay calls and one transfer in a single transaction, depleting all the SOUL in the Tombkeeper account.

Summary#

ng8 end

Account abstraction is an important part of StarkNet, and the two tasks operate on custom contracts from both the contract side and the SDK side, allowing users to deepen their understanding of account abstraction. Additionally, there is an introduction to using the SDK, learning how to connect to contracts and send bundled transactions.

Loading...
Ownership of this post data is guaranteed by blockchain and smart contracts to the creator alone.