Lash/abi encoder
This commit is contained in:
parent
1f19aecd0e
commit
b3d782b4bd
7
chainlib/block.py
Normal file
7
chainlib/block.py
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
# standard imports
|
||||||
|
import enum
|
||||||
|
|
||||||
|
|
||||||
|
class BlockSpec(enum.IntEnum):
|
||||||
|
PENDING = -1
|
||||||
|
LATEST = 0
|
@ -1,3 +1,7 @@
|
|||||||
|
# standard imports
|
||||||
|
import copy
|
||||||
|
|
||||||
|
|
||||||
class ChainSpec:
|
class ChainSpec:
|
||||||
|
|
||||||
def __init__(self, engine, common_name, network_id, tag=None):
|
def __init__(self, engine, common_name, network_id, tag=None):
|
||||||
@ -35,6 +39,15 @@ class ChainSpec:
|
|||||||
return ChainSpec(o[0], o[1], int(o[2]), tag)
|
return ChainSpec(o[0], o[1], int(o[2]), tag)
|
||||||
|
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def from_dict(o):
|
||||||
|
return ChainSpec(o['engine'], o['common_name'], o['network_id'], tag=o['tag'])
|
||||||
|
|
||||||
|
|
||||||
|
def asdict(self):
|
||||||
|
return copy.copy(self.o)
|
||||||
|
|
||||||
|
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
s = '{}:{}:{}'.format(self.o['engine'], self.o['common_name'], self.o['network_id'])
|
s = '{}:{}:{}'.format(self.o['engine'], self.o['common_name'], self.o['network_id'])
|
||||||
if self.o['tag'] != None:
|
if self.o['tag'] != None:
|
||||||
|
217
chainlib/connection.py
Normal file
217
chainlib/connection.py
Normal file
@ -0,0 +1,217 @@
|
|||||||
|
# standard imports
|
||||||
|
import socket
|
||||||
|
import os
|
||||||
|
import logging
|
||||||
|
import enum
|
||||||
|
import re
|
||||||
|
import json
|
||||||
|
from urllib.request import (
|
||||||
|
Request,
|
||||||
|
urlopen,
|
||||||
|
urlparse,
|
||||||
|
urljoin,
|
||||||
|
build_opener,
|
||||||
|
install_opener,
|
||||||
|
)
|
||||||
|
|
||||||
|
# local imports
|
||||||
|
from .jsonrpc import (
|
||||||
|
jsonrpc_template,
|
||||||
|
jsonrpc_result,
|
||||||
|
DefaultErrorParser,
|
||||||
|
)
|
||||||
|
from .http import PreemptiveBasicAuthHandler
|
||||||
|
|
||||||
|
logg = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
error_parser = DefaultErrorParser()
|
||||||
|
|
||||||
|
|
||||||
|
class ConnType(enum.Enum):
|
||||||
|
|
||||||
|
CUSTOM = 0x00
|
||||||
|
HTTP = 0x100
|
||||||
|
HTTP_SSL = 0x101
|
||||||
|
WEBSOCKET = 0x200
|
||||||
|
WEBSOCKET_SSL = 0x201
|
||||||
|
UNIX = 0x1000
|
||||||
|
|
||||||
|
|
||||||
|
re_http = '^http(s)?://'
|
||||||
|
re_ws = '^ws(s)?://'
|
||||||
|
re_unix = '^ipc://'
|
||||||
|
|
||||||
|
def str_to_connspec(s):
|
||||||
|
if s == 'custom':
|
||||||
|
return ConnType.CUSTOM
|
||||||
|
|
||||||
|
m = re.match(re_http, s)
|
||||||
|
if m != None:
|
||||||
|
if m.group(1) != None:
|
||||||
|
return ConnType.HTTP_SSL
|
||||||
|
return ConnType.HTTP
|
||||||
|
|
||||||
|
m = re.match(re_ws, s)
|
||||||
|
if m != None:
|
||||||
|
if m.group(1) != None:
|
||||||
|
return ConnType.WEBSOCKET_SSL
|
||||||
|
return ConnType.WEBSOCKET
|
||||||
|
|
||||||
|
|
||||||
|
m = re.match(re_unix, s)
|
||||||
|
if m != None:
|
||||||
|
return ConnType.UNIX
|
||||||
|
|
||||||
|
raise ValueError('unknown connection type {}'.format(s))
|
||||||
|
|
||||||
|
|
||||||
|
def from_conntype(t):
|
||||||
|
if t in [ConnType.HTTP, ConnType.HTTP_SSL]:
|
||||||
|
return JSONRPCHTTPConnection
|
||||||
|
elif t in [ConnType.UNIX]:
|
||||||
|
return JSONRPCUnixConnection
|
||||||
|
raise NotImplementedError(t)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
class RPCConnection():
|
||||||
|
|
||||||
|
__locations = {}
|
||||||
|
__constructors = {}
|
||||||
|
|
||||||
|
def __init__(self, url=None, chain_spec=None):
|
||||||
|
self.chain_spec = chain_spec
|
||||||
|
self.location = None
|
||||||
|
self.basic = None
|
||||||
|
if url == None:
|
||||||
|
return
|
||||||
|
|
||||||
|
url_parsed = urlparse(url)
|
||||||
|
logg.debug('creating connection {} -> {}'.format(url, url_parsed))
|
||||||
|
basic = url_parsed.netloc.split('@')
|
||||||
|
location = None
|
||||||
|
if len(basic) == 1:
|
||||||
|
location = url_parsed.netloc
|
||||||
|
else:
|
||||||
|
location = basic[1]
|
||||||
|
self.basic = basic[0].split(':')
|
||||||
|
#if url_parsed.port != None:
|
||||||
|
# location += ':' + str(url_parsed.port)
|
||||||
|
|
||||||
|
self.location = os.path.join('{}://'.format(url_parsed.scheme), location)
|
||||||
|
self.location = urljoin(self.location, url_parsed.path)
|
||||||
|
|
||||||
|
logg.debug('parsed url {} to location {}'.format(url, self.location))
|
||||||
|
|
||||||
|
|
||||||
|
# TODO: constructor needs to be constructor-factory, that itself can select on url type
|
||||||
|
@staticmethod
|
||||||
|
def register_location(location, chain_spec, tag='default', constructor=None, exist_ok=False):
|
||||||
|
chain_str = str(chain_spec)
|
||||||
|
if RPCConnection.__locations.get(chain_str) == None:
|
||||||
|
RPCConnection.__locations[chain_str] = {}
|
||||||
|
RPCConnection.__constructors[chain_str] = {}
|
||||||
|
elif not exist_ok:
|
||||||
|
v = RPCConnection.__locations[chain_str].get(tag)
|
||||||
|
if v != None:
|
||||||
|
raise ValueError('duplicate registration of tag {}:{}, requested {} already had {}'.format(chain_str, tag, location, v))
|
||||||
|
conntype = str_to_connspec(location)
|
||||||
|
RPCConnection.__locations[chain_str][tag] = (conntype, location)
|
||||||
|
if constructor != None:
|
||||||
|
RPCConnection.__constructors[chain_str][tag] = constructor
|
||||||
|
logg.info('registered rpc connection {} ({}:{}) as {} with custom constructor {}'.format(location, chain_str, tag, conntype, constructor))
|
||||||
|
else:
|
||||||
|
logg.info('registered rpc connection {} ({}:{}) as {}'.format(location, chain_str, tag, conntype))
|
||||||
|
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def connect(chain_spec, tag='default'):
|
||||||
|
chain_str = str(chain_spec)
|
||||||
|
c = RPCConnection.__locations[chain_str][tag]
|
||||||
|
constructor = RPCConnection.__constructors[chain_str].get(tag)
|
||||||
|
if constructor == None:
|
||||||
|
constructor = from_conntype(c[0])
|
||||||
|
logg.debug('cons {} {}'.format(constructor, c))
|
||||||
|
return constructor(url=c[1], chain_spec=chain_spec)
|
||||||
|
|
||||||
|
|
||||||
|
class HTTPConnection(RPCConnection):
|
||||||
|
|
||||||
|
def disconnect(self):
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
def __del__(self):
|
||||||
|
self.disconnect()
|
||||||
|
|
||||||
|
|
||||||
|
class UnixConnection(RPCConnection):
|
||||||
|
|
||||||
|
def disconnect(self):
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
def __del__(self):
|
||||||
|
self.disconnect()
|
||||||
|
|
||||||
|
|
||||||
|
class JSONRPCHTTPConnection(HTTPConnection):
|
||||||
|
|
||||||
|
def do(self, o, error_parser=error_parser):
|
||||||
|
req = Request(
|
||||||
|
self.location,
|
||||||
|
method='POST',
|
||||||
|
)
|
||||||
|
req.add_header('Content-Type', 'application/json')
|
||||||
|
data = json.dumps(o)
|
||||||
|
logg.debug('(HTTP) send {}'.format(data))
|
||||||
|
|
||||||
|
if self.basic != None:
|
||||||
|
handler = PreemptiveBasicAuthHandler()
|
||||||
|
handler.add_password(
|
||||||
|
realm=None,
|
||||||
|
uri=self.location,
|
||||||
|
user=self.basic[0],
|
||||||
|
passwd=self.basic[1],
|
||||||
|
)
|
||||||
|
ho = build_opener(handler)
|
||||||
|
install_opener(ho)
|
||||||
|
|
||||||
|
r = urlopen(req, data=data.encode('utf-8'))
|
||||||
|
result = json.load(r)
|
||||||
|
logg.debug('(HTTP) recv {}'.format(result))
|
||||||
|
if o['id'] != result['id']:
|
||||||
|
raise ValueError('RPC id mismatch; sent {} received {}'.format(o['id'], result['id']))
|
||||||
|
return jsonrpc_result(result, error_parser)
|
||||||
|
|
||||||
|
|
||||||
|
class JSONRPCUnixConnection(UnixConnection):
|
||||||
|
|
||||||
|
def do(self, o, error_parser=error_parser):
|
||||||
|
conn = socket.socket(family=socket.AF_UNIX, type=socket.SOCK_STREAM, proto=0)
|
||||||
|
conn.connect(self.location)
|
||||||
|
data = json.dumps(o)
|
||||||
|
|
||||||
|
logg.debug('unix socket send {}'.format(data))
|
||||||
|
l = len(data)
|
||||||
|
n = 0
|
||||||
|
while n < l:
|
||||||
|
c = conn.send(data.encode('utf-8'))
|
||||||
|
if c == 0:
|
||||||
|
s.close()
|
||||||
|
raise IOError('unix socket ({}/{}) {}'.format(n, l, data))
|
||||||
|
n += c
|
||||||
|
r = b''
|
||||||
|
while True:
|
||||||
|
b = conn.recv(4096)
|
||||||
|
if len(b) == 0:
|
||||||
|
break
|
||||||
|
r += b
|
||||||
|
conn.close()
|
||||||
|
logg.debug('unix socket recv {}'.format(r.decode('utf-8')))
|
||||||
|
result = json.loads(r)
|
||||||
|
if result['id'] != o['id']:
|
||||||
|
raise ValueError('RPC id mismatch; sent {} received {}'.format(o['id'], result['id']))
|
||||||
|
|
||||||
|
return jsonrpc_result(result, error_parser)
|
||||||
|
|
7
chainlib/error.py
Normal file
7
chainlib/error.py
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
# TODO: use json-rpc module
|
||||||
|
class JSONRPCException(Exception):
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
class ExecutionError(Exception):
|
||||||
|
pass
|
@ -4,44 +4,10 @@ from hexathon import (
|
|||||||
strip_0x,
|
strip_0x,
|
||||||
uniform,
|
uniform,
|
||||||
)
|
)
|
||||||
|
from crypto_dev_signer.encoding import (
|
||||||
|
is_address,
|
||||||
|
is_checksum_address,
|
||||||
|
to_checksum_address,
|
||||||
|
)
|
||||||
|
|
||||||
|
to_checksum = to_checksum_address
|
||||||
def is_address(address_hex):
|
|
||||||
try:
|
|
||||||
address_hex = strip_0x(address_hex)
|
|
||||||
except ValueError:
|
|
||||||
return False
|
|
||||||
return len(address_hex) == 40
|
|
||||||
|
|
||||||
|
|
||||||
def is_checksum_address(address_hex):
|
|
||||||
hx = None
|
|
||||||
try:
|
|
||||||
hx = to_checksum(address_hex)
|
|
||||||
except ValueError:
|
|
||||||
return False
|
|
||||||
print('{} {}'.format(hx, address_hex))
|
|
||||||
return hx == address_hex
|
|
||||||
|
|
||||||
|
|
||||||
def to_checksum(address_hex):
|
|
||||||
address_hex = strip_0x(address_hex)
|
|
||||||
address_hex = uniform(address_hex)
|
|
||||||
if len(address_hex) != 40:
|
|
||||||
raise ValueError('Invalid address length')
|
|
||||||
h = sha3.keccak_256()
|
|
||||||
h.update(address_hex.encode('utf-8'))
|
|
||||||
z = h.digest()
|
|
||||||
|
|
||||||
checksum_address_hex = '0x'
|
|
||||||
|
|
||||||
for (i, c) in enumerate(address_hex):
|
|
||||||
if c in '1234567890':
|
|
||||||
checksum_address_hex += c
|
|
||||||
elif c in 'abcdef':
|
|
||||||
if z[int(i / 2)] & (0x80 >> ((i % 2) * 4)) > 1:
|
|
||||||
checksum_address_hex += c.upper()
|
|
||||||
else:
|
|
||||||
checksum_address_hex += c
|
|
||||||
|
|
||||||
return checksum_address_hex
|
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
# third-party imports
|
# third-party imports
|
||||||
from chainlib.eth.rpc import jsonrpc_template
|
from chainlib.jsonrpc import jsonrpc_template
|
||||||
from chainlib.eth.tx import Tx
|
from chainlib.eth.tx import Tx
|
||||||
from hexathon import (
|
from hexathon import (
|
||||||
add_0x,
|
add_0x,
|
||||||
@ -7,25 +7,34 @@ from hexathon import (
|
|||||||
even,
|
even,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
def block_latest():
|
def block_latest():
|
||||||
o = jsonrpc_template()
|
o = jsonrpc_template()
|
||||||
o['method'] = 'eth_blockNumber'
|
o['method'] = 'eth_blockNumber'
|
||||||
return o
|
return o
|
||||||
|
|
||||||
|
|
||||||
def block_by_hash(hsh):
|
def block_by_hash(hsh, include_tx=True):
|
||||||
o = jsonrpc_template()
|
o = jsonrpc_template()
|
||||||
o['method'] = 'eth_getBlockByHash'
|
o['method'] = 'eth_getBlockByHash'
|
||||||
o['params'].append(hsh)
|
o['params'].append(hsh)
|
||||||
|
o['params'].append(include_tx)
|
||||||
return o
|
return o
|
||||||
|
|
||||||
|
|
||||||
def block_by_number(n):
|
def block_by_number(n, include_tx=True):
|
||||||
nhx = add_0x(even(hex(n)[2:]))
|
nhx = add_0x(even(hex(n)[2:]))
|
||||||
o = jsonrpc_template()
|
o = jsonrpc_template()
|
||||||
o['method'] = 'eth_getBlockByNumber'
|
o['method'] = 'eth_getBlockByNumber'
|
||||||
o['params'].append(nhx)
|
o['params'].append(nhx)
|
||||||
o['params'].append(True)
|
o['params'].append(include_tx)
|
||||||
|
return o
|
||||||
|
|
||||||
|
|
||||||
|
def transaction_count(block_hash):
|
||||||
|
o = jsonrpc_template()
|
||||||
|
o['method'] = 'eth_getBlockTransactionCountByHash'
|
||||||
|
o['params'].append(block_hash)
|
||||||
return o
|
return o
|
||||||
|
|
||||||
|
|
||||||
@ -36,6 +45,7 @@ class Block:
|
|||||||
self.number = int(strip_0x(src['number']), 16)
|
self.number = int(strip_0x(src['number']), 16)
|
||||||
self.txs = src['transactions']
|
self.txs = src['transactions']
|
||||||
self.block_src = src
|
self.block_src = src
|
||||||
|
self.timestamp = int(strip_0x(src['timestamp']), 16)
|
||||||
|
|
||||||
|
|
||||||
def src(self):
|
def src(self):
|
||||||
|
@ -1,8 +1,10 @@
|
|||||||
# standard imports
|
# standard imports
|
||||||
|
import copy
|
||||||
import logging
|
import logging
|
||||||
import json
|
import json
|
||||||
import datetime
|
import datetime
|
||||||
import time
|
import time
|
||||||
|
import socket
|
||||||
from urllib.request import (
|
from urllib.request import (
|
||||||
Request,
|
Request,
|
||||||
urlopen,
|
urlopen,
|
||||||
@ -19,36 +21,28 @@ from .error import (
|
|||||||
DefaultErrorParser,
|
DefaultErrorParser,
|
||||||
RevertEthException,
|
RevertEthException,
|
||||||
)
|
)
|
||||||
from .rpc import (
|
from .sign import (
|
||||||
|
sign_transaction,
|
||||||
|
)
|
||||||
|
from chainlib.connection import (
|
||||||
|
JSONRPCHTTPConnection,
|
||||||
|
JSONRPCUnixConnection,
|
||||||
|
error_parser,
|
||||||
|
)
|
||||||
|
from chainlib.jsonrpc import (
|
||||||
jsonrpc_template,
|
jsonrpc_template,
|
||||||
jsonrpc_result,
|
jsonrpc_result,
|
||||||
)
|
)
|
||||||
|
from chainlib.eth.tx import (
|
||||||
|
unpack,
|
||||||
|
)
|
||||||
|
|
||||||
error_parser = DefaultErrorParser()
|
|
||||||
logg = logging.getLogger(__name__)
|
logg = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
class HTTPConnection:
|
class EthHTTPConnection(JSONRPCHTTPConnection):
|
||||||
|
|
||||||
def __init__(self, url):
|
def wait(self, tx_hash_hex, delay=0.5, timeout=0.0, error_parser=error_parser):
|
||||||
self.url = url
|
|
||||||
|
|
||||||
|
|
||||||
def do(self, o, error_parser=error_parser):
|
|
||||||
req = Request(
|
|
||||||
self.url,
|
|
||||||
method='POST',
|
|
||||||
)
|
|
||||||
req.add_header('Content-Type', 'application/json')
|
|
||||||
data = json.dumps(o)
|
|
||||||
logg.debug('(HTTP) send {}'.format(data))
|
|
||||||
res = urlopen(req, data=data.encode('utf-8'))
|
|
||||||
o = json.load(res)
|
|
||||||
logg.debug('(HTTP) recv {}'.format(o))
|
|
||||||
return jsonrpc_result(o, error_parser)
|
|
||||||
|
|
||||||
|
|
||||||
def wait(self, tx_hash_hex, delay=0.5, timeout=0.0):
|
|
||||||
t = datetime.datetime.utcnow()
|
t = datetime.datetime.utcnow()
|
||||||
i = 0
|
i = 0
|
||||||
while True:
|
while True:
|
||||||
@ -56,17 +50,18 @@ class HTTPConnection:
|
|||||||
o['method'] ='eth_getTransactionReceipt'
|
o['method'] ='eth_getTransactionReceipt'
|
||||||
o['params'].append(add_0x(tx_hash_hex))
|
o['params'].append(add_0x(tx_hash_hex))
|
||||||
req = Request(
|
req = Request(
|
||||||
self.url,
|
self.location,
|
||||||
method='POST',
|
method='POST',
|
||||||
)
|
)
|
||||||
req.add_header('Content-Type', 'application/json')
|
req.add_header('Content-Type', 'application/json')
|
||||||
data = json.dumps(o)
|
data = json.dumps(o)
|
||||||
logg.debug('(HTTP) receipt attempt {} {}'.format(i, data))
|
logg.debug('(HTTP) poll receipt attempt {} {}'.format(i, data))
|
||||||
res = urlopen(req, data=data.encode('utf-8'))
|
res = urlopen(req, data=data.encode('utf-8'))
|
||||||
r = json.load(res)
|
r = json.load(res)
|
||||||
|
|
||||||
e = jsonrpc_result(r, error_parser)
|
e = jsonrpc_result(r, error_parser)
|
||||||
if e != None:
|
if e != None:
|
||||||
|
logg.debug('(HTTP) poll receipt completed {}'.format(r))
|
||||||
logg.debug('e {}'.format(strip_0x(e['status'])))
|
logg.debug('e {}'.format(strip_0x(e['status'])))
|
||||||
if strip_0x(e['status']) == '00':
|
if strip_0x(e['status']) == '00':
|
||||||
raise RevertEthException(tx_hash_hex)
|
raise RevertEthException(tx_hash_hex)
|
||||||
@ -79,3 +74,31 @@ class HTTPConnection:
|
|||||||
|
|
||||||
time.sleep(delay)
|
time.sleep(delay)
|
||||||
i += 1
|
i += 1
|
||||||
|
|
||||||
|
|
||||||
|
class EthUnixConnection(JSONRPCUnixConnection):
|
||||||
|
|
||||||
|
def wait(self, tx_hash_hex, delay=0.5, timeout=0.0, error_parser=error_parser):
|
||||||
|
raise NotImplementedError('Not yet implemented for unix socket')
|
||||||
|
|
||||||
|
|
||||||
|
class EthUnixSignerConnection(EthUnixConnection):
|
||||||
|
|
||||||
|
def sign_transaction_to_rlp(self, tx):
|
||||||
|
txs = tx.serialize()
|
||||||
|
logg.debug('serializing {}'.format(txs))
|
||||||
|
# TODO: because some rpc servers may fail when chainId is included, we are forced to spend cpu here on this
|
||||||
|
chain_id = txs.get('chainId') or 1
|
||||||
|
if self.chain_spec != None:
|
||||||
|
chain_id = self.chain_spec.chain_id()
|
||||||
|
txs['chainId'] = add_0x(chain_id.to_bytes(2, 'big').hex())
|
||||||
|
txs['from'] = add_0x(tx.sender)
|
||||||
|
o = sign_transaction(txs)
|
||||||
|
r = self.do(o)
|
||||||
|
logg.debug('sig got {}'.format(r))
|
||||||
|
return bytes.fromhex(strip_0x(r))
|
||||||
|
|
||||||
|
|
||||||
|
def sign_message(self, msg):
|
||||||
|
o = sign_message(msg)
|
||||||
|
return self.do(o)
|
||||||
|
@ -2,3 +2,4 @@ ZERO_ADDRESS = '0x{:040x}'.format(0)
|
|||||||
ZERO_CONTENT = '0x{:064x}'.format(0)
|
ZERO_CONTENT = '0x{:064x}'.format(0)
|
||||||
MINIMUM_FEE_UNITS = 21000
|
MINIMUM_FEE_UNITS = 21000
|
||||||
MINIMUM_FEE_PRICE = 1000000000
|
MINIMUM_FEE_PRICE = 1000000000
|
||||||
|
MAX_UINT = int('0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff', 16)
|
||||||
|
263
chainlib/eth/contract.py
Normal file
263
chainlib/eth/contract.py
Normal file
@ -0,0 +1,263 @@
|
|||||||
|
# standard imports
|
||||||
|
import enum
|
||||||
|
import re
|
||||||
|
import logging
|
||||||
|
|
||||||
|
# external imports
|
||||||
|
from hexathon import (
|
||||||
|
strip_0x,
|
||||||
|
pad,
|
||||||
|
)
|
||||||
|
|
||||||
|
# local imports
|
||||||
|
from chainlib.hash import keccak256_string_to_hex
|
||||||
|
from chainlib.block import BlockSpec
|
||||||
|
from chainlib.jsonrpc import jsonrpc_template
|
||||||
|
from .address import to_checksum_address
|
||||||
|
|
||||||
|
#logg = logging.getLogger(__name__)
|
||||||
|
logg = logging.getLogger()
|
||||||
|
|
||||||
|
|
||||||
|
re_method = r'^[a-zA-Z0-9_]+$'
|
||||||
|
|
||||||
|
class ABIContractType(enum.Enum):
|
||||||
|
|
||||||
|
BYTES32 = 'bytes32'
|
||||||
|
UINT256 = 'uint256'
|
||||||
|
ADDRESS = 'address'
|
||||||
|
STRING = 'string'
|
||||||
|
BOOLEAN = 'bool'
|
||||||
|
|
||||||
|
dynamic_contract_types = [
|
||||||
|
ABIContractType.STRING,
|
||||||
|
]
|
||||||
|
|
||||||
|
class ABIContractDecoder:
|
||||||
|
|
||||||
|
def __init__(self):
|
||||||
|
self.types = []
|
||||||
|
self.contents = []
|
||||||
|
|
||||||
|
|
||||||
|
def typ(self, v):
|
||||||
|
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)
|
||||||
|
self.__log_typ()
|
||||||
|
|
||||||
|
|
||||||
|
def val(self, v):
|
||||||
|
self.contents.append(v)
|
||||||
|
logg.debug('content is now {}'.format(self.contents))
|
||||||
|
|
||||||
|
|
||||||
|
def uint256(self, v):
|
||||||
|
return int(v, 16)
|
||||||
|
|
||||||
|
|
||||||
|
def bool(self, v):
|
||||||
|
return bool(self.uint256(v))
|
||||||
|
|
||||||
|
|
||||||
|
def address(self, v):
|
||||||
|
a = strip_0x(v)[64-40:]
|
||||||
|
return to_checksum_address(a)
|
||||||
|
|
||||||
|
|
||||||
|
def string(self, v):
|
||||||
|
s = strip_0x(v)
|
||||||
|
b = bytes.fromhex(s)
|
||||||
|
cursor = 0
|
||||||
|
offset = int.from_bytes(b[cursor:cursor+32], 'big')
|
||||||
|
cursor += 32
|
||||||
|
length = int.from_bytes(b[cursor:cursor+32], 'big')
|
||||||
|
cursor += 32
|
||||||
|
content = b[cursor:cursor+length]
|
||||||
|
logg.debug('parsing {}'.format(content))
|
||||||
|
return content.decode('utf-8')
|
||||||
|
|
||||||
|
|
||||||
|
def __log_typ(self):
|
||||||
|
logg.debug('types set to ({})'.format(','.join(self.types)))
|
||||||
|
|
||||||
|
|
||||||
|
def decode(self):
|
||||||
|
r = []
|
||||||
|
for i in range(len(self.types)):
|
||||||
|
m = getattr(self, self.types[i])
|
||||||
|
r.append(m(self.contents[i]))
|
||||||
|
return r
|
||||||
|
|
||||||
|
|
||||||
|
def get(self):
|
||||||
|
return self.decode()
|
||||||
|
|
||||||
|
|
||||||
|
def __str__(self):
|
||||||
|
return self.decode()
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
class ABIContractEncoder:
|
||||||
|
|
||||||
|
|
||||||
|
def __init__(self):
|
||||||
|
self.types = []
|
||||||
|
self.contents = []
|
||||||
|
self.method_name = None
|
||||||
|
self.method_contents = []
|
||||||
|
|
||||||
|
|
||||||
|
def method(self, m):
|
||||||
|
if re.match(re_method, m) == None:
|
||||||
|
raise ValueError('Invalid method {}, must match regular expression {}'.format(re_method))
|
||||||
|
self.method_name = m
|
||||||
|
self.__log_method()
|
||||||
|
|
||||||
|
|
||||||
|
def typ(self, v):
|
||||||
|
if self.method_name == None:
|
||||||
|
raise AttributeError('method name must be set before adding types')
|
||||||
|
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)
|
||||||
|
self.__log_method()
|
||||||
|
|
||||||
|
|
||||||
|
def __log_method(self):
|
||||||
|
logg.debug('method set to {}'.format(self.get_method()))
|
||||||
|
|
||||||
|
|
||||||
|
def __log_latest(self, v):
|
||||||
|
l = len(self.types) - 1
|
||||||
|
logg.debug('Encoder added {} -> {} ({})'.format(v, self.contents[l], self.types[l].value))
|
||||||
|
|
||||||
|
|
||||||
|
def uint256(self, v):
|
||||||
|
v = int(v)
|
||||||
|
b = v.to_bytes(32, 'big')
|
||||||
|
self.contents.append(b.hex())
|
||||||
|
self.types.append(ABIContractType.UINT256)
|
||||||
|
self.__log_latest(v)
|
||||||
|
|
||||||
|
|
||||||
|
def address(self, v):
|
||||||
|
self.bytes_fixed(32, v, 20)
|
||||||
|
self.types.append(ABIContractType.ADDRESS)
|
||||||
|
self.__log_latest(v)
|
||||||
|
|
||||||
|
|
||||||
|
def bytes32(self, v):
|
||||||
|
self.bytes_fixed(32, v)
|
||||||
|
self.types.append(ABIContractType.BYTES32)
|
||||||
|
self.__log_latest(v)
|
||||||
|
|
||||||
|
|
||||||
|
def string(self, v):
|
||||||
|
b = v.encode('utf-8')
|
||||||
|
l = len(b)
|
||||||
|
contents = l.to_bytes(32, 'big')
|
||||||
|
contents += b
|
||||||
|
padlen = 32 - (l % 32)
|
||||||
|
contents += padlen * b'\x00'
|
||||||
|
self.bytes_fixed(len(contents), contents)
|
||||||
|
self.types.append(ABIContractType.STRING)
|
||||||
|
self.__log_latest(v)
|
||||||
|
return contents
|
||||||
|
|
||||||
|
|
||||||
|
def bytes_fixed(self, mx, v, exact=0):
|
||||||
|
typ = type(v).__name__
|
||||||
|
if typ == 'str':
|
||||||
|
v = strip_0x(v)
|
||||||
|
l = len(v)
|
||||||
|
if exact > 0 and l != exact * 2:
|
||||||
|
raise ValueError('value wrong size; expected {}, got {})'.format(mx, l))
|
||||||
|
if l > mx * 2:
|
||||||
|
raise ValueError('value too long ({})'.format(l))
|
||||||
|
v = pad(v, mx)
|
||||||
|
elif typ == 'bytes':
|
||||||
|
l = len(v)
|
||||||
|
if exact > 0 and l != exact:
|
||||||
|
raise ValueError('value wrong size; expected {}, got {})'.format(mx, l))
|
||||||
|
b = bytearray(mx)
|
||||||
|
b[mx-l:] = v
|
||||||
|
v = pad(b.hex(), mx)
|
||||||
|
else:
|
||||||
|
raise ValueError('invalid input {}'.format(typ))
|
||||||
|
self.contents.append(v)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
def get_method(self):
|
||||||
|
if self.method_name == None:
|
||||||
|
return ''
|
||||||
|
return '{}({})'.format(self.method_name, ','.join(self.method_contents))
|
||||||
|
|
||||||
|
|
||||||
|
def get_method_signature(self):
|
||||||
|
s = self.get_method()
|
||||||
|
if s == '':
|
||||||
|
return s
|
||||||
|
return keccak256_string_to_hex(s)[:8]
|
||||||
|
|
||||||
|
|
||||||
|
def get_contents(self):
|
||||||
|
direct_contents = ''
|
||||||
|
pointer_contents = ''
|
||||||
|
l = len(self.types)
|
||||||
|
pointer_cursor = 32 * l
|
||||||
|
for i in range(l):
|
||||||
|
if self.types[i] in dynamic_contract_types:
|
||||||
|
content_length = len(self.contents[i])
|
||||||
|
pointer_contents += self.contents[i]
|
||||||
|
direct_contents += pointer_cursor.to_bytes(32, 'big').hex()
|
||||||
|
pointer_cursor += int(content_length / 2)
|
||||||
|
else:
|
||||||
|
direct_contents += self.contents[i]
|
||||||
|
s = ''.join(direct_contents + pointer_contents)
|
||||||
|
for i in range(0, len(s), 64):
|
||||||
|
l = len(s) - i
|
||||||
|
if l > 64:
|
||||||
|
l = 64
|
||||||
|
logg.debug('code word {} {}'.format(int(i / 64), s[i:i+64]))
|
||||||
|
return s
|
||||||
|
|
||||||
|
|
||||||
|
def get(self):
|
||||||
|
return self.encode()
|
||||||
|
|
||||||
|
|
||||||
|
def encode(self):
|
||||||
|
m = self.get_method_signature()
|
||||||
|
c = self.get_contents()
|
||||||
|
return m + c
|
||||||
|
|
||||||
|
|
||||||
|
def __str__(self):
|
||||||
|
return self.encode()
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
def abi_decode_single(typ, v):
|
||||||
|
d = ABIContractDecoder()
|
||||||
|
d.typ(typ)
|
||||||
|
d.val(v)
|
||||||
|
r = d.decode()
|
||||||
|
return r[0]
|
||||||
|
|
||||||
|
|
||||||
|
def code(address, block_spec=BlockSpec.LATEST):
|
||||||
|
block_height = None
|
||||||
|
if block_spec == BlockSpec.LATEST:
|
||||||
|
block_height = 'latest'
|
||||||
|
elif block_spec == BlockSpec.PENDING:
|
||||||
|
block_height = 'pending'
|
||||||
|
else:
|
||||||
|
block_height = int(block_spec)
|
||||||
|
o = jsonrpc_template()
|
||||||
|
o['method'] = 'eth_getCode'
|
||||||
|
o['params'].append(address)
|
||||||
|
o['params'].append(block_height)
|
||||||
|
return o
|
@ -1,20 +0,0 @@
|
|||||||
from eth_abi import (
|
|
||||||
encode_single as __encode_single,
|
|
||||||
decode_single as __decode_single,
|
|
||||||
)
|
|
||||||
|
|
||||||
def abi_encode(signature, *args):
|
|
||||||
return __encode_single(signature, *args)
|
|
||||||
|
|
||||||
|
|
||||||
def abi_encode_hex(signature, *args):
|
|
||||||
return __encode_single(signature, *args).hex()
|
|
||||||
|
|
||||||
|
|
||||||
def abi_decode(signature, *args):
|
|
||||||
return __decode_single(signature, *args)
|
|
||||||
|
|
||||||
|
|
||||||
def abi_decode_hex(signature, *args):
|
|
||||||
return __decode_single(signature, *args).hex()
|
|
||||||
|
|
@ -1,6 +1,12 @@
|
|||||||
# third-party imports
|
# standard imports
|
||||||
|
import logging
|
||||||
|
|
||||||
|
# external imports
|
||||||
import sha3
|
import sha3
|
||||||
from hexathon import add_0x
|
from hexathon import (
|
||||||
|
add_0x,
|
||||||
|
strip_0x,
|
||||||
|
)
|
||||||
from crypto_dev_signer.eth.transaction import EIP155Transaction
|
from crypto_dev_signer.eth.transaction import EIP155Transaction
|
||||||
|
|
||||||
# local imports
|
# local imports
|
||||||
@ -9,26 +15,33 @@ from chainlib.hash import (
|
|||||||
keccak256_string_to_hex,
|
keccak256_string_to_hex,
|
||||||
)
|
)
|
||||||
from .constant import ZERO_ADDRESS
|
from .constant import ZERO_ADDRESS
|
||||||
from .rpc import jsonrpc_template
|
from .tx import (
|
||||||
from .tx import TxFactory
|
TxFactory,
|
||||||
from .encoding import abi_encode
|
TxFormat,
|
||||||
|
)
|
||||||
|
from .contract import (
|
||||||
|
ABIContractEncoder,
|
||||||
|
ABIContractDecoder,
|
||||||
|
ABIContractType,
|
||||||
|
abi_decode_single,
|
||||||
|
)
|
||||||
|
from chainlib.jsonrpc import jsonrpc_template
|
||||||
|
from .error import RequestMismatchException
|
||||||
|
|
||||||
|
logg = logging.getLogger()
|
||||||
|
|
||||||
|
|
||||||
# TODO: move to cic-contracts
|
class ERC20(TxFactory):
|
||||||
erc20_balance_signature = keccak256_string_to_hex('balanceOf(address)')[:8]
|
|
||||||
erc20_decimals_signature = keccak256_string_to_hex('decimals()')[:8]
|
|
||||||
erc20_transfer_signature = keccak256_string_to_hex('transfer(address,uint256)')[:8]
|
|
||||||
|
|
||||||
|
|
||||||
class ERC20TxFactory(TxFactory):
|
def balance_of(self, contract_address, address, sender_address=ZERO_ADDRESS):
|
||||||
|
|
||||||
|
|
||||||
def erc20_balance(self, contract_address, address, sender_address=ZERO_ADDRESS):
|
|
||||||
o = jsonrpc_template()
|
o = jsonrpc_template()
|
||||||
o['method'] = 'eth_call'
|
o['method'] = 'eth_call'
|
||||||
data = erc20_balance_signature
|
enc = ABIContractEncoder()
|
||||||
data += abi_encode('address', address).hex()
|
enc.method('balanceOf')
|
||||||
data = add_0x(data)
|
enc.typ(ABIContractType.ADDRESS)
|
||||||
|
enc.address(address)
|
||||||
|
data = add_0x(enc.get())
|
||||||
tx = self.template(sender_address, contract_address)
|
tx = self.template(sender_address, contract_address)
|
||||||
tx = self.set_code(tx, data)
|
tx = self.set_code(tx, data)
|
||||||
o['params'].append(self.normalize(tx))
|
o['params'].append(self.normalize(tx))
|
||||||
@ -36,10 +49,16 @@ class ERC20TxFactory(TxFactory):
|
|||||||
return o
|
return o
|
||||||
|
|
||||||
|
|
||||||
def erc20_decimals(self, contract_address, sender_address=ZERO_ADDRESS):
|
def balance(self, contract_address, address, sender_address=ZERO_ADDRESS):
|
||||||
|
return self.balance_of(contract_address, address, sender_address=ZERO_ADDRESS)
|
||||||
|
|
||||||
|
|
||||||
|
def symbol(self, contract_address, sender_address=ZERO_ADDRESS):
|
||||||
o = jsonrpc_template()
|
o = jsonrpc_template()
|
||||||
o['method'] = 'eth_call'
|
o['method'] = 'eth_call'
|
||||||
data = add_0x(erc20_decimals_signature)
|
enc = ABIContractEncoder()
|
||||||
|
enc.method('symbol')
|
||||||
|
data = add_0x(enc.get())
|
||||||
tx = self.template(sender_address, contract_address)
|
tx = self.template(sender_address, contract_address)
|
||||||
tx = self.set_code(tx, data)
|
tx = self.set_code(tx, data)
|
||||||
o['params'].append(self.normalize(tx))
|
o['params'].append(self.normalize(tx))
|
||||||
@ -47,11 +66,173 @@ class ERC20TxFactory(TxFactory):
|
|||||||
return o
|
return o
|
||||||
|
|
||||||
|
|
||||||
def erc20_transfer(self, contract_address, sender_address, recipient_address, value):
|
def name(self, contract_address, sender_address=ZERO_ADDRESS):
|
||||||
data = erc20_transfer_signature
|
o = jsonrpc_template()
|
||||||
data += abi_encode('address', recipient_address).hex()
|
o['method'] = 'eth_call'
|
||||||
data += abi_encode('uint256', value).hex()
|
enc = ABIContractEncoder()
|
||||||
data = add_0x(data)
|
enc.method('name')
|
||||||
|
data = add_0x(enc.get())
|
||||||
|
tx = self.template(sender_address, contract_address)
|
||||||
|
tx = self.set_code(tx, data)
|
||||||
|
o['params'].append(self.normalize(tx))
|
||||||
|
o['params'].append('latest')
|
||||||
|
return o
|
||||||
|
|
||||||
|
|
||||||
|
def decimals(self, contract_address, sender_address=ZERO_ADDRESS):
|
||||||
|
o = jsonrpc_template()
|
||||||
|
o['method'] = 'eth_call'
|
||||||
|
enc = ABIContractEncoder()
|
||||||
|
enc.method('decimals')
|
||||||
|
data = add_0x(enc.get())
|
||||||
|
tx = self.template(sender_address, contract_address)
|
||||||
|
tx = self.set_code(tx, data)
|
||||||
|
o['params'].append(self.normalize(tx))
|
||||||
|
o['params'].append('latest')
|
||||||
|
return o
|
||||||
|
|
||||||
|
|
||||||
|
def transfer(self, contract_address, sender_address, recipient_address, value, tx_format=TxFormat.JSONRPC):
|
||||||
|
enc = ABIContractEncoder()
|
||||||
|
enc.method('transfer')
|
||||||
|
enc.typ(ABIContractType.ADDRESS)
|
||||||
|
enc.typ(ABIContractType.UINT256)
|
||||||
|
enc.address(recipient_address)
|
||||||
|
enc.uint256(value)
|
||||||
|
data = add_0x(enc.get())
|
||||||
tx = self.template(sender_address, contract_address, use_nonce=True)
|
tx = self.template(sender_address, contract_address, use_nonce=True)
|
||||||
tx = self.set_code(tx, data)
|
tx = self.set_code(tx, data)
|
||||||
return self.build(tx)
|
tx = self.finalize(tx, tx_format)
|
||||||
|
return tx
|
||||||
|
|
||||||
|
|
||||||
|
def transfer_from(self, contract_address, sender_address, holder_address, recipient_address, value, tx_format=TxFormat.JSONRPC):
|
||||||
|
enc = ABIContractEncoder()
|
||||||
|
enc.method('transfer')
|
||||||
|
enc.typ(ABIContractType.ADDRESS)
|
||||||
|
enc.typ(ABIContractType.ADDRESS)
|
||||||
|
enc.typ(ABIContractType.UINT256)
|
||||||
|
enc.address(holder_address)
|
||||||
|
enc.address(recipient_address)
|
||||||
|
enc.uint256(value)
|
||||||
|
data = add_0x(enc.get())
|
||||||
|
tx = self.template(sender_address, contract_address, use_nonce=True)
|
||||||
|
tx = self.set_code(tx, data)
|
||||||
|
tx = self.finalize(tx, tx_format)
|
||||||
|
return tx
|
||||||
|
|
||||||
|
|
||||||
|
def approve(self, contract_address, sender_address, recipient_address, value, tx_format=TxFormat.JSONRPC):
|
||||||
|
enc = ABIContractEncoder()
|
||||||
|
enc.method('approve')
|
||||||
|
enc.typ(ABIContractType.ADDRESS)
|
||||||
|
enc.typ(ABIContractType.UINT256)
|
||||||
|
enc.address(recipient_address)
|
||||||
|
enc.uint256(value)
|
||||||
|
data = add_0x(enc.get())
|
||||||
|
tx = self.template(sender_address, contract_address, use_nonce=True)
|
||||||
|
tx = self.set_code(tx, data)
|
||||||
|
tx = self.finalize(tx, tx_format)
|
||||||
|
return tx
|
||||||
|
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def parse_symbol(self, v):
|
||||||
|
return abi_decode_single(ABIContractType.STRING, v)
|
||||||
|
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def parse_name(self, v):
|
||||||
|
return abi_decode_single(ABIContractType.STRING, v)
|
||||||
|
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def parse_decimals(self, v):
|
||||||
|
return abi_decode_single(ABIContractType.UINT256, v)
|
||||||
|
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def parse_balance(self, v):
|
||||||
|
return abi_decode_single(ABIContractType.UINT256, v)
|
||||||
|
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def parse_transfer_request(self, v):
|
||||||
|
v = strip_0x(v)
|
||||||
|
cursor = 0
|
||||||
|
enc = ABIContractEncoder()
|
||||||
|
enc.method('transfer')
|
||||||
|
enc.typ(ABIContractType.ADDRESS)
|
||||||
|
enc.typ(ABIContractType.UINT256)
|
||||||
|
r = enc.get()
|
||||||
|
l = len(r)
|
||||||
|
m = v[:l]
|
||||||
|
if m != r:
|
||||||
|
logg.error('method mismatch, expected {}, got {}'.format(r, m))
|
||||||
|
raise RequestMismatchException(v)
|
||||||
|
cursor += l
|
||||||
|
|
||||||
|
dec = ABIContractDecoder()
|
||||||
|
dec.typ(ABIContractType.ADDRESS)
|
||||||
|
dec.typ(ABIContractType.UINT256)
|
||||||
|
dec.val(v[cursor:cursor+64])
|
||||||
|
cursor += 64
|
||||||
|
dec.val(v[cursor:cursor+64])
|
||||||
|
r = dec.decode()
|
||||||
|
return r
|
||||||
|
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def parse_transfer_from_request(self, v):
|
||||||
|
v = strip_0x(v)
|
||||||
|
cursor = 0
|
||||||
|
enc = ABIContractEncoder()
|
||||||
|
enc.method('transferFrom')
|
||||||
|
enc.typ(ABIContractType.ADDRESS)
|
||||||
|
enc.typ(ABIContractType.ADDRESS)
|
||||||
|
enc.typ(ABIContractType.UINT256)
|
||||||
|
r = enc.get()
|
||||||
|
l = len(r)
|
||||||
|
m = v[:l]
|
||||||
|
if m != r:
|
||||||
|
logg.error('method mismatch, expected {}, got {}'.format(r, m))
|
||||||
|
raise RequestMismatchException(v)
|
||||||
|
cursor += l
|
||||||
|
|
||||||
|
dec = ABIContractDecoder()
|
||||||
|
dec.typ(ABIContractType.ADDRESS)
|
||||||
|
dec.typ(ABIContractType.ADDRESS)
|
||||||
|
dec.typ(ABIContractType.UINT256)
|
||||||
|
dec.val(v[cursor:cursor+64])
|
||||||
|
cursor += 64
|
||||||
|
dec.val(v[cursor:cursor+64])
|
||||||
|
cursor += 64
|
||||||
|
dec.val(v[cursor:cursor+64])
|
||||||
|
r = dec.decode()
|
||||||
|
return r
|
||||||
|
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def parse_approve_request(self, v):
|
||||||
|
v = strip_0x(v)
|
||||||
|
cursor = 0
|
||||||
|
enc = ABIContractEncoder()
|
||||||
|
enc.method('approve')
|
||||||
|
enc.typ(ABIContractType.ADDRESS)
|
||||||
|
enc.typ(ABIContractType.UINT256)
|
||||||
|
r = enc.get()
|
||||||
|
l = len(r)
|
||||||
|
m = v[:l]
|
||||||
|
if m != r:
|
||||||
|
logg.error('method mismatch, expected {}, got {}'.format(r, m))
|
||||||
|
raise RequestMismatchException(v)
|
||||||
|
cursor += l
|
||||||
|
|
||||||
|
dec = ABIContractDecoder()
|
||||||
|
dec.typ(ABIContractType.ADDRESS)
|
||||||
|
dec.typ(ABIContractType.UINT256)
|
||||||
|
dec.val(v[cursor:cursor+64])
|
||||||
|
cursor += 64
|
||||||
|
dec.val(v[cursor:cursor+64])
|
||||||
|
r = dec.decode()
|
||||||
|
return r
|
||||||
|
@ -1,8 +1,19 @@
|
|||||||
|
# local imports
|
||||||
|
from chainlib.error import ExecutionError
|
||||||
|
|
||||||
class EthException(Exception):
|
class EthException(Exception):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
||||||
class RevertEthException(EthException):
|
class RevertEthException(EthException, ExecutionError):
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
class NotFoundEthException(EthException):
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
class RequestMismatchException(EthException):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
@ -1,3 +1,6 @@
|
|||||||
|
# standard imports
|
||||||
|
import logging
|
||||||
|
|
||||||
# third-party imports
|
# third-party imports
|
||||||
from hexathon import (
|
from hexathon import (
|
||||||
add_0x,
|
add_0x,
|
||||||
@ -7,12 +10,18 @@ from crypto_dev_signer.eth.transaction import EIP155Transaction
|
|||||||
|
|
||||||
# local imports
|
# local imports
|
||||||
from chainlib.hash import keccak256_hex_to_hex
|
from chainlib.hash import keccak256_hex_to_hex
|
||||||
from chainlib.eth.rpc import jsonrpc_template
|
from chainlib.jsonrpc import jsonrpc_template
|
||||||
from chainlib.eth.tx import TxFactory
|
from chainlib.eth.tx import (
|
||||||
|
TxFactory,
|
||||||
|
TxFormat,
|
||||||
|
raw,
|
||||||
|
)
|
||||||
from chainlib.eth.constant import (
|
from chainlib.eth.constant import (
|
||||||
MINIMUM_FEE_UNITS,
|
MINIMUM_FEE_UNITS,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
logg = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
def price():
|
def price():
|
||||||
o = jsonrpc_template()
|
o = jsonrpc_template()
|
||||||
@ -24,47 +33,87 @@ def balance(address):
|
|||||||
o = jsonrpc_template()
|
o = jsonrpc_template()
|
||||||
o['method'] = 'eth_getBalance'
|
o['method'] = 'eth_getBalance'
|
||||||
o['params'].append(address)
|
o['params'].append(address)
|
||||||
|
o['params'].append('latest')
|
||||||
return o
|
return o
|
||||||
|
|
||||||
|
|
||||||
class GasTxFactory(TxFactory):
|
class Gas(TxFactory):
|
||||||
|
|
||||||
def create(self, sender, recipient, value):
|
def create(self, sender_address, recipient_address, value, tx_format=TxFormat.JSONRPC):
|
||||||
tx = self.template(sender, recipient, use_nonce=True)
|
tx = self.template(sender_address, recipient_address, use_nonce=True)
|
||||||
tx['value'] = value
|
tx['value'] = value
|
||||||
txe = EIP155Transaction(tx, tx['nonce'], tx['chainId'])
|
txe = EIP155Transaction(tx, tx['nonce'], tx['chainId'])
|
||||||
self.signer.signTransaction(txe)
|
tx_raw = self.signer.sign_transaction_to_rlp(txe)
|
||||||
tx_raw = txe.rlp_serialize()
|
|
||||||
tx_raw_hex = add_0x(tx_raw.hex())
|
tx_raw_hex = add_0x(tx_raw.hex())
|
||||||
tx_hash_hex = add_0x(keccak256_hex_to_hex(tx_raw_hex))
|
tx_hash_hex = add_0x(keccak256_hex_to_hex(tx_raw_hex))
|
||||||
|
|
||||||
o = jsonrpc_template()
|
if tx_format == TxFormat.JSONRPC:
|
||||||
o['method'] = 'eth_sendRawTransaction'
|
o = raw(tx_raw_hex)
|
||||||
o['params'].append(tx_raw_hex)
|
elif tx_format == TxFormat.RLP_SIGNED:
|
||||||
|
o = tx_raw_hex
|
||||||
|
|
||||||
return (tx_hash_hex, o)
|
return (tx_hash_hex, o)
|
||||||
|
|
||||||
|
|
||||||
class DefaultGasOracle:
|
class RPCGasOracle:
|
||||||
|
|
||||||
def __init__(self, conn):
|
def __init__(self, conn, code_callback=None):
|
||||||
self.conn = conn
|
self.conn = conn
|
||||||
|
self.code_callback = code_callback
|
||||||
|
|
||||||
|
|
||||||
def get(self, code=None):
|
def get_gas(self, code=None):
|
||||||
o = price()
|
o = price()
|
||||||
r = self.conn.do(o)
|
r = self.conn.do(o)
|
||||||
n = strip_0x(r)
|
n = strip_0x(r)
|
||||||
return (int(n, 16), MINIMUM_FEE_UNITS)
|
fee_units = MINIMUM_FEE_UNITS
|
||||||
|
if self.code_callback != None:
|
||||||
|
fee_units = self.code_callback(code)
|
||||||
|
return (int(n, 16), fee_units)
|
||||||
|
|
||||||
|
|
||||||
class OverrideGasOracle:
|
class OverrideGasOracle(RPCGasOracle):
|
||||||
|
|
||||||
def __init__(self, price, limit=None):
|
def __init__(self, price=None, limit=None, conn=None, code_callback=None):
|
||||||
if limit == None:
|
self.conn = None
|
||||||
limit = MINIMUM_FEE_UNITS
|
self.code_callback = None
|
||||||
|
if conn != None:
|
||||||
|
logg.debug('override gas oracle with rpc fallback')
|
||||||
|
super(OverrideGasOracle, self).__init__(conn, code_callback)
|
||||||
self.limit = limit
|
self.limit = limit
|
||||||
self.price = price
|
self.price = price
|
||||||
|
|
||||||
def get(self):
|
|
||||||
return (self.price, self.limit)
|
def get_gas(self, code=None):
|
||||||
|
r = None
|
||||||
|
fee_units = None
|
||||||
|
fee_price = None
|
||||||
|
|
||||||
|
rpc_results = None
|
||||||
|
if self.conn != None:
|
||||||
|
rpc_results = super(OverrideGasOracle, self).get_gas(code)
|
||||||
|
|
||||||
|
if self.limit != None:
|
||||||
|
fee_units = self.limit
|
||||||
|
if self.price != None:
|
||||||
|
fee_price = self.price
|
||||||
|
|
||||||
|
if fee_price == None:
|
||||||
|
if rpc_results != None:
|
||||||
|
fee_price = rpc_results[0]
|
||||||
|
logg.debug('override gas oracle without explicit price, setting from rpc {}'.format(fee_price))
|
||||||
|
else:
|
||||||
|
fee_price = MINIMUM_FEE_PRICE
|
||||||
|
logg.debug('override gas oracle without explicit price, setting default {}'.format(fee_price))
|
||||||
|
if fee_units == None:
|
||||||
|
if rpc_results != None:
|
||||||
|
fee_units = rpc_results[1]
|
||||||
|
logg.debug('override gas oracle without explicit limit, setting from rpc {}'.format(fee_limit))
|
||||||
|
else:
|
||||||
|
fee_units = MINIMUM_FEE_UNITS
|
||||||
|
logg.debug('override gas oracle without explicit limit, setting default {}'.format(fee_limit))
|
||||||
|
|
||||||
|
return (fee_price, fee_units)
|
||||||
|
|
||||||
|
|
||||||
|
DefaultGasOracle = RPCGasOracle
|
||||||
|
@ -5,7 +5,7 @@ from hexathon import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
# local imports
|
# local imports
|
||||||
from chainlib.eth.rpc import jsonrpc_template
|
from chainlib.jsonrpc import jsonrpc_template
|
||||||
|
|
||||||
|
|
||||||
def nonce(address):
|
def nonce(address):
|
||||||
@ -16,34 +16,47 @@ def nonce(address):
|
|||||||
return o
|
return o
|
||||||
|
|
||||||
|
|
||||||
class DefaultNonceOracle:
|
class NonceOracle:
|
||||||
|
|
||||||
|
def __init__(self, address):
|
||||||
|
self.address = address
|
||||||
|
self.nonce = self.get_nonce()
|
||||||
|
|
||||||
|
|
||||||
|
def get_nonce(self):
|
||||||
|
raise NotImplementedError('Class must be extended')
|
||||||
|
|
||||||
|
|
||||||
|
def next_nonce(self):
|
||||||
|
n = self.nonce
|
||||||
|
self.nonce += 1
|
||||||
|
return n
|
||||||
|
|
||||||
|
|
||||||
|
class RPCNonceOracle(NonceOracle):
|
||||||
|
|
||||||
def __init__(self, address, conn):
|
def __init__(self, address, conn):
|
||||||
self.address = address
|
|
||||||
self.conn = conn
|
self.conn = conn
|
||||||
self.nonce = self.get()
|
super(RPCNonceOracle, self).__init__(address)
|
||||||
|
|
||||||
|
|
||||||
def get(self):
|
def get_nonce(self):
|
||||||
o = nonce(self.address)
|
o = nonce(self.address)
|
||||||
r = self.conn.do(o)
|
r = self.conn.do(o)
|
||||||
n = strip_0x(r)
|
n = strip_0x(r)
|
||||||
return int(n, 16)
|
return int(n, 16)
|
||||||
|
|
||||||
|
|
||||||
def next(self):
|
class OverrideNonceOracle(NonceOracle):
|
||||||
n = self.nonce
|
|
||||||
self.nonce += 1
|
|
||||||
return n
|
|
||||||
|
|
||||||
|
|
||||||
class OverrideNonceOracle(DefaultNonceOracle):
|
|
||||||
|
|
||||||
|
|
||||||
def __init__(self, address, nonce):
|
def __init__(self, address, nonce):
|
||||||
self.nonce = nonce
|
self.nonce = nonce
|
||||||
super(OverrideNonceOracle, self).__init__(address, None)
|
super(OverrideNonceOracle, self).__init__(address)
|
||||||
|
|
||||||
|
|
||||||
def get(self):
|
def get_nonce(self):
|
||||||
return self.nonce
|
return self.nonce
|
||||||
|
|
||||||
|
|
||||||
|
DefaultNonceOracle = RPCNonceOracle
|
||||||
|
3
chainlib/eth/pytest/__init__.py
Normal file
3
chainlib/eth/pytest/__init__.py
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
from .fixtures_ethtester import *
|
||||||
|
from .fixtures_chain import *
|
||||||
|
from .fixtures_signer import *
|
17
chainlib/eth/pytest/fixtures_chain.py
Normal file
17
chainlib/eth/pytest/fixtures_chain.py
Normal file
@ -0,0 +1,17 @@
|
|||||||
|
# external imports
|
||||||
|
import pytest
|
||||||
|
|
||||||
|
# local imports
|
||||||
|
from chainlib.chain import ChainSpec
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.fixture(scope='session')
|
||||||
|
def default_chain_spec():
|
||||||
|
return ChainSpec('evm', 'foo', 42)
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.fixture(scope='session')
|
||||||
|
def default_chain_config():
|
||||||
|
return {
|
||||||
|
'foo': 42,
|
||||||
|
}
|
102
chainlib/eth/pytest/fixtures_ethtester.py
Normal file
102
chainlib/eth/pytest/fixtures_ethtester.py
Normal file
@ -0,0 +1,102 @@
|
|||||||
|
# standard imports
|
||||||
|
import os
|
||||||
|
import logging
|
||||||
|
|
||||||
|
# external imports
|
||||||
|
import eth_tester
|
||||||
|
import pytest
|
||||||
|
from crypto_dev_signer.eth.signer import ReferenceSigner as EIP155Signer
|
||||||
|
from crypto_dev_signer.keystore.dict import DictKeystore
|
||||||
|
|
||||||
|
# local imports
|
||||||
|
from chainlib.eth.unittest.base import *
|
||||||
|
from chainlib.connection import (
|
||||||
|
RPCConnection,
|
||||||
|
)
|
||||||
|
from chainlib.eth.unittest.ethtester import create_tester_signer
|
||||||
|
from chainlib.eth.address import to_checksum_address
|
||||||
|
|
||||||
|
logg = logging.getLogger() #__name__)
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.fixture(scope='function')
|
||||||
|
def eth_keystore():
|
||||||
|
return DictKeystore()
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.fixture(scope='function')
|
||||||
|
def init_eth_tester(
|
||||||
|
eth_keystore,
|
||||||
|
):
|
||||||
|
return create_tester_signer(eth_keystore)
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.fixture(scope='function')
|
||||||
|
def call_sender(
|
||||||
|
eth_accounts,
|
||||||
|
):
|
||||||
|
return eth_accounts[0]
|
||||||
|
#
|
||||||
|
#
|
||||||
|
#@pytest.fixture(scope='function')
|
||||||
|
#def eth_signer(
|
||||||
|
# init_eth_tester,
|
||||||
|
# ):
|
||||||
|
# return init_eth_tester
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.fixture(scope='function')
|
||||||
|
def eth_rpc(
|
||||||
|
default_chain_spec,
|
||||||
|
init_eth_rpc,
|
||||||
|
):
|
||||||
|
return RPCConnection.connect(default_chain_spec, 'default')
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.fixture(scope='function')
|
||||||
|
def eth_accounts(
|
||||||
|
init_eth_tester,
|
||||||
|
):
|
||||||
|
addresses = list(init_eth_tester.get_accounts())
|
||||||
|
for address in addresses:
|
||||||
|
balance = init_eth_tester.get_balance(address)
|
||||||
|
logg.debug('prefilled account {} balance {}'.format(address, balance))
|
||||||
|
return addresses
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.fixture(scope='function')
|
||||||
|
def eth_empty_accounts(
|
||||||
|
eth_keystore,
|
||||||
|
init_eth_tester,
|
||||||
|
):
|
||||||
|
a = []
|
||||||
|
for i in range(10):
|
||||||
|
#address = init_eth_tester.new_account()
|
||||||
|
address = eth_keystore.new()
|
||||||
|
checksum_address = add_0x(to_checksum_address(address))
|
||||||
|
a.append(checksum_address)
|
||||||
|
logg.info('added address {}'.format(checksum_address))
|
||||||
|
return a
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.fixture(scope='function')
|
||||||
|
def eth_signer(
|
||||||
|
eth_keystore,
|
||||||
|
):
|
||||||
|
return EIP155Signer(eth_keystore)
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.fixture(scope='function')
|
||||||
|
def init_eth_rpc(
|
||||||
|
default_chain_spec,
|
||||||
|
init_eth_tester,
|
||||||
|
eth_signer,
|
||||||
|
):
|
||||||
|
|
||||||
|
rpc_conn = TestRPCConnection(None, init_eth_tester, eth_signer)
|
||||||
|
def rpc_with_tester(url=None, chain_spec=default_chain_spec):
|
||||||
|
return rpc_conn
|
||||||
|
|
||||||
|
RPCConnection.register_location('custom', default_chain_spec, tag='default', constructor=rpc_with_tester, exist_ok=True)
|
||||||
|
RPCConnection.register_location('custom', default_chain_spec, tag='signer', constructor=rpc_with_tester, exist_ok=True)
|
||||||
|
return None
|
18
chainlib/eth/pytest/fixtures_signer.py
Normal file
18
chainlib/eth/pytest/fixtures_signer.py
Normal file
@ -0,0 +1,18 @@
|
|||||||
|
# standard imports
|
||||||
|
#import os
|
||||||
|
|
||||||
|
# external imports
|
||||||
|
import pytest
|
||||||
|
#from crypto_dev_signer.eth.signer import ReferenceSigner as EIP155Signer
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.fixture(scope='function')
|
||||||
|
def agent_roles(
|
||||||
|
eth_accounts,
|
||||||
|
):
|
||||||
|
return {
|
||||||
|
'ALICE': eth_accounts[20],
|
||||||
|
'BOB': eth_accounts[21],
|
||||||
|
'CAROL': eth_accounts[23],
|
||||||
|
'DAVE': eth_accounts[24],
|
||||||
|
}
|
@ -1,17 +0,0 @@
|
|||||||
# standard imports
|
|
||||||
import uuid
|
|
||||||
|
|
||||||
|
|
||||||
def jsonrpc_template():
|
|
||||||
return {
|
|
||||||
'jsonrpc': '2.0',
|
|
||||||
'id': str(uuid.uuid4()),
|
|
||||||
'method': None,
|
|
||||||
'params': [],
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
def jsonrpc_result(o, ep):
|
|
||||||
if o.get('error') != None:
|
|
||||||
raise ep.translate(o)
|
|
||||||
return o['result']
|
|
@ -26,14 +26,16 @@ from eth_abi import encode_single
|
|||||||
|
|
||||||
# local imports
|
# local imports
|
||||||
from chainlib.eth.address import to_checksum
|
from chainlib.eth.address import to_checksum
|
||||||
from chainlib.eth.rpc import (
|
from chainlib.jsonrpc import (
|
||||||
jsonrpc_template,
|
jsonrpc_template,
|
||||||
jsonrpc_result,
|
jsonrpc_result,
|
||||||
)
|
)
|
||||||
from chainlib.eth.erc20 import ERC20TxFactory
|
from chainlib.eth.erc20 import ERC20
|
||||||
from chainlib.eth.connection import HTTPConnection
|
from chainlib.eth.connection import EthHTTPConnection
|
||||||
from chainlib.eth.nonce import DefaultNonceOracle
|
from chainlib.eth.gas import (
|
||||||
from chainlib.eth.gas import DefaultGasOracle
|
OverrideGasOracle,
|
||||||
|
balance,
|
||||||
|
)
|
||||||
|
|
||||||
logging.basicConfig(level=logging.WARNING)
|
logging.basicConfig(level=logging.WARNING)
|
||||||
logg = logging.getLogger()
|
logg = logging.getLogger()
|
||||||
@ -43,50 +45,59 @@ default_eth_provider = os.environ.get('ETH_PROVIDER', 'http://localhost:8545')
|
|||||||
|
|
||||||
argparser = argparse.ArgumentParser()
|
argparser = argparse.ArgumentParser()
|
||||||
argparser.add_argument('-p', '--provider', dest='p', default=default_eth_provider, type=str, help='Web3 provider url (http only)')
|
argparser.add_argument('-p', '--provider', dest='p', default=default_eth_provider, type=str, help='Web3 provider url (http only)')
|
||||||
argparser.add_argument('-t', '--token-address', dest='t', type=str, help='Token address. If not set, will return gas balance')
|
argparser.add_argument('-a', '--token-address', dest='a', type=str, help='Token address. If not set, will return gas balance')
|
||||||
|
argparser.add_argument('-i', '--chain-spec', dest='i', type=str, default='evm:ethereum:1', help='Chain specification string')
|
||||||
argparser.add_argument('-u', '--unsafe', dest='u', action='store_true', help='Auto-convert address to checksum adddress')
|
argparser.add_argument('-u', '--unsafe', dest='u', action='store_true', help='Auto-convert address to checksum adddress')
|
||||||
argparser.add_argument('--abi-dir', dest='abi_dir', type=str, default=default_abi_dir, help='Directory containing bytecode and abi (default {})'.format(default_abi_dir))
|
argparser.add_argument('--abi-dir', dest='abi_dir', type=str, default=default_abi_dir, help='Directory containing bytecode and abi (default {})'.format(default_abi_dir))
|
||||||
argparser.add_argument('-v', action='store_true', help='Be verbose')
|
argparser.add_argument('-v', action='store_true', help='Be verbose')
|
||||||
argparser.add_argument('account', type=str, help='Account address')
|
argparser.add_argument('-vv', action='store_true', help='Be more verbose')
|
||||||
|
argparser.add_argument('address', type=str, help='Account address')
|
||||||
args = argparser.parse_args()
|
args = argparser.parse_args()
|
||||||
|
|
||||||
|
|
||||||
if args.v:
|
if args.vv:
|
||||||
logg.setLevel(logging.DEBUG)
|
logg.setLevel(logging.DEBUG)
|
||||||
|
elif args.v:
|
||||||
|
logg.setLevel(logging.INFO)
|
||||||
|
|
||||||
conn = HTTPConnection(args.p)
|
conn = EthHTTPConnection(args.p)
|
||||||
gas_oracle = DefaultGasOracle(conn)
|
gas_oracle = OverrideGasOracle(conn)
|
||||||
|
|
||||||
|
address = to_checksum(args.address)
|
||||||
def main():
|
if not args.u and address != add_0x(args.address):
|
||||||
account = to_checksum(args.account)
|
|
||||||
if not args.u and account != add_0x(args.account):
|
|
||||||
raise ValueError('invalid checksum address')
|
raise ValueError('invalid checksum address')
|
||||||
|
|
||||||
|
token_symbol = 'eth'
|
||||||
|
|
||||||
|
chain_spec = ChainSpec.from_chain_str(args.i)
|
||||||
|
|
||||||
|
def main():
|
||||||
r = None
|
r = None
|
||||||
decimals = 18
|
decimals = 18
|
||||||
if args.t != None:
|
if args.a != None:
|
||||||
g = ERC20TxFactory(gas_oracle=gas_oracle)
|
#g = ERC20(gas_oracle=gas_oracle)
|
||||||
|
g = ERC20(chain_spec=chain_spec)
|
||||||
# determine decimals
|
# determine decimals
|
||||||
decimals_o = g.erc20_decimals(args.t)
|
decimals_o = g.decimals(args.a)
|
||||||
r = conn.do(decimals_o)
|
r = conn.do(decimals_o)
|
||||||
decimals = int(strip_0x(r), 16)
|
decimals = int(strip_0x(r), 16)
|
||||||
|
symbol_o = g.symbol(args.a)
|
||||||
|
r = conn.do(decimals_o)
|
||||||
|
token_symbol = r
|
||||||
|
|
||||||
# get balance
|
# get balance
|
||||||
balance_o = g.erc20_balance(args.t, account)
|
balance_o = g.balance(args.a, address)
|
||||||
r = conn.do(balance_o)
|
r = conn.do(balance_o)
|
||||||
|
|
||||||
else:
|
else:
|
||||||
o = jsonrpc_template()
|
o = balance(address)
|
||||||
o['method'] = 'eth_getBalance'
|
|
||||||
o['params'].append(account)
|
|
||||||
r = conn.do(o)
|
r = conn.do(o)
|
||||||
|
|
||||||
hx = strip_0x(r)
|
hx = strip_0x(r)
|
||||||
balance = int(hx, 16)
|
balance_value = int(hx, 16)
|
||||||
logg.debug('balance {} = {} decimals {}'.format(even(hx), balance, decimals))
|
logg.debug('balance {} = {} decimals {}'.format(even(hx), balance_value, decimals))
|
||||||
|
|
||||||
balance_str = str(balance)
|
balance_str = str(balance_value)
|
||||||
balance_len = len(balance_str)
|
balance_len = len(balance_str)
|
||||||
if balance_len < decimals + 1:
|
if balance_len < decimals + 1:
|
||||||
print('0.{}'.format(balance_str.zfill(decimals)))
|
print('0.{}'.format(balance_str.zfill(decimals)))
|
||||||
|
59
chainlib/eth/runnable/count.py
Normal file
59
chainlib/eth/runnable/count.py
Normal file
@ -0,0 +1,59 @@
|
|||||||
|
# SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
|
|
||||||
|
# standard imports
|
||||||
|
import sys
|
||||||
|
import os
|
||||||
|
import json
|
||||||
|
import argparse
|
||||||
|
import logging
|
||||||
|
|
||||||
|
# local imports
|
||||||
|
from chainlib.eth.address import to_checksum
|
||||||
|
from chainlib.eth.connection import EthHTTPConnection
|
||||||
|
from chainlib.eth.tx import count
|
||||||
|
from crypto_dev_signer.keystore.dict import DictKeystore
|
||||||
|
from crypto_dev_signer.eth.signer import ReferenceSigner as EIP155Signer
|
||||||
|
|
||||||
|
logging.basicConfig(level=logging.WARNING)
|
||||||
|
logg = logging.getLogger()
|
||||||
|
|
||||||
|
default_eth_provider = os.environ.get('ETH_PROVIDER', 'http://localhost:8545')
|
||||||
|
|
||||||
|
argparser = argparse.ArgumentParser()
|
||||||
|
argparser.add_argument('-p', '--provider', dest='p', default='http://localhost:8545', type=str, help='Web3 provider url (http only)')
|
||||||
|
argparser.add_argument('-i', '--chain-spec', dest='i', type=str, default='evm:ethereum:1', help='Chain specification string')
|
||||||
|
argparser.add_argument('-y', '--key-file', dest='y', type=str, help='Ethereum keystore file to use for signing')
|
||||||
|
argparser.add_argument('--env-prefix', default=os.environ.get('CONFINI_ENV_PREFIX'), dest='env_prefix', type=str, help='environment prefix for variables to overwrite configuration')
|
||||||
|
argparser.add_argument('-u', '--unsafe', dest='u', action='store_true', help='Auto-convert address to checksum adddress')
|
||||||
|
argparser.add_argument('-v', action='store_true', help='Be verbose')
|
||||||
|
argparser.add_argument('-vv', action='store_true', help='Be more verbose')
|
||||||
|
argparser.add_argument('address', type=str, help='Ethereum address of recipient')
|
||||||
|
args = argparser.parse_args()
|
||||||
|
|
||||||
|
if args.vv:
|
||||||
|
logg.setLevel(logging.DEBUG)
|
||||||
|
elif args.v:
|
||||||
|
logg.setLevel(logging.INFO)
|
||||||
|
|
||||||
|
|
||||||
|
signer_address = None
|
||||||
|
keystore = DictKeystore()
|
||||||
|
if args.y != None:
|
||||||
|
logg.debug('loading keystore file {}'.format(args.y))
|
||||||
|
signer_address = keystore.import_keystore_file(args.y, passphrase)
|
||||||
|
logg.debug('now have key for signer address {}'.format(signer_address))
|
||||||
|
signer = EIP155Signer(keystore)
|
||||||
|
|
||||||
|
rpc = EthHTTPConnection(args.p)
|
||||||
|
|
||||||
|
def main():
|
||||||
|
recipient = to_checksum(args.address)
|
||||||
|
if not args.u and recipient != add_0x(args.address):
|
||||||
|
raise ValueError('invalid checksum address')
|
||||||
|
|
||||||
|
o = count(args.address)
|
||||||
|
print(rpc.do(o))
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
main()
|
@ -10,6 +10,7 @@
|
|||||||
# SPDX-License-Identifier: GPL-3.0-or-later
|
# SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
|
|
||||||
# standard imports
|
# standard imports
|
||||||
|
import sys
|
||||||
import os
|
import os
|
||||||
import json
|
import json
|
||||||
import argparse
|
import argparse
|
||||||
@ -19,6 +20,9 @@ import logging
|
|||||||
from chainlib.eth.tx import unpack
|
from chainlib.eth.tx import unpack
|
||||||
from chainlib.chain import ChainSpec
|
from chainlib.chain import ChainSpec
|
||||||
|
|
||||||
|
# local imports
|
||||||
|
from chainlib.eth.runnable.util import decode_for_puny_humans
|
||||||
|
|
||||||
|
|
||||||
logging.basicConfig(level=logging.WARNING)
|
logging.basicConfig(level=logging.WARNING)
|
||||||
logg = logging.getLogger()
|
logg = logging.getLogger()
|
||||||
@ -28,7 +32,7 @@ default_eth_provider = os.environ.get('ETH_PROVIDER', 'http://localhost:8545')
|
|||||||
|
|
||||||
argparser = argparse.ArgumentParser()
|
argparser = argparse.ArgumentParser()
|
||||||
argparser.add_argument('-v', action='store_true', help='Be verbose')
|
argparser.add_argument('-v', action='store_true', help='Be verbose')
|
||||||
argparser.add_argument('-i', '--chain-id', dest='i', type=str, help='Numeric network id')
|
argparser.add_argument('-i', '--chain-id', dest='i', default='evm:ethereum:1', type=str, help='Numeric network id')
|
||||||
argparser.add_argument('tx', type=str, help='hex-encoded signed raw transaction')
|
argparser.add_argument('tx', type=str, help='hex-encoded signed raw transaction')
|
||||||
args = argparser.parse_args()
|
args = argparser.parse_args()
|
||||||
|
|
||||||
@ -36,26 +40,11 @@ if args.v:
|
|||||||
logg.setLevel(logging.DEBUG)
|
logg.setLevel(logging.DEBUG)
|
||||||
|
|
||||||
chain_spec = ChainSpec.from_chain_str(args.i)
|
chain_spec = ChainSpec.from_chain_str(args.i)
|
||||||
chain_id = chain_spec.network_id()
|
|
||||||
|
|
||||||
|
|
||||||
def main():
|
def main():
|
||||||
tx_raw = args.tx
|
tx_raw = args.tx
|
||||||
if tx_raw[:2] == '0x':
|
decode_for_puny_humans(tx_raw, chain_spec, sys.stdout)
|
||||||
tx_raw = tx_raw[2:]
|
|
||||||
tx_raw_bytes = bytes.fromhex(tx_raw)
|
|
||||||
tx = unpack(tx_raw_bytes, int(chain_id))
|
|
||||||
for k in tx.keys():
|
|
||||||
x = None
|
|
||||||
if k == 'value':
|
|
||||||
x = '{:.18f} eth'.format(tx[k] / (10**18))
|
|
||||||
elif k == 'gasPrice':
|
|
||||||
x = '{} gwei'.format(int(tx[k] / (10**12)))
|
|
||||||
if x != None:
|
|
||||||
print('{}: {} ({})'.format(k, tx[k], x))
|
|
||||||
else:
|
|
||||||
print('{}: {}'.format(k, tx[k]))
|
|
||||||
|
|
||||||
|
|
||||||
if __name__ == '__main__':
|
if __name__ == '__main__':
|
||||||
main()
|
main()
|
||||||
|
@ -10,6 +10,7 @@
|
|||||||
# SPDX-License-Identifier: GPL-3.0-or-later
|
# SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
|
|
||||||
# standard imports
|
# standard imports
|
||||||
|
import io
|
||||||
import sys
|
import sys
|
||||||
import os
|
import os
|
||||||
import json
|
import json
|
||||||
@ -18,7 +19,7 @@ import logging
|
|||||||
|
|
||||||
# third-party imports
|
# third-party imports
|
||||||
from crypto_dev_signer.eth.signer import ReferenceSigner as EIP155Signer
|
from crypto_dev_signer.eth.signer import ReferenceSigner as EIP155Signer
|
||||||
from crypto_dev_signer.keystore import DictKeystore
|
from crypto_dev_signer.keystore.dict import DictKeystore
|
||||||
from hexathon import (
|
from hexathon import (
|
||||||
add_0x,
|
add_0x,
|
||||||
strip_0x,
|
strip_0x,
|
||||||
@ -26,33 +27,32 @@ from hexathon import (
|
|||||||
|
|
||||||
# local imports
|
# local imports
|
||||||
from chainlib.eth.address import to_checksum
|
from chainlib.eth.address import to_checksum
|
||||||
from chainlib.eth.connection import HTTPConnection
|
from chainlib.eth.connection import EthHTTPConnection
|
||||||
from chainlib.eth.rpc import jsonrpc_template
|
from chainlib.jsonrpc import jsonrpc_template
|
||||||
from chainlib.eth.nonce import (
|
from chainlib.eth.nonce import (
|
||||||
DefaultNonceOracle,
|
RPCNonceOracle,
|
||||||
OverrideNonceOracle,
|
OverrideNonceOracle,
|
||||||
)
|
)
|
||||||
from chainlib.eth.gas import (
|
from chainlib.eth.gas import (
|
||||||
DefaultGasOracle,
|
RPCGasOracle,
|
||||||
OverrideGasOracle,
|
OverrideGasOracle,
|
||||||
GasTxFactory,
|
Gas,
|
||||||
)
|
)
|
||||||
from chainlib.eth.gas import balance as gas_balance
|
from chainlib.eth.gas import balance as gas_balance
|
||||||
from chainlib.chain import ChainSpec
|
from chainlib.chain import ChainSpec
|
||||||
|
from chainlib.eth.runnable.util import decode_for_puny_humans
|
||||||
|
|
||||||
logging.basicConfig(level=logging.WARNING)
|
logging.basicConfig(level=logging.WARNING)
|
||||||
logg = logging.getLogger()
|
logg = logging.getLogger()
|
||||||
|
|
||||||
|
|
||||||
default_abi_dir = '/usr/share/local/cic/solidity/abi'
|
|
||||||
default_eth_provider = os.environ.get('ETH_PROVIDER', 'http://localhost:8545')
|
default_eth_provider = os.environ.get('ETH_PROVIDER', 'http://localhost:8545')
|
||||||
|
|
||||||
argparser = argparse.ArgumentParser()
|
argparser = argparse.ArgumentParser()
|
||||||
argparser.add_argument('-p', '--provider', dest='p', default='http://localhost:8545', type=str, help='Web3 provider url (http only)')
|
argparser.add_argument('-p', '--provider', dest='p', default='http://localhost:8545', type=str, help='Web3 provider url (http only)')
|
||||||
argparser.add_argument('-w', action='store_true', help='Wait for the last transaction to be confirmed')
|
argparser.add_argument('-w', action='store_true', help='Wait for the last transaction to be confirmed')
|
||||||
argparser.add_argument('-ww', action='store_true', help='Wait for every transaction to be confirmed')
|
argparser.add_argument('-ww', action='store_true', help='Wait for every transaction to be confirmed')
|
||||||
argparser.add_argument('-i', '--chain-spec', dest='i', type=str, default='Ethereum:1', help='Chain specification string')
|
argparser.add_argument('-i', '--chain-spec', dest='i', type=str, default='evm:ethereum:1', help='Chain specification string')
|
||||||
argparser.add_argument('-a', '--signer-address', dest='a', type=str, help='Signing address')
|
|
||||||
argparser.add_argument('-y', '--key-file', dest='y', type=str, help='Ethereum keystore file to use for signing')
|
argparser.add_argument('-y', '--key-file', dest='y', type=str, help='Ethereum keystore file to use for signing')
|
||||||
argparser.add_argument('--env-prefix', default=os.environ.get('CONFINI_ENV_PREFIX'), dest='env_prefix', type=str, help='environment prefix for variables to overwrite configuration')
|
argparser.add_argument('--env-prefix', default=os.environ.get('CONFINI_ENV_PREFIX'), dest='env_prefix', type=str, help='environment prefix for variables to overwrite configuration')
|
||||||
argparser.add_argument('--nonce', type=int, help='override nonce')
|
argparser.add_argument('--nonce', type=int, help='override nonce')
|
||||||
@ -61,8 +61,7 @@ argparser.add_argument('--gas', type=int, help='override gas limit')
|
|||||||
argparser.add_argument('-u', '--unsafe', dest='u', action='store_true', help='Auto-convert address to checksum adddress')
|
argparser.add_argument('-u', '--unsafe', dest='u', action='store_true', help='Auto-convert address to checksum adddress')
|
||||||
argparser.add_argument('-v', action='store_true', help='Be verbose')
|
argparser.add_argument('-v', action='store_true', help='Be verbose')
|
||||||
argparser.add_argument('-vv', action='store_true', help='Be more verbose')
|
argparser.add_argument('-vv', action='store_true', help='Be more verbose')
|
||||||
argparser.add_argument('-o', action='store_true', help='Print raw to to terminal')
|
argparser.add_argument('-s', '--send', dest='s', action='store_true', help='Send to network')
|
||||||
argparser.add_argument('-n', action='store_true', help='Do not send to network')
|
|
||||||
argparser.add_argument('recipient', type=str, help='Ethereum address of recipient')
|
argparser.add_argument('recipient', type=str, help='Ethereum address of recipient')
|
||||||
argparser.add_argument('amount', type=int, help='Amount of tokens to mint and gift')
|
argparser.add_argument('amount', type=int, help='Amount of tokens to mint and gift')
|
||||||
args = argparser.parse_args()
|
args = argparser.parse_args()
|
||||||
@ -88,35 +87,32 @@ signer_address = None
|
|||||||
keystore = DictKeystore()
|
keystore = DictKeystore()
|
||||||
if args.y != None:
|
if args.y != None:
|
||||||
logg.debug('loading keystore file {}'.format(args.y))
|
logg.debug('loading keystore file {}'.format(args.y))
|
||||||
signer_address = keystore.import_keystore_file(args.y, passphrase)
|
signer_address = keystore.import_keystore_file(args.y, password=passphrase)
|
||||||
logg.debug('now have key for signer address {}'.format(signer_address))
|
logg.debug('now have key for signer address {}'.format(signer_address))
|
||||||
signer = EIP155Signer(keystore)
|
signer = EIP155Signer(keystore)
|
||||||
|
|
||||||
conn = HTTPConnection(args.p)
|
conn = EthHTTPConnection(args.p)
|
||||||
|
|
||||||
nonce_oracle = None
|
nonce_oracle = None
|
||||||
if args.nonce != None:
|
if args.nonce != None:
|
||||||
nonce_oracle = OverrideNonceOracle(signer_address, args.nonce)
|
nonce_oracle = OverrideNonceOracle(signer_address, args.nonce)
|
||||||
else:
|
else:
|
||||||
nonce_oracle = DefaultNonceOracle(signer_address, conn)
|
nonce_oracle = RPCNonceOracle(signer_address, conn)
|
||||||
|
|
||||||
gas_oracle = None
|
gas_oracle = None
|
||||||
if args.price != None:
|
if args.price or args.gas != None:
|
||||||
gas_oracle = OverrideGasOracle(args.price, args.gas)
|
gas_oracle = OverrideGasOracle(price=args.price, limit=args.gas, conn=conn)
|
||||||
else:
|
else:
|
||||||
gas_oracle = DefaultGasOracle(conn)
|
gas_oracle = RPCGasOracle(conn)
|
||||||
|
|
||||||
|
|
||||||
chain_spec = ChainSpec.from_chain_str(args.i)
|
chain_spec = ChainSpec.from_chain_str(args.i)
|
||||||
chain_id = chain_spec.network_id()
|
|
||||||
|
|
||||||
value = args.amount
|
value = args.amount
|
||||||
|
|
||||||
out = args.o
|
send = args.s
|
||||||
|
|
||||||
send = not args.n
|
g = Gas(chain_spec, signer=signer, gas_oracle=gas_oracle, nonce_oracle=nonce_oracle)
|
||||||
|
|
||||||
g = GasTxFactory(signer=signer, gas_oracle=gas_oracle, nonce_oracle=nonce_oracle, chain_id=chain_id)
|
|
||||||
|
|
||||||
|
|
||||||
def balance(address):
|
def balance(address):
|
||||||
@ -132,21 +128,31 @@ def main():
|
|||||||
raise ValueError('invalid checksum address')
|
raise ValueError('invalid checksum address')
|
||||||
|
|
||||||
logg.info('gas transfer from {} to {} value {}'.format(signer_address, recipient, value))
|
logg.info('gas transfer from {} to {} value {}'.format(signer_address, recipient, value))
|
||||||
|
if logg.isEnabledFor(logging.DEBUG):
|
||||||
logg.debug('sender {} balance before: {}'.format(signer_address, balance(signer_address)))
|
logg.debug('sender {} balance before: {}'.format(signer_address, balance(signer_address)))
|
||||||
logg.debug('recipient {} balance before: {}'.format(recipient, balance(recipient)))
|
logg.debug('recipient {} balance before: {}'.format(recipient, balance(recipient)))
|
||||||
|
|
||||||
(tx_hash_hex, o) = g.create(signer_address, recipient, value)
|
(tx_hash_hex, o) = g.create(signer_address, recipient, value)
|
||||||
if out:
|
|
||||||
print(o['params'][0])
|
|
||||||
if send:
|
if send:
|
||||||
conn.do(o)
|
conn.do(o)
|
||||||
|
|
||||||
if block_last:
|
if block_last:
|
||||||
conn.wait(tx_hash_hex)
|
r = conn.wait(tx_hash_hex)
|
||||||
|
if logg.isEnabledFor(logging.DEBUG):
|
||||||
logg.debug('sender {} balance after: {}'.format(signer_address, balance(signer_address)))
|
logg.debug('sender {} balance after: {}'.format(signer_address, balance(signer_address)))
|
||||||
logg.debug('recipient {} balance after: {}'.format(recipient, balance(recipient)))
|
logg.debug('recipient {} balance after: {}'.format(recipient, balance(recipient)))
|
||||||
|
if r['status'] == 0:
|
||||||
|
logg.critical('VM revert. Wish I could tell you more')
|
||||||
|
sys.exit(1)
|
||||||
print(tx_hash_hex)
|
print(tx_hash_hex)
|
||||||
|
else:
|
||||||
|
if logg.isEnabledFor(logging.INFO):
|
||||||
|
io_str = io.StringIO()
|
||||||
|
decode_for_puny_humans(o['params'][0], chain_spec, io_str)
|
||||||
|
print(io_str.getvalue())
|
||||||
|
else:
|
||||||
|
print(o['params'][0])
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
if __name__ == '__main__':
|
if __name__ == '__main__':
|
||||||
|
@ -10,6 +10,7 @@
|
|||||||
# SPDX-License-Identifier: GPL-3.0-or-later
|
# SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
|
|
||||||
# standard imports
|
# standard imports
|
||||||
|
import sys
|
||||||
import os
|
import os
|
||||||
import json
|
import json
|
||||||
import argparse
|
import argparse
|
||||||
@ -23,15 +24,14 @@ from hexathon import (
|
|||||||
even,
|
even,
|
||||||
)
|
)
|
||||||
import sha3
|
import sha3
|
||||||
from eth_abi import encode_single
|
|
||||||
|
|
||||||
# local imports
|
# local imports
|
||||||
from chainlib.eth.address import to_checksum
|
from chainlib.eth.address import to_checksum
|
||||||
from chainlib.eth.rpc import (
|
from chainlib.jsonrpc import (
|
||||||
jsonrpc_template,
|
jsonrpc_template,
|
||||||
jsonrpc_result,
|
jsonrpc_result,
|
||||||
)
|
)
|
||||||
from chainlib.eth.connection import HTTPConnection
|
from chainlib.eth.connection import EthHTTPConnection
|
||||||
from chainlib.eth.tx import Tx
|
from chainlib.eth.tx import Tx
|
||||||
from chainlib.eth.block import Block
|
from chainlib.eth.block import Block
|
||||||
|
|
||||||
@ -43,18 +43,22 @@ default_eth_provider = os.environ.get('ETH_PROVIDER', 'http://localhost:8545')
|
|||||||
|
|
||||||
argparser = argparse.ArgumentParser()
|
argparser = argparse.ArgumentParser()
|
||||||
argparser.add_argument('-p', '--provider', dest='p', default=default_eth_provider, type=str, help='Web3 provider url (http only)')
|
argparser.add_argument('-p', '--provider', dest='p', default=default_eth_provider, type=str, help='Web3 provider url (http only)')
|
||||||
|
argparser.add_argument('-i', '--chain-spec', dest='i', type=str, default='evm:ethereum:1', help='Chain specification string')
|
||||||
|
argparser.add_argument('-t', '--token-address', dest='t', type=str, help='Token address. If not set, will return gas balance')
|
||||||
argparser.add_argument('-t', '--token-address', dest='t', type=str, help='Token address. If not set, will return gas balance')
|
argparser.add_argument('-t', '--token-address', dest='t', type=str, help='Token address. If not set, will return gas balance')
|
||||||
argparser.add_argument('-u', '--unsafe', dest='u', action='store_true', help='Auto-convert address to checksum adddress')
|
argparser.add_argument('-u', '--unsafe', dest='u', action='store_true', help='Auto-convert address to checksum adddress')
|
||||||
argparser.add_argument('--abi-dir', dest='abi_dir', type=str, default=default_abi_dir, help='Directory containing bytecode and abi (default {})'.format(default_abi_dir))
|
argparser.add_argument('--abi-dir', dest='abi_dir', type=str, default=default_abi_dir, help='Directory containing bytecode and abi (default {})'.format(default_abi_dir))
|
||||||
argparser.add_argument('-v', action='store_true', help='Be verbose')
|
argparser.add_argument('-v', action='store_true', help='Be verbose')
|
||||||
|
argparser.add_argument('-vv', action='store_true', help='Be more verbose')
|
||||||
argparser.add_argument('tx_hash', type=str, help='Transaction hash')
|
argparser.add_argument('tx_hash', type=str, help='Transaction hash')
|
||||||
args = argparser.parse_args()
|
args = argparser.parse_args()
|
||||||
|
|
||||||
|
if args.vv:
|
||||||
if args.v:
|
|
||||||
logg.setLevel(logging.DEBUG)
|
logg.setLevel(logging.DEBUG)
|
||||||
|
elif args.v:
|
||||||
|
logg.setLevel(logging.INFO)
|
||||||
|
|
||||||
conn = HTTPConnection(args.p)
|
conn = EthHTTPConnection(args.p)
|
||||||
|
|
||||||
tx_hash = args.tx_hash
|
tx_hash = args.tx_hash
|
||||||
|
|
||||||
@ -70,21 +74,14 @@ def main():
|
|||||||
o['method'] = 'eth_getTransactionByHash'
|
o['method'] = 'eth_getTransactionByHash'
|
||||||
o['params'].append(tx_hash)
|
o['params'].append(tx_hash)
|
||||||
tx_src = conn.do(o)
|
tx_src = conn.do(o)
|
||||||
|
if tx_src == None:
|
||||||
|
logg.error('Transaction {} not found'.format(tx_hash))
|
||||||
|
sys.exit(1)
|
||||||
|
|
||||||
tx = None
|
tx = None
|
||||||
status = -1
|
status = -1
|
||||||
rcpt = None
|
rcpt = None
|
||||||
if tx_src['blockHash'] != None:
|
|
||||||
o = jsonrpc_template()
|
|
||||||
o['method'] = 'eth_getBlockByHash'
|
|
||||||
o['params'].append(tx_src['blockHash'])
|
|
||||||
o['params'].append(True)
|
|
||||||
block_src = conn.do(o)
|
|
||||||
block = Block(block_src)
|
|
||||||
for t in block.txs:
|
|
||||||
if t['hash'] == tx_hash:
|
|
||||||
tx = Tx(t, block)
|
|
||||||
break
|
|
||||||
o = jsonrpc_template()
|
o = jsonrpc_template()
|
||||||
o['method'] = 'eth_getTransactionReceipt'
|
o['method'] = 'eth_getTransactionReceipt'
|
||||||
o['params'].append(tx_hash)
|
o['params'].append(tx_hash)
|
||||||
|
@ -11,26 +11,33 @@
|
|||||||
|
|
||||||
# standard imports
|
# standard imports
|
||||||
import os
|
import os
|
||||||
|
import io
|
||||||
import json
|
import json
|
||||||
import argparse
|
import argparse
|
||||||
import logging
|
import logging
|
||||||
|
|
||||||
# third-party imports
|
# third-party imports
|
||||||
from crypto_dev_signer.eth.signer import ReferenceSigner as EIP155Signer
|
from crypto_dev_signer.eth.signer import ReferenceSigner as EIP155Signer
|
||||||
from crypto_dev_signer.keystore import DictKeystore
|
from crypto_dev_signer.keystore.dict import DictKeystore
|
||||||
from hexathon import (
|
from hexathon import (
|
||||||
add_0x,
|
add_0x,
|
||||||
strip_0x,
|
strip_0x,
|
||||||
)
|
)
|
||||||
|
|
||||||
# local imports
|
# local imports
|
||||||
from chainlib.eth.address import to_checksum
|
from chainlib.eth.connection import EthHTTPConnection
|
||||||
from chainlib.eth.connection import HTTPConnection
|
from chainlib.jsonrpc import jsonrpc_template
|
||||||
from chainlib.eth.rpc import jsonrpc_template
|
from chainlib.eth.nonce import (
|
||||||
from chainlib.eth.nonce import DefaultNonceOracle
|
RPCNonceOracle,
|
||||||
from chainlib.eth.gas import DefaultGasOracle
|
OverrideNonceOracle,
|
||||||
from chainlib.eth.erc20 import ERC20TxFactory
|
)
|
||||||
|
from chainlib.eth.gas import (
|
||||||
|
RPCGasOracle,
|
||||||
|
OverrideGasOracle,
|
||||||
|
)
|
||||||
|
from chainlib.eth.erc20 import ERC20
|
||||||
from chainlib.chain import ChainSpec
|
from chainlib.chain import ChainSpec
|
||||||
|
from chainlib.eth.runnable.util import decode_for_puny_humans
|
||||||
|
|
||||||
|
|
||||||
logging.basicConfig(level=logging.WARNING)
|
logging.basicConfig(level=logging.WARNING)
|
||||||
@ -44,13 +51,15 @@ argparser = argparse.ArgumentParser()
|
|||||||
argparser.add_argument('-p', '--provider', dest='p', default='http://localhost:8545', type=str, help='Web3 provider url (http only)')
|
argparser.add_argument('-p', '--provider', dest='p', default='http://localhost:8545', type=str, help='Web3 provider url (http only)')
|
||||||
argparser.add_argument('-w', action='store_true', help='Wait for the last transaction to be confirmed')
|
argparser.add_argument('-w', action='store_true', help='Wait for the last transaction to be confirmed')
|
||||||
argparser.add_argument('-ww', action='store_true', help='Wait for every transaction to be confirmed')
|
argparser.add_argument('-ww', action='store_true', help='Wait for every transaction to be confirmed')
|
||||||
argparser.add_argument('-i', '--chain-spec', dest='i', type=str, default='Ethereum:1', help='Chain specification string')
|
argparser.add_argument('-i', '--chain-spec', dest='i', type=str, default='evm:ethereum:1', help='Chain specification string')
|
||||||
argparser.add_argument('--token-address', required='True', dest='t', type=str, help='Token address')
|
argparser.add_argument('-a', '--token-address', required='True', dest='a', type=str, help='Token address')
|
||||||
argparser.add_argument('-a', '--sender-address', dest='s', type=str, help='Sender account address')
|
|
||||||
argparser.add_argument('-y', '--key-file', dest='y', type=str, help='Ethereum keystore file to use for signing')
|
argparser.add_argument('-y', '--key-file', dest='y', type=str, help='Ethereum keystore file to use for signing')
|
||||||
argparser.add_argument('--abi-dir', dest='abi_dir', type=str, default=default_abi_dir, help='Directory containing bytecode and abi (default {})'.format(default_abi_dir))
|
|
||||||
argparser.add_argument('--env-prefix', default=os.environ.get('CONFINI_ENV_PREFIX'), dest='env_prefix', type=str, help='environment prefix for variables to overwrite configuration')
|
argparser.add_argument('--env-prefix', default=os.environ.get('CONFINI_ENV_PREFIX'), dest='env_prefix', type=str, help='environment prefix for variables to overwrite configuration')
|
||||||
argparser.add_argument('-u', '--unsafe', dest='u', action='store_true', help='Auto-convert address to checksum adddress')
|
argparser.add_argument('-u', '--unsafe', dest='u', action='store_true', help='Auto-convert address to checksum adddress')
|
||||||
|
argparser.add_argument('-s', '--send', dest='s', action='store_true', help='Send to network')
|
||||||
|
argparser.add_argument('--nonce', type=int, help='Override nonce')
|
||||||
|
argparser.add_argument('--gas-price', dest='gas_price', type=int, help='Override gas price')
|
||||||
|
argparser.add_argument('--gas-limit', dest='gas_limit', type=int, help='Override gas limit')
|
||||||
argparser.add_argument('-v', action='store_true', help='Be verbose')
|
argparser.add_argument('-v', action='store_true', help='Be verbose')
|
||||||
argparser.add_argument('-vv', action='store_true', help='Be more verbose')
|
argparser.add_argument('-vv', action='store_true', help='Be more verbose')
|
||||||
argparser.add_argument('recipient', type=str, help='Recipient account address')
|
argparser.add_argument('recipient', type=str, help='Recipient account address')
|
||||||
@ -73,29 +82,45 @@ passphrase = os.environ.get(passphrase_env)
|
|||||||
logg.error('pass {}'.format(passphrase_env))
|
logg.error('pass {}'.format(passphrase_env))
|
||||||
if passphrase == None:
|
if passphrase == None:
|
||||||
logg.warning('no passphrase given')
|
logg.warning('no passphrase given')
|
||||||
|
passphrase=''
|
||||||
|
|
||||||
signer_address = None
|
signer_address = None
|
||||||
keystore = DictKeystore()
|
keystore = DictKeystore()
|
||||||
if args.y != None:
|
if args.y != None:
|
||||||
logg.debug('loading keystore file {}'.format(args.y))
|
logg.debug('loading keystore file {}'.format(args.y))
|
||||||
signer_address = keystore.import_keystore_file(args.y)
|
signer_address = keystore.import_keystore_file(args.y, password=passphrase)
|
||||||
logg.debug('now have key for signer address {}'.format(signer_address))
|
logg.debug('now have key for signer address {}'.format(signer_address))
|
||||||
signer = EIP155Signer(keystore)
|
signer = EIP155Signer(keystore)
|
||||||
|
|
||||||
conn = HTTPConnection(args.p)
|
conn = EthHTTPConnection(args.p)
|
||||||
nonce_oracle = DefaultNonceOracle(signer_address, conn)
|
|
||||||
gas_oracle = DefaultGasOracle(conn)
|
nonce_oracle = None
|
||||||
|
if args.nonce != None:
|
||||||
|
nonce_oracle = OverrideNonceOracle(signer_address, args.nonce)
|
||||||
|
else:
|
||||||
|
nonce_oracle = RPCNonceOracle(signer_address, conn)
|
||||||
|
|
||||||
|
def _max_gas(code=None):
|
||||||
|
return 8000000
|
||||||
|
|
||||||
|
gas_oracle = None
|
||||||
|
if args.gas_price != None:
|
||||||
|
gas_oracle = OverrideGasOracle(args.gas_price, args.gas_limit)
|
||||||
|
else:
|
||||||
|
gas_oracle = RPCGasOracle(conn, code_callback=_max_gas)
|
||||||
|
|
||||||
chain_spec = ChainSpec.from_chain_str(args.i)
|
chain_spec = ChainSpec.from_chain_str(args.i)
|
||||||
chain_id = chain_spec.network_id()
|
chain_id = chain_spec.network_id()
|
||||||
|
|
||||||
value = args.amount
|
value = args.amount
|
||||||
|
|
||||||
g = ERC20TxFactory(signer=signer, gas_oracle=gas_oracle, nonce_oracle=nonce_oracle, chain_id=chain_id)
|
send = args.s
|
||||||
|
|
||||||
|
g = ERC20(chain_spec, signer=signer, gas_oracle=gas_oracle, nonce_oracle=nonce_oracle)
|
||||||
|
|
||||||
|
|
||||||
def balance(token_address, address):
|
def balance(token_address, address):
|
||||||
o = g.erc20_balance(token_address, address)
|
o = g.balance(token_address, address)
|
||||||
r = conn.do(o)
|
r = conn.do(o)
|
||||||
hx = strip_0x(r)
|
hx = strip_0x(r)
|
||||||
return int(hx, 16)
|
return int(hx, 16)
|
||||||
@ -106,19 +131,32 @@ def main():
|
|||||||
if not args.u and recipient != add_0x(args.recipient):
|
if not args.u and recipient != add_0x(args.recipient):
|
||||||
raise ValueError('invalid checksum address')
|
raise ValueError('invalid checksum address')
|
||||||
|
|
||||||
logg.debug('sender {} balance before: {}'.format(signer_address, balance(args.t, signer_address)))
|
if logg.isEnabledFor(logging.DEBUG):
|
||||||
logg.debug('recipient {} balance before: {}'.format(recipient, balance(args.t, recipient)))
|
logg.debug('sender {} balance after: {}'.format(signer_address, balance(args.a, signer_address)))
|
||||||
|
logg.debug('recipient {} balance after: {}'.format(recipient, balance(args.a, recipient)))
|
||||||
|
|
||||||
(tx_hash_hex, o) = g.erc20_transfer(args.t, signer_address, recipient, value)
|
(tx_hash_hex, o) = g.transfer(args.a, signer_address, recipient, value)
|
||||||
|
|
||||||
|
if send:
|
||||||
conn.do(o)
|
conn.do(o)
|
||||||
|
|
||||||
if block_last:
|
if block_last:
|
||||||
conn.wait(tx_hash_hex)
|
r = conn.wait(tx_hash_hex)
|
||||||
logg.debug('sender {} balance after: {}'.format(signer_address, balance(args.t, signer_address)))
|
if logg.isEnabledFor(logging.DEBUG):
|
||||||
logg.debug('recipient {} balance after: {}'.format(recipient, balance(args.t, recipient)))
|
logg.debug('sender {} balance after: {}'.format(signer_address, balance(args.a, signer_address)))
|
||||||
|
logg.debug('recipient {} balance after: {}'.format(recipient, balance(args.a, recipient)))
|
||||||
|
if r['status'] == 0:
|
||||||
|
logg.critical('VM revert. Wish I could tell you more')
|
||||||
|
sys.exit(1)
|
||||||
print(tx_hash_hex)
|
print(tx_hash_hex)
|
||||||
|
|
||||||
|
else:
|
||||||
|
if logg.isEnabledFor(logging.INFO):
|
||||||
|
io_str = io.StringIO()
|
||||||
|
decode_for_puny_humans(o['params'][0], chain_spec, io_str)
|
||||||
|
print(io_str.getvalue())
|
||||||
|
else:
|
||||||
|
print(o['params'][0])
|
||||||
|
|
||||||
|
|
||||||
if __name__ == '__main__':
|
if __name__ == '__main__':
|
||||||
main()
|
main()
|
||||||
|
22
chainlib/eth/runnable/util.py
Normal file
22
chainlib/eth/runnable/util.py
Normal file
@ -0,0 +1,22 @@
|
|||||||
|
# local imports
|
||||||
|
from chainlib.eth.tx import unpack
|
||||||
|
from hexathon import (
|
||||||
|
strip_0x,
|
||||||
|
add_0x,
|
||||||
|
)
|
||||||
|
|
||||||
|
def decode_for_puny_humans(tx_raw, chain_spec, writer):
|
||||||
|
tx_raw = strip_0x(tx_raw)
|
||||||
|
tx_raw_bytes = bytes.fromhex(tx_raw)
|
||||||
|
tx = unpack(tx_raw_bytes, chain_spec)
|
||||||
|
for k in tx.keys():
|
||||||
|
x = None
|
||||||
|
if k == 'value':
|
||||||
|
x = '{:.18f} eth'.format(tx[k] / (10**18))
|
||||||
|
elif k == 'gasPrice':
|
||||||
|
x = '{} gwei'.format(int(tx[k] / (10**9)))
|
||||||
|
if x != None:
|
||||||
|
writer.write('{}: {} ({})\n'.format(k, tx[k], x))
|
||||||
|
else:
|
||||||
|
writer.write('{}: {}\n'.format(k, tx[k]))
|
||||||
|
writer.write('src: {}\n'.format(add_0x(tx_raw)))
|
23
chainlib/eth/sign.py
Normal file
23
chainlib/eth/sign.py
Normal file
@ -0,0 +1,23 @@
|
|||||||
|
# local imports
|
||||||
|
from chainlib.jsonrpc import jsonrpc_template
|
||||||
|
|
||||||
|
|
||||||
|
def new_account(passphrase=''):
|
||||||
|
o = jsonrpc_template()
|
||||||
|
o['method'] = 'personal_newAccount'
|
||||||
|
o['params'] = [passphrase]
|
||||||
|
return o
|
||||||
|
|
||||||
|
|
||||||
|
def sign_transaction(payload):
|
||||||
|
o = jsonrpc_template()
|
||||||
|
o['method'] = 'eth_signTransaction'
|
||||||
|
o['params'] = [payload]
|
||||||
|
return o
|
||||||
|
|
||||||
|
|
||||||
|
def sign_message(address, payload):
|
||||||
|
o = jsonrpc_template()
|
||||||
|
o['method'] = 'eth_sign'
|
||||||
|
o['params'] = [address, payload]
|
||||||
|
return o
|
@ -1,17 +1,18 @@
|
|||||||
# standard imports
|
# standard imports
|
||||||
import logging
|
import logging
|
||||||
|
import enum
|
||||||
|
|
||||||
# third-party imports
|
# external imports
|
||||||
|
import coincurve
|
||||||
import sha3
|
import sha3
|
||||||
from hexathon import (
|
from hexathon import (
|
||||||
strip_0x,
|
strip_0x,
|
||||||
add_0x,
|
add_0x,
|
||||||
)
|
)
|
||||||
from eth_keys import KeyAPI
|
|
||||||
from eth_keys.backends import NativeECCBackend
|
|
||||||
from rlp import decode as rlp_decode
|
from rlp import decode as rlp_decode
|
||||||
from rlp import encode as rlp_encode
|
from rlp import encode as rlp_encode
|
||||||
from crypto_dev_signer.eth.transaction import EIP155Transaction
|
from crypto_dev_signer.eth.transaction import EIP155Transaction
|
||||||
|
from crypto_dev_signer.encoding import public_key_to_address
|
||||||
|
|
||||||
|
|
||||||
# local imports
|
# local imports
|
||||||
@ -23,9 +24,19 @@ from .constant import (
|
|||||||
MINIMUM_FEE_PRICE,
|
MINIMUM_FEE_PRICE,
|
||||||
ZERO_ADDRESS,
|
ZERO_ADDRESS,
|
||||||
)
|
)
|
||||||
from .rpc import jsonrpc_template
|
from chainlib.jsonrpc import jsonrpc_template
|
||||||
|
|
||||||
logg = logging.getLogger(__name__)
|
logg = logging.getLogger().getChild(__name__)
|
||||||
|
|
||||||
|
|
||||||
|
class TxFormat(enum.IntEnum):
|
||||||
|
DICT = 0x00
|
||||||
|
RAW = 0x01
|
||||||
|
RAW_SIGNED = 0x02
|
||||||
|
RAW_ARGS = 0x03
|
||||||
|
RLP = 0x10
|
||||||
|
RLP_SIGNED = 0x11
|
||||||
|
JSONRPC = 0x10
|
||||||
|
|
||||||
|
|
||||||
field_debugs = [
|
field_debugs = [
|
||||||
@ -40,20 +51,65 @@ field_debugs = [
|
|||||||
's',
|
's',
|
||||||
]
|
]
|
||||||
|
|
||||||
def unpack(tx_raw_bytes, chain_id=1):
|
def count(address, confirmed=False):
|
||||||
|
o = jsonrpc_template()
|
||||||
|
o['method'] = 'eth_getTransactionCount'
|
||||||
|
o['params'].append(address)
|
||||||
|
if confirmed:
|
||||||
|
o['params'].append('latest')
|
||||||
|
else:
|
||||||
|
o['params'].append('pending')
|
||||||
|
return o
|
||||||
|
|
||||||
|
count_pending = count
|
||||||
|
|
||||||
|
def count_confirmed(address):
|
||||||
|
return count(address, True)
|
||||||
|
|
||||||
|
|
||||||
|
def unpack(tx_raw_bytes, chain_spec):
|
||||||
|
chain_id = chain_spec.chain_id()
|
||||||
|
tx = __unpack_raw(tx_raw_bytes, chain_id)
|
||||||
|
tx['nonce'] = int.from_bytes(tx['nonce'], 'big')
|
||||||
|
tx['gasPrice'] = int.from_bytes(tx['gasPrice'], 'big')
|
||||||
|
tx['gas'] = int.from_bytes(tx['gas'], 'big')
|
||||||
|
tx['value'] = int.from_bytes(tx['value'], 'big')
|
||||||
|
return tx
|
||||||
|
|
||||||
|
|
||||||
|
def unpack_hex(tx_raw_bytes, chain_spec):
|
||||||
|
chain_id = chain_spec.chain_id()
|
||||||
|
tx = __unpack_raw(tx_raw_bytes, chain_id)
|
||||||
|
tx['nonce'] = add_0x(hex(tx['nonce']))
|
||||||
|
tx['gasPrice'] = add_0x(hex(tx['gasPrice']))
|
||||||
|
tx['gas'] = add_0x(hex(tx['gas']))
|
||||||
|
tx['value'] = add_0x(hex(tx['value']))
|
||||||
|
tx['chainId'] = add_0x(hex(tx['chainId']))
|
||||||
|
return tx
|
||||||
|
|
||||||
|
|
||||||
|
def __unpack_raw(tx_raw_bytes, chain_id=1):
|
||||||
d = rlp_decode(tx_raw_bytes)
|
d = rlp_decode(tx_raw_bytes)
|
||||||
|
|
||||||
logg.debug('decoding using chain id {}'.format(chain_id))
|
logg.debug('decoding using chain id {}'.format(str(chain_id)))
|
||||||
|
|
||||||
j = 0
|
j = 0
|
||||||
for i in d:
|
for i in d:
|
||||||
logg.debug('decoded {}: {}'.format(field_debugs[j], i.hex()))
|
v = i.hex()
|
||||||
|
if j != 3 and v == '':
|
||||||
|
v = '00'
|
||||||
|
logg.debug('decoded {}: {}'.format(field_debugs[j], v))
|
||||||
j += 1
|
j += 1
|
||||||
vb = chain_id
|
vb = chain_id
|
||||||
if chain_id != 0:
|
if chain_id != 0:
|
||||||
v = int.from_bytes(d[6], 'big')
|
v = int.from_bytes(d[6], 'big')
|
||||||
vb = v - (chain_id * 2) - 35
|
vb = v - (chain_id * 2) - 35
|
||||||
s = b''.join([d[7], d[8], bytes([vb])])
|
r = bytearray(32)
|
||||||
so = KeyAPI.Signature(signature_bytes=s)
|
r[32-len(d[7]):] = d[7]
|
||||||
|
s = bytearray(32)
|
||||||
|
s[32-len(d[8]):] = d[8]
|
||||||
|
sig = b''.join([r, s, bytes([vb])])
|
||||||
|
#so = KeyAPI.Signature(signature_bytes=sig)
|
||||||
|
|
||||||
h = sha3.keccak_256()
|
h = sha3.keccak_256()
|
||||||
h.update(rlp_encode(d))
|
h.update(rlp_encode(d))
|
||||||
@ -67,8 +123,10 @@ def unpack(tx_raw_bytes, chain_id=1):
|
|||||||
h.update(rlp_encode(d))
|
h.update(rlp_encode(d))
|
||||||
unsigned_hash = h.digest()
|
unsigned_hash = h.digest()
|
||||||
|
|
||||||
p = so.recover_public_key_from_msg_hash(unsigned_hash)
|
#p = so.recover_public_key_from_msg_hash(unsigned_hash)
|
||||||
a = p.to_checksum_address()
|
#a = p.to_checksum_address()
|
||||||
|
pubk = coincurve.PublicKey.from_signature_and_message(sig, unsigned_hash, hasher=None)
|
||||||
|
a = public_key_to_address(pubk)
|
||||||
logg.debug('decoded recovery byte {}'.format(vb))
|
logg.debug('decoded recovery byte {}'.format(vb))
|
||||||
logg.debug('decoded address {}'.format(a))
|
logg.debug('decoded address {}'.format(a))
|
||||||
logg.debug('decoded signed hash {}'.format(signed_hash.hex()))
|
logg.debug('decoded signed hash {}'.format(signed_hash.hex()))
|
||||||
@ -78,23 +136,43 @@ def unpack(tx_raw_bytes, chain_id=1):
|
|||||||
if to != None:
|
if to != None:
|
||||||
to = to_checksum(to)
|
to = to_checksum(to)
|
||||||
|
|
||||||
|
data = d[5].hex()
|
||||||
|
try:
|
||||||
|
data = add_0x(data)
|
||||||
|
except:
|
||||||
|
data = '0x'
|
||||||
|
|
||||||
return {
|
return {
|
||||||
'from': a,
|
'from': a,
|
||||||
'nonce': int.from_bytes(d[0], 'big'),
|
|
||||||
'gasPrice': int.from_bytes(d[1], 'big'),
|
|
||||||
'gas': int.from_bytes(d[2], 'big'),
|
|
||||||
'to': to,
|
'to': to,
|
||||||
'value': int.from_bytes(d[4], 'big'),
|
'nonce': d[0],
|
||||||
'data': '0x' + d[5].hex(),
|
'gasPrice': d[1],
|
||||||
|
'gas': d[2],
|
||||||
|
'value': d[4],
|
||||||
|
'data': data,
|
||||||
'v': chain_id,
|
'v': chain_id,
|
||||||
'r': '0x' + s[:32].hex(),
|
'r': add_0x(sig[:32].hex()),
|
||||||
's': '0x' + s[32:64].hex(),
|
's': add_0x(sig[32:64].hex()),
|
||||||
'chainId': chain_id,
|
'chainId': chain_id,
|
||||||
'hash': '0x' + signed_hash.hex(),
|
'hash': add_0x(signed_hash.hex()),
|
||||||
'hash_unsigned': '0x' + unsigned_hash.hex(),
|
'hash_unsigned': add_0x(unsigned_hash.hex()),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
def transaction(hsh):
|
||||||
|
o = jsonrpc_template()
|
||||||
|
o['method'] = 'eth_getTransactionByHash'
|
||||||
|
o['params'].append(add_0x(hsh))
|
||||||
|
return o
|
||||||
|
|
||||||
|
def transaction_by_block(hsh, idx):
|
||||||
|
o = jsonrpc_template()
|
||||||
|
o['method'] = 'eth_getTransactionByBlockHashAndIndex'
|
||||||
|
o['params'].append(add_0x(hsh))
|
||||||
|
o['params'].append(hex(idx))
|
||||||
|
return o
|
||||||
|
|
||||||
|
|
||||||
def receipt(hsh):
|
def receipt(hsh):
|
||||||
o = jsonrpc_template()
|
o = jsonrpc_template()
|
||||||
o['method'] = 'eth_getTransactionReceipt'
|
o['method'] = 'eth_getTransactionReceipt'
|
||||||
@ -102,12 +180,21 @@ def receipt(hsh):
|
|||||||
return o
|
return o
|
||||||
|
|
||||||
|
|
||||||
|
def raw(tx_raw_hex):
|
||||||
|
o = jsonrpc_template()
|
||||||
|
o['method'] = 'eth_sendRawTransaction'
|
||||||
|
o['params'].append(tx_raw_hex)
|
||||||
|
return o
|
||||||
|
|
||||||
|
|
||||||
class TxFactory:
|
class TxFactory:
|
||||||
|
|
||||||
def __init__(self, signer=None, gas_oracle=None, nonce_oracle=None, chain_id=1):
|
fee = 8000000
|
||||||
|
|
||||||
|
def __init__(self, chain_spec, signer=None, gas_oracle=None, nonce_oracle=None):
|
||||||
self.gas_oracle = gas_oracle
|
self.gas_oracle = gas_oracle
|
||||||
self.nonce_oracle = nonce_oracle
|
self.nonce_oracle = nonce_oracle
|
||||||
self.chain_id = chain_id
|
self.chain_spec = chain_spec
|
||||||
self.signer = signer
|
self.signer = signer
|
||||||
|
|
||||||
|
|
||||||
@ -115,19 +202,15 @@ class TxFactory:
|
|||||||
if tx['to'] == None or tx['to'] == '':
|
if tx['to'] == None or tx['to'] == '':
|
||||||
tx['to'] = '0x'
|
tx['to'] = '0x'
|
||||||
txe = EIP155Transaction(tx, tx['nonce'], tx['chainId'])
|
txe = EIP155Transaction(tx, tx['nonce'], tx['chainId'])
|
||||||
self.signer.signTransaction(txe)
|
tx_raw = self.signer.sign_transaction_to_rlp(txe)
|
||||||
tx_raw = txe.rlp_serialize()
|
|
||||||
tx_raw_hex = add_0x(tx_raw.hex())
|
tx_raw_hex = add_0x(tx_raw.hex())
|
||||||
tx_hash_hex = add_0x(keccak256_hex_to_hex(tx_raw_hex))
|
tx_hash_hex = add_0x(keccak256_hex_to_hex(tx_raw_hex))
|
||||||
return (tx_hash_hex, tx_raw_hex)
|
return (tx_hash_hex, tx_raw_hex)
|
||||||
|
|
||||||
|
|
||||||
def build(self, tx):
|
def build(self, tx):
|
||||||
(tx_hash_hex, tx_raw_hex) = self.build_raw(tx)
|
(tx_hash_hex, tx_raw_hex) = self.build_raw(tx)
|
||||||
|
o = raw(tx_raw_hex)
|
||||||
o = jsonrpc_template()
|
|
||||||
o['method'] = 'eth_sendRawTransaction'
|
|
||||||
o['params'].append(tx_raw_hex)
|
|
||||||
|
|
||||||
return (tx_hash_hex, o)
|
return (tx_hash_hex, o)
|
||||||
|
|
||||||
|
|
||||||
@ -135,7 +218,7 @@ class TxFactory:
|
|||||||
gas_price = MINIMUM_FEE_PRICE
|
gas_price = MINIMUM_FEE_PRICE
|
||||||
gas_limit = MINIMUM_FEE_UNITS
|
gas_limit = MINIMUM_FEE_UNITS
|
||||||
if self.gas_oracle != None:
|
if self.gas_oracle != None:
|
||||||
(gas_price, gas_limit) = self.gas_oracle.get()
|
(gas_price, gas_limit) = self.gas_oracle.get_gas()
|
||||||
logg.debug('using gas price {} limit {}'.format(gas_price, gas_limit))
|
logg.debug('using gas price {} limit {}'.format(gas_price, gas_limit))
|
||||||
nonce = 0
|
nonce = 0
|
||||||
o = {
|
o = {
|
||||||
@ -145,10 +228,10 @@ class TxFactory:
|
|||||||
'data': '0x',
|
'data': '0x',
|
||||||
'gasPrice': gas_price,
|
'gasPrice': gas_price,
|
||||||
'gas': gas_limit,
|
'gas': gas_limit,
|
||||||
'chainId': self.chain_id,
|
'chainId': self.chain_spec.chain_id(),
|
||||||
}
|
}
|
||||||
if self.nonce_oracle != None and use_nonce:
|
if self.nonce_oracle != None and use_nonce:
|
||||||
nonce = self.nonce_oracle.next()
|
nonce = self.nonce_oracle.next_nonce()
|
||||||
logg.debug('using nonce {} for address {}'.format(nonce, sender))
|
logg.debug('using nonce {} for address {}'.format(nonce, sender))
|
||||||
o['nonce'] = nonce
|
o['nonce'] = nonce
|
||||||
return o
|
return o
|
||||||
@ -166,11 +249,22 @@ class TxFactory:
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
def finalize(self, tx, tx_format=TxFormat.JSONRPC):
|
||||||
|
if tx_format == TxFormat.JSONRPC:
|
||||||
|
return self.build(tx)
|
||||||
|
elif tx_format == TxFormat.RLP_SIGNED:
|
||||||
|
return self.build_raw(tx)
|
||||||
|
raise NotImplementedError('tx formatting {} not implemented'.format(tx_format))
|
||||||
|
|
||||||
|
|
||||||
def set_code(self, tx, data, update_fee=True):
|
def set_code(self, tx, data, update_fee=True):
|
||||||
tx['data'] = data
|
tx['data'] = data
|
||||||
if update_fee:
|
if update_fee:
|
||||||
|
tx['gas'] = TxFactory.fee
|
||||||
|
if self.gas_oracle != None:
|
||||||
|
(price, tx['gas']) = self.gas_oracle.get_gas(code=data)
|
||||||
|
else:
|
||||||
logg.debug('using hardcoded gas limit of 8000000 until we have reliable vm executor')
|
logg.debug('using hardcoded gas limit of 8000000 until we have reliable vm executor')
|
||||||
tx['gas'] = 8000000
|
|
||||||
return tx
|
return tx
|
||||||
|
|
||||||
|
|
||||||
@ -187,6 +281,7 @@ class Tx:
|
|||||||
self.gasPrice = int(strip_0x(src['gasPrice']), 16)
|
self.gasPrice = int(strip_0x(src['gasPrice']), 16)
|
||||||
self.gasLimit = int(strip_0x(src['gas']), 16)
|
self.gasLimit = int(strip_0x(src['gas']), 16)
|
||||||
self.outputs = [to_checksum(address_from)]
|
self.outputs = [to_checksum(address_from)]
|
||||||
|
self.contract = None
|
||||||
|
|
||||||
inpt = src['input']
|
inpt = src['input']
|
||||||
if inpt != '0x':
|
if inpt != '0x':
|
||||||
@ -201,7 +296,11 @@ class Tx:
|
|||||||
self.inputs = [to_checksum(strip_0x(to))]
|
self.inputs = [to_checksum(strip_0x(to))]
|
||||||
|
|
||||||
self.block = block
|
self.block = block
|
||||||
|
try:
|
||||||
self.wire = src['raw']
|
self.wire = src['raw']
|
||||||
|
except KeyError:
|
||||||
|
logg.warning('no inline raw tx src, and no raw rendering implemented, field will be "None"')
|
||||||
|
|
||||||
self.src = src
|
self.src = src
|
||||||
|
|
||||||
self.status = Status.PENDING
|
self.status = Status.PENDING
|
||||||
@ -217,6 +316,12 @@ class Tx:
|
|||||||
self.status = Status.SUCCESS
|
self.status = Status.SUCCESS
|
||||||
elif status_number == 0:
|
elif status_number == 0:
|
||||||
self.status = Status.ERROR
|
self.status = Status.ERROR
|
||||||
|
# TODO: replace with rpc receipt/transaction translator when available
|
||||||
|
contract_address = rcpt.get('contractAddress')
|
||||||
|
if contract_address == None:
|
||||||
|
contract_address = rcpt.get('contract_address')
|
||||||
|
if contract_address != None:
|
||||||
|
self.contract = contract_address
|
||||||
self.logs = rcpt['logs']
|
self.logs = rcpt['logs']
|
||||||
|
|
||||||
|
|
||||||
@ -225,7 +330,7 @@ class Tx:
|
|||||||
|
|
||||||
|
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
return """hash {}
|
s = """hash {}
|
||||||
from {}
|
from {}
|
||||||
to {}
|
to {}
|
||||||
value {}
|
value {}
|
||||||
@ -245,3 +350,11 @@ status {}
|
|||||||
self.payload,
|
self.payload,
|
||||||
self.status.name,
|
self.status.name,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
if self.contract != None:
|
||||||
|
s += """contract {}
|
||||||
|
""".format(
|
||||||
|
self.contract,
|
||||||
|
)
|
||||||
|
return s
|
||||||
|
|
||||||
|
202
chainlib/eth/unittest/base.py
Normal file
202
chainlib/eth/unittest/base.py
Normal file
@ -0,0 +1,202 @@
|
|||||||
|
# standard imports
|
||||||
|
import os
|
||||||
|
import logging
|
||||||
|
|
||||||
|
# external imports
|
||||||
|
import eth_tester
|
||||||
|
import coincurve
|
||||||
|
from chainlib.connection import (
|
||||||
|
RPCConnection,
|
||||||
|
error_parser,
|
||||||
|
)
|
||||||
|
from chainlib.eth.address import (
|
||||||
|
to_checksum_address,
|
||||||
|
)
|
||||||
|
from chainlib.jsonrpc import (
|
||||||
|
jsonrpc_response,
|
||||||
|
jsonrpc_error,
|
||||||
|
jsonrpc_result,
|
||||||
|
)
|
||||||
|
from hexathon import (
|
||||||
|
unpad,
|
||||||
|
add_0x,
|
||||||
|
strip_0x,
|
||||||
|
)
|
||||||
|
|
||||||
|
from crypto_dev_signer.eth.signer import ReferenceSigner as EIP155Signer
|
||||||
|
from crypto_dev_signer.encoding import private_key_to_address
|
||||||
|
|
||||||
|
|
||||||
|
logg = logging.getLogger()
|
||||||
|
|
||||||
|
test_pk = bytes.fromhex('5087503f0a9cc35b38665955eb830c63f778453dd11b8fa5bd04bc41fd2cc6d6')
|
||||||
|
|
||||||
|
|
||||||
|
class EthTesterSigner(eth_tester.EthereumTester):
|
||||||
|
|
||||||
|
def __init__(self, backend, keystore):
|
||||||
|
super(EthTesterSigner, self).__init__(backend)
|
||||||
|
logg.debug('accounts {}'.format(self.get_accounts()))
|
||||||
|
|
||||||
|
self.keystore = keystore
|
||||||
|
self.backend = backend
|
||||||
|
self.backend.add_account(test_pk)
|
||||||
|
for pk in self.backend.account_keys:
|
||||||
|
pubk = pk.public_key
|
||||||
|
address = pubk.to_checksum_address()
|
||||||
|
logg.debug('test keystore have pk {} pubk {} addr {}'.format(pk, pk.public_key, address))
|
||||||
|
self.keystore.import_raw_key(pk._raw_key)
|
||||||
|
|
||||||
|
|
||||||
|
def new_account(self):
|
||||||
|
pk = os.urandom(32)
|
||||||
|
address = self.keystore.import_raw_key(pk)
|
||||||
|
checksum_address = add_0x(to_checksum_address(address))
|
||||||
|
self.backend.add_account(pk)
|
||||||
|
return checksum_address
|
||||||
|
|
||||||
|
|
||||||
|
class TestRPCConnection(RPCConnection):
|
||||||
|
|
||||||
|
def __init__(self, location, backend, signer):
|
||||||
|
super(TestRPCConnection, self).__init__(location)
|
||||||
|
self.backend = backend
|
||||||
|
self.signer = signer
|
||||||
|
|
||||||
|
|
||||||
|
def do(self, o, error_parser=error_parser):
|
||||||
|
logg.debug('testrpc do {}'.format(o))
|
||||||
|
m = getattr(self, o['method'])
|
||||||
|
if m == None:
|
||||||
|
raise ValueError('unhandled method {}'.format(o['method']))
|
||||||
|
r = None
|
||||||
|
try:
|
||||||
|
result = m(o['params'])
|
||||||
|
logg.debug('result {}'.format(result))
|
||||||
|
r = jsonrpc_response(o['id'], result)
|
||||||
|
except Exception as e:
|
||||||
|
logg.exception(e)
|
||||||
|
r = jsonrpc_error(o['id'], message=str(e))
|
||||||
|
return jsonrpc_result(r, error_parser)
|
||||||
|
|
||||||
|
|
||||||
|
def eth_getBlockByNumber(self, p):
|
||||||
|
b = bytes.fromhex(strip_0x(p[0]))
|
||||||
|
n = int.from_bytes(b, 'big')
|
||||||
|
block = self.backend.get_block_by_number(n)
|
||||||
|
return block
|
||||||
|
|
||||||
|
|
||||||
|
def eth_getBlockByHash(self, p):
|
||||||
|
block = self.backend.get_block_by_hash(p[0])
|
||||||
|
return block
|
||||||
|
|
||||||
|
|
||||||
|
def eth_getTransactionByBlock(self, p):
|
||||||
|
block = self.eth_getBlockByHash(p)
|
||||||
|
tx_hash = block['transactions'][p[1]]
|
||||||
|
tx = self.eth_getTransaction([tx_hash])
|
||||||
|
return tx
|
||||||
|
|
||||||
|
def eth_getBalance(self, p):
|
||||||
|
balance = self.backend.get_balance(p[0])
|
||||||
|
hx = balance.to_bytes(32, 'big').hex()
|
||||||
|
return add_0x(unpad(hx))
|
||||||
|
|
||||||
|
|
||||||
|
def eth_getTransactionCount(self, p):
|
||||||
|
nonce = self.backend.get_nonce(p[0])
|
||||||
|
hx = nonce.to_bytes(4, 'big').hex()
|
||||||
|
return add_0x(unpad(hx))
|
||||||
|
|
||||||
|
|
||||||
|
def eth_getTransactionByHash(self, p):
|
||||||
|
tx = self.backend.get_transaction_by_hash(p[0])
|
||||||
|
return tx
|
||||||
|
|
||||||
|
|
||||||
|
def eth_getTransactionReceipt(self, p):
|
||||||
|
rcpt = self.backend.get_transaction_receipt(p[0])
|
||||||
|
# TODO: use camelcase to snake case converter
|
||||||
|
if rcpt.get('block_number') == None:
|
||||||
|
rcpt['block_number'] = rcpt['blockNumber']
|
||||||
|
else:
|
||||||
|
rcpt['blockNumber'] = rcpt['block_number']
|
||||||
|
return rcpt
|
||||||
|
|
||||||
|
|
||||||
|
def eth_getCode(self, p):
|
||||||
|
r = self.backend.get_code(p[0])
|
||||||
|
return r
|
||||||
|
|
||||||
|
|
||||||
|
def eth_call(self, p):
|
||||||
|
tx_ethtester = to_ethtester_call(p[0])
|
||||||
|
r = self.backend.call(tx_ethtester)
|
||||||
|
return r
|
||||||
|
|
||||||
|
|
||||||
|
def eth_gasPrice(self, p):
|
||||||
|
return hex(1000000000)
|
||||||
|
|
||||||
|
|
||||||
|
def personal_newAccount(self, passphrase):
|
||||||
|
a = self.backend.new_account()
|
||||||
|
return a
|
||||||
|
|
||||||
|
|
||||||
|
def eth_sign(self, p):
|
||||||
|
r = self.signer.sign_ethereum_message(strip_0x(p[0]), strip_0x(p[1]))
|
||||||
|
return r
|
||||||
|
|
||||||
|
|
||||||
|
def eth_sendRawTransaction(self, p):
|
||||||
|
r = self.backend.send_raw_transaction(p[0])
|
||||||
|
return r
|
||||||
|
|
||||||
|
|
||||||
|
def eth_signTransaction(self, p):
|
||||||
|
raise NotImplementedError('needs transaction deserializer for EIP155Transaction')
|
||||||
|
tx_dict = p[0]
|
||||||
|
tx = EIP155Transaction(tx_dict, tx_dict['nonce'], tx_dict['chainId'])
|
||||||
|
passphrase = p[1]
|
||||||
|
r = self.signer.sign_transaction_to_rlp(tx, passphrase)
|
||||||
|
return r
|
||||||
|
|
||||||
|
|
||||||
|
def __verify_signer(self, tx, passphrase=''):
|
||||||
|
pk_bytes = self.backend.keystore.get(tx.sender)
|
||||||
|
pk = coincurve.PrivateKey(secret=pk_bytes)
|
||||||
|
result_address = private_key_to_address(pk)
|
||||||
|
assert strip_0x(result_address) == strip_0x(tx.sender)
|
||||||
|
|
||||||
|
|
||||||
|
def sign_transaction(self, tx, passphrase=''):
|
||||||
|
self.__verify_signer(tx, passphrase)
|
||||||
|
return self.signer.sign_transaction(tx, passphrase)
|
||||||
|
|
||||||
|
|
||||||
|
def sign_transaction_to_rlp(self, tx, passphrase=''):
|
||||||
|
self.__verify_signer(tx, passphrase)
|
||||||
|
return self.signer.sign_transaction_to_rlp(tx, passphrase)
|
||||||
|
|
||||||
|
|
||||||
|
def disconnect(self):
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
def to_ethtester_call(tx):
|
||||||
|
if tx['gas'] == '':
|
||||||
|
tx['gas'] = '0x00'
|
||||||
|
|
||||||
|
if tx['gasPrice'] == '':
|
||||||
|
tx['gasPrice'] = '0x00'
|
||||||
|
|
||||||
|
tx = {
|
||||||
|
'to': tx['to'],
|
||||||
|
'from': tx['from'],
|
||||||
|
'gas': int(tx['gas'], 16),
|
||||||
|
'gas_price': int(tx['gasPrice'], 16),
|
||||||
|
'data': tx['data'],
|
||||||
|
}
|
||||||
|
return tx
|
75
chainlib/eth/unittest/ethtester.py
Normal file
75
chainlib/eth/unittest/ethtester.py
Normal file
@ -0,0 +1,75 @@
|
|||||||
|
# standard imports
|
||||||
|
import os
|
||||||
|
import unittest
|
||||||
|
import logging
|
||||||
|
|
||||||
|
# external imports
|
||||||
|
import eth_tester
|
||||||
|
from crypto_dev_signer.eth.signer import ReferenceSigner as EIP155Signer
|
||||||
|
from crypto_dev_signer.keystore.dict import DictKeystore
|
||||||
|
from hexathon import (
|
||||||
|
strip_0x,
|
||||||
|
add_0x,
|
||||||
|
)
|
||||||
|
from eth import constants
|
||||||
|
from eth.vm.forks.byzantium import ByzantiumVM
|
||||||
|
|
||||||
|
# local imports
|
||||||
|
from .base import (
|
||||||
|
EthTesterSigner,
|
||||||
|
TestRPCConnection,
|
||||||
|
)
|
||||||
|
from chainlib.connection import RPCConnection
|
||||||
|
from chainlib.eth.address import to_checksum_address
|
||||||
|
from chainlib.chain import ChainSpec
|
||||||
|
|
||||||
|
logg = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
test_address = bytes.fromhex('Eb3907eCad74a0013c259D5874AE7f22DcBcC95C')
|
||||||
|
|
||||||
|
|
||||||
|
def create_tester_signer(keystore):
|
||||||
|
genesis_params = eth_tester.backends.pyevm.main.get_default_genesis_params({
|
||||||
|
'gas_limit': 8000000,
|
||||||
|
'coinbase': test_address, # doesn't seem to work
|
||||||
|
})
|
||||||
|
vm_configuration = (
|
||||||
|
(constants.GENESIS_BLOCK_NUMBER, ByzantiumVM),
|
||||||
|
)
|
||||||
|
genesis_state = eth_tester.PyEVMBackend._generate_genesis_state(num_accounts=30)
|
||||||
|
eth_backend = eth_tester.PyEVMBackend(
|
||||||
|
genesis_state=genesis_state,
|
||||||
|
genesis_parameters=genesis_params,
|
||||||
|
vm_configuration=vm_configuration,
|
||||||
|
)
|
||||||
|
return EthTesterSigner(eth_backend, keystore)
|
||||||
|
|
||||||
|
|
||||||
|
class EthTesterCase(unittest.TestCase):
|
||||||
|
|
||||||
|
def __init__(self, foo):
|
||||||
|
super(EthTesterCase, self).__init__(foo)
|
||||||
|
self.accounts = []
|
||||||
|
|
||||||
|
|
||||||
|
def setUp(self):
|
||||||
|
self.chain_spec = ChainSpec('evm', 'foochain', 42)
|
||||||
|
self.keystore = DictKeystore()
|
||||||
|
eth_tester_instance = create_tester_signer(self.keystore)
|
||||||
|
self.signer = EIP155Signer(self.keystore)
|
||||||
|
self.helper = eth_tester_instance
|
||||||
|
self.backend = self.helper.backend
|
||||||
|
self.rpc = TestRPCConnection(None, eth_tester_instance, self.signer)
|
||||||
|
for a in self.keystore.list():
|
||||||
|
self.accounts.append(add_0x(to_checksum_address(a)))
|
||||||
|
|
||||||
|
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)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
def tearDown(self):
|
||||||
|
pass
|
35
chainlib/http.py
Normal file
35
chainlib/http.py
Normal file
@ -0,0 +1,35 @@
|
|||||||
|
import urllib
|
||||||
|
import base64
|
||||||
|
import logging
|
||||||
|
|
||||||
|
logg = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
|
# THANKS to https://stackoverflow.com/questions/2407126/python-urllib2-basic-auth-problem
|
||||||
|
class PreemptiveBasicAuthHandler(urllib.request.HTTPBasicAuthHandler):
|
||||||
|
"""Handler for basic auth urllib callback.
|
||||||
|
|
||||||
|
:param req: Request payload
|
||||||
|
:type req: str
|
||||||
|
:return: Request payload
|
||||||
|
:rtype: str
|
||||||
|
"""
|
||||||
|
|
||||||
|
def http_request(self, req):
|
||||||
|
url = req.get_full_url()
|
||||||
|
realm = None
|
||||||
|
user, pw = self.passwd.find_user_password(realm, url)
|
||||||
|
|
||||||
|
if pw:
|
||||||
|
raw = "%s:%s" % (user, pw)
|
||||||
|
raw_bytes = raw.encode('utf-8')
|
||||||
|
auth_base_bytes = base64.encodebytes(raw_bytes)
|
||||||
|
auth_base = auth_base_bytes.decode('utf-8')
|
||||||
|
auth_base_clean = auth_base.replace('\n', '').strip()
|
||||||
|
auth = 'Basic %s' % auth_base_clean
|
||||||
|
req.add_unredirected_header(self.auth_header, auth)
|
||||||
|
logg.debug('head {}'.format(req.header_items()))
|
||||||
|
|
||||||
|
return req
|
||||||
|
|
||||||
|
https_request = http_request
|
43
chainlib/jsonrpc.py
Normal file
43
chainlib/jsonrpc.py
Normal file
@ -0,0 +1,43 @@
|
|||||||
|
# standard imports
|
||||||
|
import uuid
|
||||||
|
|
||||||
|
# local imports
|
||||||
|
from .error import JSONRPCException
|
||||||
|
|
||||||
|
|
||||||
|
class DefaultErrorParser:
|
||||||
|
|
||||||
|
def translate(self, error):
|
||||||
|
return JSONRPCException('default parser code {}'.format(error))
|
||||||
|
|
||||||
|
|
||||||
|
def jsonrpc_template():
|
||||||
|
return {
|
||||||
|
'jsonrpc': '2.0',
|
||||||
|
'id': str(uuid.uuid4()),
|
||||||
|
'method': None,
|
||||||
|
'params': [],
|
||||||
|
}
|
||||||
|
|
||||||
|
def jsonrpc_result(o, ep):
|
||||||
|
if o.get('error') != None:
|
||||||
|
raise ep.translate(o)
|
||||||
|
return o['result']
|
||||||
|
|
||||||
|
|
||||||
|
def jsonrpc_response(request_id, result):
|
||||||
|
return {
|
||||||
|
'jsonrpc': '2.0',
|
||||||
|
'id': request_id,
|
||||||
|
'result': result,
|
||||||
|
}
|
||||||
|
|
||||||
|
def jsonrpc_error(request_id, code=-32000, message='Server error'):
|
||||||
|
return {
|
||||||
|
'jsonrpc': '2.0',
|
||||||
|
'id': request_id,
|
||||||
|
'error': {
|
||||||
|
'code': code,
|
||||||
|
'message': message,
|
||||||
|
},
|
||||||
|
}
|
7
chainlib/status.py
Normal file
7
chainlib/status.py
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
# standard imports
|
||||||
|
import enum
|
||||||
|
|
||||||
|
class Status(enum.Enum):
|
||||||
|
PENDING = 0
|
||||||
|
SUCCESS = 1
|
||||||
|
ERROR = 2
|
@ -1,7 +1,5 @@
|
|||||||
crypto-dev-signer~=0.4.13rc2
|
crypto-dev-signer~=0.4.14a17
|
||||||
pysha3==1.0.2
|
pysha3==1.0.2
|
||||||
hexathon~=0.0.1a3
|
hexathon~=0.0.1a7
|
||||||
eth-abi==2.1.1
|
|
||||||
eth-keys==0.3.3
|
|
||||||
websocket-client==0.57.0
|
websocket-client==0.57.0
|
||||||
redis==3.5.3
|
redis==3.5.3
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
[metadata]
|
[metadata]
|
||||||
name = chainlib
|
name = chainlib
|
||||||
version = 0.0.1a19
|
version = 0.0.2a1
|
||||||
description = Generic blockchain access library and tooling
|
description = Generic blockchain access library and tooling
|
||||||
author = Louis Holbrook
|
author = Louis Holbrook
|
||||||
author_email = dev@holbrook.no
|
author_email = dev@holbrook.no
|
||||||
@ -30,6 +30,8 @@ packages =
|
|||||||
chainlib
|
chainlib
|
||||||
chainlib.eth
|
chainlib.eth
|
||||||
chainlib.eth.runnable
|
chainlib.eth.runnable
|
||||||
|
chainlib.eth.pytest
|
||||||
|
chainlib.eth.unittest
|
||||||
|
|
||||||
[options.entry_points]
|
[options.entry_points]
|
||||||
console_scripts =
|
console_scripts =
|
||||||
@ -38,3 +40,4 @@ console_scripts =
|
|||||||
eth-gas = chainlib.eth.runnable.gas:main
|
eth-gas = chainlib.eth.runnable.gas:main
|
||||||
eth-transfer = chainlib.eth.runnable.transfer:main
|
eth-transfer = chainlib.eth.runnable.transfer:main
|
||||||
eth-get = chainlib.eth.runnable.get:main
|
eth-get = chainlib.eth.runnable.get:main
|
||||||
|
eth-decode = chainlib.eth.runnable.decode:main
|
||||||
|
12
setup.py
12
setup.py
@ -2,6 +2,7 @@ from setuptools import setup
|
|||||||
import configparser
|
import configparser
|
||||||
import os
|
import os
|
||||||
|
|
||||||
|
|
||||||
requirements = []
|
requirements = []
|
||||||
f = open('requirements.txt', 'r')
|
f = open('requirements.txt', 'r')
|
||||||
while True:
|
while True:
|
||||||
@ -11,6 +12,17 @@ while True:
|
|||||||
requirements.append(l.rstrip())
|
requirements.append(l.rstrip())
|
||||||
f.close()
|
f.close()
|
||||||
|
|
||||||
|
test_requirements = []
|
||||||
|
f = open('test_requirements.txt', 'r')
|
||||||
|
while True:
|
||||||
|
l = f.readline()
|
||||||
|
if l == '':
|
||||||
|
break
|
||||||
|
test_requirements.append(l.rstrip())
|
||||||
|
f.close()
|
||||||
|
|
||||||
|
|
||||||
setup(
|
setup(
|
||||||
install_requires=requirements,
|
install_requires=requirements,
|
||||||
|
tests_require=test_requirements,
|
||||||
)
|
)
|
||||||
|
4
test_requirements.txt
Normal file
4
test_requirements.txt
Normal file
@ -0,0 +1,4 @@
|
|||||||
|
eth_tester==0.5.0b3
|
||||||
|
py-evm==0.3.0a20
|
||||||
|
rlp==2.0.1
|
||||||
|
pytest==6.0.1
|
25
tests/conftest.py
Normal file
25
tests/conftest.py
Normal file
@ -0,0 +1,25 @@
|
|||||||
|
# standard imports
|
||||||
|
import os
|
||||||
|
|
||||||
|
# external imports
|
||||||
|
import pytest
|
||||||
|
from crypto_dev_signer.keystore.dict import DictKeystore
|
||||||
|
from crypto_dev_signer.eth.signer import ReferenceSigner as EIP155Signer
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.fixture(scope='session')
|
||||||
|
def keystore():
|
||||||
|
ks = DictKeystore()
|
||||||
|
|
||||||
|
pk = os.urandom(32)
|
||||||
|
ks.import_raw_key(pk)
|
||||||
|
return ks
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.fixture(scope='session')
|
||||||
|
def signer(
|
||||||
|
keystore,
|
||||||
|
):
|
||||||
|
|
||||||
|
s = EIP155Signer(keystore)
|
||||||
|
return s
|
29
tests/test_abi.py
Normal file
29
tests/test_abi.py
Normal file
@ -0,0 +1,29 @@
|
|||||||
|
from chainlib.eth.contract import (
|
||||||
|
ABIContractEncoder,
|
||||||
|
ABIContractType,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def test_abi_param():
|
||||||
|
|
||||||
|
e = ABIContractEncoder()
|
||||||
|
e.uint256(42)
|
||||||
|
e.bytes32('0x666f6f')
|
||||||
|
e.address('0xdeadbeefdeadbeefdeadbeefdeadbeefdeadbeef')
|
||||||
|
e.method('foo')
|
||||||
|
e.typ(ABIContractType.UINT256)
|
||||||
|
e.typ(ABIContractType.BYTES32)
|
||||||
|
e.typ(ABIContractType.ADDRESS)
|
||||||
|
|
||||||
|
assert e.types[0] == ABIContractType.UINT256
|
||||||
|
assert e.types[1] == ABIContractType.BYTES32
|
||||||
|
assert e.types[2] == ABIContractType.ADDRESS
|
||||||
|
assert e.contents[0] == '000000000000000000000000000000000000000000000000000000000000002a'
|
||||||
|
assert e.contents[1] == '0000000000000000000000000000000000000000000000000000000000666f6f'
|
||||||
|
assert e.contents[2] == '000000000000000000000000deadbeefdeadbeefdeadbeefdeadbeefdeadbeef'
|
||||||
|
|
||||||
|
assert e.get() == 'a08f54bb000000000000000000000000000000000000000000000000000000000000002a0000000000000000000000000000000000000000000000000000000000666f6f000000000000000000000000deadbeefdeadbeefdeadbeefdeadbeefdeadbeef'
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
test_abi_param()
|
74
tests/test_erc20.py
Normal file
74
tests/test_erc20.py
Normal file
@ -0,0 +1,74 @@
|
|||||||
|
# standard imports
|
||||||
|
import logging
|
||||||
|
import os
|
||||||
|
|
||||||
|
# external imports
|
||||||
|
from hexathon import (
|
||||||
|
strip_0x,
|
||||||
|
add_0x,
|
||||||
|
)
|
||||||
|
|
||||||
|
# local imports
|
||||||
|
from chainlib.eth.erc20 import ERC20
|
||||||
|
from chainlib.eth.address import to_checksum_address
|
||||||
|
from chainlib.eth.tx import (
|
||||||
|
unpack,
|
||||||
|
TxFormat,
|
||||||
|
)
|
||||||
|
from chainlib.eth.pytest import *
|
||||||
|
|
||||||
|
logg = logging.getLogger()
|
||||||
|
|
||||||
|
contract_address = to_checksum_address('0xdeadbeefdeadbeefdeadbeefdeadbeefdeadbeef')
|
||||||
|
benefactor_address = to_checksum_address('0xefdeadbeefdeadbeefdeadbeefdeadbeefdeadbe')
|
||||||
|
|
||||||
|
|
||||||
|
# TODO: use unittest instead
|
||||||
|
def test_erc20_balance(
|
||||||
|
default_chain_spec,
|
||||||
|
):
|
||||||
|
e = ERC20(default_chain_spec,)
|
||||||
|
|
||||||
|
holder_address = to_checksum_address('0xbeefdeadbeefdeadbeefdeadbeefdeadbeefdead')
|
||||||
|
o = e.balance_of(contract_address, holder_address)
|
||||||
|
assert len(o['params'][0]['data']) == 64 + 8 + 2
|
||||||
|
assert o['params'][0]['data'][:10] == add_0x('70a08231')
|
||||||
|
|
||||||
|
|
||||||
|
def test_erc20_decimals(
|
||||||
|
default_chain_spec,
|
||||||
|
):
|
||||||
|
e = ERC20(default_chain_spec)
|
||||||
|
|
||||||
|
o = e.decimals(contract_address)
|
||||||
|
assert o['params'][0]['data'] == add_0x('313ce567')
|
||||||
|
|
||||||
|
|
||||||
|
def test_erc20_transfer(
|
||||||
|
keystore,
|
||||||
|
signer,
|
||||||
|
default_chain_spec,
|
||||||
|
):
|
||||||
|
e = ERC20(default_chain_spec, signer=signer)
|
||||||
|
|
||||||
|
addresses = keystore.list()
|
||||||
|
(tx_hash_hex, o) = e.transfer(contract_address, addresses[0], benefactor_address, 1024)
|
||||||
|
|
||||||
|
|
||||||
|
def test_erc20_parse_transfer_request(
|
||||||
|
keystore,
|
||||||
|
signer,
|
||||||
|
default_chain_spec,
|
||||||
|
):
|
||||||
|
|
||||||
|
e = ERC20(default_chain_spec, signer=signer)
|
||||||
|
|
||||||
|
addresses = keystore.list()
|
||||||
|
(tx_hash_hex, o) = e.transfer(contract_address, addresses[0], benefactor_address, 1024, tx_format=TxFormat.RLP_SIGNED)
|
||||||
|
b = bytes.fromhex(strip_0x(o))
|
||||||
|
|
||||||
|
#chain_spec = ChainSpec('evm', 'foo', 1, 'bar')
|
||||||
|
tx = unpack(b, default_chain_spec)
|
||||||
|
r = ERC20.parse_transfer_request(tx['data'])
|
||||||
|
assert r[0] == benefactor_address
|
||||||
|
assert r[1] == 1024
|
26
tests/test_nonce.py
Normal file
26
tests/test_nonce.py
Normal file
@ -0,0 +1,26 @@
|
|||||||
|
# standard imports
|
||||||
|
import os
|
||||||
|
import unittest
|
||||||
|
|
||||||
|
# local imports
|
||||||
|
from chainlib.eth.address import to_checksum_address
|
||||||
|
from chainlib.eth.nonce import OverrideNonceOracle
|
||||||
|
from hexathon import add_0x
|
||||||
|
|
||||||
|
# test imports
|
||||||
|
from tests.base import TestBase
|
||||||
|
|
||||||
|
|
||||||
|
class TestNonce(TestBase):
|
||||||
|
|
||||||
|
def test_nonce(self):
|
||||||
|
addr_bytes = os.urandom(20)
|
||||||
|
addr = add_0x(to_checksum_address(addr_bytes.hex()))
|
||||||
|
n = OverrideNonceOracle(addr, 42)
|
||||||
|
self.assertEqual(n.get_nonce(), 42)
|
||||||
|
self.assertEqual(n.next_nonce(), 42)
|
||||||
|
self.assertEqual(n.next_nonce(), 43)
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
unittest.main()
|
123
tests/test_sign.py
Normal file
123
tests/test_sign.py
Normal file
@ -0,0 +1,123 @@
|
|||||||
|
# standard imports
|
||||||
|
import os
|
||||||
|
import socket
|
||||||
|
import unittest
|
||||||
|
import unittest.mock
|
||||||
|
import logging
|
||||||
|
import json
|
||||||
|
|
||||||
|
# external imports
|
||||||
|
from crypto_dev_signer.eth.transaction import EIP155Transaction
|
||||||
|
from crypto_dev_signer.eth.signer.defaultsigner import ReferenceSigner
|
||||||
|
from crypto_dev_signer.keystore.dict import DictKeystore
|
||||||
|
|
||||||
|
# local imports
|
||||||
|
import chainlib
|
||||||
|
from chainlib.eth.connection import EthUnixSignerConnection
|
||||||
|
from chainlib.eth.sign import sign_transaction
|
||||||
|
from chainlib.eth.tx import TxFactory
|
||||||
|
from chainlib.eth.address import to_checksum_address
|
||||||
|
from chainlib.jsonrpc import (
|
||||||
|
jsonrpc_response,
|
||||||
|
jsonrpc_error,
|
||||||
|
)
|
||||||
|
from hexathon import (
|
||||||
|
add_0x,
|
||||||
|
)
|
||||||
|
from chainlib.chain import ChainSpec
|
||||||
|
|
||||||
|
from tests.base import TestBase
|
||||||
|
|
||||||
|
logging.basicConfig(level=logging.DEBUG)
|
||||||
|
logg = logging.getLogger()
|
||||||
|
|
||||||
|
keystore = DictKeystore()
|
||||||
|
alice = keystore.new()
|
||||||
|
bob = keystore.new()
|
||||||
|
|
||||||
|
|
||||||
|
class Mocket(socket.socket):
|
||||||
|
|
||||||
|
req_id = None
|
||||||
|
error = False
|
||||||
|
tx = None
|
||||||
|
signer = None
|
||||||
|
|
||||||
|
def connect(self, v):
|
||||||
|
return self
|
||||||
|
|
||||||
|
|
||||||
|
def send(self, v):
|
||||||
|
o = json.loads(v)
|
||||||
|
logg.debug('mocket received {}'.format(v))
|
||||||
|
Mocket.req_id = o['id']
|
||||||
|
params = o['params'][0]
|
||||||
|
if to_checksum_address(params.get('from')) != alice:
|
||||||
|
logg.error('from does not match alice {}'.format(params))
|
||||||
|
Mocket.error = True
|
||||||
|
if to_checksum_address(params.get('to')) != bob:
|
||||||
|
logg.error('to does not match bob {}'.format(params))
|
||||||
|
Mocket.error = True
|
||||||
|
if not Mocket.error:
|
||||||
|
Mocket.tx = EIP155Transaction(params, params['nonce'], params['chainId'])
|
||||||
|
logg.debug('mocket {}'.format(Mocket.tx))
|
||||||
|
return len(v)
|
||||||
|
|
||||||
|
|
||||||
|
def recv(self, c):
|
||||||
|
if Mocket.req_id != None:
|
||||||
|
|
||||||
|
o = None
|
||||||
|
if Mocket.error:
|
||||||
|
o = jsonrpc_error(Mocket.req_id)
|
||||||
|
else:
|
||||||
|
tx = Mocket.tx
|
||||||
|
r = Mocket.signer.sign_transaction_to_rlp(tx)
|
||||||
|
#mock_sig = os.urandom(64)
|
||||||
|
#tx.r = mock_sig[:32]
|
||||||
|
#tx.s = mock_sig[32:]
|
||||||
|
#r = add_0x(tx.rlp_serialize().hex())
|
||||||
|
Mocket.tx = None
|
||||||
|
o = jsonrpc_response(Mocket.req_id, add_0x(r.hex()))
|
||||||
|
Mocket.req_id = None
|
||||||
|
return json.dumps(o).encode('utf-8')
|
||||||
|
|
||||||
|
return b''
|
||||||
|
|
||||||
|
|
||||||
|
class TestSign(TestBase):
|
||||||
|
|
||||||
|
|
||||||
|
def setUp(self):
|
||||||
|
super(TestSign, self).__init__()
|
||||||
|
self.chain_spec = ChainSpec('evm', 'foo', 42)
|
||||||
|
|
||||||
|
|
||||||
|
logg.debug('alice {}'.format(alice))
|
||||||
|
logg.debug('bob {}'.format(bob))
|
||||||
|
|
||||||
|
self.signer = ReferenceSigner(keystore)
|
||||||
|
|
||||||
|
Mocket.signer = self.signer
|
||||||
|
|
||||||
|
|
||||||
|
def test_sign_build(self):
|
||||||
|
with unittest.mock.patch('chainlib.connection.socket.socket', Mocket) as m:
|
||||||
|
rpc = EthUnixSignerConnection('foo', chain_spec=self.chain_spec)
|
||||||
|
f = TxFactory(self.chain_spec, signer=rpc)
|
||||||
|
tx = f.template(alice, bob, use_nonce=True)
|
||||||
|
tx = f.build(tx)
|
||||||
|
logg.debug('tx result {}'.format(tx))
|
||||||
|
|
||||||
|
|
||||||
|
def test_sign_rpc(self):
|
||||||
|
with unittest.mock.patch('chainlib.connection.socket.socket', Mocket) as m:
|
||||||
|
rpc = EthUnixSignerConnection('foo')
|
||||||
|
f = TxFactory(self.chain_spec, signer=rpc)
|
||||||
|
tx = f.template(alice, bob, use_nonce=True)
|
||||||
|
tx_o = sign_transaction(tx)
|
||||||
|
rpc.do(tx_o)
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
unittest.main()
|
30
tests/test_tx.py
Normal file
30
tests/test_tx.py
Normal file
@ -0,0 +1,30 @@
|
|||||||
|
# standard imports
|
||||||
|
import unittest
|
||||||
|
|
||||||
|
# local imports
|
||||||
|
from chainlib.eth.unittest.ethtester import EthTesterCase
|
||||||
|
from chainlib.eth.nonce import RPCNonceOracle
|
||||||
|
from chainlib.eth.gas import (
|
||||||
|
RPCGasOracle,
|
||||||
|
Gas,
|
||||||
|
)
|
||||||
|
from chainlib.eth.tx import (
|
||||||
|
unpack,
|
||||||
|
TxFormat,
|
||||||
|
)
|
||||||
|
from hexathon import strip_0x
|
||||||
|
|
||||||
|
class TxTestCase(EthTesterCase):
|
||||||
|
|
||||||
|
def test_tx_reciprocal(self):
|
||||||
|
nonce_oracle = RPCNonceOracle(self.accounts[0], self.rpc)
|
||||||
|
gas_oracle = RPCGasOracle(self.rpc)
|
||||||
|
c = Gas(signer=self.signer, nonce_oracle=nonce_oracle, gas_oracle=gas_oracle, chain_spec=self.chain_spec)
|
||||||
|
(tx_hash_hex, o) = c.create(self.accounts[0], self.accounts[1], 1024, tx_format=TxFormat.RLP_SIGNED)
|
||||||
|
tx = unpack(bytes.fromhex(strip_0x(o)), self.chain_spec)
|
||||||
|
self.assertEqual(tx['from'], self.accounts[0])
|
||||||
|
self.assertEqual(tx['to'], self.accounts[1])
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
unittest.main()
|
Loading…
Reference in New Issue
Block a user