ERC20 token support (create/deploy, TX create/sign/send)
This feature is EXPERIMENTAL. Until v0.9.9 is released, mainnet use is
strictly at your own risk!
To test on dev chain, run 'test/test.py -e ethdev'
To test on Kovan, add '--testnet=1' option to all commands below
Transaction example:
Generate some ETH addresses with your default wallet:
$ mmgen-addrgen --coin=eth 1-5
Create an EOS token tracking wallet and import the addresses into it:
$ mmgen-addrimport --coin=eth --token=86fa049857e0209aa7d9e616f7eb3b3b78ecfdb0 ABCDABCD-ETH[1-5].addrs
Send 10+ EOS from an exchange or another wallet to address ABCDABCD:E:1
Create a TX sending 10 EOS to address aabbccdd..., with change to ABCDABCD:E:2:
$ mmgen-txcreate --coin=eth --token=eos aabbccddaabbccddaabbccddaabbccddaabbccdd,10 ABCDABCD:E:2
On your offline machine, sign the TX:
$ mmgen-txsign --coin=eth --token=eos ABC123-EOS[10,50000].rawtx
On your online machine, send the TX:
$ mmgen-txsend --coin=eth --token=eos ABC123-EOS[10,50000].sigtx
View your EOS tracking wallet:
$ mmgen-tool --coin=eth --token=eos twview
Token creation/deployment example:
Install the Solidity compiler ('solc') on your system.
Create a token 'MFT' with default parameters, owned by ddeeff... (ABCDABCD:E:1):
$ scripts/create-token.py --symbol=MFT --name='My First Token' ddeeffddeeffddeeffddeeffddeeffddeeffddee
Deploy the token on the ETH blockchain:
$ mmgen-txdo --coin=eth --tx-gas=200000 --contract-data=SafeMath.bin
$ mmgen-txdo --coin=eth --tx-gas=250000 --contract-data=Owned.bin
$ mmgen-txdo --coin=eth --tx-gas=1100000 --contract-data=Token.bin
...
Token address: abcd1234abcd1234abcd1234abcd1234abcd1234
Create an MFT token tracking wallet and import your ETH addresses into it:
$ mmgen-addrimport --coin=eth --token=abcd1234abcd1234abcd1234abcd1234abcd1234 ABCDABCD-ETH[1-5].addrs
View your MFT tracking wallet:
$ mmgen-tool --coin=eth --token=mft twview
This commit is contained in:
parent
e978b759d8
commit
881d55993f
18 changed files with 718 additions and 4 deletions
|
|
@ -11,6 +11,7 @@ include test/ref/monero/*
|
|||
|
||||
include scripts/bitcoind-walletunlock.py
|
||||
include scripts/compute-file-chksum.py
|
||||
include scripts/create-token.py
|
||||
include scripts/deinstall.sh
|
||||
include scripts/tx-old2new.py
|
||||
include scripts/test-release.sh
|
||||
|
|
|
|||
134
mmgen/altcoins/eth/contract.py
Executable file
134
mmgen/altcoins/eth/contract.py
Executable file
|
|
@ -0,0 +1,134 @@
|
|||
#!/usr/bin/env python
|
||||
# -*- coding: UTF-8 -*-
|
||||
#
|
||||
# mmgen = Multi-Mode GENerator, command-line Bitcoin cold storage solution
|
||||
# Copyright (C)2013-2018 The MMGen Project <mmgen@tuta.io>
|
||||
#
|
||||
# This program is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
# the Free Software Foundation, either version 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
"""
|
||||
altcoins.eth.contract: Ethereum contract and token classes for the MMGen suite
|
||||
"""
|
||||
|
||||
from sha3 import keccak_256
|
||||
from decimal import Decimal
|
||||
from ethereum.transactions import Transaction
|
||||
import rlp
|
||||
|
||||
from mmgen.globalvars import g
|
||||
from mmgen.common import *
|
||||
from mmgen.obj import MMGenObject,TokenAddr,CoinTxID,ETHAmt
|
||||
from mmgen.util import msg,msg_r,pmsg,pdie
|
||||
|
||||
def parse_abi(s):
|
||||
return [s[:8]] + [s[8+x*64:8+(x+1)*64] for x in range(len(s[8:])/64)]
|
||||
|
||||
def create_method_id(sig): return keccak_256(sig).hexdigest()[:8]
|
||||
|
||||
class Token(MMGenObject): # ERC20
|
||||
|
||||
# Test that token is in the blockchain by calling constructor w/o decimals arg
|
||||
def __init__(self,addr,decimals=None):
|
||||
self.addr = TokenAddr(addr)
|
||||
if decimals is None:
|
||||
ret_hex = self.do_call('decimals()')
|
||||
try: decimals = int(ret_hex,16)
|
||||
except: raise TokenNotInBlockchain,"token '{}' not in blockchain".format(addr)
|
||||
self.base_unit = Decimal(10) ** -decimals
|
||||
|
||||
def transferdata2amt(self,data): # online
|
||||
return ETHAmt(int(parse_abi(data)[-1],16) * self.base_unit)
|
||||
|
||||
def do_call(self,method_sig,method_args='',toUnit=False):
|
||||
data = create_method_id(method_sig) + method_args
|
||||
if g.debug:
|
||||
msg('{}: {}'.format(method_sig,'\n '.join(parse_abi(data))))
|
||||
ret = g.rpch.eth_call({ 'to': '0x'+self.addr, 'data': '0x'+data })
|
||||
return int(ret,16) * self.base_unit if toUnit else ret
|
||||
|
||||
def balance(self,acct_addr):
|
||||
return self.do_call('balanceOf(address)',acct_addr.rjust(64,'0'),toUnit=True)
|
||||
|
||||
def strip(self,s):
|
||||
return ''.join(ch for ch in s if 32 <= ord(ch) <= 127).strip()
|
||||
|
||||
def total_supply(self): return self.do_call('totalSupply()',toUnit=True)
|
||||
def decimals(self): return int(self.do_call('decimals()'),16)
|
||||
def name(self): return self.strip(self.do_call('name()')[2:].decode('hex'))
|
||||
def symbol(self): return self.strip(self.do_call('symbol()')[2:].decode('hex'))
|
||||
|
||||
def info(self):
|
||||
fs = '{:15}{}\n' * 5
|
||||
return fs.format('token address:',self.addr,
|
||||
'token symbol:',self.symbol(),
|
||||
'token name:',self.name(),
|
||||
'decimals:',self.decimals(),
|
||||
'total supply:',self.total_supply())
|
||||
|
||||
def code(self):
|
||||
return g.rpch.eth_getCode('0x'+self.addr)[2:]
|
||||
|
||||
def transfer_from(self,from_addr,to_addr,amt,key,start_gas,gasPrice):
|
||||
raise NotImplementedError,'method not implemented'
|
||||
return self.transfer( from_addr,to_addr,amt,key,start_gas,gasPrice,
|
||||
method_sig='transferFrom(address,address,uint256)',
|
||||
from_addr2=from_addr)
|
||||
|
||||
def create_data(self,to_addr,amt,method_sig='transfer(address,uint256)',from_addr=None):
|
||||
from_arg = from_addr.rjust(64,'0') if from_addr else ''
|
||||
to_arg = to_addr.rjust(64,'0')
|
||||
amt_arg = '{:064x}'.format(int(amt/self.base_unit))
|
||||
return create_method_id(method_sig) + from_arg + to_arg + amt_arg
|
||||
|
||||
def txcreate( self,from_addr,to_addr,amt,start_gas,gasPrice,nonce=None,
|
||||
method_sig='transfer(address,uint256)',from_addr2=None):
|
||||
if nonce is None:
|
||||
nonce = int(g.rpch.parity_nextNonce('0x'+from_addr),16)
|
||||
data = self.create_data(to_addr,amt,method_sig=method_sig,from_addr=from_addr2)
|
||||
return {'to': self.addr.decode('hex'),
|
||||
'startgas': start_gas.toWei(),
|
||||
'gasprice': gasPrice.toWei(),
|
||||
'value': 0,
|
||||
'nonce': nonce,
|
||||
'data': data.decode('hex') }
|
||||
|
||||
def txsign(self,tx_in,key,from_addr,chain_id=None):
|
||||
tx = Transaction(**tx_in)
|
||||
if chain_id is None:
|
||||
chain_id = int(g.rpch.parity_chainId(),16)
|
||||
tx.sign(key,chain_id)
|
||||
hex_tx = rlp.encode(tx).encode('hex')
|
||||
coin_txid = CoinTxID(tx.hash.encode('hex'))
|
||||
if tx.sender.encode('hex') != from_addr:
|
||||
m = "Sender address '{}' does not match address of key '{}'!"
|
||||
die(3,m.format(from_addr,tx.sender.encode('hex')))
|
||||
if g.debug:
|
||||
msg('{}'.format('\n '.join(parse_abi(data))))
|
||||
pmsg(tx.to_dict())
|
||||
return hex_tx,coin_txid
|
||||
|
||||
def txsend(self,hex_tx):
|
||||
return g.rpch.eth_sendRawTransaction('0x'+hex_tx).replace('0x','',1)
|
||||
|
||||
def transfer( self,from_addr,to_addr,amt,key,start_gas,gasPrice,
|
||||
method_sig='transfer(address,uint256)',
|
||||
from_addr2=None,
|
||||
return_data=False):
|
||||
tx_in = self.txcreate( from_addr,to_addr,amt,
|
||||
start_gas,gasPrice,
|
||||
nonce=None,
|
||||
method_sig=method_sig,
|
||||
from_addr2=from_addr2)
|
||||
(hex_tx,coin_txid) = self.txsign(tx_in,key,from_addr)
|
||||
return self.txsend(hex_tx)
|
||||
|
|
@ -26,6 +26,7 @@ from mmgen.common import *
|
|||
from mmgen.obj import ETHAmt,TwMMGenID,TwComment,TwLabel
|
||||
from mmgen.tw import TrackingWallet,TwAddrList,TwUnspentOutputs
|
||||
from mmgen.addr import AddrData
|
||||
from mmgen.altcoins.eth.contract import Token
|
||||
|
||||
class EthereumTrackingWallet(TrackingWallet):
|
||||
|
||||
|
|
@ -140,6 +141,31 @@ class EthereumTrackingWallet(TrackingWallet):
|
|||
m = "Address '{}' not found in '{}' section of tracking wallet"
|
||||
return ('rpcfail',(None,2,m.format(coinaddr,self.data_root_desc())))
|
||||
|
||||
class EthereumTokenTrackingWallet(EthereumTrackingWallet):
|
||||
|
||||
def token_is_in_wallet(self,addr):
|
||||
return addr in self.data['tokens']
|
||||
|
||||
def data_root_desc(self):
|
||||
return 'token ' + Token(g.token).symbol()
|
||||
|
||||
@write_mode
|
||||
def add_token(self,token):
|
||||
msg("Adding token '{}' to tracking wallet.".format(token))
|
||||
self.data['tokens'][token] = {}
|
||||
|
||||
def data_root(self): # create the token data root if necessary
|
||||
if g.token not in self.data['tokens']:
|
||||
self.add_token(g.token)
|
||||
return self.data['tokens'][g.token]
|
||||
|
||||
def sym2addr(self,sym): # online
|
||||
for addr in self.data['tokens']:
|
||||
if Token(addr).symbol().upper() == sym.upper():
|
||||
return addr
|
||||
return None
|
||||
|
||||
# No unspent outputs with Ethereum, but naming must be consistent
|
||||
class EthereumTwUnspentOutputs(TwUnspentOutputs):
|
||||
|
||||
disp_type = 'eth'
|
||||
|
|
@ -168,6 +194,21 @@ Display options: show [D]ays, show [m]mgen addr, r[e]draw screen
|
|||
'confirmations': 0, # TODO
|
||||
}, TrackingWallet().sorted_list())
|
||||
|
||||
class EthereumTokenTwUnspentOutputs(EthereumTwUnspentOutputs):
|
||||
|
||||
disp_type = 'token'
|
||||
prompt_fs = 'Total to spend: {} {}\n\n'
|
||||
|
||||
def get_display_precision(self): return 10
|
||||
|
||||
def get_addr_bal(self,addr):
|
||||
return Token(g.token).balance(addr)
|
||||
|
||||
def get_unspent_data(self):
|
||||
super(type(self),self).get_unspent_data()
|
||||
for e in self.unspent:
|
||||
e.amt2 = ETHAmt(int(g.rpch.eth_getBalance('0x'+e.addr),16),'wei')
|
||||
|
||||
class EthereumTwAddrList(TwAddrList):
|
||||
|
||||
def __init__(self,usr_addr_list,minconf,showempty,showbtcaddrs,all_labels):
|
||||
|
|
@ -197,6 +238,11 @@ class EthereumTwAddrList(TwAddrList):
|
|||
def get_addr_balance(self,addr):
|
||||
return ETHAmt(int(g.rpch.eth_getBalance('0x'+addr),16),'wei')
|
||||
|
||||
class EthereumTokenTwAddrList(EthereumTwAddrList):
|
||||
|
||||
def get_addr_balance(self,addr):
|
||||
return self.token.balance(addr)
|
||||
|
||||
from mmgen.tw import TwGetBalance
|
||||
class EthereumTwGetBalance(TwGetBalance):
|
||||
|
||||
|
|
@ -221,6 +267,11 @@ class EthereumTwGetBalance(TwGetBalance):
|
|||
def get_addr_balance(self,addr):
|
||||
return ETHAmt(int(g.rpch.eth_getBalance('0x'+addr),16),'wei')
|
||||
|
||||
class EthereumTokenTwGetBalance(EthereumTwGetBalance):
|
||||
|
||||
def get_addr_balance(self,addr):
|
||||
return Token(g.token).balance(addr)
|
||||
|
||||
class EthereumAddrData(AddrData):
|
||||
|
||||
@classmethod
|
||||
|
|
@ -229,3 +280,5 @@ class EthereumAddrData(AddrData):
|
|||
tw = TrackingWallet().mmid_ordered_dict()
|
||||
# emulate the output of RPC 'listaccounts' and 'getaddressesbyaccount'
|
||||
return [(mmid+' '+d['comment'],[d['addr']]) for mmid,d in tw.items()]
|
||||
|
||||
class EthereumTokenAddrData(EthereumAddrData): pass
|
||||
|
|
|
|||
|
|
@ -26,6 +26,8 @@ from mmgen.common import *
|
|||
from mmgen.obj import *
|
||||
|
||||
from mmgen.tx import MMGenTX,MMGenBumpTX,MMGenSplitTX,DeserializedTX,mmaddr2coinaddr
|
||||
from mmgen.altcoins.eth.contract import Token
|
||||
|
||||
class EthereumMMGenTX(MMGenTX):
|
||||
desc = 'Ethereum transaction'
|
||||
tx_gas = ETHAmt(21000,'wei') # an approximate number, used for fee estimation purposes
|
||||
|
|
@ -346,6 +348,88 @@ class EthereumMMGenTX(MMGenTX):
|
|||
self.add_blockcount()
|
||||
return True
|
||||
|
||||
class EthereumTokenMMGenTX(EthereumMMGenTX):
|
||||
desc = 'Ethereum token transaction'
|
||||
tx_gas = ETHAmt(52000,'wei')
|
||||
start_gas = ETHAmt(60000,'wei')
|
||||
fee_is_approximate = True
|
||||
|
||||
def check_sufficient_funds(self,inputs_sum,sel_unspent):
|
||||
eth_bal = ETHAmt(int(g.rpch.eth_getBalance('0x'+sel_unspent[0].addr),16),'wei')
|
||||
if eth_bal == 0: # we don't know the fee yet
|
||||
msg('This account has no ether to pay for the transaction fee!')
|
||||
return False
|
||||
if self.send_amt > inputs_sum:
|
||||
msg(self.msg_low_coin.format(self.send_amt-inputs_sum,g.dcoin))
|
||||
return False
|
||||
return True
|
||||
|
||||
def final_inputs_ok_msg(self,change_amt):
|
||||
m = u"Transaction leaves ≈{} {} and {} {} in the sender's account"
|
||||
tbal = g.proto.coin_amt(Token(g.token).balance(self.inputs[0].addr) - self.outputs[0].amt)
|
||||
chg = g.proto.coin_amt(change_amt)
|
||||
return m.format(chg.hl(),g.coin,tbal.hl(),g.dcoin)
|
||||
|
||||
def get_change_amt(self): # here we know the fee
|
||||
eth_bal = ETHAmt(int(g.rpch.eth_getBalance('0x'+self.inputs[0].addr),16),'wei')
|
||||
return Decimal(eth_bal) - self.fee
|
||||
|
||||
def set_g_token(self):
|
||||
g.dcoin = self.dcoin
|
||||
if is_hex_str(self.hex): return # for txsend we can leave g.token uninitialized
|
||||
d = json.loads(self.hex)
|
||||
if g.token.upper() == self.dcoin:
|
||||
g.token = d['token_addr']
|
||||
elif g.token != d['token_addr']:
|
||||
m1 = "'{p}': invalid --token parameter for {t} Ethereum token transaction file\n"
|
||||
m2 = "Please use '--token={t}'"
|
||||
die(1,(m1+m2).format(p=g.token,t=self.dcoin))
|
||||
|
||||
def make_txobj(self):
|
||||
super(EthereumTokenMMGenTX,self).make_txobj()
|
||||
t = Token(g.token)
|
||||
o = t.txcreate( self.inputs[0].addr,
|
||||
self.outputs[0].addr,
|
||||
self.outputs[0].amt,
|
||||
self.start_gas,
|
||||
self.usr_rel_fee or self.fee_abs2rel(self.fee,to_unit='eth'))
|
||||
self.txobj['token_addr'] = self.token_addr = t.addr
|
||||
self.txobj['decimals'] = t.decimals()
|
||||
|
||||
def check_txfile_hex_data(self):
|
||||
d = super(EthereumTokenMMGenTX,self).check_txfile_hex_data()
|
||||
o = self.txobj
|
||||
if self.check_sigs(): # online, from rlp
|
||||
rpc_init()
|
||||
o['token_addr'] = TokenAddr(o['to'])
|
||||
o['amt'] = Token(o['token_addr']).transferdata2amt(o['data'])
|
||||
else: # offline, from json
|
||||
o['token_addr'] = TokenAddr(d['token_addr'])
|
||||
o['decimals'] = Int(d['decimals'])
|
||||
t = Token(o['token_addr'],o['decimals'])
|
||||
self.data = o['data'] = t.create_data(o['to'],o['amt'])
|
||||
|
||||
def format_view_body(self,*args,**kwargs):
|
||||
return 'Token: {d} {c}\n{r}'.format(
|
||||
d=self.txobj['token_addr'].hl(),
|
||||
c=blue('(' + g.dcoin + ')'),
|
||||
r=super(EthereumTokenMMGenTX,self).format_view_body(*args,**kwargs))
|
||||
|
||||
def do_sign(self,d,wif,tx_num_str):
|
||||
d = self.txobj
|
||||
msg_r('Signing transaction{}...'.format(tx_num_str))
|
||||
try:
|
||||
t = Token(d['token_addr'],decimals=d['decimals'])
|
||||
tx_in = t.txcreate(d['from'],d['to'],d['amt'],self.start_gas,d['gasPrice'],nonce=d['nonce'])
|
||||
(self.hex,self.coin_txid) = t.txsign(tx_in,wif,d['from'],chain_id=d['chainId'])
|
||||
msg('OK')
|
||||
except Exception as e:
|
||||
m = "{!r}: transaction signing failed!"
|
||||
msg(m.format(e[0]))
|
||||
return False
|
||||
|
||||
return self.check_sigs()
|
||||
|
||||
class EthereumMMGenBumpTX(EthereumMMGenTX,MMGenBumpTX):
|
||||
|
||||
def choose_output(self): pass
|
||||
|
|
@ -356,5 +440,7 @@ class EthereumMMGenBumpTX(EthereumMMGenTX,MMGenBumpTX):
|
|||
def update_fee(self,foo,fee):
|
||||
self.fee = fee
|
||||
|
||||
class EthereumTokenMMGenBumpTX(EthereumTokenMMGenTX,EthereumMMGenBumpTX): pass
|
||||
|
||||
class EthereumMMGenSplitTX(MMGenSplitTX): pass
|
||||
class EthereumDeserializedTX(DeserializedTX): pass
|
||||
|
|
|
|||
|
|
@ -22,6 +22,7 @@ common.py: Common imports for all MMGen scripts
|
|||
"""
|
||||
|
||||
import sys,os
|
||||
from mmgen.exception import *
|
||||
from mmgen.globalvars import g
|
||||
import mmgen.opts as opts
|
||||
from mmgen.opts import opt
|
||||
|
|
|
|||
25
mmgen/exception.py
Executable file
25
mmgen/exception.py
Executable file
|
|
@ -0,0 +1,25 @@
|
|||
#!/usr/bin/env python
|
||||
# -*- coding: UTF-8 -*-
|
||||
#
|
||||
# mmgen = Multi-Mode GENerator, command-line Bitcoin cold storage solution
|
||||
# Copyright (C)2013-2018 The MMGen Project <mmgen@tuta.io>
|
||||
#
|
||||
# This program is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
# the Free Software Foundation, either version 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
"""
|
||||
mmgen.exception: Exception classes for the MMGen suite
|
||||
"""
|
||||
|
||||
class UnrecognizedTokenSymbol(Exception): pass
|
||||
class TokenNotInBlockchain(Exception): pass
|
||||
|
|
@ -35,6 +35,7 @@ opts_data = lambda: {
|
|||
-c, --comment-file=f Source the transaction's comment from file 'f'
|
||||
-C, --tx-confs= c Desired number of confirmations (default: {g.tx_confs})
|
||||
-d, --outdir= d Specify an alternate directory 'd' for output
|
||||
-D, --contract-data=D Path to hex-encoded contract data (ETH only)
|
||||
-f, --tx-fee= f Transaction fee, as a decimal {cu} amount or as
|
||||
{fu} (an integer followed by {fl}).
|
||||
See FEE SPECIFICATION below. If omitted, fee will be
|
||||
|
|
|
|||
|
|
@ -36,6 +36,7 @@ opts_data = lambda: {
|
|||
-c, --comment-file= f Source the transaction's comment from file 'f'
|
||||
-C, --tx-confs= c Desired number of confirmations (default: {g.tx_confs})
|
||||
-d, --outdir= d Specify an alternate directory 'd' for output
|
||||
-D, --contract-data= D Path to hex-encoded contract data (ETH only)
|
||||
-e, --echo-passphrase Print passphrase to screen when typing it
|
||||
-f, --tx-fee= f Transaction fee, as a decimal {cu} amount or as
|
||||
{fu} (an integer followed by {fl}).
|
||||
|
|
@ -108,3 +109,6 @@ tx.write_to_file(ask_write=False)
|
|||
tx.send(exit_on_fail=True)
|
||||
|
||||
tx.write_to_file(ask_overwrite=False,ask_write=False)
|
||||
|
||||
if hasattr(tx,'token_addr'):
|
||||
msg('Token address: {}'.format(tx.token_addr.hl()))
|
||||
|
|
|
|||
|
|
@ -66,3 +66,6 @@ if not opt.yes:
|
|||
|
||||
tx.send(exit_on_fail=True)
|
||||
tx.write_to_file(ask_overwrite=False,ask_write=False)
|
||||
|
||||
if hasattr(tx,'token_addr'):
|
||||
msg('Token address: {}'.format(tx.token_addr.hl()))
|
||||
|
|
|
|||
|
|
@ -444,6 +444,9 @@ class CoinAddr(str,Hilite,InitErrors,MMGenObject):
|
|||
else:
|
||||
return pfx_ok(vn[self.addr_fmt][1])
|
||||
|
||||
class TokenAddr(CoinAddr):
|
||||
color = 'blue'
|
||||
|
||||
class ViewKey(object):
|
||||
def __new__(cls,s,on_fail='die'):
|
||||
from mmgen.globalvars import g
|
||||
|
|
|
|||
|
|
@ -199,6 +199,7 @@ def init(opts_f,add_opts=[],opt_filter=None):
|
|||
common_opts_data = """
|
||||
--, --accept-defaults Accept defaults at all prompts
|
||||
--, --coin=c Choose coin unit. Default: {cu_dfl}. Options: {cu_all}
|
||||
--, --token=t Specify an ERC20 token by address or symbol
|
||||
--, --color=0|1 Disable or enable color output
|
||||
--, --force-256-color Force 256-color output when color is enabled
|
||||
--, --daemon-data-dir=d Specify coin daemon data directory location 'd'
|
||||
|
|
@ -344,7 +345,9 @@ def init(opts_f,add_opts=[],opt_filter=None):
|
|||
def opt_is_tx_fee(val,desc):
|
||||
from mmgen.tx import MMGenTX
|
||||
ret = MMGenTX().process_fee_spec(val,224,on_fail='return')
|
||||
if opt.contract_data or opt.tx_gas: ret = None # Non-standard startgas: disable fee checking
|
||||
# Non-standard startgas: disable fee checking
|
||||
if hasattr(opt,'contract_data') and opt.contract_data: ret = None
|
||||
if hasattr(opt,'tx_gas') and opt.tx_gas: ret = None
|
||||
if ret == False:
|
||||
msg("'{}': invalid {}\n(not a {} amount or {} specification)".format(
|
||||
val,desc,g.coin.upper(),MMGenTX().rel_fee_desc))
|
||||
|
|
@ -511,6 +514,17 @@ def check_opts(usr_opts): # Returns false if any check fails
|
|||
elif key == 'locktime':
|
||||
if not opt_is_int(val,desc): return False
|
||||
if not opt_compares(val,'>',0,desc): return False
|
||||
elif key == 'token':
|
||||
if not 'token' in g.proto.caps:
|
||||
msg("Coin '{}' does not support the --token option".format(g.coin))
|
||||
return False
|
||||
elif len(val) == 40 and is_hex_str(val):
|
||||
pass
|
||||
elif len(val) > 20 or not all(s.isalnum() for s in val):
|
||||
msg("u'{}: invalid parameter for --token option".format(val))
|
||||
return False
|
||||
elif key == 'contract_data':
|
||||
check_infile(val)
|
||||
else:
|
||||
if g.debug: Msg("check_opts(): No test for opt '{}'".format(key))
|
||||
|
||||
|
|
|
|||
|
|
@ -309,6 +309,7 @@ class EthereumProtocol(DummyWIF,BitcoinProtocolAddrgen):
|
|||
max_tx_fee = ETHAmt('0.005')
|
||||
chain_name = 'foundation'
|
||||
sign_mode = 'standalone'
|
||||
caps = ('token',)
|
||||
|
||||
@classmethod
|
||||
def verify_addr(cls,addr,hex_width,return_dict=False):
|
||||
|
|
|
|||
|
|
@ -186,7 +186,8 @@ watch-only wallet using '{}-addrimport' and then re-run this program.
|
|||
out = [self.hdr_fmt.format(' '.join(self.sort_info()),g.dcoin,self.total.hl())]
|
||||
if g.chain != 'mainnet': out += ['Chain: '+green(g.chain.upper())]
|
||||
fs = { 'btc': u' {n:%s} {t:%s} {v:2} {a} {A} {c:<}' % (col1_w,tx_w),
|
||||
'eth': u' {n:%s} {a} {A}' % col1_w }[self.disp_type]
|
||||
'eth': u' {n:%s} {a} {A}' % col1_w,
|
||||
'token': u' {n:%s} {a} {A} {A2}' % col1_w }[self.disp_type]
|
||||
out += [fs.format( n='Num',
|
||||
t='TXid'.ljust(tx_w - 5) + ' Vout',
|
||||
v='',
|
||||
|
|
@ -231,7 +232,8 @@ watch-only wallet using '{}-addrimport' and then re-run this program.
|
|||
mmid_w = max(len(('',i.twmmid)[i.twmmid.type=='mmgen']) for i in self.unspent) or 12 # DEADBEEF:S:1
|
||||
amt_w = g.proto.coin_amt.max_prec + 4
|
||||
fs = { 'btc': u' {n:4} {t:%s} {a} {m} {A:%s} {c:<8} {g:<6} {l}' % (self.txid_w+3,amt_w),
|
||||
'eth': u' {n:4} {a} {m} {A:%s} {c:<8} {g:<6} {l}' % amt_w
|
||||
'eth': u' {n:4} {a} {m} {A:%s} {c:<8} {g:<6} {l}' % amt_w,
|
||||
'token': u' {n:4} {a} {m} {A:%s} {A2:%s} {c:<8} {g:<6} {l}' % (amt_w,amt_w)
|
||||
}[self.disp_type]
|
||||
out = [fs.format( n='Num',
|
||||
t='Tx ID,Vout',
|
||||
|
|
|
|||
|
|
@ -1306,7 +1306,6 @@ Selected non-{pnm} inputs: {{}}""".strip().format(pnm=g.proj_name,pnl=g.proj_nam
|
|||
sel_nums = getattr(self,us_f)(tw.unspent)
|
||||
|
||||
msg('Selected output{}: {}'.format(suf(sel_nums,'s'),' '.join(map(str,sel_nums))))
|
||||
|
||||
sel_unspent = tw.MMGenTwOutputList([tw.unspent[i-1] for i in sel_nums])
|
||||
|
||||
inputs_sum = sum(s.amt for s in sel_unspent)
|
||||
|
|
|
|||
|
|
@ -25,6 +25,7 @@ from hashlib import sha256
|
|||
from binascii import hexlify,unhexlify
|
||||
from string import hexdigits
|
||||
from mmgen.color import *
|
||||
from mmgen.exception import *
|
||||
|
||||
def msg(s): sys.stderr.write(s.encode('utf8') + '\n')
|
||||
def msg_r(s): sys.stderr.write(s.encode('utf8'))
|
||||
|
|
@ -824,6 +825,23 @@ def get_coin_daemon_auth_cookie():
|
|||
|
||||
def rpc_init_parity():
|
||||
|
||||
def resolve_token_arg(token_arg):
|
||||
from mmgen.tw import TrackingWallet
|
||||
from mmgen.obj import CoinAddr
|
||||
from mmgen.altcoins.eth.contract import Token
|
||||
|
||||
try: addr = CoinAddr(token_arg,on_fail='raise')
|
||||
except: addr = TrackingWallet().sym2addr(token_arg)
|
||||
else: Token(addr) # test for presence in blockchain
|
||||
|
||||
if not addr:
|
||||
m = "'{}': unrecognized token symbol"
|
||||
raise UnrecognizedTokenSymbol,m.format(token_arg)
|
||||
|
||||
sym = Token(addr).symbol().upper()
|
||||
vmsg('ERC20 token resolved: {} ({})'.format(addr,sym))
|
||||
return addr,sym
|
||||
|
||||
from mmgen.rpc import EthereumRPCConnection
|
||||
g.rpch = EthereumRPCConnection(
|
||||
g.rpc_host or 'localhost',
|
||||
|
|
@ -832,6 +850,8 @@ def rpc_init_parity():
|
|||
if not g.daemon_version: # First call
|
||||
g.daemon_version = g.rpch.parity_versionInfo()['version'] # fail immediately if daemon is geth
|
||||
g.chain = g.rpch.parity_chain()
|
||||
if g.token:
|
||||
(g.token,g.dcoin) = resolve_token_arg(g.token)
|
||||
|
||||
return g.rpch
|
||||
|
||||
|
|
|
|||
186
scripts/create-token.py
Executable file
186
scripts/create-token.py
Executable file
|
|
@ -0,0 +1,186 @@
|
|||
#!/usr/bin/env python
|
||||
|
||||
import sys,os,json
|
||||
from subprocess import Popen,PIPE
|
||||
from mmgen.common import *
|
||||
from mmgen.obj import CoinAddr,is_coin_addr
|
||||
|
||||
decimals = 18
|
||||
supply = 10**26
|
||||
name = 'MMGen Token'
|
||||
symbol = 'MMT'
|
||||
|
||||
opts_data = lambda: {
|
||||
'desc': 'Create an ERC20 token contract',
|
||||
'usage':'[opts] <owner address>',
|
||||
'options': """
|
||||
-h, --help Print this help message
|
||||
-o, --outdir= d Specify output directory for *.bin files
|
||||
-d, --decimals=d Number of decimals for the token (default: {d})
|
||||
-n, --name=n Token name (default: {n})
|
||||
-t, --supply= t Total supply of the token (default: {t})
|
||||
-s, --symbol= s Token symbol (default: {s})
|
||||
-S, --stdout Output data in JSON format to stdout instead of files
|
||||
""".format(d=decimals,n=name,s=symbol,t=supply)
|
||||
}
|
||||
|
||||
g.coin = 'ETH'
|
||||
cmd_args = opts.init(opts_data)
|
||||
|
||||
if not len(cmd_args) == 1 or not is_coin_addr(cmd_args[0]):
|
||||
opts.usage()
|
||||
|
||||
owner_addr = '0x' + CoinAddr(cmd_args[0])
|
||||
|
||||
# ERC Token Standard #20 Interface
|
||||
# https://github.com/ethereum/EIPs/blob/master/EIPS/eip-20-token-standard.md
|
||||
code_in = """
|
||||
pragma solidity ^0.4.18;
|
||||
|
||||
contract SafeMath {
|
||||
function safeAdd(uint a, uint b) public pure returns (uint c) {
|
||||
c = a + b;
|
||||
require(c >= a);
|
||||
}
|
||||
function safeSub(uint a, uint b) public pure returns (uint c) {
|
||||
require(b <= a);
|
||||
c = a - b;
|
||||
}
|
||||
function safeMul(uint a, uint b) public pure returns (uint c) {
|
||||
c = a * b;
|
||||
require(a == 0 || c / a == b);
|
||||
}
|
||||
function safeDiv(uint a, uint b) public pure returns (uint c) {
|
||||
require(b > 0);
|
||||
c = a / b;
|
||||
}
|
||||
}
|
||||
|
||||
contract ERC20Interface {
|
||||
function totalSupply() public constant returns (uint);
|
||||
function balanceOf(address tokenOwner) public constant returns (uint balance);
|
||||
function allowance(address tokenOwner, address spender) public constant returns (uint remaining);
|
||||
function transfer(address to, uint tokens) public returns (bool success);
|
||||
function approve(address spender, uint tokens) public returns (bool success);
|
||||
function transferFrom(address from, address to, uint tokens) public returns (bool success);
|
||||
|
||||
event Transfer(address indexed from, address indexed to, uint tokens);
|
||||
event Approval(address indexed tokenOwner, address indexed spender, uint tokens);
|
||||
}
|
||||
|
||||
// Contract function to receive approval and execute function in one call
|
||||
contract ApproveAndCallFallBack {
|
||||
function receiveApproval(address from, uint256 tokens, address token, bytes data) public;
|
||||
}
|
||||
|
||||
contract Owned {
|
||||
address public owner;
|
||||
address public newOwner;
|
||||
|
||||
event OwnershipTransferred(address indexed _from, address indexed _to);
|
||||
|
||||
constructor() public {
|
||||
owner = msg.sender;
|
||||
}
|
||||
|
||||
modifier onlyOwner {
|
||||
require(msg.sender == owner);
|
||||
_;
|
||||
}
|
||||
|
||||
function transferOwnership(address _newOwner) public onlyOwner {
|
||||
newOwner = _newOwner;
|
||||
}
|
||||
function acceptOwnership() public {
|
||||
require(msg.sender == newOwner);
|
||||
emit OwnershipTransferred(owner, newOwner);
|
||||
owner = newOwner;
|
||||
newOwner = address(0);
|
||||
}
|
||||
}
|
||||
|
||||
// ----------------------------------------------------------------------------
|
||||
// ERC20 Token, with the addition of symbol, name and decimals and assisted
|
||||
// token transfers
|
||||
// ----------------------------------------------------------------------------
|
||||
contract Token is ERC20Interface, Owned, SafeMath {
|
||||
string public symbol;
|
||||
string public name;
|
||||
uint8 public decimals;
|
||||
uint public _totalSupply;
|
||||
|
||||
mapping(address => uint) balances;
|
||||
mapping(address => mapping(address => uint)) allowed;
|
||||
|
||||
constructor() public {
|
||||
symbol = "<SYMBOL>";
|
||||
name = "<NAME>";
|
||||
decimals = <DECIMALS>;
|
||||
_totalSupply = <SUPPLY>;
|
||||
balances[<OWNER_ADDR>] = _totalSupply;
|
||||
emit Transfer(address(0), <OWNER_ADDR>, _totalSupply);
|
||||
}
|
||||
function totalSupply() public constant returns (uint) {
|
||||
return _totalSupply - balances[address(0)];
|
||||
}
|
||||
function balanceOf(address tokenOwner) public constant returns (uint balance) {
|
||||
return balances[tokenOwner];
|
||||
}
|
||||
function transfer(address to, uint tokens) public returns (bool success) {
|
||||
balances[msg.sender] = safeSub(balances[msg.sender], tokens);
|
||||
balances[to] = safeAdd(balances[to], tokens);
|
||||
emit Transfer(msg.sender, to, tokens);
|
||||
return true;
|
||||
}
|
||||
function approve(address spender, uint tokens) public returns (bool success) {
|
||||
allowed[msg.sender][spender] = tokens;
|
||||
emit Approval(msg.sender, spender, tokens);
|
||||
return true;
|
||||
}
|
||||
function transferFrom(address from, address to, uint tokens) public returns (bool success) {
|
||||
balances[from] = safeSub(balances[from], tokens);
|
||||
allowed[from][msg.sender] = safeSub(allowed[from][msg.sender], tokens);
|
||||
balances[to] = safeAdd(balances[to], tokens);
|
||||
emit Transfer(from, to, tokens);
|
||||
return true;
|
||||
}
|
||||
function allowance(address tokenOwner, address spender) public constant returns (uint remaining) {
|
||||
return allowed[tokenOwner][spender];
|
||||
}
|
||||
function approveAndCall(address spender, uint tokens, bytes data) public returns (bool success) {
|
||||
allowed[msg.sender][spender] = tokens;
|
||||
emit Approval(msg.sender, spender, tokens);
|
||||
ApproveAndCallFallBack(spender).receiveApproval(msg.sender, tokens, this, data);
|
||||
return true;
|
||||
}
|
||||
// Don't accept ETH
|
||||
function () public payable {
|
||||
revert();
|
||||
}
|
||||
// Owner can transfer out any accidentally sent ERC20 tokens
|
||||
function transferAnyERC20Token(address tokenAddress, uint tokens) public onlyOwner returns (bool success) {
|
||||
return ERC20Interface(tokenAddress).transfer(owner, tokens);
|
||||
}
|
||||
}
|
||||
"""
|
||||
|
||||
def create_src(code):
|
||||
for k in ('decimals','supply','name','symbol','owner_addr'):
|
||||
if hasattr(opt,k) and getattr(opt,k): globals()[k] = getattr(opt,k)
|
||||
code = code.replace('<{}>'.format(k.upper()),str(globals()[k]))
|
||||
return code
|
||||
|
||||
def compile_code(code):
|
||||
cmd = ['solc','--optimize','--bin','--overwrite']
|
||||
if not opt.stdout: cmd += ['--output-dir', opt.outdir or '.']
|
||||
p = Popen(cmd,stdin=PIPE,stdout=PIPE,stderr=PIPE)
|
||||
res = p.communicate(code)
|
||||
o = res[0].replace('\r','').split('\n')
|
||||
dmsg(res[1])
|
||||
if opt.stdout:
|
||||
return dict((k,o[i+2]) for k in ('SafeMath','Owned','Token') for i in range(len(o)) if k in o[i])
|
||||
|
||||
src = create_src(code_in)
|
||||
out = compile_code(src)
|
||||
if opt.stdout:
|
||||
print(json.dumps(out))
|
||||
2
setup.py
2
setup.py
|
|
@ -115,6 +115,7 @@ setup(
|
|||
'mmgen.common',
|
||||
'mmgen.crypto',
|
||||
'mmgen.ed25519',
|
||||
'mmgen.exception',
|
||||
'mmgen.filename',
|
||||
'mmgen.globalvars',
|
||||
'mmgen.license',
|
||||
|
|
@ -137,6 +138,7 @@ setup(
|
|||
'mmgen.altcoins.__init__',
|
||||
|
||||
'mmgen.altcoins.eth.__init__',
|
||||
'mmgen.altcoins.eth.contract',
|
||||
'mmgen.altcoins.eth.obj',
|
||||
'mmgen.altcoins.eth.tx',
|
||||
'mmgen.altcoins.eth.tw',
|
||||
|
|
|
|||
179
test/test.py
179
test/test.py
|
|
@ -897,6 +897,44 @@ cmd_group['ethdev'] = (
|
|||
('ethdev_chk_label', 'the label'),
|
||||
('ethdev_remove_label', 'removing the label'),
|
||||
|
||||
('ethdev_token_compile1', 'compiling ERC20 token #1'),
|
||||
|
||||
('ethdev_token_deploy1a', 'deploying ERC20 token #1 (SafeMath)'),
|
||||
('ethdev_token_deploy1b', 'deploying ERC20 token #1 (Owned)'),
|
||||
('ethdev_token_deploy1c', 'deploying ERC20 token #1 (Token)'),
|
||||
|
||||
('ethdev_token_compile2', 'compiling ERC20 token #2'),
|
||||
|
||||
('ethdev_token_deploy2a', 'deploying ERC20 token #2 (SafeMath)'),
|
||||
('ethdev_token_deploy2b', 'deploying ERC20 token #2 (Owned)'),
|
||||
('ethdev_token_deploy2c', 'deploying ERC20 token #2 (Token)'),
|
||||
|
||||
('ethdev_contract_deploy', 'deploying contract (create,sign,send)'),
|
||||
|
||||
('ethdev_token_transfer_funds','transferring token funds from dev to user'),
|
||||
('ethdev_token_addrgen', 'generating token addresses'),
|
||||
('ethdev_token_addrimport', 'importing token addresses'),
|
||||
|
||||
('ethdev_token_txcreate1', 'creating a token transaction'),
|
||||
('ethdev_token_txsign1', 'signing the transaction'),
|
||||
('ethdev_token_txsend1', 'sending the transaction'),
|
||||
|
||||
('ethdev_token_txcreate2', 'creating a token transaction (to burn address)'),
|
||||
('ethdev_token_txbump', 'bumping the transaction fee'),
|
||||
|
||||
('ethdev_token_txsign2', 'signing the transaction'),
|
||||
('ethdev_token_txsend2', 'sending the transaction'),
|
||||
|
||||
('ethdev_del_dev_addr', "deleting the dev address"),
|
||||
|
||||
('ethdev_bal2', 'the ETH balance'),
|
||||
('ethdev_bal2_getbalance', 'the ETH balance (getbalance)'),
|
||||
|
||||
('ethdev_addrimport_token_burn_addr',"importing the token burn address"),
|
||||
|
||||
('ethdev_token_bal', 'the token balance'),
|
||||
('ethdev_token_bal_getbalance','the token balance (getbalance)'),
|
||||
|
||||
('ethdev_stop', 'stopping parity'),
|
||||
)
|
||||
|
||||
|
|
@ -3251,6 +3289,147 @@ class MMGenTestSuite(object):
|
|||
g.proto.rpc_port = 8549
|
||||
rpc_init()
|
||||
|
||||
def ethdev_token_compile(self,name,token_data={}):
|
||||
MMGenExpect(name,'',msg_only=True)
|
||||
cmd_args = ['--{}={}'.format(k,v) for k,v in token_data.items()]
|
||||
silence()
|
||||
imsg("Compiling solidity token contract '{}' with 'solc'".format(token_data['symbol']))
|
||||
cmd = ['scripts/create-token.py','--outdir='+cfg['tmpdir']] + cmd_args + [eth_addr]
|
||||
imsg("Executing: {}".format(' '.join(cmd)))
|
||||
subprocess.check_output(cmd)
|
||||
imsg("ERC20 token '{}' compiled".format(token_data['symbol']))
|
||||
end_silence()
|
||||
ok()
|
||||
|
||||
def ethdev_token_compile1(self,name):
|
||||
token_data = { 'name':'MMGen Token 1', 'symbol':'MM1', 'supply':10**26, 'decimals':18 }
|
||||
self.ethdev_token_compile(name,token_data)
|
||||
|
||||
def ethdev_token_compile2(self,name):
|
||||
token_data = { 'name':'MMGen Token 2', 'symbol':'MM2', 'supply':10**18, 'decimals':10 }
|
||||
self.ethdev_token_compile(name,token_data)
|
||||
|
||||
def ethdev_token_deploy(self,name,num,key,gas,mmgen_cmd='txdo'):
|
||||
self.init_ethdev_common()
|
||||
key_fn = get_tmpfile_fn(cfg,cfg['parity_keyfile'])
|
||||
fn = os.path.join(cfg['tmpdir'],key+'.bin')
|
||||
os.environ['MMGEN_BOGUS_SEND'] = ''
|
||||
args = ['-B','--tx-fee=8G','--tx-gas={}'.format(gas),'--contract-data='+fn,'--inputs='+eth_addr,'--yes']
|
||||
if mmgen_cmd == 'txdo': args += ['-k',key_fn]
|
||||
t = MMGenExpect(name,'mmgen-'+mmgen_cmd, eth_args + args)
|
||||
if mmgen_cmd == 'txcreate':
|
||||
t.written_to_file('Ethereum transaction')
|
||||
tx_fn = get_file_with_ext('[0,8000].rawtx',cfg['tmpdir'],no_dot=True)
|
||||
t = MMGenExpect(name,'mmgen-txsign', eth_args + ['--yes','-k',key_fn,tx_fn],no_msg=True)
|
||||
self.txsign_ui_common(t,name,ni=True,no_ok=True)
|
||||
tx_fn = tx_fn.replace('.rawtx','.sigtx')
|
||||
t = MMGenExpect(name,'mmgen-txsend', eth_args + [tx_fn],no_msg=True)
|
||||
|
||||
os.environ['MMGEN_BOGUS_SEND'] = '1'
|
||||
txid = self.txsend_ui_common(t,mmgen_cmd,quiet=True,bogus_send=False,no_ok=True)
|
||||
addr = t.expect_getend('Token address: ')
|
||||
from mmgen.altcoins.eth.tx import EthereumMMGenTX as etx
|
||||
assert etx.get_exec_status(txid) != 0,"Contract '{}:{}' failed to execute. Aborting".format(num,key)
|
||||
if key == 'Token':
|
||||
write_to_tmpfile(cfg,'token_addr{}'.format(num),addr+'\n')
|
||||
silence()
|
||||
imsg('\nToken #{} ({}) deployed!'.format(num,addr))
|
||||
end_silence()
|
||||
t.ok()
|
||||
|
||||
def ethdev_token_deploy1a(self,name): self.ethdev_token_deploy(name,num=1,key='SafeMath',gas=200000)
|
||||
def ethdev_token_deploy1b(self,name): self.ethdev_token_deploy(name,num=1,key='Owned',gas=250000)
|
||||
def ethdev_token_deploy1c(self,name): self.ethdev_token_deploy(name,num=1,key='Token',gas=1100000)
|
||||
def ethdev_token_deploy2a(self,name): self.ethdev_token_deploy(name,num=2,key='SafeMath',gas=200000)
|
||||
def ethdev_token_deploy2b(self,name): self.ethdev_token_deploy(name,num=2,key='Owned',gas=250000)
|
||||
def ethdev_token_deploy2c(self,name): self.ethdev_token_deploy(name,num=2,key='Token',gas=1100000)
|
||||
|
||||
def ethdev_contract_deploy(self,name): # test create,sign,send
|
||||
self.ethdev_token_deploy(name,num=2,key='SafeMath',gas=1100000,mmgen_cmd='txcreate')
|
||||
|
||||
def ethdev_token_transfer_funds(self,name):
|
||||
MMGenExpect(name,'',msg_only=True)
|
||||
sid = cfgs['8']['seed_id']
|
||||
cmd = lambda i: ['mmgen-tool','--coin=eth','gen_addr','{}:E:{}'.format(sid,i),'wallet='+dfl_words]
|
||||
silence()
|
||||
usr_addrs = [subprocess.check_output(cmd(i),stderr=sys.stderr).strip() for i in 11,21]
|
||||
self.init_ethdev_common()
|
||||
from mmgen.altcoins.eth.contract import Token
|
||||
from mmgen.altcoins.eth.tx import EthereumMMGenTX as etx
|
||||
for i in range(2):
|
||||
tk = Token(read_from_tmpfile(cfg,'token_addr{}'.format(i+1)).strip())
|
||||
imsg('\n'+tk.info())
|
||||
txid = tk.transfer(eth_addr,usr_addrs[i],1000,eth_key,
|
||||
start_gas=ETHAmt(60000,'wei'),gasPrice=ETHAmt(8,'Gwei'))
|
||||
assert etx.get_exec_status(txid) != 0,'Transfer of token funds failed. Aborting'
|
||||
imsg('dev token balance: {}'.format(tk.balance(eth_addr)))
|
||||
imsg('usr{} token balance: {}'.format(i+1,tk.balance(usr_addrs[i])))
|
||||
end_silence()
|
||||
ok()
|
||||
|
||||
def ethdev_token_addrgen(self,name):
|
||||
self.ethdev_addrgen(name,addrs='11-13')
|
||||
self.ethdev_addrgen(name,addrs='21-23')
|
||||
|
||||
def ethdev_token_addrimport(self,name):
|
||||
for n,r in ('1','11-13'),('2','21-23'):
|
||||
tk_addr = read_from_tmpfile(cfg,'token_addr'+n).strip()
|
||||
self.ethdev_addrimport(name,ext='['+r+'].addrs',expect='3/3',add_args=['--token='+tk_addr])
|
||||
|
||||
def ethdev_token_txcreate(self,name,args=[],token='',inputs='1'):
|
||||
t = MMGenExpect(name,'mmgen-txcreate', eth_args + ['--token='+token,'-B','--tx-fee=50G'] + args)
|
||||
self.txcreate_ui_common(t,name,menu=[],
|
||||
input_sels_prompt='to spend from',
|
||||
inputs=inputs,file_desc='Ethereum token transaction',
|
||||
add_comment=ref_tx_label_lat_cyr_gr)
|
||||
return t
|
||||
def ethdev_token_txsign(self,name,ext='',token=''):
|
||||
self.ethdev_txsign(name,ni=True,ext=ext,add_args=['--token='+token])
|
||||
def ethdev_token_txsend(self,name,ext='',token=''):
|
||||
self.ethdev_txsend(name,ext=ext,add_args=['--token=mm1'])
|
||||
|
||||
def ethdev_token_txcreate1(self,name):
|
||||
return self.ethdev_token_txcreate(name,args=['98831F3A:E:12,1.23456'],token='mm1')
|
||||
def ethdev_token_txsign1(self,name):
|
||||
self.ethdev_token_txsign(name,ext='1.23456,50000].rawtx',token='mm1')
|
||||
def ethdev_token_txsend1(self,name):
|
||||
self.ethdev_token_txsend(name,ext='1.23456,50000].sigtx',token='mm1')
|
||||
|
||||
def ethdev_token_txcreate2(self,name):
|
||||
return self.ethdev_token_txcreate(name,args=[eth_burn_addr+','+eth_amt2],token='mm1')
|
||||
|
||||
def ethdev_token_txbump(self,name):
|
||||
self.ethdev_txbump(name,ext=eth_amt2+',50000].rawtx',fee='56G',add_args=['--token=mm1'])
|
||||
|
||||
def ethdev_token_txsign2(self,name):
|
||||
self.ethdev_token_txsign(name,ext=eth_amt2+',50000].rawtx',token='mm1')
|
||||
def ethdev_token_txsend2(self,name):
|
||||
self.ethdev_token_txsend(name,ext=eth_amt2+',50000].sigtx',token='mm1')
|
||||
|
||||
def ethdev_del_dev_addr(self,name):
|
||||
t = MMGenExpect(name,'mmgen-tool', eth_args + ['remove_address',eth_addr])
|
||||
t.read() # TODO
|
||||
t.ok()
|
||||
|
||||
def ethdev_addrimport_token_burn_addr(self,name):
|
||||
self.ethdev_addrimport_one_addr(name,addr=eth_burn_addr,extra_args=['--token=mm1'])
|
||||
|
||||
def ethdev_bal2(self,name,expect_str=''):
|
||||
self.ethdev_bal(name,expect_str=r'deadbeef.* 999999.12345689012345678')
|
||||
|
||||
def ethdev_bal2_getbalance(self,name,t_non_mmgen='',t_mmgen=''):
|
||||
self.ethdev_bal_getbalance(name,t_non_mmgen='999999.12345689012345678',t_mmgen='127.0287876')
|
||||
|
||||
def ethdev_token_bal(self,name):
|
||||
t = MMGenExpect(name,'mmgen-tool', eth_args + ['--token=mm1','twview','wide=1'])
|
||||
t.expect(r'deadbeef.* '+eth_amt2,regex=True)
|
||||
t.read()
|
||||
t.ok()
|
||||
|
||||
def ethdev_token_bal_getbalance(self,name):
|
||||
self.ethdev_bal_getbalance(name,
|
||||
t_non_mmgen='888.111122223333444455',t_mmgen='111.888877776666555545',extra_args=['--token=mm1'])
|
||||
|
||||
def ethdev_stop(self,name):
|
||||
MMGenExpect(name,'',msg_only=True)
|
||||
pid = read_from_tmpfile(cfg,cfg['parity_pidfile'])
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue