Merge pull request #1127 from ethereum/deposit_contract
Move deposit contract back
This commit is contained in:
commit
6f82480df2
|
@ -1,7 +1,7 @@
|
|||
version: 2.1
|
||||
commands:
|
||||
restore_cached_venv:
|
||||
description: "Restores a cached venv"
|
||||
description: "Restore a cached venv"
|
||||
parameters:
|
||||
reqs_checksum:
|
||||
type: string
|
||||
|
@ -16,7 +16,7 @@ commands:
|
|||
# fallback to using the latest cache if no exact match is found
|
||||
- << parameters.venv_name >>-venv-
|
||||
save_cached_venv:
|
||||
description: "Saves a venv into a cache"
|
||||
description: "Save a venv into a cache"
|
||||
parameters:
|
||||
reqs_checksum:
|
||||
type: string
|
||||
|
@ -31,6 +31,32 @@ commands:
|
|||
- save_cache:
|
||||
key: << parameters.venv_name >>-venv-<< parameters.reqs_checksum >>
|
||||
paths: << parameters.venv_path >>
|
||||
restore_pyspec_cached_venv:
|
||||
description: "Restore the cache with pyspec keys"
|
||||
steps:
|
||||
- restore_cached_venv:
|
||||
venv_name: v2-pyspec
|
||||
reqs_checksum: cache-{{ checksum "test_libs/pyspec/requirements.txt" }}-{{ checksum "test_libs/pyspec/requirements-testing.txt" }}
|
||||
save_pyspec_cached_venv:
|
||||
description: Save a venv into a cache with pyspec keys"
|
||||
steps:
|
||||
- save_cached_venv:
|
||||
venv_name: v2-pyspec
|
||||
reqs_checksum: cache-{{ checksum "test_libs/pyspec/requirements.txt" }}-{{ checksum "test_libs/pyspec/requirements-testing.txt" }}
|
||||
venv_path: ./test_libs/pyspec/venv
|
||||
restore_deposit_contract_cached_venv:
|
||||
description: "Restore the cache with deposit_contract keys"
|
||||
steps:
|
||||
- restore_cached_venv:
|
||||
venv_name: v4-deposit-contract
|
||||
reqs_checksum: cache-{{ checksum "test_libs/pyspec/requirements.txt" }}-{{ checksum "deposit_contract/requirements-testing.txt" }}
|
||||
save_deposit_contract_cached_venv:
|
||||
description: Save a venv into a cache with deposit_contract keys"
|
||||
steps:
|
||||
- save_cached_venv:
|
||||
venv_name: v4-deposit-contract
|
||||
reqs_checksum: cache-{{ checksum "test_libs/pyspec/requirements.txt" }}-{{ checksum "deposit_contract/requirements-testing.txt" }}
|
||||
venv_path: ./deposit_contract/venv
|
||||
jobs:
|
||||
checkout_specs:
|
||||
docker:
|
||||
|
@ -52,23 +78,18 @@ jobs:
|
|||
key: v1-specs-repo-{{ .Branch }}-{{ .Revision }}
|
||||
paths:
|
||||
- ~/specs-repo
|
||||
install_env:
|
||||
install_pyspec_test:
|
||||
docker:
|
||||
- image: circleci/python:3.6
|
||||
working_directory: ~/specs-repo
|
||||
steps:
|
||||
- restore_cache:
|
||||
key: v1-specs-repo-{{ .Branch }}-{{ .Revision }}
|
||||
- restore_cached_venv:
|
||||
venv_name: v2-pyspec
|
||||
reqs_checksum: '{{ checksum "test_libs/pyspec/requirements.txt" }}-{{ checksum "test_libs/pyspec/requirements-testing.txt" }}'
|
||||
- restore_pyspec_cached_venv
|
||||
- run:
|
||||
name: Install pyspec requirements
|
||||
command: make install_test
|
||||
- save_cached_venv:
|
||||
venv_name: v2-pyspec
|
||||
reqs_checksum: '{{ checksum "test_libs/pyspec/requirements.txt" }}-{{ checksum "test_libs/pyspec/requirements-testing.txt" }}'
|
||||
venv_path: ./test_libs/pyspec/venv
|
||||
- save_pyspec_cached_venv
|
||||
test:
|
||||
docker:
|
||||
- image: circleci/python:3.6
|
||||
|
@ -76,9 +97,7 @@ jobs:
|
|||
steps:
|
||||
- restore_cache:
|
||||
key: v1-specs-repo-{{ .Branch }}-{{ .Revision }}
|
||||
- restore_cached_venv:
|
||||
venv_name: v2-pyspec
|
||||
reqs_checksum: '{{ checksum "test_libs/pyspec/requirements.txt" }}-{{ checksum "test_libs/pyspec/requirements-testing.txt" }}'
|
||||
- restore_pyspec_cached_venv
|
||||
- run:
|
||||
name: Run py-tests
|
||||
command: make citest
|
||||
|
@ -91,23 +110,50 @@ jobs:
|
|||
steps:
|
||||
- restore_cache:
|
||||
key: v1-specs-repo-{{ .Branch }}-{{ .Revision }}
|
||||
- restore_cached_venv:
|
||||
venv_name: v2-pyspec
|
||||
reqs_checksum: '{{ checksum "test_libs/pyspec/requirements.txt" }}-{{ checksum "test_libs/pyspec/requirements-testing.txt" }}'
|
||||
- restore_pyspec_cached_venv
|
||||
- run:
|
||||
name: Run linter
|
||||
command: make lint
|
||||
install_deposit_contract_test:
|
||||
docker:
|
||||
- image: circleci/python:3.6
|
||||
working_directory: ~/specs-repo
|
||||
steps:
|
||||
- restore_cache:
|
||||
key: v1-specs-repo-{{ .Branch }}-{{ .Revision }}
|
||||
- restore_deposit_contract_cached_venv
|
||||
- run:
|
||||
name: Install deposit contract requirements
|
||||
command: make install_deposit_contract_test
|
||||
- save_deposit_contract_cached_venv
|
||||
deposit_contract:
|
||||
docker:
|
||||
- image: circleci/python:3.6
|
||||
working_directory: ~/specs-repo
|
||||
steps:
|
||||
- restore_cache:
|
||||
key: v1-specs-repo-{{ .Branch }}-{{ .Revision }}
|
||||
- restore_deposit_contract_cached_venv
|
||||
- run:
|
||||
name: Run deposit contract test
|
||||
command: make test_deposit_contract
|
||||
workflows:
|
||||
version: 2.1
|
||||
test_spec:
|
||||
jobs:
|
||||
- checkout_specs
|
||||
- install_env:
|
||||
- install_pyspec_test:
|
||||
requires:
|
||||
- checkout_specs
|
||||
- test:
|
||||
requires:
|
||||
- install_env
|
||||
- install_pyspec_test
|
||||
- lint:
|
||||
requires:
|
||||
- test
|
||||
- install_deposit_contract_test:
|
||||
requires:
|
||||
- checkout_specs
|
||||
- deposit_contract:
|
||||
requires:
|
||||
- install_deposit_contract_test
|
||||
|
|
15
Makefile
15
Makefile
|
@ -4,6 +4,7 @@ TEST_LIBS_DIR = ./test_libs
|
|||
PY_SPEC_DIR = $(TEST_LIBS_DIR)/pyspec
|
||||
YAML_TEST_DIR = ./eth2.0-spec-tests/tests
|
||||
GENERATOR_DIR = ./test_generators
|
||||
DEPOSIT_CONTRACT_DIR = ./deposit_contract
|
||||
CONFIGS_DIR = ./configs
|
||||
|
||||
# Collect a list of generator names
|
||||
|
@ -21,7 +22,7 @@ PY_SPEC_PHASE_1_DEPS = $(SPEC_DIR)/core/1_*.md
|
|||
PY_SPEC_ALL_TARGETS = $(PY_SPEC_PHASE_0_TARGETS) $(PY_SPEC_PHASE_1_TARGETS)
|
||||
|
||||
|
||||
.PHONY: clean all test citest gen_yaml_tests pyspec phase0 phase1 install_test
|
||||
.PHONY: clean all test citest lint gen_yaml_tests pyspec phase0 phase1 install_test install_deposit_contract_test test_deposit_contract compile_deposit_contract
|
||||
|
||||
all: $(PY_SPEC_ALL_TARGETS) $(YAML_TEST_DIR) $(YAML_TEST_TARGETS)
|
||||
|
||||
|
@ -30,6 +31,7 @@ clean:
|
|||
rm -rf $(GENERATOR_VENVS)
|
||||
rm -rf $(PY_SPEC_DIR)/venv $(PY_SPEC_DIR)/.pytest_cache
|
||||
rm -rf $(PY_SPEC_ALL_TARGETS)
|
||||
rm -rf $(DEPOSIT_CONTRACT_DIR)/venv $(DEPOSIT_CONTRACT_DIR)/.pytest_cache
|
||||
|
||||
# "make gen_yaml_tests" to run generators
|
||||
gen_yaml_tests: $(PY_SPEC_ALL_TARGETS) $(YAML_TEST_TARGETS)
|
||||
|
@ -49,6 +51,17 @@ lint: $(PY_SPEC_ALL_TARGETS)
|
|||
cd $(PY_SPEC_DIR); . venv/bin/activate; \
|
||||
flake8 --ignore=E252,W504,W503 --max-line-length=120 ./eth2spec;
|
||||
|
||||
install_deposit_contract_test: $(PY_SPEC_ALL_TARGETS)
|
||||
cd $(DEPOSIT_CONTRACT_DIR); python3 -m venv venv; . venv/bin/activate; pip3 install -r requirements-testing.txt
|
||||
|
||||
compile_deposit_contract:
|
||||
cd $(DEPOSIT_CONTRACT_DIR); . venv/bin/activate; \
|
||||
python tool/compile_deposit_contract.py contracts/validator_registration.v.py;
|
||||
|
||||
test_deposit_contract:
|
||||
cd $(DEPOSIT_CONTRACT_DIR); . venv/bin/activate; \
|
||||
python -m pytest .
|
||||
|
||||
# "make pyspec" to create the pyspec for all phases.
|
||||
pyspec: $(PY_SPEC_ALL_TARGETS)
|
||||
|
||||
|
|
|
@ -0,0 +1,24 @@
|
|||
# Deposit contract
|
||||
|
||||
## How to set up the testing environment?
|
||||
|
||||
Under the `eth2.0-specs` directory, execute:
|
||||
|
||||
```sh
|
||||
make install_deposit_contract_test
|
||||
```
|
||||
|
||||
## How to compile the contract?
|
||||
|
||||
```sh
|
||||
make compile_deposit_contract
|
||||
```
|
||||
|
||||
The ABI and bytecode will be updated at [`contracts/validator_registration.json`](./contracts/validator_registration.json).
|
||||
|
||||
|
||||
## How to run tests?
|
||||
|
||||
```sh
|
||||
make test_deposit_contract
|
||||
```
|
File diff suppressed because one or more lines are too long
|
@ -0,0 +1,140 @@
|
|||
MIN_DEPOSIT_AMOUNT: constant(uint256) = 1000000000 # Gwei
|
||||
FULL_DEPOSIT_AMOUNT: constant(uint256) = 32000000000 # Gwei
|
||||
CHAIN_START_FULL_DEPOSIT_THRESHOLD: constant(uint256) = 65536 # 2**16
|
||||
DEPOSIT_CONTRACT_TREE_DEPTH: constant(uint256) = 32
|
||||
SECONDS_PER_DAY: constant(uint256) = 86400
|
||||
MAX_64_BIT_VALUE: constant(uint256) = 18446744073709551615 # 2**64 - 1
|
||||
PUBKEY_LENGTH: constant(uint256) = 48 # bytes
|
||||
WITHDRAWAL_CREDENTIALS_LENGTH: constant(uint256) = 32 # bytes
|
||||
SIGNATURE_LENGTH: constant(uint256) = 96 # bytes
|
||||
MAX_DEPOSIT_COUNT: constant(uint256) = 4294967295 # 2**DEPOSIT_CONTRACT_TREE_DEPTH - 1
|
||||
|
||||
Deposit: event({
|
||||
pubkey: bytes[48],
|
||||
withdrawal_credentials: bytes[32],
|
||||
amount: bytes[8],
|
||||
signature: bytes[96],
|
||||
merkle_tree_index: bytes[8],
|
||||
})
|
||||
Eth2Genesis: event({deposit_root: bytes32, deposit_count: bytes[8], time: bytes[8]})
|
||||
|
||||
zerohashes: bytes32[DEPOSIT_CONTRACT_TREE_DEPTH]
|
||||
branch: bytes32[DEPOSIT_CONTRACT_TREE_DEPTH]
|
||||
deposit_count: uint256
|
||||
full_deposit_count: uint256
|
||||
chainStarted: public(bool)
|
||||
|
||||
|
||||
@public
|
||||
def __init__():
|
||||
for i in range(DEPOSIT_CONTRACT_TREE_DEPTH - 1):
|
||||
self.zerohashes[i+1] = sha256(concat(self.zerohashes[i], self.zerohashes[i]))
|
||||
|
||||
|
||||
@public
|
||||
@constant
|
||||
def to_little_endian_64(value: uint256) -> bytes[8]:
|
||||
assert value <= MAX_64_BIT_VALUE
|
||||
|
||||
# array access for bytes[] not currently supported in vyper so
|
||||
# reversing bytes using bitwise uint256 manipulations
|
||||
y: uint256 = 0
|
||||
x: uint256 = value
|
||||
for i in range(8):
|
||||
y = shift(y, 8)
|
||||
y = y + bitwise_and(x, 255)
|
||||
x = shift(x, -8)
|
||||
|
||||
return slice(convert(y, bytes32), start=24, len=8)
|
||||
|
||||
|
||||
@public
|
||||
@constant
|
||||
def get_deposit_root() -> bytes32:
|
||||
root: bytes32 = 0x0000000000000000000000000000000000000000000000000000000000000000
|
||||
size: uint256 = self.deposit_count
|
||||
for h in range(DEPOSIT_CONTRACT_TREE_DEPTH):
|
||||
if bitwise_and(size, 1) == 1:
|
||||
root = sha256(concat(self.branch[h], root))
|
||||
else:
|
||||
root = sha256(concat(root, self.zerohashes[h]))
|
||||
size /= 2
|
||||
return root
|
||||
|
||||
@public
|
||||
@constant
|
||||
def get_deposit_count() -> bytes[8]:
|
||||
return self.to_little_endian_64(self.deposit_count)
|
||||
|
||||
@payable
|
||||
@public
|
||||
def deposit(pubkey: bytes[PUBKEY_LENGTH],
|
||||
withdrawal_credentials: bytes[WITHDRAWAL_CREDENTIALS_LENGTH],
|
||||
signature: bytes[SIGNATURE_LENGTH]):
|
||||
# Prevent edge case in computing `self.branch` when `self.deposit_count == MAX_DEPOSIT_COUNT`
|
||||
# NOTE: reaching this point with the constants as currently defined is impossible due to the
|
||||
# uni-directional nature of transfers from eth1 to eth2 and the total ether supply (< 130M).
|
||||
assert self.deposit_count < MAX_DEPOSIT_COUNT
|
||||
|
||||
assert len(pubkey) == PUBKEY_LENGTH
|
||||
assert len(withdrawal_credentials) == WITHDRAWAL_CREDENTIALS_LENGTH
|
||||
assert len(signature) == SIGNATURE_LENGTH
|
||||
|
||||
deposit_amount: uint256 = msg.value / as_wei_value(1, "gwei")
|
||||
assert deposit_amount >= MIN_DEPOSIT_AMOUNT
|
||||
amount: bytes[8] = self.to_little_endian_64(deposit_amount)
|
||||
|
||||
index: uint256 = self.deposit_count
|
||||
|
||||
# add deposit to merkle tree
|
||||
i: int128 = 0
|
||||
size: uint256 = index + 1
|
||||
for _ in range(DEPOSIT_CONTRACT_TREE_DEPTH):
|
||||
if bitwise_and(size, 1) == 1:
|
||||
break
|
||||
i += 1
|
||||
size /= 2
|
||||
|
||||
zero_bytes_32: bytes32
|
||||
pubkey_root: bytes32 = sha256(concat(pubkey, slice(zero_bytes_32, start=0, len=16)))
|
||||
signature_root: bytes32 = sha256(concat(
|
||||
sha256(slice(signature, start=0, len=64)),
|
||||
sha256(concat(slice(signature, start=64, len=32), zero_bytes_32))
|
||||
))
|
||||
value: bytes32 = sha256(concat(
|
||||
sha256(concat(pubkey_root, withdrawal_credentials)),
|
||||
sha256(concat(
|
||||
amount,
|
||||
slice(zero_bytes_32, start=0, len=24),
|
||||
signature_root,
|
||||
))
|
||||
))
|
||||
for j in range(DEPOSIT_CONTRACT_TREE_DEPTH):
|
||||
if j < i:
|
||||
value = sha256(concat(self.branch[j], value))
|
||||
else:
|
||||
break
|
||||
self.branch[i] = value
|
||||
|
||||
self.deposit_count += 1
|
||||
log.Deposit(
|
||||
pubkey,
|
||||
withdrawal_credentials,
|
||||
amount,
|
||||
signature,
|
||||
self.to_little_endian_64(index),
|
||||
)
|
||||
|
||||
if deposit_amount >= FULL_DEPOSIT_AMOUNT:
|
||||
self.full_deposit_count += 1
|
||||
if self.full_deposit_count == CHAIN_START_FULL_DEPOSIT_THRESHOLD:
|
||||
timestamp_day_boundary: uint256 = (
|
||||
as_unitless_number(block.timestamp) -
|
||||
as_unitless_number(block.timestamp) % SECONDS_PER_DAY +
|
||||
2 * SECONDS_PER_DAY
|
||||
)
|
||||
new_deposit_root: bytes32 = self.get_deposit_root()
|
||||
log.Eth2Genesis(new_deposit_root,
|
||||
self.to_little_endian_64(self.deposit_count),
|
||||
self.to_little_endian_64(timestamp_day_boundary))
|
||||
self.chainStarted = True
|
|
@ -0,0 +1,5 @@
|
|||
eth-tester[py-evm]==0.1.0b39
|
||||
vyper==0.1.0b9
|
||||
web3==5.0.0b2
|
||||
pytest==3.6.1
|
||||
../test_libs/pyspec
|
|
@ -0,0 +1,112 @@
|
|||
from random import (
|
||||
randint,
|
||||
)
|
||||
import re
|
||||
|
||||
import pytest
|
||||
|
||||
import eth_tester
|
||||
from eth_tester import (
|
||||
EthereumTester,
|
||||
PyEVMBackend,
|
||||
)
|
||||
from vyper import (
|
||||
compiler,
|
||||
)
|
||||
from web3 import Web3
|
||||
from web3.providers.eth_tester import (
|
||||
EthereumTesterProvider,
|
||||
)
|
||||
from .utils import (
|
||||
get_deposit_contract_code,
|
||||
get_deposit_contract_json,
|
||||
)
|
||||
|
||||
|
||||
# Constants
|
||||
MIN_DEPOSIT_AMOUNT = 1000000000 # Gwei
|
||||
FULL_DEPOSIT_AMOUNT = 32000000000 # Gwei
|
||||
CHAIN_START_FULL_DEPOSIT_THRESHOLD = 65536 # 2**16
|
||||
DEPOSIT_CONTRACT_TREE_DEPTH = 32
|
||||
TWO_TO_POWER_OF_TREE_DEPTH = 2**DEPOSIT_CONTRACT_TREE_DEPTH
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def tester():
|
||||
return EthereumTester(PyEVMBackend())
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def a0(tester):
|
||||
return tester.get_accounts()[0]
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def w3(tester):
|
||||
web3 = Web3(EthereumTesterProvider(tester))
|
||||
return web3
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def registration_contract(w3, tester):
|
||||
contract_bytecode = get_deposit_contract_json()['bytecode']
|
||||
contract_abi = get_deposit_contract_json()['abi']
|
||||
registration = w3.eth.contract(
|
||||
abi=contract_abi,
|
||||
bytecode=contract_bytecode)
|
||||
tx_hash = registration.constructor().transact()
|
||||
tx_receipt = w3.eth.waitForTransactionReceipt(tx_hash)
|
||||
registration_deployed = w3.eth.contract(
|
||||
address=tx_receipt.contractAddress,
|
||||
abi=contract_abi
|
||||
)
|
||||
return registration_deployed
|
||||
|
||||
|
||||
@pytest.fixture(scope="session")
|
||||
def chain_start_full_deposit_thresholds():
|
||||
return [randint(1, 5), randint(6, 10), randint(11, 15)]
|
||||
|
||||
|
||||
@pytest.fixture(params=[0, 1, 2])
|
||||
def modified_registration_contract(
|
||||
request,
|
||||
w3,
|
||||
tester,
|
||||
chain_start_full_deposit_thresholds):
|
||||
# Set CHAIN_START_FULL_DEPOSIT_THRESHOLD to different threshold t
|
||||
registration_code = get_deposit_contract_code()
|
||||
t = str(chain_start_full_deposit_thresholds[request.param])
|
||||
modified_registration_code = re.sub(
|
||||
r'CHAIN_START_FULL_DEPOSIT_THRESHOLD: constant\(uint256\) = [0-9]+',
|
||||
'CHAIN_START_FULL_DEPOSIT_THRESHOLD: constant(uint256) = ' + t,
|
||||
registration_code,
|
||||
)
|
||||
assert modified_registration_code != registration_code
|
||||
contract_bytecode = compiler.compile_code(modified_registration_code)['bytecode']
|
||||
contract_abi = compiler.mk_full_signature(modified_registration_code)
|
||||
registration = w3.eth.contract(
|
||||
abi=contract_abi,
|
||||
bytecode=contract_bytecode)
|
||||
tx_hash = registration.constructor().transact()
|
||||
tx_receipt = w3.eth.waitForTransactionReceipt(tx_hash)
|
||||
registration_deployed = w3.eth.contract(
|
||||
address=tx_receipt.contractAddress,
|
||||
abi=contract_abi
|
||||
)
|
||||
setattr(
|
||||
registration_deployed,
|
||||
'chain_start_full_deposit_threshold',
|
||||
chain_start_full_deposit_thresholds[request.param]
|
||||
)
|
||||
return registration_deployed
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def assert_tx_failed(tester):
|
||||
def assert_tx_failed(function_to_test, exception=eth_tester.exceptions.TransactionFailed):
|
||||
snapshot_id = tester.take_snapshot()
|
||||
with pytest.raises(exception):
|
||||
function_to_test()
|
||||
tester.revert_to_snapshot(snapshot_id)
|
||||
return assert_tx_failed
|
|
@ -0,0 +1,19 @@
|
|||
from vyper import (
|
||||
compiler,
|
||||
)
|
||||
|
||||
from .utils import (
|
||||
get_deposit_contract_code,
|
||||
get_deposit_contract_json,
|
||||
)
|
||||
|
||||
|
||||
def test_compile_deposit_contract():
|
||||
compiled_deposit_contract_json = get_deposit_contract_json()
|
||||
|
||||
deposit_contract_code = get_deposit_contract_code()
|
||||
abi = compiler.mk_full_signature(deposit_contract_code)
|
||||
bytecode = compiler.compile_code(deposit_contract_code)['bytecode']
|
||||
|
||||
assert abi == compiled_deposit_contract_json["abi"]
|
||||
assert bytecode == compiled_deposit_contract_json["bytecode"]
|
|
@ -0,0 +1,236 @@
|
|||
from random import (
|
||||
randint,
|
||||
)
|
||||
|
||||
import pytest
|
||||
|
||||
import eth_utils
|
||||
from tests.contracts.conftest import (
|
||||
DEPOSIT_CONTRACT_TREE_DEPTH,
|
||||
FULL_DEPOSIT_AMOUNT,
|
||||
MIN_DEPOSIT_AMOUNT,
|
||||
)
|
||||
|
||||
from eth2spec.phase0.spec import (
|
||||
DepositData,
|
||||
)
|
||||
from eth2spec.utils.hash_function import hash
|
||||
from eth2spec.utils.ssz.ssz_impl import (
|
||||
hash_tree_root,
|
||||
)
|
||||
|
||||
|
||||
def compute_merkle_root(leaf_nodes):
|
||||
assert len(leaf_nodes) >= 1
|
||||
empty_node = b'\x00' * 32
|
||||
child_nodes = leaf_nodes[:]
|
||||
for _ in range(DEPOSIT_CONTRACT_TREE_DEPTH):
|
||||
parent_nodes = []
|
||||
if len(child_nodes) % 2 == 1:
|
||||
child_nodes.append(empty_node)
|
||||
for j in range(0, len(child_nodes), 2):
|
||||
parent_nodes.append(hash(child_nodes[j] + child_nodes[j + 1]))
|
||||
child_nodes = parent_nodes
|
||||
empty_node = hash(empty_node + empty_node)
|
||||
return child_nodes[0]
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def deposit_input():
|
||||
"""
|
||||
pubkey: bytes[48]
|
||||
withdrawal_credentials: bytes[32]
|
||||
signature: bytes[96]
|
||||
"""
|
||||
return (
|
||||
b'\x11' * 48,
|
||||
b'\x22' * 32,
|
||||
b'\x33' * 96,
|
||||
)
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
'value,success',
|
||||
[
|
||||
(0, True),
|
||||
(10, True),
|
||||
(55555, True),
|
||||
(2**64 - 1, True),
|
||||
(2**64, False),
|
||||
]
|
||||
)
|
||||
def test_to_little_endian_64(registration_contract, value, success, assert_tx_failed):
|
||||
call = registration_contract.functions.to_little_endian_64(value)
|
||||
|
||||
if success:
|
||||
little_endian_64 = call.call()
|
||||
assert little_endian_64 == (value).to_bytes(8, 'little')
|
||||
else:
|
||||
assert_tx_failed(
|
||||
lambda: call.call()
|
||||
)
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
'success,deposit_amount',
|
||||
[
|
||||
(True, FULL_DEPOSIT_AMOUNT),
|
||||
(True, MIN_DEPOSIT_AMOUNT),
|
||||
(False, MIN_DEPOSIT_AMOUNT - 1),
|
||||
(True, FULL_DEPOSIT_AMOUNT + 1)
|
||||
]
|
||||
)
|
||||
def test_deposit_amount(registration_contract,
|
||||
w3,
|
||||
success,
|
||||
deposit_amount,
|
||||
assert_tx_failed,
|
||||
deposit_input):
|
||||
call = registration_contract.functions.deposit(*deposit_input)
|
||||
if success:
|
||||
assert call.transact({"value": deposit_amount * eth_utils.denoms.gwei})
|
||||
else:
|
||||
assert_tx_failed(
|
||||
lambda: call.transact({"value": deposit_amount * eth_utils.denoms.gwei})
|
||||
)
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
'invalid_pubkey,invalid_withdrawal_credentials,invalid_signature,success',
|
||||
[
|
||||
(False, False, False, True),
|
||||
(True, False, False, False),
|
||||
(False, True, False, False),
|
||||
(False, False, True, False),
|
||||
]
|
||||
)
|
||||
def test_deposit_inputs(registration_contract,
|
||||
w3,
|
||||
assert_tx_failed,
|
||||
deposit_input,
|
||||
invalid_pubkey,
|
||||
invalid_withdrawal_credentials,
|
||||
invalid_signature,
|
||||
success):
|
||||
pubkey = deposit_input[0][2:] if invalid_pubkey else deposit_input[0]
|
||||
if invalid_withdrawal_credentials: # this one is different to satisfy linter
|
||||
withdrawal_credentials = deposit_input[1][2:]
|
||||
else:
|
||||
withdrawal_credentials = deposit_input[1]
|
||||
signature = deposit_input[2][2:] if invalid_signature else deposit_input[2]
|
||||
|
||||
call = registration_contract.functions.deposit(
|
||||
pubkey,
|
||||
withdrawal_credentials,
|
||||
signature,
|
||||
)
|
||||
if success:
|
||||
assert call.transact({"value": FULL_DEPOSIT_AMOUNT * eth_utils.denoms.gwei})
|
||||
else:
|
||||
assert_tx_failed(
|
||||
lambda: call.transact({"value": FULL_DEPOSIT_AMOUNT * eth_utils.denoms.gwei})
|
||||
)
|
||||
|
||||
|
||||
def test_deposit_log(registration_contract, a0, w3, deposit_input):
|
||||
log_filter = registration_contract.events.Deposit.createFilter(
|
||||
fromBlock='latest',
|
||||
)
|
||||
|
||||
deposit_amount_list = [randint(MIN_DEPOSIT_AMOUNT, FULL_DEPOSIT_AMOUNT * 2) for _ in range(3)]
|
||||
for i in range(3):
|
||||
registration_contract.functions.deposit(
|
||||
*deposit_input,
|
||||
).transact({"value": deposit_amount_list[i] * eth_utils.denoms.gwei})
|
||||
|
||||
logs = log_filter.get_new_entries()
|
||||
assert len(logs) == 1
|
||||
log = logs[0]['args']
|
||||
|
||||
assert log['pubkey'] == deposit_input[0]
|
||||
assert log['withdrawal_credentials'] == deposit_input[1]
|
||||
assert log['amount'] == deposit_amount_list[i].to_bytes(8, 'little')
|
||||
assert log['signature'] == deposit_input[2]
|
||||
assert log['merkle_tree_index'] == i.to_bytes(8, 'little')
|
||||
|
||||
|
||||
def test_deposit_tree(registration_contract, w3, assert_tx_failed, deposit_input):
|
||||
log_filter = registration_contract.events.Deposit.createFilter(
|
||||
fromBlock='latest',
|
||||
)
|
||||
|
||||
deposit_amount_list = [randint(MIN_DEPOSIT_AMOUNT, FULL_DEPOSIT_AMOUNT * 2) for _ in range(10)]
|
||||
leaf_nodes = []
|
||||
for i in range(0, 10):
|
||||
tx_hash = registration_contract.functions.deposit(
|
||||
*deposit_input,
|
||||
).transact({"value": deposit_amount_list[i] * eth_utils.denoms.gwei})
|
||||
receipt = w3.eth.getTransactionReceipt(tx_hash)
|
||||
print("deposit transaction consumes %d gas" % receipt['gasUsed'])
|
||||
|
||||
logs = log_filter.get_new_entries()
|
||||
assert len(logs) == 1
|
||||
log = logs[0]['args']
|
||||
|
||||
assert log["merkle_tree_index"] == i.to_bytes(8, 'little')
|
||||
|
||||
deposit_data = DepositData(
|
||||
pubkey=deposit_input[0],
|
||||
withdrawal_credentials=deposit_input[1],
|
||||
amount=deposit_amount_list[i],
|
||||
signature=deposit_input[2],
|
||||
)
|
||||
hash_tree_root_result = hash_tree_root(deposit_data)
|
||||
leaf_nodes.append(hash_tree_root_result)
|
||||
root = compute_merkle_root(leaf_nodes)
|
||||
assert root == registration_contract.functions.get_deposit_root().call()
|
||||
|
||||
|
||||
def test_chain_start(modified_registration_contract, w3, assert_tx_failed, deposit_input):
|
||||
t = getattr(modified_registration_contract, 'chain_start_full_deposit_threshold')
|
||||
# CHAIN_START_FULL_DEPOSIT_THRESHOLD is set to t
|
||||
min_deposit_amount = MIN_DEPOSIT_AMOUNT * eth_utils.denoms.gwei # in wei
|
||||
full_deposit_amount = FULL_DEPOSIT_AMOUNT * eth_utils.denoms.gwei
|
||||
log_filter = modified_registration_contract.events.Eth2Genesis.createFilter(
|
||||
fromBlock='latest',
|
||||
)
|
||||
|
||||
index_not_full_deposit = randint(0, t - 1)
|
||||
for i in range(t):
|
||||
if i == index_not_full_deposit:
|
||||
# Deposit with value below FULL_DEPOSIT_AMOUNT
|
||||
modified_registration_contract.functions.deposit(
|
||||
*deposit_input,
|
||||
).transact({"value": min_deposit_amount})
|
||||
logs = log_filter.get_new_entries()
|
||||
# Eth2Genesis event should not be triggered
|
||||
assert len(logs) == 0
|
||||
else:
|
||||
# Deposit with value FULL_DEPOSIT_AMOUNT
|
||||
modified_registration_contract.functions.deposit(
|
||||
*deposit_input,
|
||||
).transact({"value": full_deposit_amount})
|
||||
logs = log_filter.get_new_entries()
|
||||
# Eth2Genesis event should not be triggered
|
||||
assert len(logs) == 0
|
||||
|
||||
# Make 1 more deposit with value FULL_DEPOSIT_AMOUNT to trigger Eth2Genesis event
|
||||
modified_registration_contract.functions.deposit(
|
||||
*deposit_input,
|
||||
).transact({"value": full_deposit_amount})
|
||||
logs = log_filter.get_new_entries()
|
||||
assert len(logs) == 1
|
||||
timestamp = int(w3.eth.getBlock(w3.eth.blockNumber)['timestamp'])
|
||||
timestamp_day_boundary = timestamp + (86400 - timestamp % 86400) + 86400
|
||||
log = logs[0]['args']
|
||||
assert log['deposit_root'] == modified_registration_contract.functions.get_deposit_root().call()
|
||||
assert int.from_bytes(log['time'], byteorder='little') == timestamp_day_boundary
|
||||
assert modified_registration_contract.functions.chainStarted().call() is True
|
||||
|
||||
# Make 1 deposit with value FULL_DEPOSIT_AMOUNT and
|
||||
# check that Eth2Genesis event is not triggered
|
||||
modified_registration_contract.functions.deposit(
|
||||
*deposit_input,
|
||||
).transact({"value": full_deposit_amount})
|
||||
logs = log_filter.get_new_entries()
|
||||
assert len(logs) == 0
|
|
@ -0,0 +1,16 @@
|
|||
import json
|
||||
import os
|
||||
|
||||
DIR = os.path.dirname(__file__)
|
||||
|
||||
|
||||
def get_deposit_contract_code():
|
||||
file_path = os.path.join(DIR, './../../contracts/validator_registration.v.py')
|
||||
deposit_contract_code = open(file_path).read()
|
||||
return deposit_contract_code
|
||||
|
||||
|
||||
def get_deposit_contract_json():
|
||||
file_path = os.path.join(DIR, './../../contracts/validator_registration.json')
|
||||
deposit_contract_json = open(file_path).read()
|
||||
return json.loads(deposit_contract_json)
|
|
@ -0,0 +1,33 @@
|
|||
import argparse
|
||||
import json
|
||||
import os
|
||||
|
||||
from vyper import (
|
||||
compiler,
|
||||
)
|
||||
|
||||
DIR = os.path.dirname(__file__)
|
||||
|
||||
|
||||
def generate_compiled_json(file_path: str):
|
||||
deposit_contract_code = open(file_path).read()
|
||||
abi = compiler.mk_full_signature(deposit_contract_code)
|
||||
bytecode = compiler.compile_code(deposit_contract_code)['bytecode']
|
||||
contract_json = {
|
||||
'abi': abi,
|
||||
'bytecode': bytecode,
|
||||
}
|
||||
# write json
|
||||
basename = os.path.basename(file_path)
|
||||
dirname = os.path.dirname(file_path)
|
||||
contract_name = basename.split('.')[0]
|
||||
with open(dirname + "/{}.json".format(contract_name), 'w') as f_write:
|
||||
json.dump(contract_json, f_write)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
parser = argparse.ArgumentParser()
|
||||
parser.add_argument("path", type=str, help="the path of the contract")
|
||||
args = parser.parse_args()
|
||||
path = args.path
|
||||
generate_compiled_json(path)
|
Loading…
Reference in New Issue