Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
33 changes: 33 additions & 0 deletions tests/examples/contracts/upgradable_storage.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
# v0.1.0
# { "Depends": "py-genlayer:latest" }

from genlayer import *


class UpgradableStorage(gl.Contract):
storage: str

def __init__(self, initial_storage: str):
self.storage = initial_storage

# make the deployer an upgrader
root = gl.storage.Root.get()
root.upgraders.get().append(gl.message.sender_address)

# lock critical slots so only upgraders can modify them
root.lock_default()

@gl.public.view
def get_storage(self) -> str:
return self.storage

@gl.public.write
def update_storage(self, new_storage: str) -> None:
self.storage = new_storage

@gl.public.write
def upgrade(self, new_code: bytes) -> None:
root = gl.storage.Root.get()
code = root.code.get()
code.truncate()
code.extend(new_code)
32 changes: 32 additions & 0 deletions tests/examples/contracts/upgradable_storage_v2.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
# v0.1.0
# { "Depends": "py-genlayer:latest" }

from genlayer import *


class UpgradableStorage(gl.Contract):
# storage layout must remain compatible with v1
storage: str

def __init__(self):
pass

@gl.public.view
def get_storage(self) -> str:
return self.storage

@gl.public.write
def update_storage(self, new_storage: str) -> None:
self.storage = new_storage

# new method added in v2
@gl.public.view
def get_storage_length(self) -> int:
return len(self.storage)

@gl.public.write
def upgrade(self, new_code: bytes) -> None:
root = gl.storage.Root.get()
code = root.code.get()
code.truncate()
code.extend(new_code)
48 changes: 48 additions & 0 deletions tests/examples/tests/test_upgradable_storage.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
from pathlib import Path
from gltest import get_contract_factory
from gltest.assertions import tx_execution_succeeded


CONTRACTS_DIR = Path(__file__).parent.parent / "contracts"
INITIAL_STATE = "hello"
UPDATED_STATE = "world"


def test_upgradable_storage():
# Deploy v1
factory = get_contract_factory(
contract_file_path=CONTRACTS_DIR / "upgradable_storage.py"
)
contract = factory.deploy(args=[INITIAL_STATE])

# Verify initial state
assert contract.get_storage(args=[]).call() == INITIAL_STATE

# Update state using v1 method
tx = contract.update_storage(args=[UPDATED_STATE]).transact()
assert tx_execution_succeeded(tx)
assert contract.get_storage(args=[]).call() == UPDATED_STATE

# Read v2 code and upgrade the contract
v2_code = (CONTRACTS_DIR / "upgradable_storage_v2.py").read_bytes()
tx = contract.upgrade(args=[v2_code]).transact()
assert tx_execution_succeeded(tx)

# After upgrade, rebuild the contract proxy from the v2 schema
# so we can access newly added methods
v2_factory = get_contract_factory(
contract_file_path=CONTRACTS_DIR / "upgradable_storage_v2.py"
)
contract_v2 = v2_factory.build_contract(contract_address=contract.address)

# Storage persists across upgrades
assert contract_v2.get_storage(args=[]).call() == UPDATED_STATE

# New v2 method works
assert contract_v2.get_storage_length(args=[]).call() == len(UPDATED_STATE)

# Existing methods still work
tx = contract_v2.update_storage(args=["upgraded"]).transact()
assert tx_execution_succeeded(tx)
assert contract_v2.get_storage(args=[]).call() == "upgraded"
assert contract_v2.get_storage_length(args=[]).call() == len("upgraded")