commit 8be9b595565bd8d1638390221391fbc710168da4
parent 1214434f4b747ecf5d22b0b35bb2cef3c6841ac9
Author: nolash <dev@holbrook.no>
Date: Tue, 9 Feb 2021 12:12:37 +0100
WIP add erc20 transfer executable
Diffstat:
13 files changed, 261 insertions(+), 92 deletions(-)
diff --git a/MANIFEST.in b/MANIFEST.in
@@ -0,0 +1 @@
+include requirements.txt
diff --git a/cic_tools/eth/connection.py b/cic_tools/eth/connection.py
@@ -6,7 +6,7 @@ from urllib.request import (
)
from .error import DefaultErrorParser
-from .method import jsonrpc_result
+from .rpc import jsonrpc_result
error_parser = DefaultErrorParser()
logg = logging.getLogger(__name__)
diff --git a/cic_tools/eth/constant.py b/cic_tools/eth/constant.py
@@ -1,3 +1,4 @@
ZERO_ADDRESS = '0x{:040x}'.format(0)
ZERO_CONTENT = '0x{:064x}'.format(0)
MINIMUM_FEE_UNITS = 21000
+MINIMUM_FEE_PRICE = 1000000000
diff --git a/cic_tools/eth/erc20.py b/cic_tools/eth/erc20.py
@@ -0,0 +1,69 @@
+# third-party imports
+import sha3
+from hexathon import add_0x
+from eth_abi import encode_single
+
+# local imports
+from .hash import keccak256_string_to_hex
+from .constant import ZERO_ADDRESS
+from .rpc import jsonrpc_template
+from .tx import TxFactory
+
+
+# TODO: move to cic-contracts
+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 build(self, tx):
+ txe = EIP155Transaction(tx, tx['nonce'], tx['chainId'])
+ self.signer.signTransaction(txe)
+ tx_raw = txe.rlp_serialize()
+ tx_raw_hex = add_0x(tx_raw.hex())
+ tx_hash_hex = add_0x(keccak256_hex_to_hex(tx_raw_hex))
+
+ o = jsonrpc_template()
+ o['method'] = 'eth_sendRawTransaction'
+ o['params'].append(tx_raw_hex)
+
+ return (tx_hash_hex, o)
+
+
+ def erc20_balance(self, contract_address, address, sender_address=ZERO_ADDRESS):
+ o = jsonrpc_template()
+ o['method'] = 'eth_call'
+ data = erc20_balance_signature
+ data += encode_single('address', address).hex()
+ data = add_0x(data)
+ 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 erc20_decimals(self, contract_address, sender_address=ZERO_ADDRESS):
+ o = jsonrpc_template()
+ o['method'] = 'eth_call'
+ data = add_0x(erc20_decimals_signature)
+ tx = self.template(sender_address, contract_address)
+ tx = self.set_code(tx, data, update_fee=False)
+ o['params'].append(self.normalize(tx))
+ o['params'].append('latest')
+ return o
+
+
+ def erc20_transfer(self, contract_address, sender_address, value):
+ o = jsonrpc_template()
+ o['method'] = 'eth_sendRawTransaction'
+ data = erc20_transfer_signature
+ data += encode_single('address', sender_address).hex()
+ data += encode_single('uint256', value).hex()
+ data = add_0x(data)
+ tx = self.template(sender_address, contract_address)
+ tx = self.set_code(tx, data)
+ o['params'].append(tx)
+ return self.build(o)
diff --git a/cic_tools/eth/gas.py b/cic_tools/eth/gas.py
@@ -6,9 +6,7 @@ from hexathon import (
from crypto_dev_signer.eth.transaction import EIP155Transaction
# local imports
-from cic_tools.eth.method import (
- jsonrpc_template,
- )
+from cic_tools.eth.rpc import jsonrpc_template
from cic_tools.eth.tx import TxFactory
from cic_tools.eth.hash import keccak256_hex_to_hex
diff --git a/cic_tools/eth/method.py b/cic_tools/eth/method.py
@@ -1,61 +0,0 @@
-import sha3
-import uuid
-
-from hexathon import add_0x
-from eth_abi import encode_single
-
-from .hash import keccak256_string_to_hex
-from .constant import ZERO_ADDRESS
-
-
-# TODO: move to cic-contracts
-erc20_balance_signature = keccak256_string_to_hex('balanceOf(address)')[:8]
-erc20_decimals_signature = keccak256_string_to_hex('decimals()')[:8]
-
-
-def jsonrpc_template():
- return {
- 'jsonrpc': '2.0',
- 'id': str(uuid.uuid4()),
- 'method': None,
- 'params': [],
- }
-
-
-def erc20_balance(contract_address, address, sender_address=ZERO_ADDRESS):
- o = jsonrpc_template()
- o['method'] = 'eth_call'
- data = erc20_balance_signature
- data += encode_single('address', address).hex()
- data = add_0x(data)
- a = call(contract_address, data=data)
- o['params'].append(a)
- o['params'].append('latest')
- return o
-
-
-def erc20_decimals(contract_address, sender_address=ZERO_ADDRESS):
- o = jsonrpc_template()
- o['method'] = 'eth_call'
- arg = add_0x(erc20_decimals_signature)
- #o['params'].append(arg)
- a = call(contract_address, arg)
- o['params'].append(a)
- o['params'].append('latest')
- return o
-
-
-def call(contract_address, data, sender_address=ZERO_ADDRESS):
- return {
- 'from': sender_address,
- 'to': contract_address,
- 'data': data,
- }
-
-
-def jsonrpc_result(o, ep):
- if o.get('error') != None:
- raise ep.translate(o)
- return o['result']
-
-
diff --git a/cic_tools/eth/nonce.py b/cic_tools/eth/nonce.py
@@ -5,9 +5,7 @@ from hexathon import (
)
# local imports
-from cic_tools.eth.method import (
- jsonrpc_template,
- )
+from cic_tools.eth.rpc import jsonrpc_template
def nonce(address):
diff --git a/cic_tools/eth/rpc.py b/cic_tools/eth/rpc.py
@@ -0,0 +1,17 @@
+# 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']
diff --git a/cic_tools/eth/runnable/balance.py b/cic_tools/eth/runnable/balance.py
@@ -26,14 +26,14 @@ from eth_abi import encode_single
# local imports
from cic_tools.eth.address import to_checksum
-from cic_tools.eth.method import (
+from cic_tools.eth.rpc import (
jsonrpc_template,
- erc20_balance,
- erc20_decimals,
jsonrpc_result,
)
+from cic_tools.eth.erc20 import ERC20TxFactory
from cic_tools.eth.connection import HTTPConnection
-
+from cic_tools.eth.nonce import DefaultNonceOracle
+from cic_tools.eth.gas import DefaultGasOracle
logging.basicConfig(level=logging.WARNING)
logg = logging.getLogger()
@@ -55,22 +55,25 @@ if args.v:
logg.setLevel(logging.DEBUG)
conn = HTTPConnection(args.p)
+gas_oracle = DefaultGasOracle(conn)
+
def main():
account = to_checksum(args.account)
if not args.u and account != add_0x(args.account):
raise ValueError('invalid checksum address')
-
+
r = None
decimals = 18
if args.t != None:
+ g = ERC20TxFactory()
# determine decimals
- decimals_o = erc20_decimals(args.t)
+ decimals_o = g.erc20_decimals(args.t)
r = conn.do(decimals_o)
decimals = int(strip_0x(r), 16)
# get balance
- balance_o = erc20_balance(args.t, account)
+ balance_o = g.erc20_balance(args.t, account)
r = conn.do(balance_o)
else:
diff --git a/cic_tools/eth/runnable/gas.py b/cic_tools/eth/runnable/gas.py
@@ -19,7 +19,6 @@ import logging
# third-party imports
from crypto_dev_signer.eth.signer import ReferenceSigner as EIP155Signer
from crypto_dev_signer.keystore import DictKeystore
-from crypto_dev_signer.eth.helper import EthTxExecutor
from hexathon import (
add_0x,
strip_0x,
@@ -28,9 +27,7 @@ from hexathon import (
# local imports
from cic_tools.eth.address import to_checksum
from cic_tools.eth.connection import HTTPConnection
-from cic_tools.eth.method import (
- jsonrpc_template,
- )
+from cic_tools.eth.rpc import jsonrpc_template
from cic_tools.eth.nonce import DefaultNonceOracle
from cic_tools.eth.gas import (
DefaultGasOracle,
@@ -52,8 +49,8 @@ argparser.add_argument('-ww', action='store_true', help='Wait for every transact
argparser.add_argument('-i', '--chain-spec', dest='i', type=str, default='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('-v', action='store_true', help='Be verbose')
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('recipient', type=str, help='Ethereum address of recipient')
argparser.add_argument('amount', type=int, help='Amount of tokens to mint and gift')
@@ -101,10 +98,14 @@ def main():
logg.debug('sender {} balance before: {}'.format(signer_address, balance(signer_address)))
logg.debug('recipient {} balance before: {}'.format(recipient, balance(recipient)))
- g = GasTxFactory(signer, gas_oracle, nonce_oracle, chain_id=chain_id)
+ g = GasTxFactory(signer=signer, gas_oracle=gas_oracle, nonce_oracle=nonce_oracle, chain_id=chain_id)
(tx_hash_hex, o) = g.create(signer_address, recipient, value)
conn.do(o)
+ logg.debug('sender {} balance after: {}'.format(signer_address, balance(signer_address)))
+ logg.debug('recipient {} balance after: {}'.format(recipient, balance(recipient)))
+
+
print(tx_hash_hex)
diff --git a/cic_tools/eth/runnable/transfer.py b/cic_tools/eth/runnable/transfer.py
@@ -0,0 +1,113 @@
+#!python3
+
+"""Token transfer script
+
+.. moduleauthor:: Louis Holbrook <dev@holbrook.no>
+.. pgp:: 0826EDA1702D1E87C6E2875121D2E7BB88C2A746
+
+"""
+
+# SPDX-License-Identifier: GPL-3.0-or-later
+
+# standard imports
+import os
+import json
+import argparse
+import logging
+
+# third-party imports
+from crypto_dev_signer.eth.signer import ReferenceSigner as EIP155Signer
+from crypto_dev_signer.keystore import DictKeystore
+from crypto_dev_signer.eth.helper import EthTxExecutor
+from hexathon import (
+ add_0x,
+ strip_0x,
+ )
+
+# local imports
+from cic_tools.eth.address import to_checksum
+from cic_tools.eth.connection import HTTPConnection
+from cic_tools.eth.rpc import jsonrpc_template
+from cic_tools.eth.nonce import DefaultNonceOracle
+from cic_tools.eth.gas import DefaultGasOracle
+from cic_tools.eth.erc20 import ERC20TxFactory
+
+
+logging.basicConfig(level=logging.WARNING)
+logg = logging.getLogger()
+
+logging.getLogger('web3').setLevel(logging.WARNING)
+logging.getLogger('urllib3').setLevel(logging.WARNING)
+
+default_abi_dir = '/usr/local/share/cic/solidity/abi'
+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('-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('-i', '--chain-spec', dest='i', type=str, default='Ethereum:1', help='Chain specification string')
+argparser.add_argument('--token-address', required='True', dest='t', 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('--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('-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('recipient', type=str, help='Recipient account address')
+argparser.add_argument('amount', type=int, help='Amount of tokens to mint and gift')
+args = argparser.parse_args()
+
+
+if args.vv:
+ logg.setLevel(logging.DEBUG)
+elif args.v:
+ logg.setLevel(logging.INFO)
+
+block_last = args.w
+block_all = args.ww
+
+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)
+ logg.debug('now have key for signer address {}'.format(signer_address))
+signer = EIP155Signer(keystore)
+
+conn = HTTPConnection(args.p)
+nonce_oracle = DefaultNonceOracle(signer_address, conn)
+gas_oracle = DefaultGasOracle(conn)
+
+chain_pair = args.i.split(':')
+chain_id = int(chain_pair[1])
+#g = ERC20TxFactory(signer=signer, gas_oracle=gas_oracle, nonce_oracle=nonce_oracle, chain_id=chain_id)
+g = ERC20TxFactory()
+
+def balance(token_address, address):
+ o = g.erc20_balance(token_address, address)
+ r = conn.do(o)
+ hx = strip_0x(r)
+ return int(hx, 16)
+
+
+def main():
+ recipient = args.recipient
+ if not args.u and recipient != add_0x(args.recipient):
+ raise ValueError('invalid checksum address')
+
+ value = args.amount
+
+ logg.debug('sender {} balance before: {}'.format(signer_address, balance(args.t, signer_address)))
+ logg.debug('recipient {} balance before: {}'.format(recipient, balance(args.t, recipient)))
+
+ logg.debug('sender {} balance after: {}'.format(signer_address, balance(args.t, signer_address)))
+ logg.debug('recipient {} balance after: {}'.format(recipient, balance(args.t, recipient)))
+
+
+ if block_last:
+ helper.wait_for()
+
+ print(tx_hash)
+
+
+if __name__ == '__main__':
+ main()
diff --git a/cic_tools/eth/tx.py b/cic_tools/eth/tx.py
@@ -7,10 +7,14 @@ from eth_keys import KeyAPI
from eth_keys.backends import NativeECCBackend
from rlp import decode as rlp_decode
from rlp import encode as rlp_encode
+from crypto_dev_signer.eth.transaction import EIP155Transaction
# local imports
from .address import to_checksum
-from .constant import MINIMUM_FEE_UNITS
+from .constant import (
+ MINIMUM_FEE_UNITS,
+ MINIMUM_FEE_PRICE,
+ )
logg = logging.getLogger(__name__)
@@ -84,7 +88,7 @@ def unpack_signed(tx_raw_bytes, chain_id=1):
class TxFactory:
- def __init__(self, signer, gas_oracle, nonce_oracle, chain_id=1):
+ def __init__(self, signer=None, gas_oracle=None, nonce_oracle=None, chain_id=1):
self.gas_oracle = gas_oracle
self.nonce_oracle = nonce_oracle
self.chain_id = chain_id
@@ -92,9 +96,13 @@ class TxFactory:
def template(self, sender, recipient):
- gas_price = self.gas_oracle.get()
+ gas_price = MINIMUM_FEE_PRICE
+ if self.gas_oracle != None:
+ gas_price = self.gas_oracle.get()
logg.debug('using gas price {}'.format(gas_price))
- nonce = self.nonce_oracle.next()
+ nonce = 0
+ if self.nonce_oracle != None:
+ nonce = self.nonce_oracle.next()
logg.debug('using nonce {} for address {}'.format(nonce, sender))
return {
'from': sender,
@@ -106,3 +114,24 @@ class TxFactory:
'gas': MINIMUM_FEE_UNITS,
'chainId': self.chain_id,
}
+
+
+ def normalize(self, tx):
+ txe = EIP155Transaction(tx, tx['nonce'], tx['chainId'])
+ txes = txe.serialize()
+ print(txes)
+ return {
+ 'from': tx['from'],
+ 'to': txes['to'],
+ 'gasPrice': txes['gasPrice'],
+ 'gas': txes['gas'],
+ 'data': txes['data'],
+ }
+
+
+ def set_code(self, tx, data, update_fee=True):
+ tx['data'] = data
+ if update_fee:
+ logg.debug('using hardcoded gas limit of 8000000 until we have reliable vm executor')
+ tx['gas'] = 8000000
+ return tx
diff --git a/requirements.txt b/requirements.txt
@@ -1,13 +1,13 @@
cryptocurrency-cli-tools==0.0.4
-giftable-erc20-token==0.0.7b6
-eth-accounts-index==0.0.10a6
-erc20-single-shot-faucet==0.2.0a5
-erc20-approval-escrow==0.3.0a4
-cic-eth==0.10.0a21+build.e4161b3e
+giftable-erc20-token~=0.0.7b7
+eth-accounts-index~=0.0.10a7
+erc20-single-shot-faucet~=0.2.0a6
+erc20-approval-escrow~=0.3.0a5
+cic-eth~=0.10.0a25
vobject==0.9.6.1
faker==4.17.1
-eth-address-index==0.1.0a5
-crypto-dev-signer==0.4.13rc2
+eth-address-index~=0.1.0a6
+crypto-dev-signer~=0.4.13rc2
pysha3==1.0.2
hexathon==0.0.1a2
eth-abi==2.1.1