commit 219c24b624377585bac39a7d8e2907debc09e689
parent 3241dfa9c5a0ed5d415210f309425f9ae913a2b6
Author: nolash <dev@holbrook.no>
Date:   Thu, 29 Apr 2021 08:35:53 +0200
Add EIP165 support
Diffstat:
9 files changed, 145 insertions(+), 9 deletions(-)
diff --git 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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -0,0 +1 @@
+608060405234801561001057600080fd5b506101c9806100206000396000f3fe608060405234801561001057600080fd5b5060043610610048576000357c01000000000000000000000000000000000000000000000000000000009004806301ffc9a71461004d575b600080fd5b610067600480360381019061006291906100f1565b61007d565b6040516100749190610129565b60405180910390f35b600063deadbeef7c010000000000000000000000000000000000000000000000000000000002827bffffffffffffffffffffffffffffffffffffffffffffffffffffffff191614156100d257600190506100d7565b600090505b919050565b6000813590506100eb8161017c565b92915050565b60006020828403121561010357600080fd5b6000610111848285016100dc565b91505092915050565b61012381610144565b82525050565b600060208201905061013e600083018461011a565b92915050565b60008115159050919050565b60007fffffffff0000000000000000000000000000000000000000000000000000000082169050919050565b61018581610150565b811461019057600080fd5b5056fea264697066735822122073144ec34d71a86680325f1aee9a3b9041e22c422e7b1b82d409b303d52192de64736f6c63430008030033
+\ No newline at end of file
diff --git a/tests/testdata/Supports.sol 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;
+	}
+}