Solidity and Ethereum int256 for Python, SQLAlchemy and SQL Databases, efficiently as 32 bytes blobs
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
class LiquidityChanged(TransactionEvent): | |
"""A sampled liquidity at any moment.""" | |
__tablename__ = "liquidity" | |
delta0 = sa.Column(Int257, nullable=False, index=False) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
from utils import Int257 | |
def test_max_positive(): | |
max_positive = int(2**256 - 1) | |
int257 = Int257() | |
bin = int257.process_bind_param(max_positive, None) | |
assert bin == b'\x00\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff' | |
i = int257.process_result_value(bin, None) | |
assert i == max_positive | |
def test_max_negative(): | |
max_negative = -int(2**256 - 1) | |
int257 = Int257() | |
bin = int257.process_bind_param(max_negative, None) | |
assert bin == b'\x01\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff' | |
i = int257.process_result_value(bin, None) | |
assert i == max_negative |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
from sqlalchemy import types | |
class Int257(types.TypeDecorator): | |
"""SQLAlchemy class to store Ethereum Virtual Machine (EVM) uint256s in an SQL database. | |
Unlike Solidity's native uin256, this is one allows to store the sign and negative numbers, | |
making it more useful for accounting data. | |
Should be a bit more efficient than storing stringified decimals in a database. | |
""" | |
# uint256 is 32 bytes | |
impl = types.BINARY(33) | |
MAX_UINT256 = 2**256 | |
BYTE_ORDER = "big" | |
def load_dialect_impl(self, dialect): | |
return dialect.type_descriptor(types.BINARY(33)) | |
def process_bind_param(self, val: int, dialect): | |
assert type(val) == int | |
assert val < Int257.MAX_UINT256 | |
# Separate sign and 256-bit int body | |
# The sign is stored as the highest 33th byte | |
b = abs(val).to_bytes(32, byteorder=Int257.BYTE_ORDER) | |
sign = b"\0" if val >= 0 else b"\1" | |
return sign + b | |
def process_result_value(self, val: bytes, dialect): | |
assert type(val) == bytes | |
sign = -1 if val[0] else 1 | |
lower = val[1:] | |
number = int.from_bytes(lower, byteorder=Int257.BYTE_ORDER) | |
return sign * number |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment