Skip to content

Commit

Permalink
feat[lang]: singleton modules with ownership hierarchy (vyperlang#3729)
Browse files Browse the repository at this point in the history
this commit implements "singleton modules with ownership hierarchy" as
described in vyperlang#3722.

to accomplish this, two new language constructs are added: `UsesDecl`
and `InitializesDecl`. these are exposed to the user as `uses:` and
`initializes:`. they are also accompanied by new `AnalysisResult` data
structures: `UsesInfo` and `InitializesInfo`.

`uses` and `initializes` can be thought of as a constraint system on the
module system. a `uses: my-module` annotation is required if
`my_module`'s state is accessed (read or written), and
`initializes: my_module` is required to call `my_module.__init__()`. a
module can be `use`d any number of times; it can only be `initialize`d
once. a module which has been used (directly, or transitively) by the
compilation target (main entry point module), must be `initialize`d
exactly once. `initializes:` is also required to declare which modules
it has been `initialize`d with. for example, if `mod1` declares it
`uses: mod2`, then any `initializes: mod1` statement must declare
*which* instance of `mod2` it has been initialized with. although there
is only ever a single instance of `mod2`, this user-facing requirement
improves readability by forcing the user to be aware of what the state
access dependencies are for a given, `initialize`d module.

the `NamedExpr` node ("walrus operator") has been added to the AST to
support the initializer syntax. (note: the walrus operator is used,
because the originally proposed syntax, `mod1[mod2 = mod2]` is rejected
by the python parser).

a new compiler pass, `vyper/semantics/analysis/global.py` has been
added to implement the global initializer constraint, as it cannot be
defined recursively (without a global context).

since `__init__()` functions can now be called from other `__init__()`
functions (which is not allowed for normal `@external` functions!), a
new `@deploy` visibility has been added to vyper's visibility system.
`@deploy` functions can be called from other `@deploy` functions, and
never from `@external` or `@internal` functions. they also have special
treatment in the ABI relative to other `@external` functions.

`initializes:` is useful since it also serves the purpose of being a
storage allocator directive. wherever `initializes:` is placed, is where
the module will be placed in storage (and code, transient storage, or
any other future storage locations).

this commit refactors the storage allocator so that it recurses into
child modules whenever it sees an `initializes:` statement. it refactors
several data structures surrounding the storage allocator, including
removing inheritance on the `DataPosition` data structure (which has
also been renamed to `VarOffset`). some utility functions have been
added for calculating the size of a given variable, which also get used
in codegen (`get_element_ptr()`).

additional work/refactoring in this commit:
- new analysis machinery for detecting reads/writes for all `ExprInfo`s
- dynamic programming on the `get_expr_info()` routine
- refactoring of `visit_Expr`, which fixes call mutability analysis
- move `StringEnum` back to vyper/utils.py
- remove the "TYPE_DEFINITION" kludge in certain builtins, replace with
  usage of `TYPE_T`
- improve `tag_exceptions()` formatting
- remove `Context.globals`, as we rely on the results of the front-end
  analyser now.
- remove dead variable: `Context.in_assertion`
- refactor `generate_ir_for_function` into
  `generate_ir_for_external_function` and
  `generate_ir_for_internal_function`
- move `get_nonreentrant_lock` to `function_definitions/common.py`
- simplify layout allocation across locations into single function
- add `VyperType.get_size_in()` and `VarInfo.get_size()` helper
  functions so we don't need to do as much switch/case in implementation
  functions
- refactor `codegen/core.py` functions to use `VyperType.get_size()`
- fix interfaces access from `.vyi` files
  • Loading branch information
charles-cooper authored Feb 10, 2024
1 parent c6b29c7 commit 8ccacb3
Show file tree
Hide file tree
Showing 112 changed files with 3,566 additions and 845 deletions.
4 changes: 3 additions & 1 deletion examples/auctions/blind_auction.vy
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
#pragma version >0.3.10

# Blind Auction. Adapted to Vyper from [Solidity by Example](https://github.com/ethereum/solidity/blob/develop/docs/solidity-by-example.rst#blind-auction-1)

struct Bid:
Expand Down Expand Up @@ -36,7 +38,7 @@ pendingReturns: HashMap[address, uint256]
# Create a blinded auction with `_biddingTime` seconds bidding time and
# `_revealTime` seconds reveal time on behalf of the beneficiary address
# `_beneficiary`.
@external
@deploy
def __init__(_beneficiary: address, _biddingTime: uint256, _revealTime: uint256):
self.beneficiary = _beneficiary
self.biddingEnd = block.timestamp + _biddingTime
Expand Down
4 changes: 3 additions & 1 deletion examples/auctions/simple_open_auction.vy
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
#pragma version >0.3.10

# Open Auction

# Auction params
Expand All @@ -19,7 +21,7 @@ pendingReturns: public(HashMap[address, uint256])
# Create a simple auction with `_auction_start` and
# `_bidding_time` seconds bidding time on behalf of the
# beneficiary address `_beneficiary`.
@external
@deploy
def __init__(_beneficiary: address, _auction_start: uint256, _bidding_time: uint256):
self.beneficiary = _beneficiary
self.auctionStart = _auction_start # auction start time can be in the past, present or future
Expand Down
4 changes: 3 additions & 1 deletion examples/crowdfund.vy
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
#pragma version >0.3.10

###########################################################################
## THIS IS EXAMPLE CODE, NOT MEANT TO BE USED IN PRODUCTION! CAVEAT EMPTOR!
###########################################################################
Expand All @@ -11,7 +13,7 @@ goal: public(uint256)
timelimit: public(uint256)

# Setup global variables
@external
@deploy
def __init__(_beneficiary: address, _goal: uint256, _timelimit: uint256):
self.beneficiary = _beneficiary
self.deadline = block.timestamp + _timelimit
Expand Down
4 changes: 3 additions & 1 deletion examples/factory/Exchange.vy
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
#pragma version >0.3.10

from ethereum.ercs import ERC20


Expand All @@ -9,7 +11,7 @@ token: public(ERC20)
factory: Factory


@external
@deploy
def __init__(_token: ERC20, _factory: Factory):
self.token = _token
self.factory = _factory
Expand Down
4 changes: 3 additions & 1 deletion examples/factory/Factory.vy
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
#pragma version >0.3.10

from ethereum.ercs import ERC20

interface Exchange:
Expand All @@ -11,7 +13,7 @@ exchange_codehash: public(bytes32)
exchanges: public(HashMap[ERC20, Exchange])


@external
@deploy
def __init__(_exchange_codehash: bytes32):
# Register the exchange code hash during deployment of the factory
self.exchange_codehash = _exchange_codehash
Expand Down
2 changes: 2 additions & 0 deletions examples/market_maker/on_chain_market_maker.vy
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
#pragma version >0.3.10

from ethereum.ercs import ERC20


Expand Down
1 change: 1 addition & 0 deletions examples/name_registry/name_registry.vy
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
#pragma version >0.3.10

registry: HashMap[Bytes[100], address]

Expand Down
4 changes: 3 additions & 1 deletion examples/safe_remote_purchase/safe_remote_purchase.vy
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
#pragma version >0.3.10

# Safe Remote Purchase
# Originally from
# https://github.com/ethereum/solidity/blob/develop/docs/solidity-by-example.rst
Expand All @@ -19,7 +21,7 @@ buyer: public(address)
unlocked: public(bool)
ended: public(bool)

@external
@deploy
@payable
def __init__():
assert (msg.value % 2) == 0
Expand Down
4 changes: 3 additions & 1 deletion examples/stock/company.vy
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
#pragma version >0.3.10

# Financial events the contract logs

event Transfer:
Expand Down Expand Up @@ -27,7 +29,7 @@ price: public(uint256)
holdings: HashMap[address, uint256]

# Set up the company.
@external
@deploy
def __init__(_company: address, _total_shares: uint256, initial_price: uint256):
assert _total_shares > 0
assert initial_price > 0
Expand Down
4 changes: 3 additions & 1 deletion examples/storage/advanced_storage.vy
Original file line number Diff line number Diff line change
@@ -1,10 +1,12 @@
#pragma version >0.3.10

event DataChange:
setter: indexed(address)
value: int128

storedData: public(int128)

@external
@deploy
def __init__(_x: int128):
self.storedData = _x

Expand Down
6 changes: 4 additions & 2 deletions examples/storage/storage.vy
Original file line number Diff line number Diff line change
@@ -1,9 +1,11 @@
#pragma version >0.3.10

storedData: public(int128)

@external
@deploy
def __init__(_x: int128):
self.storedData = _x

@external
def set(_x: int128):
self.storedData = _x
self.storedData = _x
5 changes: 3 additions & 2 deletions examples/tokens/ERC1155ownable.vy
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
#pragma version >0.3.10

###########################################################################
## THIS IS EXAMPLE CODE, NOT MEANT TO BE USED IN PRODUCTION! CAVEAT EMPTOR!
###########################################################################

# @version >=0.3.4
"""
@dev example implementation of ERC-1155 non-fungible token standard ownable, with approval, OPENSEA compatible (name, symbol)
@author Dr. Pixel (github: @Doc-Pixel)
Expand Down Expand Up @@ -122,7 +123,7 @@ interface IERC1155MetadataURI:

############### functions ###############

@external
@deploy
def __init__(name: String[128], symbol: String[16], uri: String[MAX_URI_LENGTH], contractUri: String[MAX_URI_LENGTH]):
"""
@dev contract initialization on deployment
Expand Down
4 changes: 3 additions & 1 deletion examples/tokens/ERC20.vy
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
#pragma version >0.3.10

###########################################################################
## THIS IS EXAMPLE CODE, NOT MEANT TO BE USED IN PRODUCTION! CAVEAT EMPTOR!
###########################################################################
Expand Down Expand Up @@ -38,7 +40,7 @@ totalSupply: public(uint256)
minter: address


@external
@deploy
def __init__(_name: String[32], _symbol: String[32], _decimals: uint8, _supply: uint256):
init_supply: uint256 = _supply * 10 ** convert(_decimals, uint256)
self.name = _name
Expand Down
4 changes: 3 additions & 1 deletion examples/tokens/ERC4626.vy
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
#pragma version >0.3.10

# NOTE: Copied from https://github.com/fubuloubu/ERC4626/blob/1a10b051928b11eeaad15d80397ed36603c2a49b/contracts/VyperVault.vy

# example implementation of an ERC4626 vault
Expand Down Expand Up @@ -50,7 +52,7 @@ event Withdraw:
shares: uint256


@external
@deploy
def __init__(asset: ERC20):
self.asset = asset

Expand Down
4 changes: 3 additions & 1 deletion examples/tokens/ERC721.vy
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
#pragma version >0.3.10

###########################################################################
## THIS IS EXAMPLE CODE, NOT MEANT TO BE USED IN PRODUCTION! CAVEAT EMPTOR!
###########################################################################
Expand Down Expand Up @@ -82,7 +84,7 @@ SUPPORTED_INTERFACES: constant(bytes4[2]) = [
0x80ac58cd,
]

@external
@deploy
def __init__():
"""
@dev Contract constructor.
Expand Down
4 changes: 3 additions & 1 deletion examples/voting/ballot.vy
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
#pragma version >0.3.10

# Voting with delegation.

# Information about voters
Expand Down Expand Up @@ -50,7 +52,7 @@ def directlyVoted(addr: address) -> bool:


# Setup global variables
@external
@deploy
def __init__(_proposalNames: bytes32[2]):
self.chairperson = msg.sender
self.voterCount = 0
Expand Down
4 changes: 3 additions & 1 deletion examples/wallet/wallet.vy
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
#pragma version >0.3.10

###########################################################################
## THIS IS EXAMPLE CODE, NOT MEANT TO BE USED IN PRODUCTION! CAVEAT EMPTOR!
###########################################################################
Expand All @@ -12,7 +14,7 @@ threshold: int128
seq: public(int128)


@external
@deploy
def __init__(_owners: address[5], _threshold: int128):
for i: uint256 in range(5):
if _owners[i] != empty(address):
Expand Down
4 changes: 2 additions & 2 deletions tests/functional/builtins/codegen/test_abi.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,14 +8,14 @@
"""
x: int128
@external
@deploy
def __init__():
self.x = 1
""",
"""
x: int128
@external
@deploy
def __init__():
pass
""",
Expand Down
2 changes: 1 addition & 1 deletion tests/functional/builtins/codegen/test_abi_decode.py
Original file line number Diff line number Diff line change
Expand Up @@ -224,7 +224,7 @@ def test_side_effects_evaluation(get_contract):
contract_1 = """
counter: uint256
@external
@deploy
def __init__():
self.counter = 0
Expand Down
2 changes: 1 addition & 1 deletion tests/functional/builtins/codegen/test_abi_encode.py
Original file line number Diff line number Diff line change
Expand Up @@ -263,7 +263,7 @@ def test_side_effects_evaluation(get_contract):
contract_1 = """
counter: uint256
@external
@deploy
def __init__():
self.counter = 0
Expand Down
4 changes: 2 additions & 2 deletions tests/functional/builtins/codegen/test_ceil.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ def test_ceil(get_contract_with_gas_estimation):
code = """
x: decimal
@external
@deploy
def __init__():
self.x = 504.0000000001
Expand Down Expand Up @@ -53,7 +53,7 @@ def test_ceil_negative(get_contract_with_gas_estimation):
code = """
x: decimal
@external
@deploy
def __init__():
self.x = -504.0000000001
Expand Down
4 changes: 2 additions & 2 deletions tests/functional/builtins/codegen/test_concat.py
Original file line number Diff line number Diff line change
Expand Up @@ -79,7 +79,7 @@ def test_concat_buffer2(get_contract):
code = """
i: immutable(int256)
@external
@deploy
def __init__():
i = -1
s: String[2] = concat("a", "b")
Expand All @@ -99,7 +99,7 @@ def test_concat_buffer3(get_contract):
s2: String[33]
s3: String[34]
@external
@deploy
def __init__():
self.s = "a"
self.s2 = "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" # 33*'a'
Expand Down
10 changes: 5 additions & 5 deletions tests/functional/builtins/codegen/test_create_functions.py
Original file line number Diff line number Diff line change
Expand Up @@ -214,7 +214,7 @@ def test_create_from_blueprint_bad_code_offset(
deployer_code = """
BLUEPRINT: immutable(address)
@external
@deploy
def __init__(blueprint_address: address):
BLUEPRINT = blueprint_address
Expand Down Expand Up @@ -269,7 +269,7 @@ def test_create_from_blueprint_args(
FOO: immutable(String[128])
BAR: immutable(Bar)
@external
@deploy
def __init__(foo: String[128], bar: Bar):
FOO = foo
BAR = bar
Expand Down Expand Up @@ -450,7 +450,7 @@ def test_create_from_blueprint_complex_value(
code = """
var: uint256
@external
@deploy
@payable
def __init__(x: uint256):
self.var = x
Expand Down Expand Up @@ -507,7 +507,7 @@ def test_create_from_blueprint_complex_salt_raw_args(
code = """
var: uint256
@external
@deploy
@payable
def __init__(x: uint256):
self.var = x
Expand Down Expand Up @@ -565,7 +565,7 @@ def test_create_from_blueprint_complex_salt_no_constructor_args(
code = """
var: uint256
@external
@deploy
@payable
def __init__():
self.var = 12
Expand Down
2 changes: 1 addition & 1 deletion tests/functional/builtins/codegen/test_ecrecover.py
Original file line number Diff line number Diff line change
Expand Up @@ -68,7 +68,7 @@ def test_invalid_signature2(get_contract):
owner: immutable(address)
@external
@deploy
def __init__():
owner = 0x7E5F4552091A69125d5DfCb7b8C2659029395Bdf
Expand Down
4 changes: 2 additions & 2 deletions tests/functional/builtins/codegen/test_floor.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ def test_floor(get_contract_with_gas_estimation):
code = """
x: decimal
@external
@deploy
def __init__():
self.x = 504.0000000001
Expand Down Expand Up @@ -55,7 +55,7 @@ def test_floor_negative(get_contract_with_gas_estimation):
code = """
x: decimal
@external
@deploy
def __init__():
self.x = -504.0000000001
Expand Down
2 changes: 1 addition & 1 deletion tests/functional/builtins/codegen/test_raw_call.py
Original file line number Diff line number Diff line change
Expand Up @@ -137,7 +137,7 @@ def set_owner(i: int128, o: address):
owners: public(address[5])
@external
@deploy
def __init__(_owner_setter: address):
self.owner_setter_contract = _owner_setter
Expand Down
Loading

0 comments on commit 8ccacb3

Please sign in to comment.