-
Notifications
You must be signed in to change notification settings - Fork 154
feat(balance_overrides): use debug_traceCall to find slots #3937
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
Conversation
Implement a better approach for detecting. Rather than scanning solidity storage slots for a mapping, a new detector strategy `DirectSlot` is added to detect the storage slot through a single `debug_traceCall` on the ERC20 `balanceOf` function, and reading the resolved `SLOAD` slots. This approach is very similar to [one followed by Foundry for their `deal` cheatcode](https://github.com/foundry-rs/foundry/blob/9b13b811849e73654fae046986b8730df8a0d64d/crates/anvil/src/eth/api.rs#L2259). This method has a few advantages: * Assuming that there is only one `SLOAD` in a `balanceOf` call (most cases), only a single `debug_traceCall` is required for a single token/address pair. In the case of more than one SLOAD, an additional call is required for each slot to validate the correct storage, which is still inexpensive. * Basically any token that stores a user's balance in a single `uint256` will be supported by this method. Since slot detection using this method is only able to find the storage slot for a specific account in question, the interface needed to be updated in a couple places to reflect this. This also means that there is a potential performance disadvantage with this method since balance overrides for different addresses cannot be detected due to not computing via Solidity mapping. This could be mitigated by only supporting balance overrides on the spardose contract (followed by a `transfer` call) to the needed account as required. This method requires a node that supports the `debug_traceCall` API. I believe this is the case for our infra, but please double check 🙏. For now I left the original `SolidityMapping` detector strategy as a backup in case `DirectSlot` fails. However, there should theoretically be no cases where `SolidityMapping` would detect when `DirectSlot` does not, so it would be worth removing and relying solely on `DirectSlot`. I added a E2E test and contract to validate that the storage slot detection is working as expected.
crates/shared/src/price_estimation/trade_verifier/balance_overrides/detector.rs
Outdated
Show resolved
Hide resolved
| /// debug_traceCall. This is similar to Foundry's `deal` approach where | ||
| /// we trace a balanceOf call to find which storage slot is accessed for | ||
| /// a given account. | ||
| DirectSlot { slot: H256 }, |
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.
I think this needs to also store a target_contract. IIRC tokens like EURe on gnosis chain store the balances in a completely different contract.
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.
yea i probably should have mentioned this. for right now in order to keep the implementation simpler I decided not to update storage slots in other contracts since its somewhat rare and I thought it would amke things a lot more complicated with the trace early on. But its not like it would be hard to add looking at it now.
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.
so I added target contract as you suggest, but in order to properly encode the overrides object in the case that its DirectSlot, both SolidityMapping and SoladyMapping also need to have the target_contract as well. This turned out to be a somewhat big refactor after all. LMK if we should keep it or revert. a5f70bc
crates/shared/src/price_estimation/trade_verifier/balance_overrides/detector.rs
Outdated
Show resolved
Hide resolved
crates/shared/src/price_estimation/trade_verifier/balance_overrides/detector.rs
Outdated
Show resolved
Hide resolved
crates/shared/src/price_estimation/trade_verifier/balance_overrides/detector.rs
Outdated
Show resolved
Hide resolved
crates/shared/src/price_estimation/trade_verifier/balance_overrides/detector.rs
Outdated
Show resolved
Hide resolved
crates/shared/src/price_estimation/trade_verifier/balance_overrides/detector.rs
Outdated
Show resolved
Hide resolved
…wprotocol/services into feat/tracing-balance-override
for now not intermingling this with the SolidityMapper strategy because we are likely going to refactor this out in the future according to discussions.
…wprotocol/services into feat/tracing-balance-override
crates/shared/src/price_estimation/trade_verifier/balance_overrides/detector.rs
Outdated
Show resolved
Hide resolved
…rides/detector.rs Co-authored-by: Jan [Yann] <4518474+fafk@users.noreply.github.com>
| import "./NonStandardERC20Balances.sol"; | ||
|
|
||
| contract RemoteERC20Balances is NonStandardERC20Balances { | ||
| bool internal immutable balanceFromHere; |
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.
Nit
| bool internal immutable balanceFromHere; | |
| bool internal immutable isBalanceFromHere; |
And possible what does it mean to be "here", the users[user].balance seems to show up from nowhere and it's kind of confusing
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.
the users[user].balance seems to show up from nowhere and it's kind of confusing
yea, sorry about that. as you probably saw it inherits from the original ERC20 contract. The alternative was code duplication, or making a base contract that we dont already have it appears
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.
renamed to isLocalBalance for now. I'm guessing ew are ok on the users[user].balance showing up out of nowhere?
crates/shared/src/price_estimation/trade_verifier/balance_overrides/detector.rs
Outdated
Show resolved
Hide resolved
crates/shared/src/price_estimation/trade_verifier/balance_overrides/detector.rs
Outdated
Show resolved
Hide resolved
crates/shared/src/price_estimation/trade_verifier/balance_overrides/detector.rs
Outdated
Show resolved
Hide resolved
crates/shared/src/price_estimation/trade_verifier/balance_overrides/detector.rs
Outdated
Show resolved
Hide resolved
crates/shared/src/price_estimation/trade_verifier/balance_overrides/mod.rs
Outdated
Show resolved
Hide resolved
…rides/detector.rs Co-authored-by: José Duarte <duarte.gmj@gmail.com>
…rides/detector.rs Co-authored-by: José Duarte <duarte.gmj@gmail.com>
…wprotocol/services into feat/tracing-balance-override
53c8378 to
0a0408b
Compare
crates/shared/src/price_estimation/trade_verifier/balance_overrides/mod.rs
Outdated
Show resolved
Hide resolved
crates/shared/src/price_estimation/trade_verifier/balance_overrides/mod.rs
Outdated
Show resolved
Hide resolved
squadgazzz
left a comment
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.
Great job 🚀
Left a few nit comments.
Apologies for the delayed reviewing process and leaving so many comments.
…rces (#3991) # Description <!--- Describe your changes to provide context for reviewers, including why it is needed --> On my other PR #3937 , I was having difficulty generating the solidity JSONs, and when I did generate them, the format was not correct. I realized that part of the problem is that the CI should be natively checking for this, but it currently does not, so I added a job. Additionally, my computer is ARM64, but the ethereum solidity image is only compatible with `x86_64`, so I had to add a flag to fix this. Finally, the format followed by the other files in the artifacts JSON directory uses a pretty-printed JSON format, but the existing Makefile uses compact format with `jq -c`. So I removed the `-c` flag to ensure this consistent format. It turns out taht, likely due to a newer solidity version, many of the artifacts have changed slightly since they were originally built. So that is included as part of this PR. One other issue that can occur is if the artifact file already exists on your computer, it may fail to write the new version since `make` only builds files that don't exist (or something like that). To resolve this, the `-B` option is used to force rebuilding of all targets. # Changes <!-- List of detailed changes (how the change is accomplished) --> - [x] modify the Makefile as described above - [x] add CI job - [x] Ensure the CI job can run properly on ARM64 - [x] run `cd crates/contracts/solidity && make -B artifacts` ## How to test Check the new CI job `solidity-artifacts` Run `make -B artifacts` from `crates/contracts/solidity` to see how it behaves on your own machine. <!-- ## Related Issues Fixes # -->
…rides/mod.rs Co-authored-by: ilya <ilya@cow.fi>
…rides/mod.rs Co-authored-by: ilya <ilya@cow.fi>
No problem at all! This PR rightly deserves scrutiny considering the breadth of impact it can have. Reviews have been great all around 👍 |
Description
Implement a more reliable, and likely (usually) faster approach for detecting solidity balance override storage addresses.
Rather than bulk storage override/scanning many storage slots for a match, a single
debug_traceCallis made on the ERC20balanceOffunction, and reading the resolvedSLOADslots. If only oneSLOADis detected, then we are done. If more than one slot is found, each slot can be tested througheth_callwith a single slot override. We test from last accessed to first accessed, as the last accessed storage slot is theoretically the most likely to be the balance slot (as that would be the return value). Since storage slots could be ccessed from anywhere (ex. not just in a solidity mapping), a newDirectSlotstrategy has been added.Probably the only major drawback of the
DirectSlotstrategy is that it can only be used for a single account. If a separate account balance from the check needs to be overridden, either the slot has to be recalculated, or the balance has to be sent from a separate address after allocation (ex. Spardose). Due to this limitation, any detected slots are additionally tested to match up withSolidityMapping, and theSolidityMappingstrategy is returned instead ofDirectSlotif able.This approach is very similar to one followed by Foundry for their
dealcheatcode. This is replacing the existing pattern of overriding 50 storage slots and seeing which test balance gets applied.This method has advantages:
SLOADin abalanceOfcall (most cases), only a singledebug_traceCallis required for a single token/address pair. In the case of more than one SLOAD, an additional call is required for each slot to validate the correct storage, which is still inexpensive.uint256will be supported by this method.Potential drawbacks:
debug_traceCallisnt supported everywhere. This could be overcome by usingeth_createAccessListas a fallback, but the createAccessList API does not return accessed storage slots in order, so the check could be considerably more expensive if the balance function accesses many slots.DirectSlotstrategy can only update the balance of a single token holder whereasSolidityMappingcan update any holder.Other considerations:
Since slot detection using this method is only able to find the storage slot for a specific account in question, the interface needed to be updated in a couple places to reflect this. This also means that there is a potential performance disadvantage with this method since balance overrides for different addresses cannot be detected due to not computing via Solidity mapping. This could be mitigated by only supporting balance overrides on the spardose contract (followed by a
transfercall) to the needed account as required.This method requires a node that supports the
debug_traceCallAPI. I believe this is the case for our infra, but please double check 🙏.When possible this storage detection method will use
SolidityMappingto assist in finding cases where it is possible to use. So there should be no need to do heuristic scanning anymore, so I have removed this code segment (and any supporting code).I added a E2E test and contract to validate that the storage slot detection is working as expected.
Changes
DirectSlotdetectorHow to test
Run
just test-e2e local_node_trace_based_balance_detection