Representing Cairo Addresses in Solidity
by Zendy Boa —unaffiliated — https://twitter.com/zendyboa
Not everyone natively computes bits,bytes,hex,addresses and EVM opcodes, and it can get confusing sometimes.
The bottom line? Use uint256
to represent your Cairo addresses in solidity
Why?
Hex, Decimals and Strings
An Ethereum address is a 42-character hexadecimal address derived from the last 20 bytes of the public key controlling the account with 0x appended in front. e.g., 0x71C7656EC7ab88b098defB751B7401B5f6d8976F
.
If we ignore the 0x
at the start, the minimal representation would take 160 bits. (20 bytes = 160 bits)
An example of a Cairo address is:0xc4409294b0cd7c0972c687979e329942b36c018459569440daf6619a7c9595
This is a 64 character hexadecimal address. This needs 64 bytes to store as a UTF-8 string.
The maximum Cairo address would look as such 0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
(64 fs).
As part of a larger personal application, I tested using a string to represent the Cairo address, removing the 0x
as that is extra bits we don't need to waste in storage.
When using string
to represent the Cairo address, the application costs 172298 gas when saving the following to storage as a string. (This gas cost includes other parts of my solidity and is for reference when compared with the numbers subsequently presented)."ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff"
(64 fs)
For the same address above, when converted to decimal using some simple python int("0xff....f",16)
, we get
115792089237316195423570985008687907853269984665640564039457584007913129639935
This is also the max number that can fit in a Uint256. Simply calculated by doing 2²⁵⁶-1
We can convert the hex to a Uint256
, where it becomes 115792089237316195423570985008687907853269984665640564039457584007913129639935. The gas cost for saving this to storage in my application as a solidity uint256 is 125971, which is significantly cheaper when relatively compared to 172298.
EVM Storage Gas Costs
Solidity storage slots are chunked into 256 bits (32 bytes). Each 32 byte chunk costs 20,000 gas. This means, to store Cairo address as a UTF-8 string, it would cost 40,000 gas, as it’s made up of 64 characters, which is 64 bytes in UTF-8.
We can see this to be true when tested. For example, I stored a 64 character string composed of only fs (i.e. “ffffff…..ffffff”) and the gas cost is 172298. When changed to a 32 character string composed of only f , the gas cost is 149604. (172298 -149604= 22694). This difference of 22694 gas is approximately as expected.
Now let’s try storing a 65 character string. Our cost jumps from 172298 -> 194620 . A jump of 22322 with the addition of only a single character!
But what about a 63 character string? Our cost goes from 172298 -> 172286. Only a difference of 3 gas? The reason for this is because when we go from a 64 character string to a 65 character string, the EVM has to allocate the entire next 32 bytes for the single character. That immediately incurs the extra 20,000 gas cost. When going from storing a 64 character string to a 63 character string, the difference is minimal as the 20,000 gas cost is still incurred.
Now consider storing it as a uint256, which is 32 bytes. 32 bytes is only one Solidity storage slot and hence the 20,000 gas cost is only incurred once. For a 64 character string, this gas cost is incurred twice. Therefore, use uint256 to store your Cairo addresses in Solidity :)
Follow me on twitter for more
https://twitter.com/zendyboa
Ref:
(https://docs.soliditylang.org/en/v0.8.11/internals/layout_in_storage.html)
https://medium.com/bandprotocol/solidity-102-1-keeping-gas-cost-under-control-ae95b835807f
https://docs.google.com/spreadsheets/d/1n6mRqkBz3iWcOlRem_mO09GtSKEKrAsfO7Frgx18pNU/edit?usp=sharing