test.py ethdev, scripts/create-token.py: cleanups
This commit is contained in:
parent
d2d2ce2ef7
commit
4742255ad2
4 changed files with 163 additions and 100 deletions
|
|
@ -60,12 +60,20 @@ class EthereumMMGenTX:
|
|||
def is_replaceable(self):
|
||||
return True
|
||||
|
||||
async def get_exec_status(self,txid,silent=False):
|
||||
d = await self.rpc.call('eth_getTransactionReceipt','0x'+txid)
|
||||
if not silent:
|
||||
if 'contractAddress' in d and d['contractAddress']:
|
||||
msg('Contract address: {}'.format(d['contractAddress'].replace('0x','')))
|
||||
return int(d['status'],16)
|
||||
async def get_receipt(self,txid,silent=False):
|
||||
rx = await self.rpc.call('eth_getTransactionReceipt','0x'+txid) # -> null if pending
|
||||
if not rx:
|
||||
return None
|
||||
tx = await self.rpc.call('eth_getTransactionByHash','0x'+txid)
|
||||
return namedtuple('exec_status',['status','gas_sent','gas_used','gas_price','contract_addr','tx','rx'])(
|
||||
status = Int(rx['status'],16), # zero is failure, non-zero success
|
||||
gas_sent = Int(tx['gas'],16),
|
||||
gas_used = Int(rx['gasUsed'],16),
|
||||
gas_price = ETHAmt(int(tx['gasPrice'],16),from_unit='wei'),
|
||||
contract_addr = self.proto.coin_addr(rx['contractAddress'][2:]) if rx['contractAddress'] else None,
|
||||
tx = tx,
|
||||
rx = rx,
|
||||
)
|
||||
|
||||
class New(Base,MMGenTX.New):
|
||||
hexdata_type = 'hex'
|
||||
|
|
|
|||
|
|
@ -520,6 +520,14 @@ class CoinAmt(Decimal,Hilite,InitErrors): # abstract class
|
|||
def hl(self,color=True):
|
||||
return self.colorize(self.__str__(),color=color)
|
||||
|
||||
def hl2(self,color=True,encl=''): # display with coin symbol
|
||||
return (
|
||||
encl[:-1]
|
||||
+ self.colorize(self.__str__(),color=color)
|
||||
+ ' ' + type(self).__name__[:-3]
|
||||
+ encl[1:]
|
||||
)
|
||||
|
||||
def __str__(self): # format simply, with no exponential notation
|
||||
return str(int(self)) if int(self) == self else self.normalize().__format__('f')
|
||||
|
||||
|
|
|
|||
|
|
@ -19,56 +19,46 @@
|
|||
import sys,os,json,re
|
||||
from subprocess import run,PIPE
|
||||
from mmgen.common import *
|
||||
from mmgen.obj import CoinAddr,is_coin_addr
|
||||
|
||||
decimals = 18
|
||||
supply = 10**26
|
||||
name = 'MMGen Token'
|
||||
symbol = 'MMT'
|
||||
solc_version_pat = r'0.5.[123]'
|
||||
class TokenData:
|
||||
attrs = ('decimals','supply','name','symbol','owner_addr')
|
||||
decimals = 18
|
||||
supply = 10**26
|
||||
name = 'MMGen Token'
|
||||
symbol = 'MMT'
|
||||
owner_addr = None
|
||||
|
||||
token_data = TokenData()
|
||||
|
||||
req_solc_ver_pat = '^0.5.2'
|
||||
|
||||
opts_data = {
|
||||
'text': {
|
||||
'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
|
||||
-v, --verbose Produce more verbose output
|
||||
'options': f"""
|
||||
-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: {token_data.decimals})
|
||||
-n, --name=N Token name (default: {token_data.name!r})
|
||||
-t, --supply=T Total supply of the token (default: {token_data.supply})
|
||||
-s, --symbol=S Token symbol (default: {token_data.symbol!r})
|
||||
-S, --stdout Output JSON data to stdout instead of files
|
||||
-v, --verbose Produce more verbose output
|
||||
-c, --check-solc-version Check the installed solc version
|
||||
""",
|
||||
'notes': """
|
||||
The owner address must be in checksummed format
|
||||
"""
|
||||
},
|
||||
'code': {
|
||||
'options': lambda s: s.format(
|
||||
d=decimals,
|
||||
n=name,
|
||||
s=symbol,
|
||||
t=supply)
|
||||
}
|
||||
}
|
||||
|
||||
cmd_args = opts.init(opts_data)
|
||||
|
||||
from mmgen.protocol import init_proto_from_opts
|
||||
proto = init_proto_from_opts()
|
||||
|
||||
assert proto.coin in ('ETH','ETC'),'--coin option must be set to ETH or ETC'
|
||||
|
||||
if not len(cmd_args) == 1 or not is_coin_addr(proto,cmd_args[0].lower()):
|
||||
opts.usage()
|
||||
|
||||
owner_addr = '0x' + cmd_args[0]
|
||||
|
||||
# ERC Token Standard #20 Interface
|
||||
# https://github.com/ethereum/EIPs/blob/master/EIPS/eip-20-token-standard.md
|
||||
|
||||
code_in = """
|
||||
solidity_code_template = """
|
||||
|
||||
pragma solidity >0.5.0 <0.5.4;
|
||||
pragma solidity %s;
|
||||
|
||||
contract SafeMath {
|
||||
function safeAdd(uint a, uint b) public pure returns (uint c) {
|
||||
|
|
@ -180,23 +170,49 @@ contract Token is ERC20Interface, Owned, SafeMath {
|
|||
return ERC20Interface(tokenAddress).transfer(owner, tokens);
|
||||
}
|
||||
}
|
||||
"""
|
||||
""" % req_solc_ver_pat
|
||||
|
||||
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]))
|
||||
def create_src(code,token_data,owner_addr):
|
||||
token_data.owner_addr = '0x' + owner_addr
|
||||
for k in token_data.attrs:
|
||||
if getattr(opt,k,None):
|
||||
setattr( token_data, k, getattr(opt,k) )
|
||||
code = code.replace( f'<{k.upper()}>', str(getattr(token_data,k)) )
|
||||
return code
|
||||
|
||||
def check_version():
|
||||
res = run(['solc','--version'],stdout=PIPE).stdout.decode()
|
||||
ver = re.search(r'Version:\s*(.*)',res).group(1)
|
||||
msg(f'Installed solc version: {ver}')
|
||||
if not re.search(r'{}\b'.format(solc_version_pat),ver):
|
||||
ydie(1,f'Incorrect Solidity compiler version (need version {solc_version_pat})')
|
||||
def check_solc_version():
|
||||
"""
|
||||
The output is used by other programs, so write to stdout only
|
||||
"""
|
||||
try:
|
||||
cp = run(['solc','--version'],check=True,stdout=PIPE)
|
||||
except:
|
||||
msg('solc missing or could not be executed') # this must go to stderr
|
||||
return False
|
||||
|
||||
if cp.returncode != 0:
|
||||
Msg('solc exited with error')
|
||||
return False
|
||||
|
||||
line = cp.stdout.decode().splitlines()[1]
|
||||
version_str = re.sub(r'Version:\s*','',line)
|
||||
m = re.match(r'(\d+)\.(\d+)\.(\d+)',version_str)
|
||||
|
||||
if not m:
|
||||
Msg(f'Unrecognized solc version string: {version_str}')
|
||||
return False
|
||||
|
||||
from semantic_version import Version,NpmSpec
|
||||
version = Version('{}.{}.{}'.format(*m.groups()))
|
||||
|
||||
if version in NpmSpec(req_solc_ver_pat):
|
||||
Msg(str(version))
|
||||
return True
|
||||
else:
|
||||
Msg(f'solc version ({version_str}) does not match requirement ({req_solc_ver_pat})')
|
||||
return False
|
||||
|
||||
def compile_code(code):
|
||||
check_version()
|
||||
cmd = ['solc','--optimize','--bin','--overwrite']
|
||||
if not opt.stdout:
|
||||
cmd += ['--output-dir', opt.outdir or '.']
|
||||
|
|
@ -218,9 +234,33 @@ def compile_code(code):
|
|||
else:
|
||||
vmsg(out)
|
||||
|
||||
src = create_src(code_in)
|
||||
out = compile_code(src)
|
||||
if opt.stdout:
|
||||
print(json.dumps(out))
|
||||
if __name__ == '__main__':
|
||||
|
||||
msg('Contract successfully compiled')
|
||||
cmd_args = opts.init(opts_data)
|
||||
|
||||
if opt.check_solc_version:
|
||||
sys.exit(0 if check_solc_version() else 1)
|
||||
|
||||
from mmgen.protocol import init_proto_from_opts
|
||||
proto = init_proto_from_opts()
|
||||
|
||||
if not proto.coin in ('ETH','ETC'):
|
||||
die(1,'--coin option must be ETH or ETC')
|
||||
|
||||
if not len(cmd_args) == 1:
|
||||
opts.usage()
|
||||
|
||||
owner_addr = cmd_args[0]
|
||||
|
||||
from mmgen.obj import is_coin_addr
|
||||
if not is_coin_addr( proto, owner_addr.lower() ):
|
||||
die(1,f'{owner_addr}: not a valid {proto.coin} coin address')
|
||||
|
||||
out = compile_code(
|
||||
create_src( solidity_code_template, token_data, owner_addr )
|
||||
)
|
||||
|
||||
if opt.stdout:
|
||||
print(json.dumps(out))
|
||||
|
||||
msg('Contract successfully compiled')
|
||||
|
|
|
|||
|
|
@ -45,32 +45,30 @@ amt2 = '888.111122223333444455'
|
|||
|
||||
openethereum_key_fn = 'openethereum.devkey'
|
||||
|
||||
# Token sends require varying amounts of gas, depending on compiler version
|
||||
def get_solc_ver():
|
||||
try: cp = run(['solc','--version'],stdout=PIPE)
|
||||
except: return None
|
||||
tested_solc_ver = '0.5.3'
|
||||
|
||||
if cp.returncode:
|
||||
return None
|
||||
def check_solc_ver():
|
||||
cmd = 'scripts/create-token.py --check-solc-version'
|
||||
try:
|
||||
cp = run(cmd.split(),check=False,stdout=PIPE)
|
||||
except Exception as e:
|
||||
rdie(2,f'Unable to execute {cmd!r}: {e}')
|
||||
res = cp.stdout.decode().strip()
|
||||
if cp.returncode == 0:
|
||||
omsg(
|
||||
orange(f'Found supported solc version {res}') if res == tested_solc_ver else
|
||||
yellow(f'WARNING: solc version ({res}) does not match tested version ({tested_solc_ver})')
|
||||
)
|
||||
return True
|
||||
else:
|
||||
omsg(yellow(res + '\nUsing precompiled contract data'))
|
||||
return False
|
||||
|
||||
line = cp.stdout.decode().splitlines()[1]
|
||||
m = re.search(r'Version:\s*(\d+)\.(\d+)\.(\d+)',line)
|
||||
return '.'.join(m.groups()) if m else None
|
||||
|
||||
solc_ver = get_solc_ver()
|
||||
|
||||
if solc_ver == '0.5.1':
|
||||
vbal1 = '1.2288337'
|
||||
vbal1a = 'TODO'
|
||||
vbal2 = '99.997085083'
|
||||
vbal3 = '1.23142165'
|
||||
vbal4 = '127.0287837'
|
||||
else: # 0.5.3 or precompiled 0.5.3
|
||||
vbal1 = '1.2288487'
|
||||
vbal1a = '1.22627465'
|
||||
vbal2 = '99.997092733'
|
||||
vbal3 = '1.23142915'
|
||||
vbal4 = '127.0287987'
|
||||
vbal1 = '1.2288487'
|
||||
vbal9 = '1.22627465'
|
||||
vbal2 = '99.997092733'
|
||||
vbal3 = '1.23142915'
|
||||
vbal4 = '127.0287987'
|
||||
|
||||
bals = {
|
||||
'1': [ ('98831F3A:E:1','123.456')],
|
||||
|
|
@ -122,7 +120,7 @@ token_bals = {
|
|||
('98831F3A:E:12','0',vbal2),
|
||||
('98831F3A:E:13','1.23456','0'),
|
||||
(burn_addr + '\s+Non-MMGen',amt2,amt1)],
|
||||
'7': [ ('98831F3A:E:11','67.444317776666555545',vbal1a,'a2'),
|
||||
'7': [ ('98831F3A:E:11','67.444317776666555545',vbal9,'a2'),
|
||||
('98831F3A:E:12','43.21',vbal2),
|
||||
('98831F3A:E:13','1.23456','0'),
|
||||
(burn_addr + '\s+Non-MMGen',amt2,amt1)]
|
||||
|
|
@ -143,7 +141,6 @@ class TestSuiteEthdev(TestSuiteBase,TestSuiteShared):
|
|||
passthru_opts = ('coin','daemon_id','http_timeout')
|
||||
extra_spawn_args = ['--regtest=1']
|
||||
tmpdir_nums = [22]
|
||||
solc_vers = ('0.5.1','0.5.3') # 0.5.1: Raspbian Stretch, 0.5.3: Ubuntu Bionic
|
||||
color = True
|
||||
cmd_group = (
|
||||
('setup', 'dev mode tests for coin {} (start daemon)'.format(coin)),
|
||||
|
|
@ -311,10 +308,13 @@ class TestSuiteEthdev(TestSuiteBase,TestSuiteShared):
|
|||
|
||||
def __init__(self,trunner,cfgs,spawn):
|
||||
TestSuiteBase.__init__(self,trunner,cfgs,spawn)
|
||||
if trunner == None:
|
||||
return
|
||||
from mmgen.protocol import init_proto
|
||||
self.proto = init_proto(g.coin,network='regtest')
|
||||
from mmgen.daemon import CoinDaemon
|
||||
self.rpc_port = CoinDaemon(proto=self.proto,test_suite=True).rpc_port
|
||||
self.using_solc = check_solc_ver()
|
||||
|
||||
@property
|
||||
def eth_args(self):
|
||||
|
|
@ -322,16 +322,13 @@ class TestSuiteEthdev(TestSuiteBase,TestSuiteShared):
|
|||
|
||||
async def setup(self):
|
||||
self.spawn('',msg_only=True)
|
||||
if solc_ver in self.solc_vers:
|
||||
imsg('Found solc version {}'.format(solc_ver))
|
||||
else:
|
||||
imsg('Solc compiler {}. Using precompiled contract data'.format(
|
||||
'version {} not supported by test suite'.format(solc_ver)
|
||||
if solc_ver else 'not found' ))
|
||||
|
||||
if not self.using_solc:
|
||||
srcdir = os.path.join(self.tr.repo_root,'test','ref','ethereum','bin')
|
||||
from shutil import copytree
|
||||
for d in ('mm1','mm2'):
|
||||
copytree(os.path.join(srcdir,d),os.path.join(self.tmpdir,d))
|
||||
|
||||
if not opt.no_daemon_autostart:
|
||||
if g.daemon_id == 'geth':
|
||||
self.geth_setup()
|
||||
|
|
@ -342,6 +339,7 @@ class TestSuiteEthdev(TestSuiteBase,TestSuiteShared):
|
|||
from mmgen.rpc import rpc_init
|
||||
rpc = await rpc_init(self.proto)
|
||||
imsg('Daemon: {} v{}'.format(rpc.daemon.coind_name,rpc.daemon_version_str))
|
||||
|
||||
return 'ok'
|
||||
|
||||
def geth_setup(self):
|
||||
|
|
@ -662,8 +660,8 @@ class TestSuiteEthdev(TestSuiteBase,TestSuiteShared):
|
|||
|
||||
def token_compile(self,token_data={}):
|
||||
odir = joinpath(self.tmpdir,token_data['symbol'].lower())
|
||||
if not solc_ver:
|
||||
imsg('Using precompiled contract data in {}'.format(odir))
|
||||
if not self.using_solc:
|
||||
imsg(f'Using precompiled contract data in {odir}')
|
||||
return 'skip' if os.path.exists(odir) else False
|
||||
self.spawn('',msg_only=True)
|
||||
cmd_args = ['--{}={}'.format(k,v) for k,v in list(token_data.items())]
|
||||
|
|
@ -679,7 +677,8 @@ class TestSuiteEthdev(TestSuiteBase,TestSuiteShared):
|
|||
imsg("Executing: {}".format(' '.join(cmd)))
|
||||
cp = run(cmd,stdout=DEVNULL,stderr=PIPE)
|
||||
if cp.returncode != 0:
|
||||
rdie(2,'solc failed with the following output: {}'.format(cp.stderr))
|
||||
rmsg('solc failed with the following output:')
|
||||
ydie(2,cp.stderr.decode())
|
||||
imsg("ERC20 token '{}' compiled".format(token_data['symbol']))
|
||||
return 'ok'
|
||||
|
||||
|
|
@ -691,12 +690,18 @@ class TestSuiteEthdev(TestSuiteBase,TestSuiteShared):
|
|||
token_data = { 'name':'MMGen Token 2', 'symbol':'MM2', 'supply':10**18, 'decimals':10 }
|
||||
return self.token_compile(token_data)
|
||||
|
||||
async def get_exec_status(self,txid):
|
||||
async def get_tx_receipt(self,txid):
|
||||
from mmgen.tx import MMGenTX
|
||||
tx = MMGenTX.New(proto=self.proto)
|
||||
from mmgen.rpc import rpc_init
|
||||
tx.rpc = await rpc_init(self.proto)
|
||||
return await tx.get_exec_status(txid,True)
|
||||
res = await tx.get_receipt(txid)
|
||||
imsg(f'Gas sent: {res.gas_sent.hl():<9} {(res.gas_sent*res.gas_price).hl2(encl="()")}')
|
||||
imsg(f'Gas used: {res.gas_used.hl():<9} {(res.gas_used*res.gas_price).hl2(encl="()")}')
|
||||
imsg(f'Gas price: {res.gas_price.hl2()}')
|
||||
if res.gas_used == res.gas_sent:
|
||||
omsg(yellow(f'Warning: all gas was used!'))
|
||||
return res
|
||||
|
||||
async def token_deploy(self,num,key,gas,mmgen_cmd='txdo',tx_fee='8G'):
|
||||
keyfile = joinpath(self.tmpdir,openethereum_key_fn)
|
||||
|
|
@ -724,7 +729,8 @@ class TestSuiteEthdev(TestSuiteBase,TestSuiteShared):
|
|||
quiet = mmgen_cmd == 'txdo' or not g.debug,
|
||||
bogus_send=False)
|
||||
addr = strip_ansi_escapes(t.expect_getend('Contract address: '))
|
||||
assert (await self.get_exec_status(txid)) != 0, f'Contract {num}:{key} failed to execute. Aborting'
|
||||
if (await self.get_tx_receipt(txid)).status == 0:
|
||||
die(2,f'Contract {num}:{key} failed to execute. Aborting')
|
||||
if key == 'Token':
|
||||
self.write_to_tmpfile( f'token_addr{num}', addr+'\n' )
|
||||
imsg(f'\nToken MM{num} deployed!')
|
||||
|
|
@ -771,7 +777,8 @@ class TestSuiteEthdev(TestSuiteBase,TestSuiteShared):
|
|||
dfl_privkey,
|
||||
start_gas = ETHAmt(60000,'wei'),
|
||||
gasPrice = ETHAmt(8,'Gwei') )
|
||||
assert (await self.get_exec_status(txid)) != 0,'Transfer of token funds failed. Aborting'
|
||||
if (await self.get_tx_receipt(txid)).status == 0:
|
||||
die(2,'Transfer of token funds failed. Aborting')
|
||||
|
||||
async def show_bals(rpc):
|
||||
for i in range(2):
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue