-
Notifications
You must be signed in to change notification settings - Fork 424
⚡️ Optimize address to string #1341
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Conversation
Also not sure what's wrong with the diff ci check, showing error:
|
What made you think about this trick? |
I was benchmarking and trying to optimize address checksum computation for a project of mine. I was looking at a table of hex nibble to binary to ascii-binary table and noticed that you could relatively cheaply compute:
I noticed that a-f all have bit 4 set, but to exclude 8 & 9 you need to also check bit 2 & 3, giving you: For the ascii representation I noticed it's basically:
So yeah once you have the nibbles spaced out, padded and you have a bitmap of which byte will be a letter or not it's relatively easy to get the ascii representation. |
shl(64, and(value, 0xffffffffffffffff0000000000000000)), | ||
and(value, 0xffffffffffffffff) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
(1 << 64) - 1 = 0xffffffffffffffff
(1 << 64) - 1 << 64 = 0xffffffffffffffff0000000000000000
) | ||
w := | ||
and( | ||
0x00ff00ff00ff00ff00ff00ff00ff00ff00ff00ff00ff00ff00ff00ff00ff00ff, or(shl(8, w), w) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
0xff * (type(uint256).max / type(uint16).max) = 0x00ff00ff00ff00ff00ff00ff00ff00ff00ff00ff00ff00ff00ff00ff00ff00ff
) | ||
w := | ||
and( | ||
0x0000ffff0000ffff0000ffff0000ffff0000ffff0000ffff0000ffff0000ffff, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
0xffff * (type(uint256).max / type(uint32).max) = 0x0000ffff0000ffff0000ffff0000ffff0000ffff0000ffff0000ffff0000ffff
|
||
w := | ||
and( | ||
0x00000000ffffffff00000000ffffffff00000000ffffffff00000000ffffffff, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
0xffffffff * (type(uint256).max / type(uint64).max) = 0x00000000ffffffff00000000ffffffff00000000ffffffff00000000ffffffff
w := | ||
sub( | ||
xor( | ||
xor(w, 0x3030303030303030303030303030303030303030303030303030303030303030), |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
0x30 * (type(uint).max / 0xff) = 0x3030303030303030303030303030303030303030303030303030303030303030
Can cheaply create these masks JIT using the below formula: function repeat(uint256 x, uint strideBits) internal pure returns (uint256) {
return x * (type(uint256).max / ((1 << strideBits) - 1));
} |
Using this method to generate masks saved about 50 bytes of codesize, at the expense of additional gas likely not worth it but interesting way to generate repeat constants nonetheless. |
Description
Optimized address to hex string with some fancy bit operations. Has a fairly large gas trade-off ~360 bytes more by the looks of it, open questions:
Checklist
forge fmt
?forge test
?