forked from runtimeverification/wasm-semantics
-
Notifications
You must be signed in to change notification settings - Fork 5
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Add contract call test scripts (#48)
* add call script * extract fund script from deploy script * improve script usage * update README * add additional gas to deploy command * silence extra output on scripts except on error * add mkacct script * fix checkstyle/formatting errors * add tests for decimals and mint --------- Co-authored-by: Traian-Florin Șerbănuță <[email protected]> Co-authored-by: Virgil Șerbănuță <[email protected]>
- Loading branch information
1 parent
e48bd42
commit 1bb4927
Showing
8 changed files
with
460 additions
and
24 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,161 @@ | ||
#!/usr/bin/python3 | ||
import sys | ||
from pathlib import Path | ||
|
||
from eth_account import Account | ||
from requests.exceptions import ConnectionError | ||
from web3 import Web3 | ||
from web3.exceptions import BadFunctionCallOutput, Web3RPCError | ||
from web3.middleware import SignAndSendRawMiddlewareBuilder | ||
|
||
ABI_MAP = { | ||
'erc20': [ | ||
{'type': 'function', 'name': 'decimals', 'inputs': [], 'outputs': ['uint8'], 'stateMutability': 'view'}, | ||
{'type': 'function', 'name': 'totalSupply', 'inputs': [], 'outputs': ['uint256'], 'stateMutability': 'view'}, | ||
{ | ||
'type': 'function', | ||
'name': 'balanceOf', | ||
'inputs': [{'name': 'owner', 'type': 'address'}], | ||
'outputs': [{'name': '', 'type': 'uint256'}], | ||
'stateMutability': 'view', | ||
}, | ||
{ | ||
'type': 'function', | ||
'name': 'transfer', | ||
'inputs': [{'name': 'to', 'type': 'address'}, {'name': 'value', 'type': 'uint256'}], | ||
'outputs': [{'name': '', 'type': 'bool'}], | ||
}, | ||
{ | ||
'type': 'function', | ||
'name': 'transferFrom', | ||
'inputs': [ | ||
{'name': 'from', 'type': 'address'}, | ||
{'name': 'to', 'type': 'address'}, | ||
{'name': 'value', 'type': 'uint256'}, | ||
], | ||
'outputs': [{'name': '', 'type': 'bool'}], | ||
}, | ||
{ | ||
'type': 'function', | ||
'name': 'approve', | ||
'inputs': [{'name': 'spender', 'type': 'address'}, {'name': 'value', 'type': 'uint256'}], | ||
'outputs': [{'name': '', 'type': 'bool'}], | ||
}, | ||
{ | ||
'type': 'function', | ||
'name': 'allowance', | ||
'inputs': [{'name': 'owner', 'type': 'address'}, {'name': 'spender', 'type': 'address'}], | ||
'outputs': [{'name': '', 'type': 'uint256'}], | ||
'stateMutability': 'view', | ||
}, | ||
{ | ||
'type': 'function', | ||
'name': 'mint', | ||
'inputs': [{'name': 'account', 'type': 'address'}, {'name': 'value', 'type': 'uint256'}], | ||
'outputs': [], | ||
}, | ||
] | ||
} | ||
|
||
|
||
def parse_arg(param_ty, arg): | ||
match param_ty: | ||
case 'uint256': | ||
try: | ||
return int(arg) | ||
except ValueError: | ||
pass | ||
try: | ||
return int(arg, 16) | ||
except ValueError as err: | ||
raise ValueError(f'Failed to parse numeric argument {arg}') from err | ||
case 'address': | ||
assert Web3.is_address(arg) | ||
return arg | ||
|
||
|
||
def parse_params(abi, method, args): | ||
for elt in abi: | ||
parsed_args = [] | ||
ty, name, inputs = elt['type'], elt['name'], elt['inputs'] | ||
if ty == 'function' and name == method: | ||
if len(inputs) != len(args): | ||
raise ValueError('call to method {method} with {inputs} has incorrect parameters {params}') | ||
for param, arg in zip(inputs, args, strict=True): | ||
parsed_args.append(parse_arg(param['type'], arg)) | ||
break | ||
else: | ||
raise ValueError(f'method {method} not found in contract ABI') | ||
return parsed_args | ||
|
||
|
||
def run_method(w3, contract, sender, eth, method, params): | ||
func = contract.functions[method](*params) | ||
view_like = func.abi.get('stateMutability', 'nonpayable') in {'view', 'pure'} | ||
try: | ||
if view_like: | ||
result_or_receipt = func.call() | ||
else: | ||
tx_hash = func.transact({'from': sender.address, 'value': eth}) | ||
result_or_receipt = w3.eth.wait_for_transaction_receipt(tx_hash) | ||
except (ConnectionError, BadFunctionCallOutput, Web3RPCError) as e: | ||
if isinstance(e, (ConnectionError, ConnectionRefusedError)): | ||
msg = f'Failed to connect to node: {e.message}' | ||
elif isinstance(e, BadFunctionCallOutput): | ||
msg = f'Could not interpret function output: {",".join(e.args)}' | ||
else: | ||
msg = f'Node RPC encountered an error: {e.message}' | ||
print(msg, file=sys.stderr) | ||
sys.exit(1) | ||
|
||
return (view_like, result_or_receipt) | ||
|
||
|
||
USAGE = 'call.py <node_url> <contract_abi> <contract_address_lit_or_file> <sender_private_key_file> <eth> <method> [param...]' | ||
|
||
|
||
def main(): | ||
args = sys.argv[1:] | ||
if len(args) < 6: | ||
print(USAGE, file=sys.stderr) | ||
sys.exit(1) | ||
(node_url, abi_name, addr_lit_or_file, sender_pk_file, eth, method), params = args[:6], args[6:] | ||
# get web3 instance | ||
w3 = Web3(Web3.HTTPProvider(node_url)) | ||
# get abi | ||
abi = ABI_MAP[abi_name] | ||
# get contract | ||
try: | ||
contract_addr = Path(addr_lit_or_file).read_text().strip() | ||
except FileNotFoundError: | ||
contract_addr = addr_lit_or_file | ||
contract = w3.eth.contract(address=contract_addr, abi=abi) | ||
# validate method | ||
try: | ||
contract.functions[method] | ||
except BaseException: # noqa: B036 | ||
print(f'Invalid method {method} for {abi_name} contract ABI', file=sys.stderr) | ||
sys.exit(1) | ||
# get sender | ||
pk = bytes.fromhex(Path(sender_pk_file).read_text().strip().removeprefix('0x')) | ||
sender = Account.from_key(pk) | ||
# add signer | ||
w3.middleware_onion.inject(SignAndSendRawMiddlewareBuilder.build(sender), layer=0) | ||
# parse params | ||
params = parse_params(abi, method, params) | ||
eth = int(eth) | ||
# run method | ||
(view_like, result_or_receipt) = run_method(w3, contract, sender, eth, method, params) | ||
# handle result | ||
if view_like: | ||
print(result_or_receipt) | ||
else: | ||
# return exit code based on status which is 1 for confirmed and 0 for reverted | ||
success = bool(result_or_receipt['status']) | ||
if not success: | ||
print(result_or_receipt, file=sys.stderr) | ||
sys.exit(int(not success)) | ||
|
||
|
||
if __name__ == '__main__': | ||
main() |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,59 @@ | ||
#!/usr/bin/python3 | ||
import sys | ||
from pathlib import Path | ||
|
||
from eth_account import Account | ||
from requests.exceptions import ConnectionError | ||
from web3 import Web3 | ||
|
||
# from web3.middleware import SignAndSendRawMiddlewareBuilder | ||
|
||
|
||
def fund_acct(w3, addr): | ||
try: | ||
fund_tx_hash = w3.eth.send_transaction({'from': w3.eth.accounts[0], 'to': addr, 'value': 1000000000000000000}) | ||
fund_tx_receipt = w3.eth.wait_for_transaction_receipt(fund_tx_hash) | ||
except ConnectionError: | ||
print('Failed to connect to node', file=sys.stderr) | ||
sys.exit(1) | ||
return fund_tx_receipt | ||
|
||
|
||
USAGE = 'fund_acct.py <address_or_pk_file> [node_url]' | ||
|
||
|
||
def main(): | ||
# check arg count | ||
args = sys.argv[1:] | ||
if len(args) < 1 or len(args) > 2: | ||
print(USAGE, file=sys.stderr) | ||
sys.exit(1) | ||
|
||
# parse args | ||
addr_or_pkfile = args[0] | ||
if not Web3.is_address(addr_or_pkfile): | ||
pk = bytes.fromhex(Path(addr_or_pkfile).read_text().strip().removeprefix('0x')) | ||
addr = Account.from_key(pk).address | ||
else: | ||
addr = addr_or_pkfile | ||
node_url = 'http://localhost:8545' | ||
if len(args) > 1: | ||
node_url = args[1] | ||
|
||
# fund acct | ||
w3 = Web3(Web3.HTTPProvider(node_url)) | ||
fund_receipt = fund_acct(w3, addr) | ||
|
||
# return exit code based on status which is 1 for confirmed and 0 for reverted | ||
success = bool(fund_receipt['status']) | ||
|
||
# print receipt on failure | ||
if not success: | ||
print(fund_receipt) | ||
|
||
# set exit code | ||
sys.exit(int(not success)) | ||
|
||
|
||
if __name__ == '__main__': | ||
main() |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,16 @@ | ||
from web3 import Web3 | ||
|
||
|
||
def mkaddr(): | ||
w3 = Web3() | ||
acct = w3.eth.account.create() | ||
return (acct.address, w3.to_hex(acct.key)) | ||
|
||
|
||
def main(): | ||
address, key = mkaddr() | ||
print(f'{address} {key}') | ||
|
||
|
||
if __name__ == '__main__': | ||
main() |
Oops, something went wrong.