Merge pull request #1127 from ethereum/deposit_contract

Move deposit contract back
This commit is contained in:
Diederik Loerakker 2019-06-08 13:35:40 +02:00 committed by GitHub
commit 6f82480df2
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
14 changed files with 664 additions and 19 deletions

View File

@ -1,7 +1,7 @@
version: 2.1 version: 2.1
commands: commands:
restore_cached_venv: restore_cached_venv:
description: "Restores a cached venv" description: "Restore a cached venv"
parameters: parameters:
reqs_checksum: reqs_checksum:
type: string type: string
@ -16,7 +16,7 @@ commands:
# fallback to using the latest cache if no exact match is found # fallback to using the latest cache if no exact match is found
- << parameters.venv_name >>-venv- - << parameters.venv_name >>-venv-
save_cached_venv: save_cached_venv:
description: "Saves a venv into a cache" description: "Save a venv into a cache"
parameters: parameters:
reqs_checksum: reqs_checksum:
type: string type: string
@ -31,6 +31,32 @@ commands:
- save_cache: - save_cache:
key: << parameters.venv_name >>-venv-<< parameters.reqs_checksum >> key: << parameters.venv_name >>-venv-<< parameters.reqs_checksum >>
paths: << parameters.venv_path >> 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: jobs:
checkout_specs: checkout_specs:
docker: docker:
@ -52,23 +78,18 @@ jobs:
key: v1-specs-repo-{{ .Branch }}-{{ .Revision }} key: v1-specs-repo-{{ .Branch }}-{{ .Revision }}
paths: paths:
- ~/specs-repo - ~/specs-repo
install_env: install_pyspec_test:
docker: docker:
- image: circleci/python:3.6 - image: circleci/python:3.6
working_directory: ~/specs-repo working_directory: ~/specs-repo
steps: steps:
- restore_cache: - restore_cache:
key: v1-specs-repo-{{ .Branch }}-{{ .Revision }} key: v1-specs-repo-{{ .Branch }}-{{ .Revision }}
- restore_cached_venv: - restore_pyspec_cached_venv
venv_name: v2-pyspec
reqs_checksum: '{{ checksum "test_libs/pyspec/requirements.txt" }}-{{ checksum "test_libs/pyspec/requirements-testing.txt" }}'
- run: - run:
name: Install pyspec requirements name: Install pyspec requirements
command: make install_test command: make install_test
- save_cached_venv: - save_pyspec_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
test: test:
docker: docker:
- image: circleci/python:3.6 - image: circleci/python:3.6
@ -76,9 +97,7 @@ jobs:
steps: steps:
- restore_cache: - restore_cache:
key: v1-specs-repo-{{ .Branch }}-{{ .Revision }} key: v1-specs-repo-{{ .Branch }}-{{ .Revision }}
- restore_cached_venv: - restore_pyspec_cached_venv
venv_name: v2-pyspec
reqs_checksum: '{{ checksum "test_libs/pyspec/requirements.txt" }}-{{ checksum "test_libs/pyspec/requirements-testing.txt" }}'
- run: - run:
name: Run py-tests name: Run py-tests
command: make citest command: make citest
@ -91,23 +110,50 @@ jobs:
steps: steps:
- restore_cache: - restore_cache:
key: v1-specs-repo-{{ .Branch }}-{{ .Revision }} key: v1-specs-repo-{{ .Branch }}-{{ .Revision }}
- restore_cached_venv: - restore_pyspec_cached_venv
venv_name: v2-pyspec
reqs_checksum: '{{ checksum "test_libs/pyspec/requirements.txt" }}-{{ checksum "test_libs/pyspec/requirements-testing.txt" }}'
- run: - run:
name: Run linter name: Run linter
command: make lint 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: workflows:
version: 2.1 version: 2.1
test_spec: test_spec:
jobs: jobs:
- checkout_specs - checkout_specs
- install_env: - install_pyspec_test:
requires: requires:
- checkout_specs - checkout_specs
- test: - test:
requires: requires:
- install_env - install_pyspec_test
- lint: - lint:
requires: requires:
- test - test
- install_deposit_contract_test:
requires:
- checkout_specs
- deposit_contract:
requires:
- install_deposit_contract_test

View File

@ -4,6 +4,7 @@ TEST_LIBS_DIR = ./test_libs
PY_SPEC_DIR = $(TEST_LIBS_DIR)/pyspec PY_SPEC_DIR = $(TEST_LIBS_DIR)/pyspec
YAML_TEST_DIR = ./eth2.0-spec-tests/tests YAML_TEST_DIR = ./eth2.0-spec-tests/tests
GENERATOR_DIR = ./test_generators GENERATOR_DIR = ./test_generators
DEPOSIT_CONTRACT_DIR = ./deposit_contract
CONFIGS_DIR = ./configs CONFIGS_DIR = ./configs
# Collect a list of generator names # 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) 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) all: $(PY_SPEC_ALL_TARGETS) $(YAML_TEST_DIR) $(YAML_TEST_TARGETS)
@ -30,6 +31,7 @@ clean:
rm -rf $(GENERATOR_VENVS) rm -rf $(GENERATOR_VENVS)
rm -rf $(PY_SPEC_DIR)/venv $(PY_SPEC_DIR)/.pytest_cache rm -rf $(PY_SPEC_DIR)/venv $(PY_SPEC_DIR)/.pytest_cache
rm -rf $(PY_SPEC_ALL_TARGETS) rm -rf $(PY_SPEC_ALL_TARGETS)
rm -rf $(DEPOSIT_CONTRACT_DIR)/venv $(DEPOSIT_CONTRACT_DIR)/.pytest_cache
# "make gen_yaml_tests" to run generators # "make gen_yaml_tests" to run generators
gen_yaml_tests: $(PY_SPEC_ALL_TARGETS) $(YAML_TEST_TARGETS) 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; \ cd $(PY_SPEC_DIR); . venv/bin/activate; \
flake8 --ignore=E252,W504,W503 --max-line-length=120 ./eth2spec; 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. # "make pyspec" to create the pyspec for all phases.
pyspec: $(PY_SPEC_ALL_TARGETS) pyspec: $(PY_SPEC_ALL_TARGETS)

View File

@ -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
```

View File

File diff suppressed because one or more lines are too long

View File

@ -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

View File

@ -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

View File

View File

@ -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

View File

@ -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"]

View File

@ -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

View File

@ -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)

View File

@ -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)