Skip to content
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

feat: generate inversions of abi.encodePacked() for StakingMessages.unpack*() #482

Draft
wants to merge 30 commits into
base: main
Choose a base branch
from

Conversation

ARR4N
Copy link

@ARR4N ARR4N commented Aug 6, 2024

Why this should be merged

Save significant gas + improve readability so it's easier to find bugs in reviews.

Gas before

Ran 5 tests for contracts/staking/tests/ValidatorMessagesTests.t.sol:ValidatorMessagesTest
[PASS] testRegisterSubnetValidatorMessage() (gas: 127215)
[PASS] testSetSubnetValidatorWeightMessage() (gas: 44491)
[PASS] testSubnetValidatorRegistrationMessage() (gas: 31525)
[PASS] testSubnetValidatorWeightUpdateMessag() (gas: 44578)
[PASS] testValidationUptimeMessage() (gas: 37860)
Suite result: ok. 5 passed; 0 failed; 0 skipped; finished in 563.33µs (757.46µs CPU time)

Gas after

Ran 5 tests for contracts/staking/tests/ValidatorMessagesTests.t.sol:ValidatorMessagesTest
[PASS] testRegisterSubnetValidatorMessage() (gas: 10151)
[PASS] testSetSubnetValidatorWeightMessage() (gas: 4728)
[PASS] testSubnetValidatorRegistrationMessage() (gas: 3782)
[PASS] testSubnetValidatorWeightUpdateMessag() (gas: 4815)
[PASS] testValidationUptimeMessage() (gas: 4133)
Suite result: ok. 5 passed; 0 failed; 0 skipped; finished in 372.38µs (320.00µs CPU time)

How this works

Packing was identical to abi.encodePacked() so it's replaced as such. There is no abi.decodePacked() so there's a code generator to load words directly from memory. Solidity automatically performs the necessary masking for bytes<N> types so only the mload() needs to be explicit.

Warning

There's an option to use mcopy() but we don't have the opcode yet so the generated code defaults to using the original memory space when returning dynamically sized arrays. This is a destructive operation that corrupts the input. If this isn't acceptable I can write a manual mcopy() instead.

How this was tested

The code generator also generates round-trip fuzz tests. To demonstrate equivalence with the previous approach, I originally only changed the pack* methods to allow the existing tests to show that original unpacking worked as expected; only then did I change the unpack* methods to use the generated code.

How is this documented

Comments in line with implementation, if and where required.

Note

Please excuse the force push and chaotic commit history. I honestly have no idea what went wrong when rebasing (then merging) onto the changed upstream branch. This SHOULD be squash merged!

@ARR4N ARR4N requested a review from geoff-vball August 6, 2024 19:33
@ARR4N ARR4N mentioned this pull request Aug 6, 2024
@cam-schultz
Copy link
Contributor

Thanks for putting this together! We'll definitely want to integrate these optimizations once the Warp message definitions in question are stable.

@iansuvak
Copy link
Contributor

iansuvak commented Aug 7, 2024

Yeah big fan of this method! Thanks!

@ARR4N ARR4N changed the base branch from staking-contract to staking-contracts August 9, 2024 13:34
github-advanced-security[bot]

This comment was marked as resolved.

@ARR4N ARR4N changed the base branch from staking-contracts to staking-contract August 9, 2024 13:35
contracts/staking/Unpack.sol Fixed Show fixed Hide fixed
contracts/staking/Unpack.sol Fixed Show fixed Hide fixed
contracts/staking/Unpack.sol Fixed Show fixed Hide fixed
contracts/staking/Unpack.sol Fixed Show fixed Hide fixed
contracts/staking/Unpack.sol Fixed Show fixed Hide fixed
contracts/staking/Unpack.sol Fixed Show fixed Hide fixed
contracts/staking/Unpack.sol Fixed Show fixed Hide fixed
contracts/staking/Unpack.sol Fixed Show fixed Hide fixed
contracts/staking/Unpack.sol Fixed Show fixed Hide fixed
contracts/staking/Unpack.sol Fixed Show fixed Hide fixed
@ARR4N ARR4N changed the title perf: SetSubnetValidatorWeightMessage (un)packing gas reductions feat: generate inversions of abi.encodePacked() for StakingMessages.unpack*() Aug 9, 2024

library Unpack {
/// @dev Thrown if the input buffer to an unpack function is of the wrong length.
error IncorrectInputLength(uint256 expected, uint256 actual);

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

how strongly do you feel about custom errors? We previously reverted from custom errors back to require statements, because we felt some tooling weren't updated to custom errors and lacked in debugging

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Which tooling lacks support?

I think they're superior to string errors:

  1. Don't need to store the entire string in the contract code
  2. Can be parameterised to aid debugging and allow precise path detection in tests
  3. Avoid change-detector tests

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

we reverted to require statements over a year ago, so probably need to revisit this. I think back then some combination of forge errors, block explorers, and e2e testing just showed the hex error signature. This still seems like the case for our e2e test code, but there's likely a workaround we could do.

I am also leaning towards custom errors now, but think we should tackle this in a separate issue, and still conform to require to match the rest of the repo.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's not a hill I'll die on, but something I feel I should push back on and ask "are you sure?" before I implement a toggle between the two.

Independent benchmarking of both deployment and usage gas.

Where is the problematic e2e test code?

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

As it currently stands, these reverts can never occur because I left the original length checks in place for the time being.

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This folder holds the e2e tests I'm referring to https://github.com/ava-labs/teleporter/tree/main/tests/flows. This is also not a hill I'll die on, since I'm also leaning towards custom errors at this point, but wasn't sure to defer to a separate issue and changing all the require statements in the repo together so we can keep them consistent for now.

Copy link

@minghinmatthewlam minghinmatthewlam left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM once licensing is cleared up

@geoff-vball
Copy link
Contributor

Overall this looks so very useful! I think the current generated code was manually run on the current contracts? We should set up some sort of shell script (or add to the existing abi_bindings.sh) to make sure these are regenerated when files are updated.

@michaelkaplan13 michaelkaplan13 deleted the branch main September 20, 2024 21:40
@cam-schultz cam-schultz reopened this Sep 24, 2024
@cam-schultz cam-schultz changed the base branch from staking-contract to validator-manager September 24, 2024 14:33
@iansuvak iansuvak mentioned this pull request Oct 3, 2024
Base automatically changed from validator-manager to main October 29, 2024 18:29
@geoff-vball geoff-vball marked this pull request as draft December 2, 2024 14:02
@geoff-vball
Copy link
Contributor

Just converted this to draft as it's been stale for a while and will need some reworking before it's mergeable again.

@ARR4N
Copy link
Author

ARR4N commented Dec 3, 2024

@geoff-vball would you like me to revive it so it can be merged? At the time there wasn't consensus about its inclusion so I didn't keep up with the base branch, but if the team definitely wants it then I can update it.

@geoff-vball
Copy link
Contributor

@ARR4N I think @iansuvak was the last one that had a good idea of what we wanted to do here, I'll let him chime in :)

@iansuvak
Copy link
Contributor

iansuvak commented Dec 4, 2024

@ARR4N the issue that cropped up here was that now we have multiple dynamic fields in the message specs. All fields are prefixed with their length except for BLS key which is static at 48 bytes but appears dynamic in the definition because it's greater than 32.

I started hacking at an implementation to support this but ended up deprioritizing. If you have an idea for a clean approach implementation wise I'd love to hear/ see it but it's not high priority

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
Status: In Progress 🏗
Development

Successfully merging this pull request may close these issues.

6 participants