Skip to content

Commit

Permalink
Merge pull request #47 from skalenetwork/enhancement/backward-compati…
Browse files Browse the repository at this point in the history
…bility

Update contract_generator
  • Loading branch information
DmytroNazarenko authored Aug 1, 2022
2 parents 0883544 + 3e47dcb commit 0459c68
Show file tree
Hide file tree
Showing 5 changed files with 74 additions and 41 deletions.
64 changes: 34 additions & 30 deletions src/predeployed_generator/contract_generator.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
'''contract_generator.py
"""contract_generator.py
The module is used to generate predeployed smart contracts in genesis block
Expand All @@ -11,60 +11,64 @@
Classes:
ContractGenerator
'''
"""

from __future__ import annotations

import json
from typing import Dict, List, Union
from typing import Dict, List, Union, Optional

from web3.auto import w3
from .tools import MetaNotFoundError


def to_even_length(hex_string: str) -> str:
'''Modify hex string to have even amount of digits.
"""Modify hex string to have even amount of digits.
For example:
"0x0" becomes "0x00"
"0x123 becomes "0x0123"
"0x12" stays "0x12"
'''
"""
assert hex_string.startswith('0x')
if len(hex_string) % 2 != 0:
return "0x0" + hex_string[2:]
return hex_string


def add_0x(bytes_string: str) -> str:
'''Add "0x" prefix to the string'''
"""Add "0x" prefix to the string"""
if bytes_string.startswith('0x'):
return bytes_string
return '0x' + bytes_string


class ContractGenerator:
'''Generate smart contract allocation in a genesis block'''
"""Generate smart contract allocation in a genesis block"""

Storage = Dict[str, str]
Account = Dict[str, Union[str, Storage]]
Allocation = Dict[str, Account]

def __init__(self, bytecode: str, abi: list, meta: dict):
def __init__(self, bytecode: str, abi: list, meta: Optional[dict] = None):
self.bytecode = bytecode
self.abi = abi
self.meta = meta

@staticmethod
def from_hardhat_artifact(artifact_filename: str, meta_filename: str) -> ContractGenerator:
'''Create ContractGenerator from the artifact file built by hardhat'''
def from_hardhat_artifact(artifact_filename: str,
meta_filename: Optional[str] = None) -> ContractGenerator:
"""Create ContractGenerator from the artifact file built by hardhat"""
with open(artifact_filename, encoding='utf-8') as artifact_file:
contract = json.load(artifact_file)
with open(meta_filename, encoding='utf-8') as meta_file:
meta = json.load(meta_file)
if meta_filename:
with open(meta_filename, encoding='utf-8') as meta_file:
meta = json.load(meta_file)
return ContractGenerator(contract['deployedBytecode'], contract['abi'], meta)
return ContractGenerator(contract['deployedBytecode'], contract['abi'])

def generate(self, balance=0, nonce=0, **initial_values) -> Account:
'''Generate smart contract
"""Generate smart contract
Returns an object in format:
{
Expand All @@ -73,13 +77,13 @@ def generate(self, balance=0, nonce=0, **initial_values) -> Account:
'code': ... ,
'storage': ...
}
'''
"""
return self._generate(self.generate_storage(**initial_values), balance, nonce)

def generate_allocation(
self, contract_address: str, balance=0, nonce=0, **args
) -> ContractGenerator.Allocation:
'''Generate smart contract allocation
"""Generate smart contract allocation
Returns an object in format:
{
Expand All @@ -90,33 +94,35 @@ def generate_allocation(
'storage': ...
}
}
'''
"""
return {contract_address: self._generate(self.generate_storage(**args), balance, nonce)}

@classmethod
def generate_storage(cls, **_) -> Storage:
'''Generate smart contract storage layout
"""Generate smart contract storage layout
based on initial values provided in args
'''
"""
return {}

def get_abi(self) -> list:
'''Get the smart contract ABI
'''
"""Get the smart contract ABI
"""
return self.abi

def get_meta(self) -> dict:
'''Get the smart contract meta info
'''
"""Get the smart contract meta info
"""
if not self.meta:
raise MetaNotFoundError()
return self.meta

# private

def _generate(self, storage: Storage = None, balance: int = 0, nonce: int = 0) -> Account:
'''Produce smart contract allocation object.
"""Produce smart contract allocation object.
It consists of fields 'code', 'balance', 'nonce' and 'storage'
'''
"""
assert isinstance(self.bytecode, str)
assert isinstance(balance, int)
assert isinstance(nonce, int)
Expand Down Expand Up @@ -176,7 +182,7 @@ def calculate_mapping_value_slot(
slot: int,
key: Union[bytes, int, str],
key_type: str) -> int:
'''Calculate slot in smart contract storage where value of the key in mapping is stored'''
"""Calculate slot in smart contract storage where value of the key in mapping is stored"""

