commit 5b97b9e5f1968c10e81f11cbb7df70c01620c3a3
parent f9a715e968a5cbb047dbeea8fbc5a68c239c1997
Author: lash <dev@holbrook.no>
Date: Wed, 29 Mar 2023 10:17:24 +0100
Implement tuple abi encoding support
Diffstat:
8 files changed, 203 insertions(+), 39 deletions(-)
diff --git a/CHANGELOG b/CHANGELOG
@@ -1,3 +1,7 @@
+- 0.4.18
+ * Set a dirty bit on abi encoder when type added before contents generation
+ * Support for tuple abi encoding (decoding not implemented)
+ * Rename log decoder signature method to get_signature
- 0.4.17
* Add dialect filter to info and block cli tools
- 0.4.16
diff --git a/chainlib/eth/contract.py b/chainlib/eth/contract.py
@@ -36,12 +36,17 @@ class ABIContractType(enum.Enum):
STRING = 'string'
BYTES = 'bytes'
BOOLEAN = 'bool'
+ TUPLE = 'tuple'
dynamic_contract_types = [
ABIContractType.STRING,
ABIContractType.BYTES,
]
+pointer_contract_types = [
+ ABIContractType.TUPLE,
+ ] + dynamic_contract_types
+
class ABIContract:
"""Base class for Ethereum smart contract encoder
@@ -49,6 +54,12 @@ class ABIContract:
def __init__(self):
self.types = []
self.contents = []
+ self.dirty = False
+
+
+ def add_type(self, v):
+ self.types.append(v)
+ self.dirty = True
class ABIMethodEncoder(ABIContract):
@@ -79,9 +90,10 @@ class ABIMethodEncoder(ABIContract):
:rtype: str
:returns: Method signature
"""
+ contents = '(' + ','.join(self.method_contents) + ')'
if self.method_name == None:
- return ''
- return '{}({})'.format(self.method_name, ','.join(self.method_contents))
+ return contents
+ return self.method_name + contents
def typ(self, v):
@@ -94,8 +106,8 @@ class ABIMethodEncoder(ABIContract):
:raises AttributeError: Type set before method name
:raises TypeError: Invalid type
"""
- if self.method_name == None:
- raise AttributeError('method name must be set before adding types')
+ if isinstance(v, ABIContractEncoder):
+ return self.typ_tuple(v)
if not isinstance(v, ABIContractType):
raise TypeError('method type not valid; expected {}, got {}'.format(type(ABIContractType).__name__, type(v).__name__))
self.method_contents.append(v.value)
@@ -109,10 +121,37 @@ class ABIMethodEncoder(ABIContract):
self.__log_method()
+ def typ_tuple(self, v):
+ if not isinstance(v, ABIContractEncoder):
+ raise TypeError('tuple type not valid; expected {}, got {}'.format(type(ABIContractEncoder).__name__, type(v).__name__))
+ r = v.get_method()
+ self.method_contents.append(r)
+ self.__log_method()
+
+
def __log_method(self):
logg.debug('method set to {}'.format(self.get_method()))
+ def get_signature(self):
+ """Generate topic signature from set topic.
+
+ :rtype: str
+ :returns: Topic signature, in hex
+ """
+ if self.method_name == None:
+ return ''
+ s = self.get_method()
+ return keccak256_string_to_hex(s)
+
+
+ def get_method_signature(self):
+ s = self.get_signature()
+ if s == '':
+ return s
+ return s[:8]
+
+
class ABIContractDecoder(ABIContract):
"""Decode serialized ABI contract input data to corresponding python primitives.
@@ -127,7 +166,9 @@ class ABIContractDecoder(ABIContract):
"""
if not isinstance(v, ABIContractType):
raise TypeError('method type not valid; expected {}, got {}'.format(type(ABIContractType).__name__, type(v).__name__))
- self.types.append(v.value)
+ if v == ABIContractType.TUPLE:
+ raise NotImplementedError('sorry, tuple decoding not yet implemented')
+ self.add_type(v.value)
self.__log_typ()
@@ -286,17 +327,6 @@ class ABIContractLogDecoder(ABIMethodEncoder, ABIContractDecoder):
"""
self.method(event)
-
- def get_method_signature(self):
- """Generate topic signature from set topic.
-
- :rtype: str
- :returns: Topic signature, in hex
- """
- s = self.get_method()
- return keccak256_string_to_hex(s)
-
-
def typ(self, v):
"""Add type to event argument array.
@@ -304,7 +334,7 @@ class ABIContractLogDecoder(ABIMethodEncoder, ABIContractDecoder):
:type v: chainlib.eth.contract.ABIContractType
"""
super(ABIContractLogDecoder, self).typ(v)
- self.types.append(v.value)
+ self.add_type(v.value)
@@ -319,12 +349,18 @@ class ABIContractLogDecoder(ABIMethodEncoder, ABIContractDecoder):
:type data: str
:raises ValueError: Topic of input does not match topic set in parser
"""
- t = self.get_method_signature()
+ t = self.get_signature()
if topics[0] != t:
raise ValueError('topic mismatch')
for i in range(len(topics) - 1):
self.contents.append(topics[i+1])
self.contents += data
+
+
+ # Backwards compatibility
+ def get_method_signature(self):
+ logg.warning('ABIContractLogDecoder.get_method_signature() is deprecated. Use ABIContractLogDecoder.get_signature() instead')
+ return self.get_signature()
class ABIContractEncoder(ABIMethodEncoder):
@@ -343,7 +379,7 @@ class ABIContractEncoder(ABIMethodEncoder):
v = int(v)
b = v.to_bytes(32, 'big')
self.contents.append(b.hex())
- self.types.append(ABIContractType.UINT256)
+ self.add_type(ABIContractType.UINT256)
self.__log_latest(v)
@@ -365,7 +401,7 @@ class ABIContractEncoder(ABIMethodEncoder):
b = v.to_bytes(int(bitsize / 8), 'big')
self.contents.append(b.hex())
typ = getattr(ABIContractType, 'UINT' + str(bitsize))
- self.types.append(typ)
+ self.add_type(typ)
self.__log_latest(v)
@@ -395,7 +431,7 @@ class ABIContractEncoder(ABIMethodEncoder):
:type v: str
"""
self.bytes_fixed(32, v, 20)
- self.types.append(ABIContractType.ADDRESS)
+ self.add_type(ABIContractType.ADDRESS)
self.__log_latest(v)
@@ -406,7 +442,7 @@ class ABIContractEncoder(ABIMethodEncoder):
:type v: str
"""
self.bytes_fixed(32, v)
- self.types.append(ABIContractType.BYTES32)
+ self.add_type(ABIContractType.BYTES32)
self.__log_latest(v)
@@ -417,10 +453,18 @@ class ABIContractEncoder(ABIMethodEncoder):
:type v: str
"""
self.bytes_fixed(4, v)
- self.types.append(ABIContractType.BYTES4)
+ self.add_type(ABIContractType.BYTES4)
self.__log_latest(v)
+ def tuple(self, v):
+ if type(v).__name__ != 'ABIContractEncoder':
+ raise ValueError('Type for tuple must be ABIContractEncoder')
+ r = v.get_contents()
+ self.bytes_fixed(int(len(r) / 2), r)
+ self.add_type(ABIContractType.TUPLE)
+ self.__log_latest(v)
+
def string(self, v):
"""Encode value to string and add to input value vector.
@@ -445,12 +489,11 @@ class ABIContractEncoder(ABIMethodEncoder):
if pad:
contents += padlen * b'\x00'
self.bytes_fixed(len(contents), contents)
- self.types.append(ABIContractType.STRING)
+ self.add_type(ABIContractType.STRING)
self.__log_latest(v)
return contents
-
def bytes_fixed(self, mx, v, exact=0, enforce_word=False):
"""Add arbirary length byte data to value vector.
@@ -490,14 +533,6 @@ class ABIContractEncoder(ABIMethodEncoder):
raise ValueError('invalid input {}'.format(typ))
self.contents.append(v.ljust(64, '0'))
-
- def get_method_signature(self):
- """Return abi encoded signature of currently set method.
- """
- s = self.get_method()
- if s == '':
- return s
- return keccak256_string_to_hex(s)[:8]
def get_contents(self):
@@ -511,7 +546,7 @@ class ABIContractEncoder(ABIMethodEncoder):
l = len(self.types)
pointer_cursor = 32 * l
for i in range(l):
- if self.types[i] in dynamic_contract_types:
+ if self.types[i] in pointer_contract_types:
content_length = len(self.contents[i])
pointer_contents += self.contents[i]
direct_contents += pointer_cursor.to_bytes(32, 'big').hex()
@@ -524,6 +559,7 @@ class ABIContractEncoder(ABIMethodEncoder):
if l > 64:
l = 64
logg.debug('code word {} {}'.format(int(i / 64), s[i:i+64]))
+ self.dirty = False
return s
diff --git a/requirements.txt b/requirements.txt
@@ -1,4 +1,4 @@
-funga-eth~=0.6.1
+funga-eth~=0.6.6
pysha3==1.0.2
hexathon~=0.1.7
websocket-client==0.57.0
diff --git a/setup.cfg b/setup.cfg
@@ -1,6 +1,6 @@
[metadata]
name = chainlib-eth
-version = 0.4.17
+version = 0.4.18
description = Ethereum implementation of the chainlib interface
author = Louis Holbrook
author_email = dev@holbrook.no
diff --git a/tests/TestContract.bin b/tests/TestContract.bin
@@ -1 +1 @@
-608060405234801561001057600080fd5b50610260806100206000396000f3fe608060405234801561001057600080fd5b5060043610610048576000357c01000000000000000000000000000000000000000000000000000000009004806333aa24121461004d575b600080fd5b61006760048036038101906100629190610122565b61007d565b604051610074919061018b565b60405180910390f35b6000827fa8ed44a382304c8db2c9059e4de342080401fb8e9a71986396d595c869fa3792836040516100af91906101a6565b60405180910390a27f33c4ea6ccc21f1a14e9de7326edcc52c2b8a302ac875ae6b8fee2560eadd75ad836040516100e691906101c1565b60405180910390a16001905092915050565b600081359050610107816101fc565b92915050565b60008135905061011c81610213565b92915050565b6000806040838503121561013557600080fd5b60006101438582860161010d565b9250506020610154858286016100f8565b9150509250929050565b610167816101dc565b82525050565b610176816101e8565b82525050565b610185816101f2565b82525050565b60006020820190506101a0600083018461015e565b92915050565b60006020820190506101bb600083018461016d565b92915050565b60006020820190506101d6600083018461017c565b92915050565b60008115159050919050565b6000819050919050565b6000819050919050565b610205816101e8565b811461021057600080fd5b50565b61021c816101f2565b811461022757600080fd5b5056fea26469706673582212205c428504ca5a53a9250d76b287bec8ee4b13d5c7cb386a0f67cbf77ecb96a9ea64736f6c63430008040033
-\ No newline at end of file
+608060405234801561001057600080fd5b50610695806100206000396000f3fe608060405234801561001057600080fd5b5060043610610053576000357c01000000000000000000000000000000000000000000000000000000009004806333aa2412146100585780638cd9051d14610088575b600080fd5b610072600480360381019061006d91906101c6565b6100b8565b60405161007f9190610221565b60405180910390f35b6100a2600480360381019061009d919061050e565b610133565b6040516100af91906105e9565b60405180910390f35b6000827fa8ed44a382304c8db2c9059e4de342080401fb8e9a71986396d595c869fa3792836040516100ea919061061a565b60405180910390a27f33c4ea6ccc21f1a14e9de7326edcc52c2b8a302ac875ae6b8fee2560eadd75ad836040516101219190610644565b60405180910390a16001905092915050565b6060826000015160000151905092915050565b6000604051905090565b600080fd5b600080fd5b6000819050919050565b61016d8161015a565b811461017857600080fd5b50565b60008135905061018a81610164565b92915050565b6000819050919050565b6101a381610190565b81146101ae57600080fd5b50565b6000813590506101c08161019a565b92915050565b600080604083850312156101dd576101dc610150565b5b60006101eb8582860161017b565b92505060206101fc858286016101b1565b9150509250929050565b60008115159050919050565b61021b81610206565b82525050565b60006020820190506102366000830184610212565b92915050565b600080fd5b6000601f19601f8301169050919050565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052604160045260246000fd5b61028a82610241565b810181811067ffffffffffffffff821117156102a9576102a8610252565b5b80604052505050565b60006102bc610146565b90506102c88282610281565b919050565b600080fd5b600080fd5b600080fd5b600067ffffffffffffffff8211156102f7576102f6610252565b5b61030082610241565b9050602081019050919050565b82818337600083830152505050565b600061032f61032a846102dc565b6102b2565b90508281526020810184848401111561034b5761034a6102d7565b5b61035684828561030d565b509392505050565b600082601f830112610373576103726102d2565b5b813561038384826020860161031c565b91505092915050565b600073ffffffffffffffffffffffffffffffffffffffff82169050919050565b60006103b78261038c565b9050919050565b6103c7816103ac565b81146103d257600080fd5b50565b6000813590506103e4816103be565b92915050565b600060408284031215610400576103ff61023c565b5b61040a60406102b2565b9050600082013567ffffffffffffffff81111561042a576104296102cd565b5b6104368482850161035e565b600083015250602061044a848285016103d5565b60208301525092915050565b60006060828403121561046c5761046b61023c565b5b61047660606102b2565b9050600082013567ffffffffffffffff811115610496576104956102cd565b5b6104a2848285016103ea565b600083015250602082013567ffffffffffffffff8111156104c6576104c56102cd565b5b6104d2848285016103ea565b602083015250604082013567ffffffffffffffff8111156104f6576104f56102cd565b5b6105028482850161035e565b60408301525092915050565b6000806040838503121561052557610524610150565b5b600083013567ffffffffffffffff81111561054357610542610155565b5b61054f85828601610456565b92505060206105608582860161017b565b9150509250929050565b600081519050919050565b600082825260208201905092915050565b60005b838110156105a4578082015181840152602081019050610589565b60008484015250505050565b60006105bb8261056a565b6105c58185610575565b93506105d5818560208601610586565b6105de81610241565b840191505092915050565b6000602082019050818103600083015261060381846105b0565b905092915050565b61061481610190565b82525050565b600060208201905061062f600083018461060b565b92915050565b61063e8161015a565b82525050565b60006020820190506106596000830184610635565b9291505056fea2646970667358221220297897985f0dab43c3088bbd085bb9a8fe99c5c13dbe0eee1bd95a0d303899be64736f6c63430008130033
+\ No newline at end of file
diff --git a/tests/TestContract.sol b/tests/TestContract.sol
@@ -5,9 +5,26 @@ contract TestEventContract {
event TestEventOne(uint256 indexed _foo, bytes32 _bar);
event TestEventTwo(uint256 _foo);
+ struct Person {
+ string uid;
+ address wallet;
+ }
+
+ struct Mail {
+ Person from;
+ Person to;
+ string contents;
+ }
+
function foo(uint256 _foo, bytes32 _bar) public returns (bool) {
emit TestEventOne(_foo, _bar);
emit TestEventTwo(_foo);
return true;
}
+
+ function foo(Mail memory _mail, uint256 _nonce) public pure returns(string memory) {
+ _mail;
+ _nonce;
+ return _mail.from.uid;
+ }
}
diff --git a/tests/test_abi.py b/tests/test_abi.py
@@ -1,5 +1,7 @@
# standard imports
import unittest
+import logging
+import os
# local imports
from chainlib.eth.contract import (
@@ -7,6 +9,9 @@ from chainlib.eth.contract import (
ABIContractType,
)
+logging.basicConfig(level=logging.DEBUG)
+logg = logging.getLogger()
+
class TestContract(unittest.TestCase):
@@ -66,5 +71,107 @@ class TestContract(unittest.TestCase):
self.assertEqual(e.get(), 'f31a6969000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000000086465616462656566000000000000000000000000000000000000000000000000')
+ def test_abi_tuple(self):
+ e = ABIContractEncoder()
+ e.typ(ABIContractType.STRING)
+ e.typ(ABIContractType.BYTES32)
+ e.string('deadbeef')
+ e.bytes32('666f6f')
+
+ ee = ABIContractEncoder()
+ ee.method('foo')
+ ee.typ(e)
+ ee.typ(ABIContractType.UINT256)
+ ee.tuple(e)
+ ee.uint256(42)
+
+ self.assertEqual(ee.get_method(), 'foo((string,bytes32),uint256)')
+ r = ee.get()
+ self.assertEqual(r[:8], '7bab4ebd')
+ r = r[8:]
+ valid = [
+ '0000000000000000000000000000000000000000000000000000000000000040',
+ '000000000000000000000000000000000000000000000000000000000000002a',
+ '0000000000000000000000000000000000000000000000000000000000000040',
+ '0000000000000000000000000000000000000000000000000000000000666f6f',
+ '0000000000000000000000000000000000000000000000000000000000000008',
+ '6465616462656566000000000000000000000000000000000000000000000000',
+ ]
+
+ i = 0
+ c = 0
+ while c < len(r):
+ v = r[c:c+64]
+ logg.debug('check position {} {}'.format((i*32).to_bytes(2, byteorder='big').hex(), v))
+ self.assertEqual(v, valid[i])
+ c += 64
+ i += 1
+
+
+ def test_abi_tuple_embedded(self):
+ a = os.urandom(20)
+ ea = ABIContractEncoder()
+ ea.typ(ABIContractType.STRING)
+ ea.typ(ABIContractType.ADDRESS)
+ ea.string('foo@bar.com')
+ ea.address(a.hex())
+
+ b = os.urandom(20)
+ eb = ABIContractEncoder()
+ eb.typ(ABIContractType.STRING)
+ eb.typ(ABIContractType.ADDRESS)
+ eb.string('baz@xyzzy.org')
+ eb.address(b.hex())
+
+ ee = ABIContractEncoder()
+ ee.typ(ea)
+ ee.typ(eb)
+ ee.typ(ABIContractType.STRING)
+ ee.tuple(ea)
+ ee.tuple(eb)
+ ee.string('barbarbar')
+
+ e = ABIContractEncoder()
+ e.method('foo')
+ e.typ(ee)
+ e.typ(ABIContractType.UINT256)
+ e.tuple(ee)
+ e.uint256(42)
+
+ self.assertEqual(e.get_method(), 'foo(((string,address),(string,address),string),uint256)')
+ r = e.get()
+ print(r)
+ self.assertEqual(r[:8], '8cd9051d')
+ r = r[8:]
+
+ valid = [
+ '0000000000000000000000000000000000000000000000000000000000000040',
+ '000000000000000000000000000000000000000000000000000000000000002a',
+ '0000000000000000000000000000000000000000000000000000000000000060',
+ '00000000000000000000000000000000000000000000000000000000000000e0',
+ '0000000000000000000000000000000000000000000000000000000000000160',
+ '0000000000000000000000000000000000000000000000000000000000000040',
+ '000000000000000000000000' + a.hex(),
+ '000000000000000000000000000000000000000000000000000000000000000b',
+ '666f6f406261722e636f6d000000000000000000000000000000000000000000',
+ '0000000000000000000000000000000000000000000000000000000000000040',
+ '000000000000000000000000' + b.hex(),
+ '000000000000000000000000000000000000000000000000000000000000000d',
+ '62617a4078797a7a792e6f726700000000000000000000000000000000000000',
+ '0000000000000000000000000000000000000000000000000000000000000009',
+ '6261726261726261720000000000000000000000000000000000000000000000',
+ ]
+
+ i = 0
+ c = 0
+ while c < len(r):
+ v = r[c:c+64]
+ logg.debug('check position {} {}'.format((i*32).to_bytes(2, byteorder='big').hex(), v))
+ self.assertEqual(v, valid[i])
+ c += 64
+ i += 1
+
+
+
if __name__ == '__main__':
unittest.main()
diff --git a/tests/test_event.py b/tests/test_event.py
@@ -19,7 +19,7 @@ class TestContractLog(EthTesterCase):
dec.topic('TestEventOne')
dec.typ(ABIContractType.UINT256)
dec.typ(ABIContractType.BYTES32)
- s = dec.get_method_signature()
+ s = dec.get_signature()
n = 42
topics = [
s,