2021-06-02 16:34:55 +02:00
# standard imports
import csv
import os
import logging
import argparse
import uuid
import sys
import tempfile
import stat
2021-06-02 16:49:53 +02:00
import shutil
2021-06-02 16:34:55 +02:00
# external imports
import csv
import confini
from chainlib . eth . address import is_checksum_address
from chainlib . eth . nonce import RPCNonceOracle
from chainlib . eth . gas import OverrideGasOracle
from chainlib . eth . tx import TxFormat
from chainlib . chain import ChainSpec
from chainlib . eth . connection import EthHTTPConnection
2021-11-26 10:56:36 +01:00
from funga . eth . signer import EIP155Signer
from funga . eth . keystore . dict import DictKeystore
2021-06-02 16:34:55 +02:00
from eth_erc20 import ERC20
from cic_eth_registry . erc20 import ERC20Token
logging . basicConfig ( level = logging . WARNING )
logg = logging . getLogger ( )
required_fields = [
' to ' ,
' token ' ,
' value ' ,
]
2021-11-26 10:56:36 +01:00
config_dir = os . environ . get ( ' CONFINI_DIR ' , ' ./config ' )
2021-06-02 16:34:55 +02:00
argparser = argparse . ArgumentParser ( ' chainqueue transaction submission and trigger server ' )
2021-11-26 10:56:36 +01:00
argparser . add_argument ( ' -c ' , ' --config ' , dest = ' c ' , type = str , help = ' configuration directory ' )
2021-06-02 16:34:55 +02:00
argparser . add_argument ( ' -p ' , type = str , help = ' rpc endpoint ' )
argparser . add_argument ( ' -i ' , type = str , help = ' chain spec ' )
argparser . add_argument ( ' --session-id ' , dest = ' session_id ' , type = str , default = str ( uuid . uuid4 ( ) ) , help = ' session id to use for session ' )
2021-11-26 12:55:20 +01:00
argparser . add_argument ( ' --fee-limit ' , dest = ' fee_limit ' , type = int , default = 8000000 , help = ' override gas limit ' )
2021-06-02 16:34:55 +02:00
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 ( ' -v ' , action = ' store_true ' , help = ' be verbose ' )
argparser . add_argument ( ' -vv ' , action = ' store_true ' , help = ' be very verbose ' )
argparser . add_argument ( ' -y ' , ' --key-file ' , dest = ' y ' , type = str , help = ' Ethereum keystore file to use for signing ' )
argparser . add_argument ( ' -o ' , ' --output ' , type = str , help = ' output directory ' )
argparser . add_argument ( ' input_data ' , type = str , help = ' input file ' )
args = argparser . parse_args ( sys . argv [ 1 : ] )
if args . vv :
logg . setLevel ( logging . DEBUG )
elif args . v :
logg . setLevel ( logging . INFO )
2021-11-26 10:56:36 +01:00
config = confini . Config ( config_dir , override_dirs = args . c )
2021-06-02 16:34:55 +02:00
config . process ( )
args_override = {
' SESSION_CHAIN_SPEC ' : getattr ( args , ' i ' ) ,
' RPC_ENDPOINT ' : getattr ( args , ' p ' ) ,
}
config . dict_override ( args_override , ' cli args ' )
config . add ( getattr ( args , ' input_data ' ) , ' _INPUT_FILE ' , True )
config . add ( getattr ( args , ' session_id ' ) , ' _SESSION_ID ' , True )
2021-11-26 12:55:20 +01:00
config . add ( getattr ( args , ' fee_limit ' ) , ' _FEE_LIMIT ' , True )
2021-06-02 16:34:55 +02:00
output_dir = getattr ( args , ' output ' )
if not output_dir :
output_dir = os . path . join ( ' . ' , ' output ' , config . get ( ' _SESSION_ID ' ) )
config . add ( output_dir , ' _OUTPUT_DIR ' , True )
logg . debug ( ' config loaded: \n {} ' . format ( config ) )
logg . info ( ' using session id {} ' . format ( config . get ( ' _SESSION_ID ' ) ) )
passphrase_env = ' ETH_PASSPHRASE '
if args . env_prefix != None :
passphrase_env = args . env_prefix + ' _ ' + passphrase_env
passphrase = os . environ . get ( passphrase_env )
if passphrase == None :
logg . warning ( ' no passphrase given ' )
passphrase = ' '
signer_address = None
keystore = DictKeystore ( )
if args . y == None :
sys . stderr . write ( ' keyfile missing \n ' )
sys . exit ( 1 )
logg . debug ( ' loading keystore file {} ' . format ( args . y ) )
signer_address = keystore . import_keystore_file ( args . y , password = passphrase )
logg . debug ( ' now have key for signer address {} ' . format ( signer_address ) )
signer = EIP155Signer ( keystore )
rpc = EthHTTPConnection ( config . get ( ' RPC_ENDPOINT ' ) )
nonce_oracle = RPCNonceOracle ( signer_address , conn = rpc )
2021-11-26 12:55:20 +01:00
gas_oracle = OverrideGasOracle ( limit = config . get ( ' _FEE_LIMIT ' ) , conn = rpc )
2021-06-02 16:34:55 +02:00
chain_spec = ChainSpec . from_chain_str ( config . get ( ' SESSION_CHAIN_SPEC ' ) )
try :
2021-06-02 16:49:53 +02:00
os . stat ( config . get ( ' _OUTPUT_DIR ' ) )
2021-06-02 16:34:55 +02:00
sys . stderr . write ( ' output directory {} already exists \n ' . format ( config . get ( ' _OUTPUT_DIR ' ) ) )
sys . exit ( 1 )
2021-06-02 16:49:53 +02:00
except FileNotFoundError :
pass
2021-06-02 16:34:55 +02:00
class InvalidData ( Exception ) :
pass
token_cache = { }
erc20 = ERC20 ( chain_spec , signer = signer , gas_oracle = gas_oracle , nonce_oracle = nonce_oracle )
def validate ( row ) :
if len ( row ) != 3 :
raise InvalidData ( ' row length {} found, need {} ' . format ( len ( row ) , 3 ) )
if not is_checksum_address ( row [ 0 ] ) :
raise InvalidData ( ' invalid recipient address {} ' . format ( row [ 0 ] ) )
if not is_checksum_address ( row [ 1 ] ) :
raise InvalidData ( ' invalid token address {} ' . format ( row [ 1 ] ) )
if token_cache . get ( row [ 1 ] ) == None :
token_cache [ row [ 1 ] ] = ERC20Token ( chain_spec , rpc , row [ 1 ] )
try :
value = float ( row [ 2 ] )
except ValueError :
raise InvalidData ( ' invalid token address {} ' . format ( row [ 1 ] ) )
return token_cache [ row [ 1 ] ]
def main ( ) :
tmp_out = tempfile . mkdtemp ( )
2021-06-02 16:49:53 +02:00
logg . debug ( ' using tmp dir {} ' . format ( tmp_out ) )
2021-06-02 16:34:55 +02:00
f = open ( config . get ( ' _INPUT_FILE ' ) , ' r ' )
cr = csv . reader ( f )
i = 1
for row in cr :
token_symbol = None
try :
token = validate ( row )
except InvalidData as e :
sys . stderr . write ( str ( e ) + ' in line {} \n ' . format ( i ) )
2021-06-02 16:49:53 +02:00
shutil . rmtree ( tmp_out )
2021-06-02 16:34:55 +02:00
sys . exit ( 1 )
multiplier = 10 * * token . decimals
value = int ( multiplier * float ( row [ 2 ] ) )
( tx_hash_hex , o ) = erc20 . transfer ( row [ 1 ] , signer_address , row [ 0 ] , value , tx_format = TxFormat . RLP_SIGNED )
fname = ' {} _ {} ' . format ( i , row [ 0 ] )
2021-06-02 16:49:53 +02:00
fpath = os . path . join ( tmp_out , fname )
2021-06-02 16:34:55 +02:00
f = open ( fpath , ' x ' )
f . write ( o )
f . close ( )
logg . info ( ' tx {} : {} ( {} * 10^ {} ) {} {} -> {} ' . format ( tx_hash_hex , value , row [ 2 ] , token . decimals , token . symbol , signer_address , row [ 0 ] ) )
i + = 1
f . close ( )
2021-06-02 16:49:53 +02:00
shutil . copytree ( tmp_out , config . get ( ' _OUTPUT_DIR ' ) )
logg . debug ( ' files moved from tmp dir {} to {} ' . format ( tmp_out , config . get ( ' _OUTPUT_DIR ' ) ) )
shutil . rmtree ( tmp_out )
2021-06-02 16:34:55 +02:00
if __name__ == ' __main__ ' :
main ( )