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

refactor[tool]: refactor storage layout export #3789

Conversation

charles-cooper
Copy link
Member

@charles-cooper charles-cooper commented Feb 19, 2024

What I did

refactor the storage layout export code
cc @ritzdorf @trocher

fixes #3996, #3517, #3989

How I did it

How to verify it

Commit message

refactor storage layout allocator. separate concerns of allocating the
storage layout and exporting the storage layout into separate functions.
this is intended to make it easier to add features to the storage layout
export in the future

fix several bugs in storage layout overrides, including:
- handle stateful modules
- add a sanity check that the override file roundtrips
- ignore non-storage variables in override files
- set nonreentrant lock properly for all functions instead of panicking

misc:
add `n_slots` to each storage layout item in the export

Description for the changelog

Cute Animal Picture

Put a link to a cute animal picture inside the parenthesis-->

@codecov-commenter
Copy link

codecov-commenter commented Feb 19, 2024

Codecov Report

Attention: Patch coverage is 95.52239% with 3 lines in your changes are missing coverage. Please review.

Project coverage is 90.21%. Comparing base (abf795a) to head (a2ee205).

Current head a2ee205 differs from pull request most recent head 1042b90

Please upload reports for the commit 1042b90 to get more accurate results.

Files Patch % Lines
vyper/semantics/analysis/data_positions.py 95.08% 1 Missing and 2 partials ⚠️
Additional details and impacted files
@@            Coverage Diff             @@
##           master    #3789      +/-   ##
==========================================
- Coverage   90.82%   90.21%   -0.62%     
==========================================
  Files         106      106              
  Lines       15309    15333      +24     
  Branches     3366     3378      +12     
==========================================
- Hits        13904    13832      -72     
- Misses        966     1036      +70     
- Partials      439      465      +26     

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

@charles-cooper charles-cooper mentioned this pull request Mar 26, 2024
33 tasks
@charles-cooper charles-cooper added this to the v0.4.0 milestone Mar 26, 2024
@charles-cooper charles-cooper changed the title [wip] feat: refactor storage layout export refactor[tool]: refactor storage layout export Apr 3, 2024
@charles-cooper charles-cooper marked this pull request as ready for review April 3, 2024 20:05
Copy link
Member

@fubuloubu fubuloubu left a comment

Choose a reason for hiding this comment

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

Is this is a breaking change for tooling?

tests/unit/cli/storage_layout/__init__.py Outdated Show resolved Hide resolved
@charles-cooper
Copy link
Member Author

charles-cooper commented Apr 9, 2024

Is this is a breaking change for tooling?

in theory, no. still ironing out some bugs though

if not fn_t.nonreentrant:
continue

location = get_reentrancy_key_location()

Choose a reason for hiding this comment

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

with overrides, non-reentrant key will be in storage, but the layout (-f) will return that key in the transient_storage_layout

Copy link
Member Author

Choose a reason for hiding this comment

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

right, so you're saying the output of -f layout won't match the override file right?

Choose a reason for hiding this comment

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

thats right

Copy link
Member Author

Choose a reason for hiding this comment

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

b9acf98, do you think this addresses it appropriately?

@cyberthirst
Copy link
Collaborator

cyberthirst commented May 24, 2024

i think that overrides don't handle reentrancy lock properly:

override file:

{
  "$.nonreentrant_key": {
    "type": "uint256",
    "slot": 1
  }
}
# main.vy

c: transient(uint256)

@external
@nonreentrant
def foo():
    self.c = 0

and run with cancun:

 vyper tests/custom/main.vy -f layout --evm cancun --storage-layout-file tests/custom/override.json

outputs:

{
  "transient_storage_layout": {
    "c": {
      "type": "uint256",
      "n_slots": 1,
      "slot": 1
    },
    "$.nonreentrant_key": {
      "type": "nonreentrant lock",
      "slot": 1,
      "n_slots": 1
    }
  }
}

EDIT - adding also the corresponding IR

[label,
  external 0 foo()_common,
  var_list,
  [seq,
    [seq, [assert, [ne, 1, [tload, 1]]], [tstore, 1, 1]],
    [seq,
      [seq, [unique_symbol, tstore_1], [tstore, 1 <self.c>, 0 <0>]],
      [exit_to, external 0 foo()_cleanup],
      pass]]],
[label, external 0 foo()_cleanup, var_list, [seq, [tstore, 1, 0], stop]]]]]],

@charles-cooper
Copy link
Member Author

i added a sanity check that the storage layout roundtrips in b9acf98

@cyberthirst
Copy link
Collaborator

cyberthirst commented May 24, 2024

function not exported, but -f layout includes the lock:

# main.vy
import lib1

initializes: lib1

@deploy
def __init__():
    lib1.counter = 99
    
# lib1.vy
counter: uint256

@external
@nonreentrant
def foo() -> uint256:
    return self.counter

Layout:

{
  "storage_layout": {
    "lib1": {
      "counter": {
        "type": "uint256",
        "n_slots": 1,
        "slot": 0
      }
    }
  },
  "transient_storage_layout": {
    "$.nonreentrant_key": {
      "type": "nonreentrant lock",
      "slot": 0,
      "n_slots": 1
    }
  }
}

EDIT: updated main.vy

@cyberthirst
Copy link
Collaborator

var_name unused

def allocate_slot(self, n, var_name, node=None):

@cyberthirst
Copy link
Collaborator

cyberthirst commented May 25, 2024

rather nothing, but reporting for completness

lib1: uint256

@external
def foo():
    pass
{
  "lib1": {
    "slot": {
      "slot": 20,
      "type": "uint256",
      "n_slots": 1
    }
  }
}

panics w:

Error compiling: tests/custom/main.vy
TypeError: unsupported operand type(s) for +: 'int' and 'dict'

@charles-cooper
Copy link
Member Author

var_name unused

def allocate_slot(self, n, var_name, node=None):

cdcfb81

@cyberthirst
Copy link
Collaborator

cyberthirst commented May 27, 2024

this part won't be fun with large arrays - in the future we should try to rewrite the logic to use interval intersections

def _reserve_slots(self, slots: list[int], var_name: str) -> None:
for slot in slots:
self._reserve_slot(slot, var_name)
def _reserve_slot(self, slot: int, var_name: str) -> None:
if slot < 0 or slot >= 2**256:
raise StorageLayoutException(
f"Invalid storage slot for var {var_name}, out of bounds: {slot}"
)
if slot in self.occupied_slots:
collided_var = self.occupied_slots[slot]
raise StorageLayoutException(
f"Storage collision! Tried to assign '{var_name}' to slot {slot} but it has "
f"already been reserved by '{collided_var}'"
)
self.occupied_slots[slot] = var_name

EDIT: even for moderately-sized DynArrays (say ~2**30) we'll run out of memory on the host

@charles-cooper
Copy link
Member Author

this part won't be fun with large arrays - in the future we should try to rewrite the logic to use interval intersections

def _reserve_slots(self, slots: list[int], var_name: str) -> None:
for slot in slots:
self._reserve_slot(slot, var_name)
def _reserve_slot(self, slot: int, var_name: str) -> None:
if slot < 0 or slot >= 2**256:
raise StorageLayoutException(
f"Invalid storage slot for var {var_name}, out of bounds: {slot}"
)
if slot in self.occupied_slots:
collided_var = self.occupied_slots[slot]
raise StorageLayoutException(
f"Storage collision! Tried to assign '{var_name}' to slot {slot} but it has "
f"already been reserved by '{collided_var}'"
)
self.occupied_slots[slot] = var_name

EDIT: even for moderately-sized DynArrays (say ~2**30) we'll run out of memory on the host

dup of / tracked in #3966

@charles-cooper charles-cooper merged commit 96a8384 into vyperlang:master May 28, 2024
158 checks passed
@charles-cooper charles-cooper deleted the feat/refactor-storage-layout-export branch June 3, 2024 20:34
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.

overriding allocator does not set global lock position causing crash in codegen
5 participants