def test_not_enough_funds_to_run_payment_code(payment_node_network): network = payment_node_network node0: DockerNode = network.docker_nodes[0] blocks = parse_show_blocks(node0.d_client.show_blocks(1000)) genesis_hash = blocks[0].summary.block_hash assert len( blocks) == 1 # There should be only one block - the genesis block genesis_balance = node0.d_client.get_balance( account_address=GENESIS_ACCOUNT.public_key_hex, block_hash=genesis_hash) assert genesis_balance == INITIAL_MOTES_AMOUNT session_args = ABI.args([ ABI.account(bytes.fromhex(GENESIS_ACCOUNT.public_key_hex)), ABI.u32(10**7) ]) _, deploy_hash = node0.p_client.deploy( from_address=GENESIS_ACCOUNT.public_key_hex, session_contract=Contract.TRANSFER_TO_ACCOUNT, payment_contract=Contract.STANDARD_PAYMENT, public_key=GENESIS_ACCOUNT.public_key_path, private_key=GENESIS_ACCOUNT.private_key_path, gas_price=1, session_args=session_args, payment_args=ABI.args([ABI.u512(450)]), ) latest_block_hash = parse_show_blocks( node0.d_client.show_blocks(1000))[0].summary.block_hash genesis_balance_after_transfer = node0.d_client.get_balance( account_address=GENESIS_ACCOUNT.public_key_hex, block_hash=latest_block_hash) assert genesis_balance == genesis_balance_after_transfer
def _call_pos_bonding( node, from_address: str, amount: int, public_key: str, private_key: str, gas_price: int = 1, session_contract: str = Contract.POS_BONDING, method: bytes = b"bond", ): if method == b"bond": session_args = ABI.args([ABI.byte_array(method), ABI.u512(amount)]) elif method == b"unbond": session_args = ABI.args([ ABI.byte_array(method), ABI.option(ABI.u512(amount) if amount is not None else None), ]) else: raise Exception(f"_call_pos_bonding: method {method} not supported") node.p_client.deploy( from_address=from_address, session_contract=session_contract, gas_price=gas_price, public_key=public_key, private_key=private_key, session_args=session_args, ) block_hash = node.p_client.propose().block_hash.hex() for deploy_info in node.p_client.show_deploys(block_hash): if deploy_info.is_error: raise Exception(f"bond: {deploy_info.error_message}") return block_hash
def transfer_to_account( node, from_address: str, to_address: str, amount: int, public_key: str, private_key: str, gas_price: int = 1, session_contract=Contract.TRANSFER_TO_ACCOUNT, ): session_args = ABI.args( [ABI.account(bytes.fromhex(to_address)), ABI.u32(amount)]) _, deploy_hash_bytes = node.p_client.deploy( from_address=from_address, session_contract=session_contract, public_key=public_key, private_key=private_key, gas_price=gas_price, session_args=session_args, ) deploy_hash_hex = deploy_hash_bytes.hex() deploy_hash_hex = deploy_hash_hex block_hash = node.p_client.propose().block_hash.hex() for deploy_info in node.p_client.show_deploys(block_hash): if deploy_info.is_error: raise Exception( f"transfer_to_account: {deploy_info.error_message}") return block_hash
def remove_associated_key(node, weight_key: Account, key: Account): """ Removes a key from the IDENTITY_KEY account """ args = ABI.args([ABI.account("account", key.public_key_hex)]) return node.p_client.deploy_and_propose( from_address=IDENTITY_KEY.public_key_hex, session_contract=Contract.REMOVE_ASSOCIATED_KEY, public_key=weight_key.public_key_path, private_key=weight_key.private_key_path, session_args=args, )
def set_key_thresholds(node, weight_key, key_mgmt_weight: int, deploy_weight: int): """ Sets key management and deploy thresholds for IDENTITY_KEY account """ args = ABI.args([ABI.u32(key_mgmt_weight), ABI.u32(deploy_weight)]) return node.p_client.deploy_and_propose( from_address=IDENTITY_KEY.public_key_hex, session_contract=Contract.SET_KEY_THRESHOLDS, public_key=weight_key.public_key_path, private_key=weight_key.private_key_path, session_args=args, )
def test_error_in_payment_contract(payment_node_network): network = payment_node_network node0: DockerNode = network.docker_nodes[0] node0.use_docker_client() blocks = parse_show_blocks(node0.d_client.show_blocks(1000)) genesis_hash = blocks[0].summary.block_hash assert len( blocks) == 1 # There should be only one block - the genesis block genesis_balance = node0.client.get_balance( account_address=GENESIS_ACCOUNT.public_key_hex, block_hash=genesis_hash) assert genesis_balance == INITIAL_MOTES_AMOUNT from_account = Account("genesis") to_account = Account(1) session_args = ABI.args([ ABI.account(bytes.fromhex(to_account.public_key_hex)), ABI.u32(10**7) ]) payment_args = ABI.args([ABI.u512(10**6)]) response, deploy_hash_bytes = node0.p_client.deploy( from_address=from_account.public_key_hex, session_contract=Contract.TRANSFER_TO_ACCOUNT, payment_contract=Contract.ERR_STANDARD_PAYMENT, public_key=from_account.public_key_path, private_key=from_account.private_key_path, gas_price=1, session_args=session_args, payment_args=payment_args, ) genesis_balance_after_transfer = node0.client.get_balance( account_address=GENESIS_ACCOUNT.public_key_hex, block_hash=parse_show_blocks( node0.d_client.show_blocks(1000))[0].summary.block_hash, ) assert genesis_balance == genesis_balance_after_transfer
def _add_update_associate_key(node, weight_key: Account, key: Account, weight: int, contract: str): """ Handles both add and update calls due to commonality """ session_args = ABI.args([ ABI.account("account", key.public_key_hex), ABI.u32("amount", weight) ]) return node.p_client.deploy_and_propose( from_address=IDENTITY_KEY.public_key_hex, session_contract=contract, public_key=weight_key.public_key_path, private_key=weight_key.private_key_path, session_args=session_args, )
def test_refund_after_session_code_error(payment_node_network): network = payment_node_network node0: DockerNode = network.docker_nodes[0] blocks = parse_show_blocks(node0.d_client.show_blocks(1000)) genesis_init_balance = node0.client.get_balance( account_address=GENESIS_ACCOUNT.public_key_hex, block_hash=blocks[0].summary.block_hash, ) _, deploy_hash = node0.p_client.deploy( from_address=GENESIS_ACCOUNT.public_key_hex, session_contract=Contract.ARGS_U512, payment_contract=Contract.STANDARD_PAYMENT, public_key=GENESIS_ACCOUNT.public_key_path, private_key=GENESIS_ACCOUNT.private_key_path, gas_price=1, session_args=ABI.args([ABI.u512("number", 100)]), payment_args=ABI.args([ABI.u32("amount", 10 ** 6)]) # 100 is a revert code. ) try: node0.p_client.propose() except Exception as ex: print(ex) latest_blocks = parse_show_blocks(node0.d_client.show_blocks(1000)) deploy_hash = latest_blocks[0].summary.block_hash deploy = node0.client.show_deploys(deploy_hash)[0] assert deploy.cost == MAX_PAYMENT_COST / CONV_RATE motes = deploy.cost * CONV_RATE later_balance = node0.client.get_balance( account_address=GENESIS_ACCOUNT.public_key_hex, block_hash=deploy_hash ) expected_sum = later_balance + motes assert genesis_init_balance == expected_sum
def create_associate_deploy(acct_num: int): """ Add associated key of acct_num + 1 to acct_num account """ acct = Account(acct_num) associated_acct = Account(acct_num + 1) args = ABI.args( [ABI.account(associated_acct.public_key_binary), ABI.u32(1)]) _, deploy_hash_bytes = node.p_client.deploy( from_address=acct.public_key_hex, session_contract=Contract.ADD_ASSOCIATED_KEY, public_key=acct.public_key_path, private_key=acct.private_key_path, session_args=args, ) return deploy_hash_bytes.hex()
def test_args_parser(): account = ( b"\x00\x01\x00\x02\x00\x03\x00\x04\x00\x05\x00\x06\x00\x07\x00\x08" b"\x00\x00\x00\x01\x00\x02\x00\x03\x00\x04\x00\x05\x00\x06\x00\x07" ) amount = 123456 args = [{"name": "amount", "value": {"long_value": amount}}, {"name": "account", "value": {"bytes_value": account.hex()}}, {"name": "purse_id", "value": {"optional_value": {}}}, {"name": "number", "value": {"big_int": {"value": "2", "bit_width": 512}}}] json_str = json.dumps(args) assert ABI.args_from_json(json_str) == ABI.args( [ABI.long_value(amount), ABI.account(account), ABI.optional_value(None), ABI.big_int(2)] )
def test_args_parser(): account_hex = "0001000200030004000500060007000800000001000200030004000500060007" account_bytes = ( b"\x00\x01\x00\x02\x00\x03\x00\x04\x00\x05\x00\x06\x00\x07\x00\x08" b"\x00\x00\x00\x01\x00\x02\x00\x03\x00\x04\x00\x05\x00\x06\x00\x07") u32 = 1024 u64 = 1234567890 json_str = json.dumps([{ "u32": u32 }, { "account": account_hex }, { "u64": u64 }]) assert ABI.args_from_json(json_str) == ABI.args( [ABI.u32(1024), ABI.account(account_bytes), ABI.u64(1234567890)])
def check_cli_abi_unsigned(cli, unsigned_type, value, test_contract): account = GENESIS_ACCOUNT for number in [2, 256, 1024]: args = ABI.args([getattr(ABI, unsigned_type)("number", number)]) session_args = ABI.args_to_json(args) args = ('deploy', '--from', account.public_key_hex, '--session', cli.resource(test_contract), '--session-args', cli.format_json_str(session_args), '--payment', cli.resource(Contract.STANDARD_PAYMENT), '--payment-args', cli.payment_json, '--private-key', cli.private_key_path(account), '--public-key', cli.public_key_path(account)) logging.info(f"EXECUTING {' '.join(cli.expand_args(args))}") deploy_hash = cli(*args) cli('propose') deploy_info = cli("show-deploy", deploy_hash) exit_code = number + USER_ERROR_MIN assert deploy_info.processing_results[0].is_error is True assert deploy_info.processing_results[ 0].error_message == f"Exit code: {exit_code}"
def test_cli_abi_multiple(cli): account = GENESIS_ACCOUNT account_hex = "0101010102020202030303030404040405050505060606060707070708080808" number = 1000 total_sum = sum([1, 2, 3, 4, 5, 6, 7, 8]) * 4 + number args = ABI.args( [ABI.account("account", account_hex), ABI.int_value("number", number)]) session_args = ABI.args_to_json(args) deploy_hash = cli('deploy', '--from', account.public_key_hex, '--session', cli.resource(Contract.ARGS_MULTI), '--session-args', session_args, '--private-key', cli.private_key_path(account), '--public-key', cli.public_key_path(account), '--payment', cli.resource(Contract.STANDARD_PAYMENT), '--payment-args', cli.payment_json) cli('propose') deploy_info = cli("show-deploy", deploy_hash) exit_code = total_sum + USER_ERROR_MIN assert deploy_info.processing_results[0].is_error is True assert deploy_info.processing_results[ 0].error_message == f"Exit code: {exit_code}"
def transfer_to_account( self, to_account_id: int, amount: int, from_account_id: Union[str, int] = "genesis", session_contract: str = Contract.TRANSFER_TO_ACCOUNT_IT, payment_contract: str = Contract.STANDARD_PAYMENT, payment_args: bytes = MAX_PAYMENT_ABI, gas_price: int = 1, is_deploy_error_check: bool = True, ) -> str: """ Performs a transfer using the from account if given (or genesis if not) :param to_account_id: 1-20 index of test account for transfer into :param amount: amount of motes to transfer (mote = smallest unit of token) :param from_account_id: default 'genesis' account, but previously funded account_id is also valid. :param session_contract: session contract to execute. :param payment_contract: Payment contract to execute. :param payment_args: Payment Amount ABI :param gas_price: Gas price :param is_deploy_error_check: Check that amount transfer is success. :returns block_hash in hex str """ logging.info(f"=== Transferring {amount} to {to_account_id}") assert ( is_valid_account(to_account_id) and to_account_id != "genesis" ), "Can transfer only to non-genesis accounts in test framework (1-20)." assert is_valid_account( from_account_id ), "Must transfer from a valid account_id: 1-20 or 'genesis'" from_account = Account(from_account_id) to_account = Account(to_account_id) session_args = ABI.args( [ ABI.account("account", to_account.public_key_binary), ABI.u32("amount", amount), ] ) response, deploy_hash_bytes = self.p_client.deploy( from_address=from_account.public_key_hex, session_contract=session_contract, payment_contract=payment_contract, public_key=from_account.public_key_path, private_key=from_account.private_key_path, gas_price=gas_price, session_args=session_args, payment_args=payment_args, ) deploy_hash_hex = deploy_hash_bytes.hex() assert len(deploy_hash_hex) == 64 response = self.p_client.propose() block_hash = response.block_hash.hex() assert len(deploy_hash_hex) == 64 if is_deploy_error_check: for deploy_info in self.p_client.show_deploys(block_hash): if deploy_info.is_error: raise Exception(f"transfer_to_account: {deploy_info.error_message}") return block_hash
STANDARD_PAYMENT = "standard_payment.wasm" SUBCALL_REVERT_CALL = "subcall_revert_call.wasm" SUBCALL_REVERT_DEFINE = "subcall_revert_define.wasm" TRANSFER_TO_ACCOUNT = "transfer_to_account.wasm" UPDATE_ASSOCIATED_KEY = "update_associated_key.wasm" # TEST Account Use Notes # test_transfer_to_accounts: 300, 299, 298 # test_transfer_with_overdraft: 297, 296 # test_bonding: 295 MAX_PAYMENT_COST = 10000000 DEFAULT_PAYMENT_COST = 100000000 INITIAL_MOTES_AMOUNT = 10**20 MAX_PAYMENT_ABI = ABI.args([ABI.big_int("amount", MAX_PAYMENT_COST)]) DEFAULT_PAYMENT_ABI = ABI.args([ABI.big_int("amount", DEFAULT_PAYMENT_COST)]) CONV_RATE = 10 TEST_ACCOUNT_INITIAL_BALANCE = 1000000000 USER_ERROR_MIN = 65536 BOOTSTRAP_PATH = "/root/.casperlabs/bootstrap" CHAINSPEC_PATH = "/root/.casperlabs/chainspec" @dataclasses.dataclass(eq=True, frozen=True) class KeyPair: private_key: str public_key: str
class CLI: _DEFAULT_PAYMENT_JSON = ABI.args_to_json( ABI.args([ABI.big_int("amount", DEFAULT_PAYMENT_COST)]) ) def __init__(self, node, cli_cmd="casperlabs_client", tls_parameters=None): self.node = node self.host = ( os.environ.get("TAG_NAME", None) and node.container_name or "localhost" ) self.port = node.grpc_external_docker_port self.port_internal = node.grpc_internal_docker_port self.cli_cmd = cli_cmd self.tls_parameters = tls_parameters or {} self.default_deploy_args = [] self.resources_directory = resources_path() def set_default_deploy_args(self, *args): """ Set args that will be appended to subsequent deploy command. """ self.default_deploy_args = [str(arg) for arg in args] def resource(self, file_name): return self.resources_directory / file_name def expand_args(self, args): connection_details = [ "--host", f"{self.host}", "--port", f"{self.port}", "--port-internal", f"{self.port_internal}", ] if self.tls_parameters: connection_details += reduce( add, [[str(p), str(self.tls_parameters[p])] for p in self.tls_parameters], ) string_args = [str(a) for a in list(args)] if args and args[0] == "deploy": string_args += self.default_deploy_args return "--help" in args and string_args or connection_details + string_args @staticmethod def parse_output(command, binary_output): if command in ("make-deploy", "sign-deploy"): return binary_output output = binary_output.decode("utf-8") if command in ("deploy", "send-deploy", "bond", "unbond"): return output.split()[2] # "Success! Deploy 0d4036bebb95de793b28de452d594531a29f8dc3c5394526094d30723fa5ff65 deployed." if command in ("propose",): # "Response: Success! Block 47338c65992e7d5062aec2200ad8d7284ae49f6c3e7c37fa7eb46fb6fc8ae3d8 created and added." return output.split()[3] if command == "show-blocks": return parse_show_blocks(output) if command == "show-deploys": return parse_show_deploys(output) if command in ("show-deploy", "show-block", "query-state"): return parse(output) return output def __call__(self, *args): command_line = [str(self.cli_cmd)] + self.expand_args(args) # logging.info(f"EXECUTING []: {command_line}") logging.info(f"EXECUTING: {' '.join(command_line)}") cp = subprocess.run( command_line, stdout=subprocess.PIPE, stderr=subprocess.PIPE ) binary_output = cp.stdout if cp.returncode != 0: output = binary_output try: output = binary_output.decode("utf-8") except UnicodeDecodeError: pass raise CLIErrorExit(cp, output) return self.parse_output(args[0], binary_output) def public_key_path(self, account): return account.public_key_path def private_key_path(self, account): return account.private_key_path def format_json_str(self, args: str) -> str: return args @property def payment_json(self) -> str: return self.format_json_str(self._DEFAULT_PAYMENT_JSON)
def test_multiple_deploys_per_block(cli): """ Two deploys from the same account then propose. Both deploys should be be included in the new block. Create named purses to pay from for each deploy (standard payment cannot be used because it causes a WRITE against the main purse balance, but every deploy has a READ against that balance to check it meets the minimum balance condition, so standard payment calls always conflict with any other deploy from the same account) """ account = cli.node.test_account cli.set_default_deploy_args('--from', account.public_key_hex, '--private-key', cli.private_key_path(account), '--public-key', cli.public_key_path(account)) # Create purse_1 cli( 'deploy', '--session', cli.resource(Contract.CREATE_NAMED_PURSE), '--session-args', ABI.args_to_json( ABI.args([ ABI.big_int("amount", 100000000), ABI.string_value("purse-name", "purse_1") ])), '--payment', cli.resource(Contract.STANDARD_PAYMENT), '--payment-args', ABI.args_to_json(ABI.args([ABI.big_int("amount", 100000000)]))) propose_check_no_errors(cli) # Create purse_2 cli( 'deploy', '--session', cli.resource(Contract.CREATE_NAMED_PURSE), '--session-args', ABI.args_to_json( ABI.args([ ABI.big_int("amount", 100000000), ABI.string_value("purse-name", "purse_2") ])), '--payment', cli.resource(Contract.STANDARD_PAYMENT), '--payment-args', ABI.args_to_json(ABI.args([ABI.big_int("amount", 100000000)]))) propose_check_no_errors(cli) # First deploy uses first purse for payment deploy_hash1 = cli( 'deploy', '--from', account.public_key_hex, '--session', cli.resource(Contract.COUNTER_DEFINE), '--payment', cli.resource(Contract.PAYMENT_FROM_NAMED_PURSE), '--payment-args', ABI.args_to_json( ABI.args([ ABI.big_int("amount", 100000000), ABI.string_value("purse-name", "purse_1") ]))) # Second deploy uses second purse for payment deploy_hash2 = cli( 'deploy', '--from', account.public_key_hex, '--session', cli.resource(Contract.MAILING_LIST_DEFINE), '--payment', cli.resource(Contract.PAYMENT_FROM_NAMED_PURSE), '--payment-args', ABI.args_to_json( ABI.args([ ABI.big_int("amount", 100000000), ABI.string_value("purse-name", "purse_2") ]))) block_hash = propose_check_no_errors(cli) # Propose should include both deploys. deploys = list(cli("show-deploys", block_hash)) assert len(deploys) == 2 assert set(d.deploy.deploy_hash for d in deploys) == set( (deploy_hash1, deploy_hash2))
def test_deploy_with_args(one_node_network, genesis_public_signing_key): """ Deploys test contracts that do: revert(get_arg(0)); // for u32 and u512 and revert(sum(address_bytes[u8; 32]) + u32); for multiple argument test. Tests args get correctly encoded and decoded in the contract. Test expects the test contracts test_args_u32.wasm and test_args_u512.wasm to deserialize correctly their arguments and then call revert with value of the argument (converted to a Rust native int, as expected by revert). If the test contracts don't fail or if their exit code is different than expected, the test will fail. """ node = one_node_network.docker_nodes[0] client = node.p_client.client for wasm, encode in [ (resources_path() / Contract.ARGS_U32, ABI.u32), (resources_path() / Contract.ARGS_U512, ABI.u512), ]: for number in [1, 12, 256, 1024]: response, deploy_hash = client.deploy( session=wasm, session_args=ABI.args([encode(number)]), payment=resources_path() / Contract.STANDARD_PAYMENT, payment_args=MAX_PAYMENT_ABI, public_key=GENESIS_ACCOUNT.public_key_path, private_key=GENESIS_ACCOUNT.private_key_path, ) logging.info( f"DEPLOY RESPONSE: {response} deploy_hash: {deploy_hash.hex()}" ) response = client.propose() # Need to convert to hex string from bytes block_hash = response.block_hash.hex() for deploy_info in client.showDeploys(block_hash): assert deploy_info.is_error is True assert deploy_info.error_message == f"Exit code: {number}" wasm = resources_path() / Contract.ARGS_MULTI account_hex = "0101010102020202030303030404040405050505060606060707070708080808" number = 1000 total_sum = sum([1, 2, 3, 4, 5, 6, 7, 8]) * 4 + number response, deploy_hash = client.deploy( session=wasm, session_args=ABI.args( [ABI.account(bytes.fromhex(account_hex)), ABI.u32(number)] ), payment=resources_path() / Contract.STANDARD_PAYMENT, payment_args=MAX_PAYMENT_ABI, public_key=GENESIS_ACCOUNT.public_key_path, private_key=GENESIS_ACCOUNT.private_key_path, ) logging.info(f"DEPLOY RESPONSE: {response} deploy_hash: {deploy_hash.hex()}") response = client.propose() block_hash = response.block_hash.hex() for deploy_info in client.showDeploys(block_hash): assert deploy_info.is_error is True assert deploy_info.error_message == f"Exit code: {total_sum}" for blockInfo in client.showBlocks(10): assert blockInfo.status.stats.block_size_bytes > 0
SET_KEY_THRESHOLDS = "set_key_thresholds.wasm" STANDARD_PAYMENT = "standard_payment.wasm" SUBCALL_REVERT_CALL = "test_subcall_revert_call.wasm" SUBCALL_REVERT_DEFINE = "test_subcall_revert_define.wasm" TRANSFER_TO_ACCOUNT = "transfer_to_account.wasm" UNBONDINGCALL = "test_unbondingcall.wasm" UPDATE_ASSOCIATED_KEY = "update_associated_key.wasm" # TEST Account Use Notes # test_transfer_to_accounts: 300, 299, 298 # test_transfer_with_overdraft: 297, 296 MAX_PAYMENT_COST = 10000000 INITIAL_MOTES_AMOUNT = 10**20 MAX_PAYMENT_ABI = ABI.args([ABI.u512(MAX_PAYMENT_COST)]) CONV_RATE = 10 TEST_ACCOUNT_INITIAL_BALANCE = 100000000 BOOTSTRAP_PATH = "/root/.casperlabs/bootstrap" @dataclasses.dataclass(eq=True, frozen=True) class KeyPair: private_key: str public_key: str def testing_root_path() -> Path: cur_path = Path(os.path.realpath(__file__)).parent while cur_path.name != "integration-testing":