From 219c24b624377585bac39a7d8e2907debc09e689 Mon Sep 17 00:00:00 2001 From: nolash Date: Thu, 29 Apr 2021 08:35:53 +0200 Subject: [PATCH] Add EIP165 support --- chainlib/eth/contract.py | 10 ++++- chainlib/eth/eip165.py | 38 ++++++++++++++++++ chainlib/eth/tx.py | 1 + chainlib/eth/unittest/base.py | 18 +++++++-- chainlib/eth/unittest/ethtester.py | 11 ++++-- setup.cfg | 2 +- tests/test_eip165.py | 62 ++++++++++++++++++++++++++++++ tests/testdata/Supports.bin | 1 + tests/testdata/Supports.sol | 10 +++++ 9 files changed, 144 insertions(+), 9 deletions(-) create mode 100644 chainlib/eth/eip165.py create mode 100644 tests/test_eip165.py create mode 100644 tests/testdata/Supports.bin create mode 100644 tests/testdata/Supports.sol diff --git a/chainlib/eth/contract.py b/chainlib/eth/contract.py index 9a515d6..4ab234b 100644 --- a/chainlib/eth/contract.py +++ b/chainlib/eth/contract.py @@ -24,6 +24,7 @@ re_method = r'^[a-zA-Z0-9_]+$' class ABIContractType(enum.Enum): BYTES32 = 'bytes32' + BYTES4 = 'bytes4' UINT256 = 'uint256' ADDRESS = 'address' STRING = 'string' @@ -154,6 +155,12 @@ class ABIContractEncoder: self.__log_latest(v) + def bytes4(self, v): + self.bytes_fixed(4, v) + self.types.append(ABIContractType.BYTES4) + self.__log_latest(v) + + def string(self, v): b = v.encode('utf-8') l = len(b) @@ -186,8 +193,7 @@ class ABIContractEncoder: v = pad(b.hex(), mx) else: raise ValueError('invalid input {}'.format(typ)) - self.contents.append(v) - + self.contents.append(v.ljust(64, '0')) def get_method(self): diff --git a/chainlib/eth/eip165.py b/chainlib/eth/eip165.py new file mode 100644 index 0000000..5d9bfaf --- /dev/null +++ b/chainlib/eth/eip165.py @@ -0,0 +1,38 @@ +# standard imports +from chainlib.eth.constant import ZERO_ADDRESS + +# external imports +from chainlib.jsonrpc import ( + jsonrpc_template, + ) +from hexathon import ( + add_0x, + ) +from chainlib.eth.contract import ( + ABIContractEncoder, + ABIContractDecoder, + ABIContractType, + abi_decode_single, + ) +from chainlib.eth.tx import TxFactory + + +class EIP165(TxFactory): + + def supports_interface(self, contract_address, interface_sum, sender_address=ZERO_ADDRESS): + o = jsonrpc_template() + o['method'] = 'eth_call' + enc = ABIContractEncoder() + enc.method('supportsInterface') + enc.typ(ABIContractType.BYTES4) + enc.bytes4(interface_sum) + data = add_0x(enc.get()) + tx = self.template(sender_address, contract_address) + tx = self.set_code(tx, data) + o['params'].append(self.normalize(tx)) + return o + + + @classmethod + def parse_supports_interface(self, v): + return abi_decode_single(ABIContractType.BOOLEAN, v) diff --git a/chainlib/eth/tx.py b/chainlib/eth/tx.py index cb12a31..647134b 100644 --- a/chainlib/eth/tx.py +++ b/chainlib/eth/tx.py @@ -168,6 +168,7 @@ def transaction(hsh): o['params'].append(add_0x(hsh)) return o + def transaction_by_block(hsh, idx): o = jsonrpc_template() o['method'] = 'eth_getTransactionByBlockHashAndIndex' diff --git a/chainlib/eth/unittest/base.py b/chainlib/eth/unittest/base.py index 74e669e..48eb811 100644 --- a/chainlib/eth/unittest/base.py +++ b/chainlib/eth/unittest/base.py @@ -27,7 +27,7 @@ from crypto_dev_signer.eth.signer import ReferenceSigner as EIP155Signer from crypto_dev_signer.encoding import private_key_to_address -logg = logging.getLogger() +logg = logging.getLogger().getChild(__name__) test_pk = bytes.fromhex('5087503f0a9cc35b38665955eb830c63f778453dd11b8fa5bd04bc41fd2cc6d6') @@ -99,8 +99,12 @@ class TestRPCConnection(RPCConnection): def eth_getTransactionByBlock(self, p): block = self.eth_getBlockByHash(p) - tx_hash = block['transactions'][p[1]] - tx = self.eth_getTransaction([tx_hash]) + try: + tx_index = int(p[1], 16) + except TypeError: + tx_index = int(p[1]) + tx_hash = block['transactions'][tx_index] + tx = self.eth_getTransactionByHash([tx_hash]) return tx def eth_getBalance(self, p): @@ -120,6 +124,14 @@ class TestRPCConnection(RPCConnection): return tx + def eth_getTransactionByBlockHashAndIndex(self, p): + #logg.debug('p {}'.format(p)) + #block = self.eth_getBlockByHash(p[0]) + #tx = block.transactions[p[1]] + #return eth_getTransactionByHash(tx[0]) + return self.eth_getTransactionByBlock(p) + + def eth_getTransactionReceipt(self, p): rcpt = self.backend.get_transaction_receipt(p[0]) if rcpt.get('block_number') == None: diff --git a/chainlib/eth/unittest/ethtester.py b/chainlib/eth/unittest/ethtester.py index 1a918f4..ed3eba2 100644 --- a/chainlib/eth/unittest/ethtester.py +++ b/chainlib/eth/unittest/ethtester.py @@ -19,7 +19,10 @@ from .base import ( EthTesterSigner, TestRPCConnection, ) -from chainlib.connection import RPCConnection +from chainlib.connection import ( + RPCConnection, + ConnType, + ) from chainlib.eth.address import to_checksum_address from chainlib.chain import ChainSpec @@ -66,8 +69,10 @@ class EthTesterCase(unittest.TestCase): def rpc_with_tester(chain_spec=self.chain_spec, url=None): return self.rpc - RPCConnection.register_location('custom', self.chain_spec, tag='default', constructor=rpc_with_tester, exist_ok=True) - RPCConnection.register_location('custom', self.chain_spec, tag='signer', constructor=rpc_with_tester, exist_ok=True) + RPCConnection.register_constructor(ConnType.CUSTOM, rpc_with_tester, tag='default') + RPCConnection.register_constructor(ConnType.CUSTOM, rpc_with_tester, tag='signer') + RPCConnection.register_location('custom', self.chain_spec, tag='default', exist_ok=True) + RPCConnection.register_location('custom', self.chain_spec, tag='signer', exist_ok=True) diff --git a/setup.cfg b/setup.cfg index c32facb..12d0234 100644 --- a/setup.cfg +++ b/setup.cfg @@ -1,6 +1,6 @@ [metadata] name = chainlib -version = 0.0.2a19 +version = 0.0.2b1 description = Generic blockchain access library and tooling author = Louis Holbrook author_email = dev@holbrook.no diff --git a/tests/test_eip165.py b/tests/test_eip165.py new file mode 100644 index 0000000..c76f322 --- /dev/null +++ b/tests/test_eip165.py @@ -0,0 +1,62 @@ +# standard imports +import unittest +import os +import logging + +# local imports +from chainlib.eth.unittest.ethtester import EthTesterCase +from chainlib.eth.nonce import RPCNonceOracle +from chainlib.eth.gas import OverrideGasOracle +from chainlib.connection import RPCConnection +from chainlib.eth.tx import ( + TxFactory, + receipt, + ) +from chainlib.eth.eip165 import EIP165 + +logging.basicConfig(level=logging.DEBUG) +logg = logging.getLogger() + +script_dir = os.path.realpath(os.path.dirname(__file__)) + + +class TestSupports(EthTesterCase): + + def setUp(self): + super(TestSupports, self).setUp() + #nonce_oracle = TestNonceOracle(self.accounts[0]) + self.conn = RPCConnection.connect(self.chain_spec, 'default') + nonce_oracle = RPCNonceOracle(self.accounts[0], self.conn) + + f = open(os.path.join(script_dir, 'testdata', 'Supports.bin')) + code = f.read() + f.close() + + txf = TxFactory(self.chain_spec, signer=self.signer, nonce_oracle=nonce_oracle) + tx = txf.template(self.accounts[0], None, use_nonce=True) + tx = txf.set_code(tx, code) + (tx_hash_hex, o) = txf.build(tx) + + r = self.conn.do(o) + logg.debug('deployed with hash {}'.format(r)) + + o = receipt(tx_hash_hex) + r = self.conn.do(o) + self.address = r['contract_address'] + + + def test_supports(self): + gas_oracle = OverrideGasOracle(limit=100000, conn=self.conn) + c = EIP165(self.chain_spec, gas_oracle=gas_oracle) + o = c.supports_interface(self.address, '0xdeadbeef', sender_address=self.accounts[0]) + r = self.conn.do(o) + v = c.parse_supports_interface(r) + self.assertEqual(v, 1) + + o = c.supports_interface(self.address, '0xbeeffeed', sender_address=self.accounts[0]) + r = self.conn.do(o) + v = c.parse_supports_interface(r) + self.assertEqual(v, 0) + +if __name__ == '__main__': + unittest.main() diff --git a/tests/testdata/Supports.bin b/tests/testdata/Supports.bin new file mode 100644 index 0000000..5ef8144 --- /dev/null +++ b/tests/testdata/Supports.bin @@ -0,0 +1 @@ +608060405234801561001057600080fd5b506101c9806100206000396000f3fe608060405234801561001057600080fd5b5060043610610048576000357c01000000000000000000000000000000000000000000000000000000009004806301ffc9a71461004d575b600080fd5b610067600480360381019061006291906100f1565b61007d565b6040516100749190610129565b60405180910390f35b600063deadbeef7c010000000000000000000000000000000000000000000000000000000002827bffffffffffffffffffffffffffffffffffffffffffffffffffffffff191614156100d257600190506100d7565b600090505b919050565b6000813590506100eb8161017c565b92915050565b60006020828403121561010357600080fd5b6000610111848285016100dc565b91505092915050565b61012381610144565b82525050565b600060208201905061013e600083018461011a565b92915050565b60008115159050919050565b60007fffffffff0000000000000000000000000000000000000000000000000000000082169050919050565b61018581610150565b811461019057600080fd5b5056fea264697066735822122073144ec34d71a86680325f1aee9a3b9041e22c422e7b1b82d409b303d52192de64736f6c63430008030033 \ No newline at end of file diff --git a/tests/testdata/Supports.sol b/tests/testdata/Supports.sol new file mode 100644 index 0000000..3e27538 --- /dev/null +++ b/tests/testdata/Supports.sol @@ -0,0 +1,10 @@ +pragma solidity ^0.8.0; + +contract Supports { + function supportsInterface(bytes4 _sum) public pure returns (bool) { + if (_sum == 0xdeadbeef) { + return true; + } + return false; + } +}