script (⏱,💰)

script (⏱,💰)

NG#7 - calldata

The content of this task is about the calldata of the StarkNet contract. Calldata refers to the data passed to a function during a function call. In StarkNet, the data passed can be of various types, but it will ultimately be converted into a snapshot consisting of multiple felt252s. For more details, see the call_contract_syscall in the StarkNet contract.

There are two contracts, the cast function in the PortalSpell contract accepts an Array<PortalData>, and the cast function in the DrunkenMage contract accepts two Array<felt252>. You need to pass calldata consisting of two arrays, and the first two PortalData in the Array<PortalData> should be the same (as verified in the contract code).

struct PortalData {
  location: felt252,
  details: Array<felt252>
}

// Expected function signature
cast(portal_data: Array<PortalData>)

// Mage's function signature
cast(origin: Array<felt252>, destination: Array<felt252>)

The above cast will be called across contracts through the dispatcher, so there is no need to verify if the parameters are the same.

Approach#

First, you need to understand how the StarkNet contract parses calldata, which is explained clearly in the task walkthrough.

The goal is to encode an Array<PortalData> value, which is [ 0: { location: 'TAVERN', details: [ 'OPEN', 'PORTAL' ] }, 1: { location: 'HOME', details: [ 'CLOSE', 'PORTAL' ] } ].

Since Serde is implemented, the struct can be serialized. In the test, when printed, a single struct is encoded as follows:

[DEBUG] TAVERN          (raw: 0x54415645524e
[DEBUG]                 (raw: 0x2
[DEBUG] OPEN            (raw: 0x4f50454e
[DEBUG] PORTAL          (raw: 0x504f5254414c

When two PortalData are combined and a 2 is added at the beginning, it becomes the final encoding.

The drunk_spell.cast(origin, destination) is passed separately, and we must find the values for origin and destination so that when it is interpreted as Array<PortalData>, it reflects the desired calldata.

The only thing that needs to be verified is the first two Portals, which are ['TAVERN', 2, 'OPEN', 'PORTAL', 'HOME', 2, 'CLOSE', 'PORTAL'].

If an additional PortalData {location: 'VOID', details: ['ANY']} is added, the encoding will become [3, 'TAVERN', 2, 'OPEN', 'PORTAL', 'HOME', 2, 'CLOSE', 'PORTAL','VOID', 1 , 'ANY'], which can also be verified.

However, if it is split into two separate arrays to be passed, it will be [3, 'TAVERN', 2, 'OPEN', 'PORTAL'] and ['PORTAL', 'HOME', 2, 'CLOSE', 'PORTAL','VOID', 1 , 'ANY'], and converting 'PORTAL' to a felt decimal number will be very large. To satisfy the length requirement of details or add more portals, it will consume a lot of gas and will result in an error when running the test.

If the number of Portals is increased until the length of the second parameter array is a very small number, it will not consume much gas. Now you should know how to write it.

After passing the test, I am ready to deploy the contract to crack it. First, using starkli invoke, I found that arrays cannot be passed. snforge invoke --calldata can pass calldata, but I don't know how to pass additional parameters such as the account. Finally, I found that sending the two calldata directly through the Voyager browser is the simplest method.

Conclusion#

The composition rule of StarkNet's calldata is very simple. The challenging part of this question is adding more Portals to form a specific calldata. It is an interesting CTF question.

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