Skip to content

Commit

Permalink
Started adding tests
Browse files Browse the repository at this point in the history
  • Loading branch information
smartcontracts committed Jul 15, 2018
1 parent 658007a commit 40326f7
Show file tree
Hide file tree
Showing 18 changed files with 269 additions and 4 deletions.
21 changes: 20 additions & 1 deletion .gitignore
@@ -1 +1,20 @@
env*
# Bytecode
**/__pycache__/

# Development
env*/
.vscode/
.DS_Store

# Distibution
dist/
*.egg-info/

# Testing
.cache/
.pytest_cache/
contract_data/

# Artifacts
*.pyc
build/
2 changes: 1 addition & 1 deletion Makefile
Expand Up @@ -26,7 +26,7 @@ clean-pyc:

.PHONY: lint
lint:
flake8 contracts utilities
flake8 plasma plasma_core tests

.PHONY: test
test:
Expand Down
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
2 changes: 1 addition & 1 deletion contracts/RootChain.sol → plasma/contracts/RootChain.sol
Expand Up @@ -96,7 +96,7 @@ contract RootChain {
timestamp: block.timestamp
});

emit DepositCreated(msg.sender, msg.value, currentBlockNumber);
emit DepositCreated(msg.sender, msg.value, currentPlasmaBlockNumber);
currentPlasmaBlockNumber = currentPlasmaBlockNumber.add(1);
}

Expand Down
File renamed without changes.
File renamed without changes.
5 changes: 5 additions & 0 deletions plasma_core/account.py
@@ -0,0 +1,5 @@
class EthereumAccount(object):

def __init__(self, address, key):
self.address = address
self.key = key
3 changes: 3 additions & 0 deletions plasma_core/constants.py
@@ -0,0 +1,3 @@
NULL_BYTE = b'\x00'
NULL_ADDRESS = NULL_BYTE * 20
NULL_HASH = NULL_BYTE * 32
Empty file added plasma_core/utils/__init__.py
Empty file.
6 changes: 6 additions & 0 deletions plasma_core/utils/address.py
@@ -0,0 +1,6 @@
def address_to_hex(address):
return '0x' + address.hex()


def address_to_bytes(address):
return bytes.fromhex(address[2:])
149 changes: 149 additions & 0 deletions plasma_core/utils/deployer.py
@@ -0,0 +1,149 @@
import os
import json
from solc import compile_standard
from web3 import Web3, HTTPProvider
from web3.contract import ConciseContract


OUTPUT_DIR = 'contract_data'


class Deployer(object):

def __init__(self, contracts_dir, w3=Web3(HTTPProvider('http://localhost:8545'))):
self.contracts_dir = contracts_dir
self.w3 = w3

def get_solc_input(self):
"""Walks the contract directory and returns a Solidity input dict
Learn more about Solidity input JSON here: https://goo.gl/7zKBvj
Returns:
dict: A Solidity input JSON object as a dict
"""

solc_input = {
'language': 'Solidity',
'sources': {
file_name: {
'urls': [os.path.realpath(os.path.join(r, file_name))]
} for r, d, f in os.walk(self.contracts_dir) for file_name in f
},
'settings': {
'outputSelection': {
"*": {
"": [
"legacyAST",
"ast"
],
"*": [
"abi",
"evm.bytecode.object",
"evm.bytecode.sourceMap",
"evm.deployedBytecode.object",
"evm.deployedBytecode.sourceMap"
]
}
}
}
}

return solc_input

def compile_all(self):
"""Compiles all of the contracts in the /contracts directory
Creates {contract name}.json files in /build that contain
the build output for each contract.
"""

# Solidity input JSON
solc_input = self.get_solc_input()

# Compile the contracts
compilation_result = compile_standard(solc_input, allow_paths=self.contracts_dir)

# Create the output folder if it doesn't already exist
os.makedirs(OUTPUT_DIR, exist_ok=True)

# Write the contract ABI to output files
compiled_contracts = compilation_result['contracts']
for contract_file in compiled_contracts:
for contract in compiled_contracts[contract_file]:
contract_name = contract.split('.')[0]
contract_data = compiled_contracts[contract_file][contract_name]

contract_data_path = OUTPUT_DIR + '/{0}.json'.format(contract_name)
with open(contract_data_path, "w+") as contract_data_file:
json.dump(contract_data, contract_data_file)

@staticmethod
def get_contract_data(contract_name):
"""Returns the contract data for a given contract
Args:
contract_name (str): Name of the contract to return.
Returns:
str, str: ABI and bytecode of the contract
"""

contract_data_path = OUTPUT_DIR + '/{0}.json'.format(contract_name)
with open(contract_data_path, 'r') as contract_data_file:
contract_data = json.load(contract_data_file)

abi = contract_data['abi']
bytecode = contract_data['evm']['bytecode']['object']

return abi, bytecode

