Code
This commit is contained in:
parent
a850e0ef32
commit
8580530e09
10
pbcabi/__init__.py
Normal file
10
pbcabi/__init__.py
Normal file
|
@ -0,0 +1,10 @@
|
||||||
|
'''
|
||||||
|
Utility library for parsing and processing the PBC ABI format.
|
||||||
|
|
||||||
|
Follows the ABI specification quite closely. The full ABI specification can be found at:
|
||||||
|
|
||||||
|
https://partisiablockchain.gitlab.io/documentation/abiv.html
|
||||||
|
'''
|
||||||
|
|
||||||
|
import pbcabi.model
|
||||||
|
import pbcabi.binaryreader
|
137
pbcabi/binaryreader.py
Normal file
137
pbcabi/binaryreader.py
Normal file
|
@ -0,0 +1,137 @@
|
||||||
|
'''
|
||||||
|
Utility module for reading binary streams of data.
|
||||||
|
|
||||||
|
Follows the ABI specification quite closely. The full ABI specification can be found at:
|
||||||
|
|
||||||
|
https://partisiablockchain.gitlab.io/documentation/abiv.html
|
||||||
|
'''
|
||||||
|
|
||||||
|
import io
|
||||||
|
|
||||||
|
|
||||||
|
class BinaryReader:
|
||||||
|
'''
|
||||||
|
Wrapper for io.BytesIO for iteratively parsing byte streams.
|
||||||
|
'''
|
||||||
|
|
||||||
|
def __init__(self, buf):
|
||||||
|
'''
|
||||||
|
Initializes BinaryReader.
|
||||||
|
'''
|
||||||
|
self.buf = buf
|
||||||
|
if isinstance(self.buf, bytes):
|
||||||
|
self.buf = io.BytesIO(self.buf)
|
||||||
|
self.size = len(buf)
|
||||||
|
self.position = 0
|
||||||
|
|
||||||
|
def readBytes(self, num_bytes):
|
||||||
|
'''
|
||||||
|
Reads a number of bytes from stream.
|
||||||
|
'''
|
||||||
|
self.position += num_bytes
|
||||||
|
bts = self.buf.read(num_bytes)
|
||||||
|
if len(bts) != num_bytes:
|
||||||
|
raise Exception("Not enough bytes in buffer: Got {}, but expected {}".format(len(bts), num_bytes))
|
||||||
|
return bts
|
||||||
|
|
||||||
|
def readDynamicBytes(self, size_reader = None):
|
||||||
|
'''
|
||||||
|
Reads a dynamically sized list of bytes.
|
||||||
|
'''
|
||||||
|
size_reader = size_reader or BinaryReader.readUInt32BigEndian
|
||||||
|
num_bytes = size_reader(self)
|
||||||
|
return self.readBytes(num_bytes)
|
||||||
|
|
||||||
|
def readString(self, size_reader = None):
|
||||||
|
'''
|
||||||
|
Reads a string from stream.
|
||||||
|
'''
|
||||||
|
return self.readDynamicBytes(size_reader).decode('utf8')
|
||||||
|
|
||||||
|
def readUInt8(self):
|
||||||
|
'''
|
||||||
|
Reads a unsigned 8-bit integer from stream.
|
||||||
|
'''
|
||||||
|
bytes = self.readBytes(1)
|
||||||
|
return bytes[0]
|
||||||
|
|
||||||
|
def readUInt32BigEndian(self):
|
||||||
|
'''
|
||||||
|
Reads a unsigned 32-bit integer from stream.
|
||||||
|
'''
|
||||||
|
return self.readUIntBigEndian(4)
|
||||||
|
|
||||||
|
def readUInt32LittleEndian(self):
|
||||||
|
'''
|
||||||
|
Reads a unsigned 32-bit integer from stream.
|
||||||
|
'''
|
||||||
|
return self.readUIntLittleEndian(4)
|
||||||
|
|
||||||
|
def readUIntBigEndian(self, num_bytes):
|
||||||
|
'''
|
||||||
|
Reads an unsigned N-bit integer from stream.
|
||||||
|
'''
|
||||||
|
bytes = self.readBytes(num_bytes)
|
||||||
|
c = 0
|
||||||
|
for i in range(0, num_bytes):
|
||||||
|
c += bytes[num_bytes - i - 1] * 2**(i * 8)
|
||||||
|
return c
|
||||||
|
|
||||||
|
def readSignedIntBigEndian(self, num_bytes):
|
||||||
|
'''
|
||||||
|
Reads an signed N-bit integer from stream.
|
||||||
|
'''
|
||||||
|
# TODO: Test!
|
||||||
|
result = self.readUIntBigEndian(num_bytes)
|
||||||
|
half = 2**(num_bytes * 8)
|
||||||
|
if result > half:
|
||||||
|
result -= half
|
||||||
|
return result
|
||||||
|
|
||||||
|
def readUIntLittleEndian(self, num_bytes):
|
||||||
|
'''
|
||||||
|
Reads an unsigned N-bit integer from stream.
|
||||||
|
'''
|
||||||
|
bytes = self.readBytes(num_bytes)
|
||||||
|
c = 0
|
||||||
|
for i in range(0, num_bytes):
|
||||||
|
c += bytes[i] * 2**(i * 8)
|
||||||
|
return c
|
||||||
|
|
||||||
|
def readSignedIntLittleEndian(self, num_bytes):
|
||||||
|
'''
|
||||||
|
Reads an signed N-bit integer from stream.
|
||||||
|
'''
|
||||||
|
# TODO: Test!
|
||||||
|
result = self.readUIntLittleEndian(num_bytes)
|
||||||
|
half = 2**(num_bytes * 8)
|
||||||
|
if result > half:
|
||||||
|
result -= half
|
||||||
|
return result
|
||||||
|
|
||||||
|
def readLeb128(self):
|
||||||
|
'''
|
||||||
|
Reads a LEB-128 integer from stream.
|
||||||
|
'''
|
||||||
|
# TODO: Test!
|
||||||
|
v = self.readUInt8()
|
||||||
|
count = 0
|
||||||
|
while v & 0x80 != 0:
|
||||||
|
v = self.readUInt8()
|
||||||
|
count += 1
|
||||||
|
assert count < 6, "TODO: " + str(v)
|
||||||
|
return v
|
||||||
|
|
||||||
|
def readList(self, fn, size_reader=None):
|
||||||
|
'''
|
||||||
|
Reads a list of elements from stream, by applying the given function
|
||||||
|
several times.
|
||||||
|
|
||||||
|
Function must take a single argument of type BinaryReader.
|
||||||
|
'''
|
||||||
|
size_reader = size_reader or BinaryReader.readUInt32BigEndian
|
||||||
|
length = size_reader(self)
|
||||||
|
ls = []
|
||||||
|
for i in range(0, length):
|
||||||
|
ls.append(fn(self))
|
||||||
|
return ls
|
208
pbcabi/data.py
Normal file
208
pbcabi/data.py
Normal file
|
@ -0,0 +1,208 @@
|
||||||
|
'''
|
||||||
|
Definitions of several useful types commonly seen on Partisia Blockchain.
|
||||||
|
|
||||||
|
Follows the ABI specification quite closely. The full ABI specification can be found at:
|
||||||
|
|
||||||
|
https://partisiablockchain.gitlab.io/documentation/abiv.html
|
||||||
|
'''
|
||||||
|
|
||||||
|
import datetime
|
||||||
|
import dataclasses
|
||||||
|
from typing import Mapping, Collection, Optional, Union
|
||||||
|
from enum import Enum
|
||||||
|
import enforce_typing
|
||||||
|
|
||||||
|
def hexformat(bts: bytes, sep=''):
|
||||||
|
return sep.join('{:02X}'.format(c) for c in bts)
|
||||||
|
|
||||||
|
class AddressType(Enum):
|
||||||
|
'''
|
||||||
|
Type of a PBC BlockchainAddress.
|
||||||
|
'''
|
||||||
|
ACCOUNT = 0x00
|
||||||
|
CONTRACT_SYSTEM = 0x01
|
||||||
|
CONTRACT_PUBLIC = 0x02
|
||||||
|
CONTRACT_ZK = 0x03
|
||||||
|
CONTRACT_GOVERANCE = 0x04
|
||||||
|
|
||||||
|
def human_name(self):
|
||||||
|
'''
|
||||||
|
Produces a human-readable name for self.
|
||||||
|
'''
|
||||||
|
return ADDRESS_TYPES[self]
|
||||||
|
|
||||||
|
|
||||||
|
ADDRESS_TYPES = {
|
||||||
|
AddressType.ACCOUNT: 'Account',
|
||||||
|
AddressType.CONTRACT_SYSTEM: 'System Contract',
|
||||||
|
AddressType.CONTRACT_PUBLIC: 'Public Contract',
|
||||||
|
AddressType.CONTRACT_ZK: 'ZK Contract',
|
||||||
|
AddressType.CONTRACT_GOVERANCE: 'Governance Contract',
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@enforce_typing.enforce_types
|
||||||
|
@dataclasses.dataclass(frozen=True, order=True)
|
||||||
|
class ByteData(object):
|
||||||
|
'''
|
||||||
|
Supertype for several built-in PBC types.
|
||||||
|
'''
|
||||||
|
data: bytes
|
||||||
|
|
||||||
|
def to_hex(self):
|
||||||
|
'''
|
||||||
|
Formats this byte data as a hex string.
|
||||||
|
'''
|
||||||
|
return hexformat(self.data)
|
||||||
|
|
||||||
|
def short_str(self):
|
||||||
|
'''
|
||||||
|
Formats this byte data as a short hex string.
|
||||||
|
'''
|
||||||
|
long = str(self)
|
||||||
|
return '{}..{}'.format(long[:4], long[-4:])
|
||||||
|
|
||||||
|
|
||||||
|
@enforce_typing.enforce_types
|
||||||
|
@dataclasses.dataclass(frozen=True, order=True)
|
||||||
|
class BlockchainAddress(ByteData):
|
||||||
|
'''
|
||||||
|
Address on PBC.
|
||||||
|
'''
|
||||||
|
|
||||||
|
BYTE_LEN = 21
|
||||||
|
|
||||||
|
def __post_init__(self):
|
||||||
|
assert len(self.data) == BlockchainAddress.BYTE_LEN
|
||||||
|
assert self.type()
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def read_from(reader):
|
||||||
|
return BlockchainAddress(reader.readBytes(BlockchainAddress.BYTE_LEN))
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def from_hex_hash(s):
|
||||||
|
return BlockchainAddress(bytes.fromhex(s))
|
||||||
|
|
||||||
|
def type(self) -> AddressType:
|
||||||
|
return AddressType(self.data[0])
|
||||||
|
|
||||||
|
def __repr__(self):
|
||||||
|
return self.to_hex()
|
||||||
|
|
||||||
|
def __str__(self):
|
||||||
|
return repr(self)
|
||||||
|
|
||||||
|
|
||||||
|
@enforce_typing.enforce_types
|
||||||
|
@dataclasses.dataclass(frozen=True, order=True)
|
||||||
|
class Hash(ByteData):
|
||||||
|
'''
|
||||||
|
Hash.
|
||||||
|
'''
|
||||||
|
|
||||||
|
data: bytes
|
||||||
|
|
||||||
|
BYTE_LEN = 32
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def read_from(reader):
|
||||||
|
return Hash(reader.readBytes(Hash.BYTE_LEN))
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def from_hex(cls, s):
|
||||||
|
return Hash(bytes.fromhex(s))
|
||||||
|
|
||||||
|
def __repr__(self):
|
||||||
|
return self.to_hex()
|
||||||
|
|
||||||
|
def __str__(self):
|
||||||
|
return repr(self)
|
||||||
|
|
||||||
|
|
||||||
|
@enforce_typing.enforce_types
|
||||||
|
@dataclasses.dataclass(frozen=True, order=True)
|
||||||
|
class PublicKey(ByteData):
|
||||||
|
'''
|
||||||
|
PBC public key.
|
||||||
|
'''
|
||||||
|
|
||||||
|
data: bytes
|
||||||
|
|
||||||
|
BYTE_LEN = 33
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def read_from(reader):
|
||||||
|
return PublicKey(reader.readBytes(PublicKey.BYTE_LEN))
|
||||||
|
|
||||||
|
def __repr__(self):
|
||||||
|
return self.to_hex()
|
||||||
|
|
||||||
|
def __str__(self):
|
||||||
|
return repr(self)
|
||||||
|
|
||||||
|
|
||||||
|
@enforce_typing.enforce_types
|
||||||
|
@dataclasses.dataclass(frozen=True, order=True)
|
||||||
|
class Signature(ByteData):
|
||||||
|
'''
|
||||||
|
Message signature on PBC.
|
||||||
|
'''
|
||||||
|
|
||||||
|
data: bytes
|
||||||
|
|
||||||
|
BYTE_LEN = 65
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def read_from(reader):
|
||||||
|
return Signature(reader.readBytes(Signature.BYTE_LEN))
|
||||||
|
|
||||||
|
def __repr__(self):
|
||||||
|
return self.to_hex()
|
||||||
|
|
||||||
|
def __str__(self):
|
||||||
|
return repr(self)
|
||||||
|
|
||||||
|
|
||||||
|
@enforce_typing.enforce_types
|
||||||
|
@dataclasses.dataclass(frozen=True, order=True)
|
||||||
|
class BlsPublicKey(ByteData):
|
||||||
|
'''
|
||||||
|
BLS Public Key.
|
||||||
|
'''
|
||||||
|
|
||||||
|
data: bytes
|
||||||
|
|
||||||
|
BYTE_LEN = 96
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def read_from(reader):
|
||||||
|
return BlsPublicKey(reader.readBytes(BlsPublicKey.BYTE_LEN))
|
||||||
|
|
||||||
|
def __repr__(self):
|
||||||
|
return self.to_hex()
|
||||||
|
|
||||||
|
def __str__(self):
|
||||||
|
return repr(self)
|
||||||
|
|
||||||
|
|
||||||
|
@enforce_typing.enforce_types
|
||||||
|
@dataclasses.dataclass(frozen=True, order=True)
|
||||||
|
class BlsSignature(ByteData):
|
||||||
|
'''
|
||||||
|
BLS signature.
|
||||||
|
'''
|
||||||
|
|
||||||
|
data: bytes
|
||||||
|
|
||||||
|
BYTE_LEN = 48
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def read_from(reader):
|
||||||
|
return BlsSignature(reader.readBytes(BlsSignature.BYTE_LEN))
|
||||||
|
|
||||||
|
def __repr__(self):
|
||||||
|
return self.to_hex()
|
||||||
|
|
||||||
|
def __str__(self):
|
||||||
|
return repr(self)
|
680
pbcabi/model.py
Normal file
680
pbcabi/model.py
Normal file
|
@ -0,0 +1,680 @@
|
||||||
|
'''
|
||||||
|
Specifies the ABI Spec model and how to deserialize the ABI, and additionally
|
||||||
|
how specific type specifications can deserialize their elements.
|
||||||
|
|
||||||
|
Follows the ABI specification quite closely. The full ABI specification can be found at:
|
||||||
|
|
||||||
|
https://partisiablockchain.gitlab.io/documentation/abiv.html
|
||||||
|
'''
|
||||||
|
|
||||||
|
from frozendict import frozendict
|
||||||
|
import dataclasses
|
||||||
|
from typing import Mapping
|
||||||
|
from enum import Enum
|
||||||
|
import enforce_typing
|
||||||
|
from .binaryreader import BinaryReader
|
||||||
|
import pbcabi.data as data
|
||||||
|
|
||||||
|
Identifier = str
|
||||||
|
|
||||||
|
|
||||||
|
class SerializeMode(Enum):
|
||||||
|
RPC = 0x01
|
||||||
|
STATE = 0x02
|
||||||
|
ZK = 0x03
|
||||||
|
|
||||||
|
|
||||||
|
class SimpleType(Enum):
|
||||||
|
'''
|
||||||
|
The specific simple type.
|
||||||
|
'''
|
||||||
|
|
||||||
|
U8 = 0X01
|
||||||
|
U16 = 0X02
|
||||||
|
U32 = 0X03
|
||||||
|
U64 = 0X04
|
||||||
|
U128 = 0X05
|
||||||
|
U256 = 0X18
|
||||||
|
I8 = 0X06
|
||||||
|
I16 = 0X07
|
||||||
|
I32 = 0X08
|
||||||
|
I64 = 0X09
|
||||||
|
I128 = 0X0a
|
||||||
|
STRING = 0X0b
|
||||||
|
BOOL = 0X0c
|
||||||
|
ADDRESS = 0X0d
|
||||||
|
HASH = 0X13
|
||||||
|
PUBLIC_KEY = 0X14
|
||||||
|
SIGNATURE = 0X15
|
||||||
|
BLS_PUBLIC_KEY = 0X16
|
||||||
|
BLS_SIGNATURE = 0X17
|
||||||
|
|
||||||
|
|
||||||
|
@enforce_typing.enforce_types
|
||||||
|
@dataclasses.dataclass(frozen=True, slots=True)
|
||||||
|
class TypeSpec:
|
||||||
|
'''
|
||||||
|
Supertype of all 'TypeSpec'.
|
||||||
|
'''
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def read_from(reader: BinaryReader):
|
||||||
|
'''
|
||||||
|
Deserialize this 'TypeSpec' type from 'BinaryReader'.
|
||||||
|
'''
|
||||||
|
discriminant = reader.readUInt8()
|
||||||
|
if type_spec_read_from := TYPE_SPEC_SUBTYPE_READ_FROM_BY_DISCRIMINANT.get(
|
||||||
|
discriminant):
|
||||||
|
type_spec = type_spec_read_from(reader)
|
||||||
|
return type_spec
|
||||||
|
assert False, "Unknown discriminant: 0x{:02x}".format(discriminant)
|
||||||
|
|
||||||
|
|
||||||
|
@enforce_typing.enforce_types
|
||||||
|
@dataclasses.dataclass(frozen=True, slots=True)
|
||||||
|
class NamedTypeRef(TypeSpec):
|
||||||
|
'''
|
||||||
|
Reference to a named type.
|
||||||
|
'''
|
||||||
|
|
||||||
|
idx: int
|
||||||
|
|
||||||
|
DISCRIMINANT = 0x00
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def read_from(reader: BinaryReader):
|
||||||
|
'''
|
||||||
|
Deserialize this 'TypeSpec' type from 'BinaryReader'.
|
||||||
|
'''
|
||||||
|
assert reader.readUInt8() == NamedTypeRef.DISCRIMINANT
|
||||||
|
return NamedTypeRef.read_from_inner(reader)
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def read_from_inner(reader: BinaryReader):
|
||||||
|
'''
|
||||||
|
Deserialize this 'TypeSpec' type from 'BinaryReader'.
|
||||||
|
'''
|
||||||
|
return NamedTypeRef(reader.readUInt8())
|
||||||
|
|
||||||
|
def read_element_from(self, reader: BinaryReader, type_env,
|
||||||
|
mode: SerializeMode):
|
||||||
|
'''
|
||||||
|
Deserialize elements for this 'TypeSpec' from RPC 'BinaryReader'.
|
||||||
|
'''
|
||||||
|
return type_env[self.idx].read_element_from(reader, type_env, mode)
|
||||||
|
|
||||||
|
|
||||||
|
READ_SIMPLE_TYPE = {}
|
||||||
|
|
||||||
|
READ_SIMPLE_TYPE[SerializeMode.RPC] = {
|
||||||
|
(SimpleType.BOOL): lambda r: r.readUInt8() != 0,
|
||||||
|
(SimpleType.U8): BinaryReader.readUInt8,
|
||||||
|
(SimpleType.U16): lambda r: r.readUIntBigEndian(2),
|
||||||
|
(SimpleType.U32): BinaryReader.readUInt32BigEndian,
|
||||||
|
(SimpleType.U64): lambda r: r.readUIntBigEndian(8),
|
||||||
|
(SimpleType.U128): lambda r: r.readUIntBigEndian(16),
|
||||||
|
(SimpleType.U256): lambda r: r.readUIntBigEndian(32),
|
||||||
|
(SimpleType.I8): lambda r: r.readSignedIntBigEndian(1),
|
||||||
|
(SimpleType.I16): lambda r: r.readSignedIntBigEndian(2),
|
||||||
|
(SimpleType.I32): lambda r: r.readSignedIntBigEndian(4),
|
||||||
|
(SimpleType.I64): lambda r: r.readSignedIntBigEndian(8),
|
||||||
|
(SimpleType.I128): lambda r: r.readSignedIntBigEndian(16),
|
||||||
|
(SimpleType.STRING): lambda r: r.readString(BinaryReader.readUInt32BigEndian),
|
||||||
|
(SimpleType.ADDRESS): data.BlockchainAddress.read_from,
|
||||||
|
(SimpleType.HASH): data.Hash.read_from,
|
||||||
|
(SimpleType.PUBLIC_KEY): data.PublicKey.read_from,
|
||||||
|
(SimpleType.SIGNATURE): data.Signature.read_from,
|
||||||
|
(SimpleType.BLS_PUBLIC_KEY): data.BlsPublicKey.read_from,
|
||||||
|
(SimpleType.BLS_SIGNATURE): data.BlsSignature.read_from,
|
||||||
|
}
|
||||||
|
|
||||||
|
READ_SIMPLE_TYPE[SerializeMode.STATE] = {
|
||||||
|
(SimpleType.BOOL): lambda r: r.readUInt8() != 0,
|
||||||
|
(SimpleType.U8): BinaryReader.readUInt8,
|
||||||
|
(SimpleType.U16): lambda r: r.readUIntLittleEndian(2),
|
||||||
|
(SimpleType.U32): BinaryReader.readUInt32LittleEndian,
|
||||||
|
(SimpleType.U64): lambda r: r.readUIntLittleEndian(8),
|
||||||
|
(SimpleType.U128): lambda r: r.readUIntLittleEndian(16),
|
||||||
|
(SimpleType.U256): lambda r: r.readUIntLittleEndian(32),
|
||||||
|
(SimpleType.I8): lambda r: r.readSignedIntLittleEndian(1),
|
||||||
|
(SimpleType.I16): lambda r: r.readSignedIntLittleEndian(2),
|
||||||
|
(SimpleType.I32): lambda r: r.readSignedIntLittleEndian(4),
|
||||||
|
(SimpleType.I64): lambda r: r.readSignedIntLittleEndian(8),
|
||||||
|
(SimpleType.I128): lambda r: r.readSignedIntLittleEndian(16),
|
||||||
|
(SimpleType.STRING): lambda r: r.readString(BinaryReader.readUInt32LittleEndian),
|
||||||
|
(SimpleType.ADDRESS): data.BlockchainAddress.read_from,
|
||||||
|
(SimpleType.HASH): data.Hash.read_from,
|
||||||
|
(SimpleType.PUBLIC_KEY): data.PublicKey.read_from,
|
||||||
|
(SimpleType.SIGNATURE): data.Signature.read_from,
|
||||||
|
(SimpleType.BLS_PUBLIC_KEY): data.BlsPublicKey.read_from,
|
||||||
|
(SimpleType.BLS_SIGNATURE): data.BlsSignature.read_from,
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@enforce_typing.enforce_types
|
||||||
|
@dataclasses.dataclass(frozen=True, slots=True)
|
||||||
|
class SimpleTypeSpec(TypeSpec):
|
||||||
|
'''
|
||||||
|
Simple type spec.
|
||||||
|
'''
|
||||||
|
|
||||||
|
type: SimpleType
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def read_from(reader: BinaryReader):
|
||||||
|
'''
|
||||||
|
Deserialize this 'TypeSpec' type from 'BinaryReader'.
|
||||||
|
'''
|
||||||
|
return SimpleTypeSpec(SimpleType[reader.readUInt8()])
|
||||||
|
|
||||||
|
def read_element_from(self, reader: BinaryReader, type_env,
|
||||||
|
mode: SerializeMode):
|
||||||
|
'''
|
||||||
|
Deserialize elements for this 'TypeSpec' from RPC 'BinaryReader'.
|
||||||
|
'''
|
||||||
|
return READ_SIMPLE_TYPE[mode][self.type](reader)
|
||||||
|
|
||||||
|
|
||||||
|
SIZE_SIMPLE_TYPE_SPEC = SimpleTypeSpec(SimpleType.U32)
|
||||||
|
|
||||||
|
|
||||||
|
@enforce_typing.enforce_types
|
||||||
|
@dataclasses.dataclass(frozen=True, slots=True)
|
||||||
|
class CompositeTypeSpec(TypeSpec):
|
||||||
|
'''
|
||||||
|
Supertype of all composite type specs.
|
||||||
|
'''
|
||||||
|
|
||||||
|
|
||||||
|
@enforce_typing.enforce_types
|
||||||
|
@dataclasses.dataclass(frozen=True, slots=True)
|
||||||
|
class VecTypeSpec(TypeSpec):
|
||||||
|
'''
|
||||||
|
Type spec for a dynamically-sized vector of some some type.
|
||||||
|
'''
|
||||||
|
|
||||||
|
type_elements: TypeSpec
|
||||||
|
|
||||||
|
DISCRIMINANT = 0x0e
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def read_from(reader: BinaryReader):
|
||||||
|
'''
|
||||||
|
Deserialize this 'TypeSpec' type from 'BinaryReader'.
|
||||||
|
'''
|
||||||
|
type_elements = TypeSpec.read_from(reader)
|
||||||
|
return VecTypeSpec(type_elements)
|
||||||
|
|
||||||
|
def read_element_from(self, reader: BinaryReader, type_env,
|
||||||
|
mode: SerializeMode):
|
||||||
|
'''
|
||||||
|
Deserialize elements for this 'TypeSpec' from RPC 'BinaryReader'.
|
||||||
|
'''
|
||||||
|
length = SIZE_SIMPLE_TYPE_SPEC.read_element_from(reader, type_env, mode)
|
||||||
|
|
||||||
|
if self.type_elements == SimpleTypeSpec(SimpleType.U8):
|
||||||
|
return reader.readBytes(length)
|
||||||
|
|
||||||
|
array = []
|
||||||
|
for i in range(0, length):
|
||||||
|
elem = self.type_elements.read_element_from(reader, type_env, mode)
|
||||||
|
array.append(elem)
|
||||||
|
return array
|
||||||
|
|
||||||
|
|
||||||
|
@enforce_typing.enforce_types
|
||||||
|
@dataclasses.dataclass(frozen=True, slots=True)
|
||||||
|
class MapTypeSpec(TypeSpec):
|
||||||
|
'''
|
||||||
|
Type spec for a dynamically-sized map from some type to another type.
|
||||||
|
'''
|
||||||
|
|
||||||
|
type_key: TypeSpec
|
||||||
|
type_value: TypeSpec
|
||||||
|
|
||||||
|
DISCRIMINANT = 0x0f
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def read_from(reader: BinaryReader):
|
||||||
|
'''
|
||||||
|
Deserialize this 'TypeSpec' type from 'BinaryReader'.
|
||||||
|
'''
|
||||||
|
type_key = TypeSpec.read_from(reader)
|
||||||
|
type_value = TypeSpec.read_from(reader)
|
||||||
|
return MapTypeSpec(type_key, type_value)
|
||||||
|
|
||||||
|
def read_element_from(self, reader: BinaryReader, type_env,
|
||||||
|
mode: SerializeMode):
|
||||||
|
'''
|
||||||
|
Deserialize elements for this 'TypeSpec' from RPC 'BinaryReader'.
|
||||||
|
'''
|
||||||
|
out = {}
|
||||||
|
length = SIZE_SIMPLE_TYPE_SPEC.read_element_from(reader, type_env, mode)
|
||||||
|
for i in range(0, length):
|
||||||
|
key = self.type_key.read_element_from(reader, type_env, mode)
|
||||||
|
val = self.type_value.read_element_from(reader, type_env, mode)
|
||||||
|
out[key] = val
|
||||||
|
return out
|
||||||
|
|
||||||
|
|
||||||
|
@enforce_typing.enforce_types
|
||||||
|
@dataclasses.dataclass(frozen=True, slots=True)
|
||||||
|
class SetTypeSpec(TypeSpec):
|
||||||
|
'''
|
||||||
|
Type spec for a dynamically-sized set.
|
||||||
|
'''
|
||||||
|
|
||||||
|
type_elements: TypeSpec
|
||||||
|
|
||||||
|
DISCRIMINANT = 0x10
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def read_from(reader: BinaryReader):
|
||||||
|
'''
|
||||||
|
Deserialize this 'TypeSpec' type from 'BinaryReader'.
|
||||||
|
'''
|
||||||
|
type_elements = TypeSpec.read_from(reader)
|
||||||
|
return SetTypeSpec(type_elements)
|
||||||
|
|
||||||
|
def read_element_from(self, reader: BinaryReader, type_env,
|
||||||
|
mode: SerializeMode):
|
||||||
|
'''
|
||||||
|
Deserialize elements for this 'TypeSpec' from RPC 'BinaryReader'.
|
||||||
|
'''
|
||||||
|
array = []
|
||||||
|
length = SIZE_SIMPLE_TYPE_SPEC.read_element_from(reader, type_env, mode)
|
||||||
|
for i in range(0, length):
|
||||||
|
array.append(self.type_elements.read_element_from(reader, type_env),
|
||||||
|
mode)
|
||||||
|
return frozenset(array)
|
||||||
|
|
||||||
|
|
||||||
|
@enforce_typing.enforce_types
|
||||||
|
@dataclasses.dataclass(frozen=True, slots=True)
|
||||||
|
class ArrayTypeSpec(TypeSpec):
|
||||||
|
'''
|
||||||
|
Type spec for byte-array.
|
||||||
|
'''
|
||||||
|
|
||||||
|
length: int
|
||||||
|
|
||||||
|
DISCRIMINANT = 0x11
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def read_from(reader: BinaryReader):
|
||||||
|
'''
|
||||||
|
Deserialize this 'TypeSpec' type from 'BinaryReader'.
|
||||||
|
'''
|
||||||
|
length = reader.readUInt8()
|
||||||
|
return ArrayTypeSpec(length)
|
||||||
|
|
||||||
|
def read_element_from(self, reader: BinaryReader, type_env,
|
||||||
|
mode: SerializeMode):
|
||||||
|
'''
|
||||||
|
Deserialize elements for this 'TypeSpec' from RPC 'BinaryReader'.
|
||||||
|
'''
|
||||||
|
return reader.readBytes(self.length)
|
||||||
|
|
||||||
|
def read_element_from(self, reader: BinaryReader, type_env,
|
||||||
|
mode: SerializeMode):
|
||||||
|
'''
|
||||||
|
Deserialize elements for this 'TypeSpec' from state 'BinaryReader'.
|
||||||
|
'''
|
||||||
|
return reader.readBytes(self.length)
|
||||||
|
|
||||||
|
|
||||||
|
@enforce_typing.enforce_types
|
||||||
|
@dataclasses.dataclass(frozen=True, slots=True)
|
||||||
|
class OptionTypeSpec(TypeSpec):
|
||||||
|
'''
|
||||||
|
Type spec for some option type containing a specific sub-type.
|
||||||
|
'''
|
||||||
|
|
||||||
|
type_elements: TypeSpec
|
||||||
|
|
||||||
|
DISCRIMINANT = 0x12
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def read_from(reader: BinaryReader):
|
||||||
|
'''
|
||||||
|
Deserialize this 'TypeSpec' type from 'BinaryReader'.
|
||||||
|
'''
|
||||||
|
type_elements = TypeSpec.read_from(reader)
|
||||||
|
return OptionTypeSpec(type_elements)
|
||||||
|
|
||||||
|
def read_element_from(self, reader: BinaryReader, type_env,
|
||||||
|
mode: SerializeMode):
|
||||||
|
'''
|
||||||
|
Deserialize elements for this 'TypeSpec' from RPC 'BinaryReader'.
|
||||||
|
'''
|
||||||
|
discriminant = reader.readUInt8()
|
||||||
|
if discriminant == 0:
|
||||||
|
return None
|
||||||
|
else:
|
||||||
|
return self.type_elements.read_element_from(reader, type_env, mode)
|
||||||
|
|
||||||
|
def read_element_from(self, reader: BinaryReader, type_env,
|
||||||
|
mode: SerializeMode):
|
||||||
|
'''
|
||||||
|
Deserialize elements for this 'TypeSpec' from state 'BinaryReader'.
|
||||||
|
'''
|
||||||
|
discriminant = reader.readUInt8()
|
||||||
|
if discriminant == 0:
|
||||||
|
return None
|
||||||
|
else:
|
||||||
|
return self.type_elements.read_element_from(reader, type_env, mode)
|
||||||
|
|
||||||
|
|
||||||
|
def simple_type_spec_read_from(variant):
|
||||||
|
v = SimpleTypeSpec(variant)
|
||||||
|
return lambda r: v
|
||||||
|
|
||||||
|
|
||||||
|
TYPE_SPEC_SUBTYPE_READ_FROM_BY_DISCRIMINANT = {
|
||||||
|
0x00: NamedTypeRef.read_from_inner,
|
||||||
|
} | {
|
||||||
|
v.value: simple_type_spec_read_from(v) for v in SimpleType
|
||||||
|
} | {
|
||||||
|
t.DISCRIMINANT: t.read_from for t in
|
||||||
|
[VecTypeSpec, MapTypeSpec, SetTypeSpec, ArrayTypeSpec, OptionTypeSpec]
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@enforce_typing.enforce_types
|
||||||
|
@dataclasses.dataclass(frozen=True, slots = True, order=True)
|
||||||
|
class Version:
|
||||||
|
'''
|
||||||
|
Version field.
|
||||||
|
'''
|
||||||
|
major: int
|
||||||
|
minor: int
|
||||||
|
patch: int
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def read_from(reader: BinaryReader):
|
||||||
|
'''
|
||||||
|
Deserialize this struct from 'BinaryReader'.
|
||||||
|
'''
|
||||||
|
major = reader.readUInt8()
|
||||||
|
minor = reader.readUInt8()
|
||||||
|
patch = reader.readUInt8()
|
||||||
|
return Version(major, minor, patch)
|
||||||
|
|
||||||
|
|
||||||
|
class FnKind(Enum):
|
||||||
|
'''
|
||||||
|
Function kinds that contract can be invoked with.
|
||||||
|
'''
|
||||||
|
|
||||||
|
INIT = 0x01
|
||||||
|
ACTION = 0x02
|
||||||
|
CALLBACK = 0x03
|
||||||
|
ZK_SECRET_INPUT = 0x10
|
||||||
|
ZK_VAR_INPUTTED = 0x11
|
||||||
|
ZK_VAR_REJECTED = 0x12
|
||||||
|
ZK_COMPUTE_COMPLETE = 0x13
|
||||||
|
ZK_VAR_OPENED = 0x14
|
||||||
|
ZK_USER_VAR_OPENED = 0x15
|
||||||
|
ZK_ATTESTATION_COMPLETE = 0x16
|
||||||
|
ZK_SECRET_INPUT_WITH_EXPLICIT_TYPE = 0x17
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def read_from(reader: BinaryReader):
|
||||||
|
'''
|
||||||
|
Deserialize this struct from 'BinaryReader'.
|
||||||
|
'''
|
||||||
|
discriminant = reader.readUInt8()
|
||||||
|
if found := [kind for kind in FnKind if kind.value == discriminant]:
|
||||||
|
assert len(found) == 1
|
||||||
|
return found[0]
|
||||||
|
assert False, "Unknown discriminant: 0x{:02x}".format(discriminant)
|
||||||
|
|
||||||
|
|
||||||
|
@enforce_typing.enforce_types
|
||||||
|
@dataclasses.dataclass(frozen=True, slots=True)
|
||||||
|
class FieldAbi:
|
||||||
|
'''
|
||||||
|
Field of a struct.
|
||||||
|
'''
|
||||||
|
|
||||||
|
name: Identifier
|
||||||
|
type: TypeSpec
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def read_from(reader: BinaryReader):
|
||||||
|
'''
|
||||||
|
Deserialize this struct from 'BinaryReader'.
|
||||||
|
'''
|
||||||
|
name = reader.readString()
|
||||||
|
type = TypeSpec.read_from(reader)
|
||||||
|
return FieldAbi(name, type)
|
||||||
|
|
||||||
|
|
||||||
|
@enforce_typing.enforce_types
|
||||||
|
@dataclasses.dataclass(frozen=True, slots=True)
|
||||||
|
class ArgumentAbi:
|
||||||
|
'''
|
||||||
|
Argument of a function.
|
||||||
|
'''
|
||||||
|
name: Identifier
|
||||||
|
type: TypeSpec
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def read_from(reader: BinaryReader):
|
||||||
|
'''
|
||||||
|
Deserialize this struct from 'BinaryReader'.
|
||||||
|
'''
|
||||||
|
name = reader.readString()
|
||||||
|
type = TypeSpec.read_from(reader)
|
||||||
|
return ArgumentAbi(name, type)
|
||||||
|
|
||||||
|
|
||||||
|
@enforce_typing.enforce_types
|
||||||
|
@dataclasses.dataclass(frozen=True, slots=True)
|
||||||
|
class NamedTypeSpec:
|
||||||
|
'''
|
||||||
|
Supertype of named types.
|
||||||
|
'''
|
||||||
|
|
||||||
|
name: Identifier
|
||||||
|
type_index: int
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def read_from(reader: BinaryReader, type_index: int):
|
||||||
|
'''
|
||||||
|
Deserialize this 'TypeSpec' type from 'BinaryReader'.
|
||||||
|
'''
|
||||||
|
discriminant = reader.readUInt8()
|
||||||
|
if discriminant == StructTypeSpec.DISCRIMINANT:
|
||||||
|
return StructTypeSpec.read_from(reader, type_index)
|
||||||
|
if discriminant == EnumTypeSpec.DISCRIMINANT:
|
||||||
|
return EnumTypeSpec.read_from(reader, type_index)
|
||||||
|
|
||||||
|
|
||||||
|
@enforce_typing.enforce_types
|
||||||
|
@dataclasses.dataclass(frozen=True, slots=True)
|
||||||
|
class StructTypeSpec(NamedTypeSpec):
|
||||||
|
'''
|
||||||
|
Struct type specification.
|
||||||
|
'''
|
||||||
|
|
||||||
|
fields: list[FieldAbi]
|
||||||
|
|
||||||
|
DISCRIMINANT = 0x01
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def read_from(reader: BinaryReader, type_index: int):
|
||||||
|
'''
|
||||||
|
Deserialize this 'TypeSpec' type from 'BinaryReader'.
|
||||||
|
'''
|
||||||
|
name = reader.readString()
|
||||||
|
variants = reader.readList(FieldAbi.read_from)
|
||||||
|
return StructTypeSpec(name, type_index, variants)
|
||||||
|
|
||||||
|
def read_element_from(self, reader: BinaryReader, type_env,
|
||||||
|
mode: SerializeMode):
|
||||||
|
'''
|
||||||
|
Deserialize elements for this 'TypeSpec' from RPC 'BinaryReader'.
|
||||||
|
'''
|
||||||
|
result = {}
|
||||||
|
for field in self.fields:
|
||||||
|
result[field.name] = field.type.read_element_from(
|
||||||
|
reader, type_env, mode)
|
||||||
|
result['__type'] = self.name
|
||||||
|
return result
|
||||||
|
|
||||||
|
|
||||||
|
@enforce_typing.enforce_types
|
||||||
|
@dataclasses.dataclass(frozen=True, slots=True)
|
||||||
|
class EnumVariant:
|
||||||
|
'''
|
||||||
|
Enum variant specification.
|
||||||
|
'''
|
||||||
|
|
||||||
|
discriminant: int
|
||||||
|
definition: NamedTypeRef
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def read_from(reader: BinaryReader):
|
||||||
|
'''
|
||||||
|
Deserialize an 'EnumVariant' from 'BinaryReader'.
|
||||||
|
'''
|
||||||
|
discriminant = reader.readUInt8()
|
||||||
|
type = NamedTypeRef.read_from_inner(reader)
|
||||||
|
return EnumVariant(discriminant, type)
|
||||||
|
|
||||||
|
|
||||||
|
@enforce_typing.enforce_types
|
||||||
|
@dataclasses.dataclass(frozen=True, slots=True)
|
||||||
|
class EnumTypeSpec(NamedTypeSpec):
|
||||||
|
'''
|
||||||
|
Enum type specification.
|
||||||
|
'''
|
||||||
|
|
||||||
|
variants_by_discriminant: Mapping[int, EnumVariant]
|
||||||
|
|
||||||
|
DISCRIMINANT = 0x02
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def read_from(reader: BinaryReader, type_index: int):
|
||||||
|
'''
|
||||||
|
Deserialize this 'TypeSpec' type from 'BinaryReader'.
|
||||||
|
'''
|
||||||
|
name = reader.readString()
|
||||||
|
variants = reader.readList(EnumVariant.read_from)
|
||||||
|
variants_by_discriminant = frozendict(
|
||||||
|
{v.discriminant: v for v in variants})
|
||||||
|
return EnumTypeSpec(name, type_index, variants_by_discriminant)
|
||||||
|
|
||||||
|
def read_element_from(self, reader: BinaryReader, type_env,
|
||||||
|
mode: SerializeMode):
|
||||||
|
'''
|
||||||
|
Deserialize elements for this 'TypeSpec' from RPC 'BinaryReader'.
|
||||||
|
'''
|
||||||
|
discriminant = reader.readUInt8()
|
||||||
|
variant = self.variants_by_discriminant[discriminant]
|
||||||
|
return variant.definition.read_element_from(reader, type_env, mode)
|
||||||
|
|
||||||
|
|
||||||
|
@enforce_typing.enforce_types
|
||||||
|
@dataclasses.dataclass(frozen=True, slots=True)
|
||||||
|
class FnAbi:
|
||||||
|
'''
|
||||||
|
Function definition.
|
||||||
|
'''
|
||||||
|
|
||||||
|
kind: FnKind
|
||||||
|
name: Identifier
|
||||||
|
shortname: int
|
||||||
|
arguments: list[ArgumentAbi]
|
||||||
|
secret_argument: ArgumentAbi | None
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def read_from(reader: BinaryReader):
|
||||||
|
'''
|
||||||
|
Deserialize 'FnAbi' from 'BinaryReader'.
|
||||||
|
'''
|
||||||
|
kind = FnKind.read_from(reader)
|
||||||
|
name = reader.readString()
|
||||||
|
shortname = reader.readLeb128()
|
||||||
|
arguments = reader.readList(ArgumentAbi.read_from)
|
||||||
|
secret_argument = None
|
||||||
|
if kind == FnKind.ZK_SECRET_INPUT_WITH_EXPLICIT_TYPE:
|
||||||
|
secret_argument = ArgumentAbi.read_from(reader)
|
||||||
|
|
||||||
|
return FnAbi(kind, name, shortname, arguments, secret_argument)
|
||||||
|
|
||||||
|
@enforce_typing.enforce_types
|
||||||
|
@dataclasses.dataclass(frozen=True, slots=True)
|
||||||
|
class ContractAbi:
|
||||||
|
'''
|
||||||
|
Contract definition.
|
||||||
|
'''
|
||||||
|
|
||||||
|
named_types_by_id: Mapping[Identifier, NamedTypeSpec]
|
||||||
|
named_types_by_idx: Mapping[int, NamedTypeSpec]
|
||||||
|
hooks: Mapping[Identifier, FnAbi]
|
||||||
|
state_type: TypeSpec
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def read_from(reader: BinaryReader | str):
|
||||||
|
'''
|
||||||
|
Deserialize 'ContractAbi' from 'BinaryReader'.
|
||||||
|
'''
|
||||||
|
|
||||||
|
num_named_types = reader.readUInt32BigEndian()
|
||||||
|
named_types = []
|
||||||
|
for type_index in range(0, num_named_types):
|
||||||
|
named_types.append(NamedTypeSpec.read_from(reader, type_index))
|
||||||
|
|
||||||
|
hooks = reader.readList(FnAbi.read_from)
|
||||||
|
state_type = TypeSpec.read_from(reader)
|
||||||
|
return ContractAbi(frozendict({t.name: t for t in named_types}),
|
||||||
|
frozendict({t.type_index: t for t in named_types}),
|
||||||
|
frozendict({t.name: t for t in hooks}), state_type)
|
||||||
|
|
||||||
|
def read_type_element_from_rpc(self, type_name: Identifier,
|
||||||
|
rpc: BinaryReader):
|
||||||
|
'''
|
||||||
|
Reads element of the given type name from RPC 'BinaryReader'.
|
||||||
|
'''
|
||||||
|
return self.named_types_by_id[type_name].read_element_from(
|
||||||
|
rpc, self.named_types_by_idx, SerializeMode.RPC)
|
||||||
|
|
||||||
|
def read_state(self, reader: BinaryReader):
|
||||||
|
if not isinstance(reader, BinaryReader):
|
||||||
|
reader = BinaryReader(reader)
|
||||||
|
|
||||||
|
return self.state_type.read_element_from(reader, self.named_types_by_idx,
|
||||||
|
SerializeMode.STATE)
|
||||||
|
|
||||||
|
|
||||||
|
@enforce_typing.enforce_types
|
||||||
|
@dataclasses.dataclass(frozen=True, slots=True)
|
||||||
|
class FileAbi:
|
||||||
|
'''
|
||||||
|
File definition.
|
||||||
|
'''
|
||||||
|
|
||||||
|
version_binder: Version
|
||||||
|
version_client: Version
|
||||||
|
contract: ContractAbi
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def read_from(reader: BinaryReader):
|
||||||
|
'''
|
||||||
|
Deserialize 'FileAbi' from 'BinaryReader'.
|
||||||
|
'''
|
||||||
|
|
||||||
|
if not isinstance(reader, BinaryReader):
|
||||||
|
reader = BinaryReader(reader)
|
||||||
|
|
||||||
|
header = reader.readBytes(6)
|
||||||
|
assert header == b'PBCABI'
|
||||||
|
version_binder = Version.read_from(reader)
|
||||||
|
version_client = Version.read_from(reader)
|
||||||
|
|
||||||
|
assert version_client >= Version(5, 0, 0), version_client
|
||||||
|
|
||||||
|
contract = ContractAbi.read_from(reader)
|
||||||
|
return FileAbi(version_binder, version_client, contract)
|
1
requirements.txt
Normal file
1
requirements.txt
Normal file
|
@ -0,0 +1 @@
|
||||||
|
enforce_typing
|
Loading…
Reference in New Issue
Block a user