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

slither-read-storage: Handle unstructured storage #1752

Closed

Conversation

webthethird
Copy link
Contributor

This approach scans for constant bytes32 variables and uses a couple heuristics to determine (a) that it is a storage slot, and (b) the type of value stored in the slot.

For example, running the following command yields the table below:

slither-read-storage 0x8315177aB297bA92A06054cE80a67Ed4DBd7ed3a --contract TransparentUpgradeableProxy --value --table --rpc-url <MY_RPC_URL>
+----------------------+-------------+-------------------------------------------------------------------------------+------+--------+--------------------------------------------+
|         name         | type_string |                                      slot                                     | size | offset |                   value                    |
+----------------------+-------------+-------------------------------------------------------------------------------+------+--------+--------------------------------------------+
|     _BEACON_SLOT     |   address   | 74152234768234802001998023604048924213078445070507226371336425913862612794704 | 160  |   0    | 0x0000000000000000000000000000000000000000 |
|     _ADMIN_SLOT      |   address   | 81955473079516046949633743016697847541294818689821282749996681496272635257091 | 160  |   0    | 0x554723262467F125Ac9e1cDFa9Ce15cc53822dbD |
| _IMPLEMENTATION_SLOT |   address   | 24440054405305269366569402256811496959409073762505157381672968839269610695612 | 160  |   0    | 0x1066CEcC8880948FE55e427E94F1FF221d626591 |
|    _ROLLBACK_SLOT    |     bool    | 33048860383849004559742813297059419343339852917517107368639918720169455489347 |  1   |   0    |                   False                    |
+----------------------+-------------+-------------------------------------------------------------------------------+------+--------+--------------------------------------------+

@montyly
Copy link
Member

montyly commented Mar 14, 2023

Can we add a test with an address that uses this format?

@webthethird
Copy link
Contributor Author

Can we add a test with an address that uses this format?

The address I used is 0x8315177aB297bA92A06054cE80a67Ed4DBd7ed3a, which is the Arbitrum Bridge's Transparent Upgradeable Proxy.

Where would you recommend adding the test? /scripts/ci_test_upgradeability.sh currently just tests slither-check-upgradeability, and this isn't really part of that tool. But at a glance I don't see a test script for slither-read-storage, though I do see the /tests/storage-layout directory.

@0xalpharush
Copy link
Contributor

@webthethird You would deploy a contract to ganache and run slither-read-storage against the local network as is done here https://github.com/crytic/slither/blob/master/tests/test_read_storage.py

@webthethird
Copy link
Contributor Author

@0xalpharush Would it make sense to add it to the existing test? Since this PR applies to slither-read-storage itself, just adding to what would print if the contract used any constant storage slots.

@webthethird
Copy link
Contributor Author

@montyly @0xalpharush I added a new test, tests/test_unstructured_storage.py.

I believe the failing CI test is related to #1710

ret = func.return_type[0]
size, _ = ret.storage_size
return str(ret), size * 8
for node in func.all_nodes():
Copy link
Contributor

Choose a reason for hiding this comment

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

You may be able to use https://crytic.github.io/slither/slither/core/declarations/contract.html#Contract.get_functions_reading_from_variable to make this more general and not have to rely on matching these specific function names. If it only returns one function and that function has one return value that's less than or equal to 32 bytes, assume the return type is that.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

If it only returns one function and that function has one return value that's less than or equal to 32 bytes, assume the return type is that.

For the most part I am doing this on lines 275-278. Though I could streamline it by using Contract.get_functions_reading_from_variable.

to make this more general and not have to rely on matching these specific function names.

The reason I resorted to checking for the function names from the StorageSlot library is because there may be some slot (i.e., _ROLLBACK_SLOT in the standard ERC1967Upgrade.sol) which do not have any setter or getter functions. With the _ROLLBACK_SLOT example, the only function which reads from that variable is _upgradeToAndCallUUPS which contains the line if (StorageSlot.getBooleanSlot(_ROLLBACK_SLOT).value).

check function return type sizes, and add comments
in `find_constant_slot_storage_type`
instead of string casting
Comment on lines 492 to 497
state_var_slots = [
self.get_variable_info(contract, var)[0]
for contract, var in self.target_variables
]
if int_value in state_var_slots:
return None
Copy link
Contributor

Choose a reason for hiding this comment

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

One thing to be aware of is that the storage layout is only calculated for contracts_derived and not contracts so there's a potential that a KeyError happens if a variable is found in an abstract contract. It's not clear to me this can happen in practice, but the error is handled by get_storage_slot for this reason.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Is it possible for a variable in an abstract contract to end up in self.target_variables?

@webthethird
Copy link
Contributor Author

@0xalpharush After merging from dev I'm getting some odd test errors. Any idea why?

Also please let me know if there's anything else to do on this PR to get it ready to merge. This one has probably been around too long!

@webthethird
Copy link
Contributor Author

Now I'm getting an error when trying to run Black

Error: Unable to resolve action `github/super-linter@v4.9.2`, unable to find version `v4.9.2`

@webthethird
Copy link
Contributor Author

With the Black issue resolved, all checks are passing

@0xalpharush
Copy link
Contributor

I think we can squash and merge

@0xalpharush
Copy link
Contributor

@webthethird Can you resolve merge conflicts?

@webthethird
Copy link
Contributor Author

@0xalpharush merge conflicts resolved, and tests passing

@0xalpharush 0xalpharush added this to the 0.9.4 milestone Jun 9, 2023
@@ -63,25 +64,35 @@ def result(self) -> "Literal":
# emulate 256-bit wrapping
if str(self._type).startswith("uint"):
value = value & (2**256 - 1)
if str(self._type).startswith("byte"):
Copy link
Member

Choose a reason for hiding this comment

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

mhh would this cause problem for bytes1/ bytes2/ ...

Copy link
Contributor Author

Choose a reason for hiding this comment

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

@0xalpharush what do you think? This is part of the constant folding change you made I think.

@webthethird
Copy link
Contributor Author

@0xalpharush I just merged dev into this branch again, and suddenly we're getting a bunch of test failures. Any idea what the cause might be? I had already merged from dev yesterday, so it must be something recent.

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

Successfully merging this pull request may close these issues.

3 participants