The function try_into(self: felt252)
does not convert correctly a 31-byte felt252 to ByteArray. This gives wrong result during depositing on L2. The PoC clearly shows the difference.
Function try_into(self: felt252)
which converts felt252 to ByteArray is not creating a standard ByteArray. As explained in the following link a ByteArray is actually a struct with the following members:
data: Array<felt252>
: Contains 31-byte chunks of the byte array. Each felt252
value has exactly 31 bytes. If the number of bytes in the byte array is less than 31, then this array is empty.
pending_word: felt252
: The bytes that remain after filling the data
array with full 31-byte chunks. The pending word consists of at most 30 bytes.
pending_word_len: usize
: The number of bytes in pending_word
.
https://docs.starknet.io/architecture-and-concepts/smart-contracts/serialization-of-cairo-types/#serialization_of_byte_arrays
As it clearly states pending_word
consists of at most 30 bytes. But, the way the function try_into()
is implemented, is not compatible with this structure.
For example, giving a 31-byte felt252 'qwertyuiopasdfghjklzxcvbnm12345'
as parameter to the function try_into(self: felt252)
returns the following data:
data.is_empty()
: true
pending_word
: 200477761143354947435595494819051448281747198402755701270381458527154811957
pending_word_len
: 31
While a ByteArray equal to "qwertyuiopasdfghjklzxcvbnm12345"
has the following specifications:
data.is_empty()
: false
pending_word
: 0
pending_word_len
: 0
The root cause of this issue is that when a 31-byte flet252 is forwarded as a parameter to this function, it should be considered exactly as a chunk of 31-byte, so there should be left nothing for the pending word. But, the function try_into(self: felt252)
always assumes that data is empty, and stores the forwarded parameter in the pending word.
The impact is as follows:
When depositing on L2, during calculation of erc721_metadata
, the collection contract is called to get the token uri.
https://github.com/Cyfrin/2024-07-ark-project/blob/main/apps/blockchain/starknet/src/bridge.cairo#L266
https://github.com/Cyfrin/2024-07-ark-project/blob/main/apps/blockchain/starknet/src/token/collection_manager.cairo#L50
The returned data from the collection is processed by invoking try_into(self: Span<felt252>)
.
If the length of self
is equal to 1
, the function try_into(self: felt252)
would be called.
Here is where the problem shows up. As explained before, if self
is 31-byte felt252, then the returned ByteArray will have incorrect format.
https://github.com/Cyfrin/2024-07-ark-project/blob/main/apps/blockchain/starknet/src/byte_array_extra.cairo#L4
In the following test, tow variables a
and b
are equal to 31-byte qwertyuiopasdfghjklzxcvbnm12345
, where a
is felt252, and b
is ByteArray. It is expected that when a
is converted from flet252 to ByteArray, the specification of resulted ByteArray is equal to b
. But this is not the case for 31-byte input.
The output log is:
Running the same test for 10-byte input shows that the outputs are equal:
The output log is:
Wrong conversion of felt252 to ByteArray during depositing on L2.
It is recommended that when the forwarded parameter has length 31-byte, it should set pending_word = ''
and pending_word_len = 0
, and the forwarded parameter (self) should be assigned to data
.
Great catch ! Unfortunatelly there is no impact... :/ I simulated the end of the calling functions and it seems that the "span" conversion handle that case. Here is my PoC: ``` fn input_31_byte() { let a: felt252 = 'qwertyuiopasdfghjklzxcvbnm12345'; let mut out_uris = array![]; let convert: Option<ByteArray> = a.try_into(); match convert { Option::Some(e) => { out_uris.append(e); }, Option::None => panic!("Should not be None") } let span = out_uris.span(); println!("Real data used in the calling function {:?}", span[0]); let b: ByteArray = "qwertyuiopasdfghjklzxcvbnm12345"; let mut out_uris_2 = array![]; out_uris_2.append(b); println!("Real data used in the calling function {:?}", out_uris_2.span()[0]); } ```
The contest is live. Earn rewards by submitting a finding.
This is your time to appeal against judgements on your submissions.
Appeals are being carefully reviewed by our judges.