if key_type == 'bytes32':
assert isinstance(key, bytes)
Expand All @@ -193,16 +199,14 @@ def calculate_mapping_value_slot(

return int.from_bytes(w3.solidityKeccak([key_type, 'uint256'], [key, slot]), 'big')


@staticmethod
def calculate_array_value_slot(slot: int, index: int) -> int:
'''Calculate slot in smart contract storage
"""Calculate slot in smart contract storage
where value of the array in the index is stored
'''
"""
return int.from_bytes(w3.solidityKeccak(['uint256'], [slot]), 'big') + index


@staticmethod
def next_slot(previous_slot: int) -> int:
'''Return next slot in smart contract storage'''
"""Return next slot in smart contract storage"""
return previous_slot + 1
4 changes: 4 additions & 0 deletions src/predeployed_generator/tools.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,10 @@
from os.path import normpath, join


class MetaNotFoundError(Exception):
"""Meta file does not exist"""


class ArtifactsHandler:
"""Generate and copy smart contract artifacts in pkg dir"""

Expand Down
14 changes: 7 additions & 7 deletions src/predeployed_generator/upgradeable_contract_generator.py
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
'''upgradeable_contract_generator.py
"""upgradeable_contract_generator.py
The module is used to generate predeployed smart contracts in genesis block
that is upgradeable using EIP-1967 tranparent upgradeable proxy
Classes:
UpgradeableContractGenerator
'''
"""

from web3.auto import w3

Expand All @@ -15,21 +15,21 @@


class UpgradeableContractGenerator(TransparentUpgradeableProxyGenerator):
'''Generates transparent upgradeable proxy based on implementation generator'''
"""Generates transparent upgradeable proxy based on implementation generator"""
def __init__(
self,
implementation_generator: ContractGenerator):
super().__init__()
self.implementation_generator = implementation_generator

def generate(self, balance=0, nonce=0, **_) -> ContractGenerator.Account:
raise RuntimeError('''Can\'t generate upgradeable contract without implementation.
Use `generate_allocation` method instead''')
raise RuntimeError("""Can\'t generate upgradeable contract without implementation.
Use `generate_allocation` method instead""")

def generate_allocation(
self, contract_address, balance=0, nonce=0, **kwargs
) -> ContractGenerator.Allocation:
'''Generate smart contract allocation.
"""Generate smart contract allocation.
It's pair of 2 smart contract:
the first is upgradeable proxy
and the second is an implementation
Expand Down Expand Up @@ -61,7 +61,7 @@ def generate_allocation(
'storage': ...
}
}
'''
"""
proxy_admin_address = kwargs.pop('proxy_admin_address')
implementation_address = kwargs.pop(
'implementation_address',
Expand Down
23 changes: 23 additions & 0 deletions test/test_contract_generator.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import pytest
from web3.auto import w3

from src.predeployed_generator.tools import MetaNotFoundError
from .tools.custom_contract_generator import CustomContractGenerator
from .tools.test_solidity_project import TestSolidityProject
from src.predeployed_generator.contract_generator import ContractGenerator
Expand Down Expand Up @@ -86,3 +87,25 @@ class EmptyGenerator(ContractGenerator):
def test_non_existent_map_key_type(self):
with pytest.raises(TypeError):
ContractGenerator.calculate_mapping_value_slot(0, 'key', 'nonexistent')

def test_generator_without_meta(self):
class EmptyGenerator(ContractGenerator):
pass

bytecode = '0xbytecode'
abi = ['function']
generator = EmptyGenerator(bytecode, abi)
assert generator.meta is None
with pytest.raises(MetaNotFoundError):
generator.get_meta()

def test_generator_from_hardhat_artifact(self):
class EmptyGenerator(ContractGenerator):
pass

generator = EmptyGenerator.from_hardhat_artifact(
self.get_artifacts_path(CustomContractGenerator.CONTRACT_NAME)
)
assert generator.meta is None
with pytest.raises(MetaNotFoundError):
generator.get_meta()
10 changes: 6 additions & 4 deletions test/tools/test_solidity_project.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,13 +6,15 @@

class TestSolidityProject(TestPredeployed):
def get_abi(self, contract: str) -> list:
with open(join(
self.get_artifacts_dir(),
f'contracts/{contract}.sol/',
f'{contract}.json')) as f:
with open(self.get_artifacts_path(contract)) as f:
data = json.load(f)
return data['abi']

@staticmethod
def get_artifacts_dir():
return normpath(join(dirname(__file__), '../test_solidity_project/artifacts/'))

def get_artifacts_path(self, contract: str) -> str:
return join(self.get_artifacts_dir(),
f'contracts/{contract}.sol/',
f'{contract}.json')

0 comments on commit 0459c68

Please sign in to comment.