For types that require parameters extracted from the type string (e.g., null2 where 2 is a width), you should register custom classes that inherit from BaseEncoder and BaseDecoder.
To implement this pattern:
- Matcher Function: Instead of a literal string, pass a function to
registry.register that returns True if the type string matches (e.g., lambda x: x.startswith('null')). from_type_str Class Method: Implement this method on both classes to parse the type string and return an instance of the class with the necessary settings.- Settings via
__init__: Any keyword arguments passed to the constructor via from_type_str are automatically assigned to class properties of the same name (inherited from BaseCoder). encode and decode: Implement these methods to perform the actual logic using the instance's settings.validate_value: For BaseEncoder subclasses, implement this method to allow is_encodable to check compatibility without performing a full encoding.
from eth_abi.decoding import BaseDecoder
from eth_abi.encoding import BaseEncoder
from eth_abi.exceptions import EncodingError, DecodingError
from eth_abi.registry import registry
from eth_abi import encode, decode
NULL_ENCODING = b'\x00' * 32
class EncodeNull(BaseEncoder):
word_width = None
@classmethod
def from_type_str(cls, type_str, registry):
# Extract width from 'null2' -> 2
word_width = int(type_str[4:])
return cls(word_width=word_width)
def encode(self, value):
self.validate_value(value)
return NULL_ENCODING * self.word_width
def validate_value(self, value):
if value is not None:
raise EncodingError('Unsupported value')
class DecodeNull(BaseDecoder):
word_width = None
@classmethod
def from_type_str(cls, type_str, registry):
word_width = int(type_str[4:])
return cls(word_width=word_width)
def decode(self, stream):
byts = stream.read(32 * self.word_width)
if byts != NULL_ENCODING * self.word_width:
raise DecodingError('Not enough data or wrong data')
return None
# Register using a matcher function and a label for easy unregistering
registry.register(
lambda x: x.startswith('null'),
EncodeNull,
DecodeNull,
label='null',
)
# Usage
assert encode(['null2'], [None]) == NULL_ENCODING * 2
(decoded_null_val,) = decode(['null2'], NULL_ENCODING * 2)
assert decoded_null_val is None
# Cleanup
registry.unregister('null')