Skip to content

Commit

Permalink
feat(contracts): bytecode verification in contract native (#350)
Browse files Browse the repository at this point in the history
* update validate bytecode method

* update test for compiler validate bc

* use validate bc in contract native when using at method

* add test case for validate bc

* update docs

closes #350
  • Loading branch information
shekhar-shubhendu committed Jan 27, 2020
1 parent 7c9da39 commit 1bd7929
Show file tree
Hide file tree
Showing 6 changed files with 35 additions and 6 deletions.
5 changes: 4 additions & 1 deletion aeternity/compiler.py
Original file line number Diff line number Diff line change
Expand Up @@ -115,7 +115,10 @@ def validate_bytecode(self, sourcecode, bytecode, compiler_options={}):
"bytecode": bytecode,
"options": compiler_options
}
return self.compiler_cli.validate_byte_code(body=body)
result = self.compiler_cli.validate_byte_code(body=body)
if result != {}:
raise CompilerError(result["message"])
return result

@staticmethod
def decode_bytecode(compiled):
Expand Down
8 changes: 7 additions & 1 deletion aeternity/contract_native.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,8 @@ def __init__(self, **kwargs):
:param use_dry_run: use dry run for all method calls except for payable and stateful methods (default: True)
"""
if 'client' in kwargs:
self.contract = Contract(kwargs.get('client'))
self.client = kwargs.get('client')
self.contract = Contract(self.client)
else:
raise ValueError("Node client is not provided")
self.compiler = kwargs.get('compiler', None)
Expand Down Expand Up @@ -132,9 +133,14 @@ def at(self, address):
raise ValueError(f"Invalid contract address {address}")
if not self.contract.is_deployed(address):
raise ValueError("Contract not deployed")
self._compare_bytecode(address)
self.address = address
self.deployed = True

def _compare_bytecode(self, address):
onchain_bc = self.client.get_contract_code(pubkey=address).bytecode
self.compiler.validate_bytecode(self.source, onchain_bc)

def set_account(self, account):
if account is None:
raise ValueError("Account can not be of None type")
Expand Down
4 changes: 2 additions & 2 deletions docs/intro/tutorial02-contracts-call.rst
Original file line number Diff line number Diff line change
Expand Up @@ -86,8 +86,8 @@ you can disable it by passing `use-dry-run=False` to the `ContractNative` constr
Now pass the address of the deployed contract

.. warning::
If the contract is not found at the provided address and for
the given network, the method will fail
If the contract is not found at the provided address or the on-chain bytecode
does not match, for the given network, the method will fail.

.. literalinclude:: ../../tests/test_tutorial05-contracts.py
:lines: 62-63
Expand Down
2 changes: 1 addition & 1 deletion docs/intro/tutorial03-aens.rst
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
======================
Claimin a name
Claiming a name
======================

This guide describes how you can leverage aepp-sdk to claim a name
Expand Down
10 changes: 9 additions & 1 deletion tests/test_compiler.py
Original file line number Diff line number Diff line change
Expand Up @@ -133,10 +133,18 @@ def test_sophia_validate_bytecode(compiler_fixture, chain_fixture):

chain_bytecode = node_client.get_contract_code(pubkey=contract_native.address).bytecode
result = compiler.validate_bytecode(sourcecode, chain_bytecode)
print(f"Contract Validation Result: {result}")

assert result == {}

sourcecode_identity = """contract Identity =
entrypoint main(x : int) = x
entrypoint mainString(x : string) = x"""

try:
result = compiler.validate_bytecode(sourcecode_identity, chain_bytecode)
raise RuntimeError("Method call should fail")
except Exception as e:
assert str(e) == 'Invalid contract'

@pytest.mark.skip("to be verified")
def test_sophia_decode_calldata_sourcecode(compiler_fixture):
Expand Down
12 changes: 12 additions & 0 deletions tests/test_contract_native.py
Original file line number Diff line number Diff line change
Expand Up @@ -193,14 +193,26 @@ def test_contract_native_dry_run_without_account(compiler_fixture, chain_fixture

def test_contract_native_verify_contract_id(compiler_fixture, chain_fixture):
identity_contract = "contract Identity =\n entrypoint main(x : int) = x"
identity_contract_2 = """contract Identity =
entrypoint main(x : int) = x
entrypoint mainString(x : string) = x"""
compiler = compiler_fixture.COMPILER
account = chain_fixture.ALICE
contract_native = ContractNative(client=chain_fixture.NODE_CLI, source=identity_contract, compiler=compiler, account=account)
contract_native.deploy()
contract_native.at(contract_native.address)

contract_native_2 = ContractNative(client=chain_fixture.NODE_CLI, source=identity_contract_2, compiler=compiler, account=account)
contract_native_2.deploy()

INVALID_ADDRESS = 'ct_M9yohHgcLjhpp1Z8SaA1UTmRMQzR4FWjJHajGga8KBoZTEPwC'

try:
contract_native_2.at(contract_native.address)
raise RuntimeError("Method call should fail")
except Exception as e:
assert str(e) == 'Invalid contract'

try:
contract_native.at(INVALID_ADDRESS)
raise RuntimeError("Method call should fail")
Expand Down

0 comments on commit 1bd7929

Please sign in to comment.