def deploy_contract(self, contract_name, gas=5000000, args=(), concise=True):
"""Deploys a contract to the given Ethereum network using Web3
Args:
contract_name (str): Name of the contract to deploy. Must already be compiled.
provider (HTTPProvider): The Web3 provider to deploy with.
gas (int): Amount of gas to use when creating the contract.
args (obj): Any additional arguments to include with the contract creation.
concise (bool): Whether to return a Contract or ConciseContract instance.
Returns:
Contract: A Web3 contract instance.
"""

abi, bytecode = self.get_contract_data(contract_name)

contract = self.w3.eth.contract(abi=abi, bytecode=bytecode)

# Get transaction hash from deployed contract
tx_hash = contract.deploy(transaction={
'from': self.w3.eth.accounts[0],
'gas': gas
}, args=args)

# Get tx receipt to get contract address
tx_receipt = self.w3.eth.getTransactionReceipt(tx_hash)
contract_address = tx_receipt['contractAddress']

contract_instance = self.w3.eth.contract(address=contract_address, abi=abi)

return ConciseContract(contract_instance) if concise else contract_instance

def get_contract_at_address(self, contract_name, address, concise=True):
"""Returns a Web3 instance of the given contract at the given address
Args:
contract_name (str): Name of the contract. Must already be compiled.
address (str): Address of the contract.
concise (bool): Whether to return a Contract or ConciseContract instance.
Returns:
Contract: A Web3 contract instance.
"""

abi, _ = self.get_contract_data(contract_name)

contract_instance = self.w3.eth.contract(abi=abi, address=address)

return ConciseContract(contract_instance) if concise else contract_instance
3 changes: 2 additions & 1 deletion setup.py
Expand Up @@ -25,6 +25,7 @@
install_requires=[
'ethereum==2.3.0',
'rlp==0.6.0',
'py-solc==3.1.0'
'py-solc==3.1.0',
'web3==4.4.1'
]
)
55 changes: 55 additions & 0 deletions tests/conftest.py
@@ -0,0 +1,55 @@
import os
import pytest
from ethereum import utils
from ethereum.abi import ContractTranslator
from ethereum.tools import tester
from ethereum.config import config_metropolis
from plasma_core.account import EthereumAccount
from plasma_core.utils.deployer import Deployer
from plasma_core.utils.address import address_to_hex


GAS_LIMIT = 8000000
START_GAS = GAS_LIMIT - 1000000
config_metropolis['BLOCK_GAS_LIMIT'] = GAS_LIMIT


OWN_DIR = os.path.dirname(os.path.realpath(__file__))
CONTRACTS_DIR = os.path.abspath(os.path.realpath(os.path.join(OWN_DIR, '../plasma/contracts')))
deployer = Deployer(CONTRACTS_DIR)
deployer.compile_all()


@pytest.fixture
def ethutils():
return utils


@pytest.fixture
def ethtester():
tester.chain = tester.Chain()
tester.accounts = []
for i in range(10):
address = getattr(tester, 'a{0}'.format(i))
key = getattr(tester, 'k{0}'.format(i))
tester.accounts.append(EthereumAccount(address_to_hex(address), key))
return tester


@pytest.fixture
def get_contract(ethtester, ethutils):
def create_contract(path, args=(), sender=ethtester.k0):
abi, hexcode = deployer.get_contract_data(path)
bytecode = ethutils.decode_hex(hexcode)
encoded_args = (ContractTranslator(abi).encode_constructor_arguments(args) if args else b'')
code = bytecode + encoded_args
address = ethtester.chain.tx(sender=sender, to=b'', startgas=START_GAS, data=code)
return ethtester.ABIContract(ethtester.chain, abi, address)
return create_contract


@pytest.fixture
def root_chain(ethtester, get_contract):
contract = get_contract('RootChain')
ethtester.chain.mine()
return contract
27 changes: 27 additions & 0 deletions tests/contracts/root_chain/test_commit_plasma_block_root.py
@@ -0,0 +1,27 @@
import pytest
from ethereum.tools.tester import TransactionFailed
from plasma_core.constants import NULL_HASH


def test_commit_plasma_block_root_should_succeed(root_chain, ethtester, ethutils):
random_hash = ethutils.sha3('abc123')
operator = ethtester.accounts[0]
root_chain.commitPlasmaBlockRoot(random_hash, sender=operator.key)

plasma_block_root = root_chain.plasmaBlockRoots(0)
assert plasma_block_root[0] == random_hash
assert plasma_block_root[1] == ethtester.chain.head_state.timestamp
assert root_chain.currentPlasmaBlockNumber() == 1


def test_commit_plasma_block_root_not_operator_should_fail(root_chain, ethtester, ethutils):
random_hash = ethutils.sha3('abc123')
non_operator = ethtester.accounts[1]

with pytest.raises(TransactionFailed):
root_chain.commitPlasmaBlockRoot(random_hash, sender=non_operator.key)

plasma_block_root = root_chain.plasmaBlockRoots(0)
assert plasma_block_root[0] == NULL_HASH
assert plasma_block_root[1] == 0
assert root_chain.currentPlasmaBlockNumber() == 0

0 comments on commit 40326f7

Please sign in to comment.