From bc6c15b305cce64abac8daf6840c785bc58b7f4d Mon Sep 17 00:00:00 2001 From: Aaro Altonen Date: Sat, 6 Nov 2021 09:05:25 +0200 Subject: [PATCH] tests: Add functional tests for Destination::FundPP --- test/functional/assets/pooltester.json | 138 ++++--- test/functional/assets/pooltester.wasm | Bin 6455 -> 5996 bytes .../functional/feature_smart_contract_test.py | 383 ++++++++++++------ .../test_framework/mintlayer/utxo.py | 27 +- 4 files changed, 370 insertions(+), 178 deletions(-) diff --git a/test/functional/assets/pooltester.json b/test/functional/assets/pooltester.json index 6501429..d3f6d8e 100644 --- a/test/functional/assets/pooltester.json +++ b/test/functional/assets/pooltester.json @@ -1,44 +1,26 @@ { "metadataVersion": "0.1.0", "source": { - "hash": "0x2d1e1ed093dfb95c258342e80e7e1800c8c87cf4459837291a35245d23d15038", - "language": "ink! 3.0.0-rc4", + "hash": "0xc8d99645ca13ba2acbf5cc7942aa22fa3c94d9b5e4f0b6dc0f023494ceaf8893", + "language": "ink! 3.0.0-rc3", "compiler": "rustc 1.56.0-nightly" }, "contract": { "name": "pooltester", "version": "0.1.0", "authors": [ - "RBB S.r.l" + "[your_name] <[your_email]>" ] }, "spec": { "constructors": [ { - "args": [ - { - "name": "value", - "type": { - "displayName": [ - "i64" - ], - "type": 1 - } - } - ], + "args": [], "docs": [], "name": [ "new" ], "selector": "0x9bae9d5e" - }, - { - "args": [], - "docs": [], - "name": [ - "default" - ], - "selector": "0xed4b9d1b" } ], "docs": [], @@ -47,7 +29,7 @@ { "args": [], "docs": [], - "mutates": true, + "mutates": false, "name": [ "get" ], @@ -81,6 +63,15 @@ ], "type": 2 } + }, + { + "name": "value", + "type": { + "displayName": [ + "u128" + ], + "type": 5 + } } ], "docs": [], @@ -89,31 +80,14 @@ "send_to_pubkey" ], "payable": false, - "returnType": null, + "returnType": { + "displayName": [ + "Result" + ], + "type": 6 + }, "selector": "0xd10be299" }, - { - "args": [], - "docs": [], - "mutates": true, - "name": [ - "fund" - ], - "payable": false, - "returnType": null, - "selector": "0x4aafa343" - }, - { - "args": [], - "docs": [], - "mutates": true, - "name": [ - "send_to_self" - ], - "payable": false, - "returnType": null, - "selector": "0xba6ee83a" - }, { "args": [ { @@ -129,16 +103,16 @@ "name": "selector", "type": { "displayName": [], - "type": 5 + "type": 9 } }, { "name": "value", "type": { "displayName": [ - "i64" + "u128" ], - "type": 1 + "type": 5 } } ], @@ -148,7 +122,12 @@ "call_contract" ], "payable": false, - "returnType": null, + "returnType": { + "displayName": [ + "Result" + ], + "type": 6 + }, "selector": "0xc7c0b7ca" } ] @@ -204,6 +183,65 @@ "primitive": "u8" } }, + { + "def": { + "primitive": "u128" + } + }, + { + "def": { + "variant": { + "variants": [ + { + "fields": [ + { + "type": 7, + "typeName": "T" + } + ], + "name": "Ok" + }, + { + "fields": [ + { + "type": 8, + "typeName": "E" + } + ], + "name": "Err" + } + ] + } + }, + "params": [ + 7, + 8 + ], + "path": [ + "Result" + ] + }, + { + "def": { + "tuple": [] + } + }, + { + "def": { + "variant": { + "variants": [ + { + "discriminant": 0, + "name": "FailGetRandomSource" + } + ] + } + }, + "path": [ + "pooltester", + "TransferError" + ] + }, { "def": { "array": { diff --git a/test/functional/assets/pooltester.wasm b/test/functional/assets/pooltester.wasm index 515b1d4c46428bf23d181edf4caa00440729eb6a..948b10a2c5340a85742afa8e4ad387f37d5576d4 100644 GIT binary patch literal 5996 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 diff --git a/test/functional/feature_smart_contract_test.py b/test/functional/feature_smart_contract_test.py index 487c841..450e2cd 100755 --- a/test/functional/feature_smart_contract_test.py +++ b/test/functional/feature_smart_contract_test.py @@ -24,20 +24,18 @@ import os # helper function to reduce code duplication -def submit_pp_tx(client, input_utxo, alice, value, output): +def submit_pp_tx(client, input_utxo, alice, value, outputs): + outputs.insert(0, utxo.Output( + value=value, + data=None, + destination=utxo.DestPubkey(alice.public_key) + )) tx = utxo.Transaction( client, inputs=[ utxo.Input(input_utxo.outpoint(0)), ], - outputs=[ - utxo.Output( - value=value, - destination=utxo.DestPubkey(alice.public_key), - data=None, - ), - output - ] + outputs = outputs, ).sign(alice, [input_utxo.outputs[0]], [0]) return tx, client.submit(alice, tx) @@ -79,7 +77,7 @@ def run_test(self): client = self.nodes[0].rpc_client substrate = client.substrate alice = Keypair.create_from_uri('//Alice') - bob = Keypair.create_from_uri('//Erin') + erin = Keypair.create_from_uri('//Erin') initial_utxo = [x for x in client.utxos_for(alice) if x[1].value >= 50][0] value = initial_utxo[1].json()["value"] @@ -101,41 +99,47 @@ def run_test(self): ).sign(alice, [initial_utxo[1]]) client.submit(alice, tx) - # invalid bytecode + """ + Invalid bytecode + """ value -= 1 - (tx, (_, blk, _)) = submit_pp_tx(client, tx, alice, value, utxo.Output( + (tx, (_, blk, _)) = submit_pp_tx(client, tx, alice, value, [utxo.Output( value=1, destination=utxo.DestCreatePP( code=[0x00], data=[0xed, 0x4b, 0x9d, 0x1b], ), data=None, - )) + )]) assert_equal(contract.getContractAddresses(substrate, blk), None) - # invalid value - (invalid_tx, res) = submit_pp_tx(client, tx, alice, value, utxo.Output( + """ + Invalid value + """ + (invalid_tx, res) = submit_pp_tx(client, tx, alice, value, [utxo.Output( value=0, destination=utxo.DestCreatePP( code=os.path.join(os.path.dirname(__file__), "assets/pooltester.wasm"), data=[0xed, 0x4b, 0x9d, 0x1b], ), data=None, - )) + )]) assert_equal(res, None) - # valid data + """ + Valid data + """ value -= 1 - (tx, (_, blk, _)) = submit_pp_tx(client, tx, alice, value, utxo.Output( + (tx, (_, blk, _)) = submit_pp_tx(client, tx, alice, value, [utxo.Output( value=1, destination=utxo.DestCreatePP( code=os.path.join(os.path.dirname(__file__), "assets/pooltester.wasm"), - data=[0xed, 0x4b, 0x9d, 0x1b], + data=[0x9b, 0xae, 0x9d, 0x5e], ), data=None, - )) + )]) (ss58, acc_id) = contract.getContractAddresses(substrate, blk) contractInstance = contract.ContractInstance( @@ -148,126 +152,256 @@ def run_test(self): result = contractInstance.read(alice, "get") assert_equal(result.contract_result_data.value, 1337) - # valid contract call + """ + Valid contract call + """ value -= 1 msg_data = contractInstance.generate_message_data("flip", {}) - (tx, _) = submit_pp_tx(client, tx, alice, value, utxo.Output( + (tx, _) = submit_pp_tx(client, tx, alice, value, [utxo.Output( value=1, destination=utxo.DestCallPP( dest_account=acc_id, - fund=False, input_data=bytes.fromhex(msg_data.to_hex()[2:]), ), data=None, - )) + )]) result = contractInstance.read(alice, "get") assert_equal(result.contract_result_data.value, -1337) - # invalid `value` given + """ + Invalid `value` given + """ msg_data = contractInstance.generate_message_data("flip", {}) - (invalid_tx, res) = submit_pp_tx(client, tx, alice, value, utxo.Output( + (invalid_tx, res) = submit_pp_tx(client, tx, alice, value, [utxo.Output( value=0, destination=utxo.DestCallPP( dest_account=alice.public_key, - fund=False, input_data=bytes.fromhex(msg_data.to_hex()[2:]), ), data=None, - )) + )]) assert_equal(res, None) - # test contract-to-p2k transfer from alice to bob + # query the initial value of the contract # - # `send_to_pubkey()` first funds the smart contract from alice's funds - # and when the wasm code is executed, the funds are transferred to bob - msg_data = contractInstance.generate_message_data("send_to_pubkey", { "dest": bob.public_key }) + # each successful tranfser will update the value by one + # and each call that fails doesn't change the value + result = contractInstance.read(alice, "get") + assert_equal(result.contract_result_data.value, -1337) + + """ + Try to fund a contract that doesn't exist + """ + value -= 400 + + (tx, _) = submit_pp_tx(client, tx, alice, value, [utxo.Output( + value = 400, + data = None, + destination = utxo.DestFundPP(alice.public_key) + )]) + + """ + Try to call contract without funding it + """ + msg_data = contractInstance.generate_message_data("send_to_pubkey", { "dest": erin.public_key, "value": 555 }) value -= 555 - (tx, _) = submit_pp_tx(client, tx, alice, value, utxo.Output( + (tx, _) = submit_pp_tx(client, tx, alice, value, [utxo.Output( value = 555, destination = utxo.DestCallPP( dest_account = acc_id, - fund = True, input_data = bytes.fromhex(msg_data.to_hex()[2:]), ), data = None, - )) + )]) - # verify that bob actually received the utxo - bobs_utxos = [x for x in client.utxos_for(bob)] - assert_equal(len(bobs_utxos), 1) - assert_equal(bobs_utxos[0][1].json()['value'], 555) + # call failed, the value is not updated + result = contractInstance.read(alice, "get") + assert_equal(result.contract_result_data.value, -1337) - # test contract-to-p2pk again but this time don't fund the contract - # meaning that after the TX, bob only has the UTXO he received in the previous test case - # and the contract has a UTXO with value 666 - msg_data = contractInstance.generate_message_data("send_to_pubkey", { "dest": bob.public_key }) - value -= 666 + """ + Fund the contract (but not enough) and call it + """ + msg_data = contractInstance.generate_message_data("send_to_pubkey", { "dest": erin.public_key, "value": 500 }) + value -= 500 - (tx, _) = submit_pp_tx(client, tx, alice, value, utxo.Output( - value = 666, - destination = utxo.DestCallPP( - dest_account = acc_id, - fund = False, - input_data = bytes.fromhex(msg_data.to_hex()[2:]), + (tx, _) = submit_pp_tx(client, tx, alice, value, [ + utxo.Output( + value = 400, + data = None, + destination = utxo.DestFundPP(acc_id) ), - data = None, - )) + utxo.Output( + value = 100, + data = None, + destination = utxo.DestCallPP( + dest_account = acc_id, + input_data = bytes.fromhex(msg_data.to_hex()[2:]), + ) + ), + ]) - # verify that bob still has the same amount of UTXOs - utxos = [x for x in client.utxos_for(bob)] - assert_equal(len(utxos), 1) + # call failed, the value is not updated + result = contractInstance.read(alice, "get") + assert_equal(result.contract_result_data.value, -1337) + + """ + Fund the contract and call it + """ + msg_data = contractInstance.generate_message_data("send_to_pubkey", { "dest": erin.public_key, "value": 500 }) + value -= 200 + + (tx, _) = submit_pp_tx(client, tx, alice, value, [ + utxo.Output( + value = 100, + data = None, + destination = utxo.DestFundPP(acc_id) + ), + utxo.Output( + value = 100, + data = None, + destination = utxo.DestCallPP( + dest_account = acc_id, + input_data = bytes.fromhex(msg_data.to_hex()[2:]), + ) + ), + ]) + + # call succeeded, the value is updated + result = contractInstance.read(alice, "get") + assert_equal(result.contract_result_data.value, -1336) + + # verify that Erin has 1 UTXO with value 500 + erins = [x for x in client.utxos_for(erin.public_key)] + assert_equal(len(erins), 1) + assert_equal(erins[0][1].json()["value"], 500) + + # verify that the contract only has CallPP UTXOs + contract_utxos = [x for x in client.utxos_for(acc_id[2:])] + callpp_utxos = [x for x in contract_utxos if list(x[1].json()["destination"])[0] == "CallPP"] + assert_equal(len(contract_utxos), len(callpp_utxos)) + + """ + Fund the contract and call it but don't transfer all of the funds + """ + msg_data = contractInstance.generate_message_data("send_to_pubkey", { "dest": erin.public_key, "value": 200 }) + value -= 600 + + (tx, _) = submit_pp_tx(client, tx, alice, value, [ + utxo.Output( + value = 500, + data = None, + destination = utxo.DestFundPP(acc_id) + ), + utxo.Output( + value = 100, + data = None, + destination = utxo.DestCallPP( + dest_account = acc_id, + input_data = bytes.fromhex(msg_data.to_hex()[2:]), + ) + ), + ]) + + # call succeeded, the value is updated + result = contractInstance.read(alice, "get") + assert_equal(result.contract_result_data.value, -1335) + + # verify that erin has two UTXOs and that their total value is 700 + erins = [x for x in client.utxos_for(erin.public_key)] + total_value = sum([x[1].json()["value"] for x in erins]) + assert_equal(len(erins), 2) + assert_equal(total_value, 700) - # verify that the contract has one utxo with value 666 + # verify that the contract has one FundPP UTXO with value 300 + fundpps = [x for x in client.utxos_for(acc_id[2:]) if list(x[1].json()["destination"])[0] == "FundPP"] + assert_equal(len(fundpps), 1) + assert_equal(fundpps[0][1].json()["value"], 300) + + """ + Try to transfer all of the funds of a contract + + The contract has a UTXO with a value 300 from the previous test, try to spend it entirely + and check the state after the TX. + + Erin should have X and the contract should only have CallPP UTXOs + """ + msg_data = contractInstance.generate_message_data("send_to_pubkey", { "dest": erin.public_key, "value": 300 }) + value -= 100 + + (tx, _) = submit_pp_tx(client, tx, alice, value, [ + utxo.Output( + value = 100, + data = None, + destination = utxo.DestCallPP( + dest_account = acc_id, + input_data = bytes.fromhex(msg_data.to_hex()[2:]), + ) + ), + ]) + + # call succeeded, the value is updated + result = contractInstance.read(alice, "get") + assert_equal(result.contract_result_data.value, -1334) + + # verify that erin has two UTXOs and that their total value is 700 + erins = [x for x in client.utxos_for(erin.public_key)] + total_value = sum([x[1].json()["value"] for x in erins]) + assert_equal(len(erins), 3) + assert_equal(total_value, 1000) + + # verify that the contract doesn't have FundPP UTXOs but only CallPP UTXOs utxos = [x for x in client.utxos_for(acc_id[2:])] - assert_equal(len(utxos), 1) - assert_equal(utxos[0][1].json()["value"], 666) + fundpps = [x for x in utxos if list(x[1].json()["destination"])[0] == "FundPP"] + assert_equal(len(utxos), 6) + assert_equal(len(fundpps), 0) - # try to call a contract that doesn't exist (alice's public key - # doesn't point to a valid smart contract) - # - # TODO: because we don't have gas refunding, the money is still - # spent, i.e., if the UTXO set is queried, you'll find a UTXO - # with value 888 meaning user just lost his money which is - # not the correct behavior but the implementation is still under way - msg_data = contractInstance.generate_message_data("fund", {}) + """ + Try to call a contract that doesn't exist (alice's public key doesn't point to a valid smart contract) + + TODO: because we don't have gas refunding, the money is still spent, i.e., if the UTXO set is queried, + you'll find a UTXO with value 888 meaning user just lost his money which is not the correct behavior + but the implementation is still under way + """ value -= 888 - (tx, _) = submit_pp_tx(client, tx, alice, value, utxo.Output( + (tx, _) = submit_pp_tx(client, tx, alice, value, [utxo.Output( value = 888, destination = utxo.DestCallPP( dest_account = alice.public_key, - fund = True, - input_data = bytes.fromhex(msg_data.to_hex()[2:]), + input_data = [0x00, 0x01, 0x02, 0x03], ), data = None, - )) + )]) + # call failed, the value is not updated result = contractInstance.read(alice, "get") - assert_equal(result.contract_result_data.value, -1337) + assert_equal(result.contract_result_data.value, -1334) - # Test cross-contract calls - # - # First instantiate another smart contract and verify it has - # been created correctly by querying its value. - # - # Then call the `set_value()` method of newly instantiated contract - # indirectly by creating a UTXO that calls the pooltester's - # `call_contract()` method which dispatches the call to `set_value()` - # - # When all that's done, query the value again and verify that it has been updated + """ + Test cross-contract calls + + First instantiate another smart contract and verify it has + been created correctly by querying its value. + + Then call the `set_value()` method of newly instantiated contract + indirectly by creating a UTXO that calls the pooltester's + `call_contract()` method which dispatches the call to `set_value()` + + When all that's done, query the value again and verify that it has been updated + """ value -= 111 - (tx, (_, blk, _)) = submit_pp_tx(client, tx, alice, value, utxo.Output( + (tx, (_, blk, _)) = submit_pp_tx(client, tx, alice, value, [utxo.Output( value = 111, destination = utxo.DestCreatePP( code = os.path.join(os.path.dirname(__file__), "assets/c2c_tester.wasm"), data = [0xed, 0x4b, 0x9d, 0x1b], ), data = None, - )) + )]) (ss58_c2c, acc_id_c2c) = contract.getContractAddresses(substrate, blk) c2cInstance = contract.ContractInstance( @@ -285,56 +419,57 @@ def run_test(self): "selector": "0xc6298215", "value": 999, }) - value -= 222 + value -= 600 - (tx, _) = submit_pp_tx(client, tx, alice, value, utxo.Output( - value = 222, - destination = utxo.DestCallPP( - dest_account = acc_id, - fund = True, - input_data = bytes.fromhex(msg_data.to_hex()[2:]), + (tx, _) = submit_pp_tx(client, tx, alice, value, [ + utxo.Output( + value = 500, + data = None, + destination = utxo.DestFundPP(acc_id) ), - data = None, - )) + utxo.Output( + value = 100, + data = None, + destination = utxo.DestCallPP( + dest_account = acc_id, + input_data = bytes.fromhex(msg_data.to_hex()[2:]), + ) + ) + ]) # verify that the call succeeded result = c2cInstance.read(alice, "get") assert_equal(result.contract_result_data.value, 999) - # Try to spend the funds of a contract - # - # First fund the contract with some amount of UTXO, - # verify that the fund worked (updated state variable) - # and then try to spend those funds and verify that the - # spend is rejected by the local PP node because the - # smart contract has not spent them and thus the outpoint - # hash is not in the local storage - # - # NOTE: spending the DestCallPP UTXOs doesn't require signatures - # but instead the outpoint hash of the UTXO. This is queried - # from the runtime storage as the smart contract has not transferred - # these funds, the outpoint hash is **not** found from the storage - # and this TX is rejected as invalid - msg_data = contractInstance.generate_message_data("fund", {}) + result = contractInstance.read(alice, "get") + assert_equal(result.contract_result_data.value, -1333) + + """ + Try to spend the funds of a contract + + First fund the contract with some amount of UTXO, + verify that the fund worked (updated state variable) + and then try to spend those funds and verify that the + spend is rejected by the local PP node because the + smart contract has not spent them and thus the outpoint + hash is not in the local storage + + NOTE: spending the DestCallPP UTXOs doesn't require signatures + but instead the outpoint hash of the UTXO. This is queried + from the runtime storage as the smart contract has not transferred + these funds, the outpoint hash is **not** found from the storage + and this TX is rejected as invalid + """ value -= 555 - self.log.info("here") - self.log.error(tx) - - (tx, _) = submit_pp_tx(client, tx, alice, value, utxo.Output( + (tx, _) = submit_pp_tx(client, tx, alice, value, [utxo.Output( value = 555, - destination = utxo.DestCallPP( - dest_account = acc_id, - fund = True, - input_data = bytes.fromhex(msg_data.to_hex()[2:]), - ), data = None, - )) + destination = utxo.DestFundPP(acc_id) + )]) - result = contractInstance.read(alice, "get") - assert_equal(result.contract_result_data.value, 1338) - - utxos = [x for x in client.utxos_for(acc_id[2:])] + # fetch the FundPP UTXO that was just sent + utxos = [x for x in client.utxos_for(acc_id[2:]) if list(x[1].json()["destination"])[0] == "FundPP"] assert_equal(len(utxos), 1) assert_equal(utxos[0][1].json()["value"], 555) @@ -345,9 +480,9 @@ def run_test(self): ], outputs=[ utxo.Output( - value=555, - destination=utxo.DestPubkey(alice.public_key), - data=None, + value = 555, + data = None, + destination = utxo.DestPubkey(alice.public_key) ), ] ) diff --git a/test/functional/test_framework/mintlayer/utxo.py b/test/functional/test_framework/mintlayer/utxo.py index d95e5b3..4b95230 100644 --- a/test/functional/test_framework/mintlayer/utxo.py +++ b/test/functional/test_framework/mintlayer/utxo.py @@ -30,6 +30,10 @@ def __init__(self, url="ws://127.0.0.1", port=9944): def encode_obj(self, ty, obj): return self.substrate.encode_scale(ty, obj) + """ SCALE-decode given object """ + def decode_obj(self, ty, obj): + return self.substrate.decode_scale(ty, obj) + """ SCALE-encode given object """ def encode(self, obj): return self.encode_obj(obj.type_string(), obj.json()) @@ -155,6 +159,8 @@ def load(obj): return DestCreatePP.load(obj['CreatePP']) if 'CallPP' in obj: return DestCallPP.load(obj['CallPP']) + if 'FundPP' in obj: + return DestFundPP.load(obj['FundPP']) if 'LockForStaking' in obj: return DestLockForStaking.load(obj['LockForStaking']) if 'LockExtraForStaking' in obj: @@ -209,17 +215,30 @@ def json(self): return { 'CreatePP': { 'code': self.code, 'data': self.data } } class DestCallPP(Destination): - def __init__(self, dest_account, fund, input_data): + def __init__(self, dest_account, input_data): self.acct = dest_account - self.fund = fund self.data = input_data @staticmethod def load(obj): - return DestCallPP(ss58_decode(obj['dest_account']), obj['fund'], obj['input_data']) + return DestCallPP(ss58_decode(obj['dest_account']), obj['input_data']) + + def json(self): + return { 'CallPP': { 'dest_account': self.acct, 'input_data': self.data } } + + def get_pubkey(self): + return str(self.acct) + +class DestFundPP(Destination): + def __init__(self, dest_account): + self.acct = dest_account + + @staticmethod + def load(obj): + return DestFundPP(ss58_decode(obj['dest_account'])) def json(self): - return { 'CallPP': { 'dest_account': self.acct, 'fund': self.fund, 'input_data': self.data } } + return { 'FundPP': { 'dest_account': self.acct } } def get_pubkey(self): return str(self.acct)