From acbb3802eba21a17928467b624aee54fe8e4708b Mon Sep 17 00:00:00 2001 From: Piotr Stachyra Date: Mon, 14 Nov 2022 14:32:02 +0100 Subject: [PATCH 1/7] update cardano_wallet rb --- test/e2e/Gemfile | 2 +- test/e2e/Gemfile.lock | 4 ++-- test/e2e/gemset.nix | 28 ++++++++++++++-------------- 3 files changed, 17 insertions(+), 17 deletions(-) diff --git a/test/e2e/Gemfile b/test/e2e/Gemfile index fc6bcae454c..e53ff51dccd 100644 --- a/test/e2e/Gemfile +++ b/test/e2e/Gemfile @@ -2,7 +2,7 @@ source 'https://rubygems.org' -gem 'cardano_wallet', '~> 0.3.27' +gem 'cardano_wallet', '~> 0.4.0' # gem 'cardano_wallet', path: "~/wb/cardano_wallet" gem 'blake2b', '0.10.0' gem 'cbor', '0.5.9.6' diff --git a/test/e2e/Gemfile.lock b/test/e2e/Gemfile.lock index e7c1648baac..99d20ea4074 100644 --- a/test/e2e/Gemfile.lock +++ b/test/e2e/Gemfile.lock @@ -3,7 +3,7 @@ GEM specs: bip_mnemonic (0.0.4) blake2b (0.10.0) - cardano_wallet (0.3.27) + cardano_wallet (0.4.0) bip_mnemonic (~> 0.0.4) httparty (~> 0.18.0) cbor (0.5.9.6) @@ -36,7 +36,7 @@ PLATFORMS DEPENDENCIES blake2b (= 0.10.0) - cardano_wallet (~> 0.3.27) + cardano_wallet (~> 0.4.0) cbor (= 0.5.9.6) mustache (= 1.1.1) rake (= 12.3.3) diff --git a/test/e2e/gemset.nix b/test/e2e/gemset.nix index dfbc03d4a17..fea62f8bfcb 100644 --- a/test/e2e/gemset.nix +++ b/test/e2e/gemset.nix @@ -25,10 +25,10 @@ platforms = []; source = { remotes = ["https://rubygems.org"]; - sha256 = "1ay11nh7wcri87zzvn4i3n9lx0wnv2d5wfknk5nx8zyvlk8305r7"; + sha256 = "1az3d89mlkvy53mfdaxd73y91m1v9z96zsilvkdgagfcf21ywisj"; type = "gem"; }; - version = "0.3.27"; + version = "0.4.0"; }; cbor = { groups = ["default"]; @@ -45,10 +45,10 @@ platforms = []; source = { remotes = ["https://rubygems.org"]; - sha256 = "0m925b8xc6kbpnif9dldna24q1szg4mk0fvszrki837pfn46afmz"; + sha256 = "0rwvjahnp7cpmracd8x732rjgnilqv2sx7d1gfrysslc3h039fa9"; type = "gem"; }; - version = "1.4.4"; + version = "1.5.0"; }; httparty = { dependencies = ["mime-types" "multi_xml"]; @@ -118,10 +118,10 @@ platforms = []; source = { remotes = ["https://rubygems.org"]; - sha256 = "1dwai7jnwmdmd7ajbi2q0k0lx1dh88knv5wl7c34wjmf94yv8w5q"; + sha256 = "19dyb6rcvgi9j2mksd29wfdhfdyzqk7yjhy1ai77559hbhpg61w9"; type = "gem"; }; - version = "3.10.0"; + version = "3.11.0"; }; rspec-core = { dependencies = ["rspec-support"]; @@ -129,10 +129,10 @@ platforms = []; source = { remotes = ["https://rubygems.org"]; - sha256 = "0wwnfhxxvrlxlk1a3yxlb82k2f9lm0yn0598x7lk8fksaz4vv6mc"; + sha256 = "118hkfw9b11hvvalr7qlylwal5h8dihagm9xg7k4gskg7587hca6"; type = "gem"; }; - version = "3.10.1"; + version = "3.11.0"; }; rspec-expectations = { dependencies = ["diff-lcs" "rspec-support"]; @@ -140,10 +140,10 @@ platforms = []; source = { remotes = ["https://rubygems.org"]; - sha256 = "1sz9bj4ri28adsklnh257pnbq4r5ayziw02qf67wry0kvzazbb17"; + sha256 = "0l1bzk6a68i1b2qix83vs40r0pbjawv67hixiq2qxsja19bbq3bc"; type = "gem"; }; - version = "3.10.1"; + version = "3.11.1"; }; rspec-mocks = { dependencies = ["diff-lcs" "rspec-support"]; @@ -151,19 +151,19 @@ platforms = []; source = { remotes = ["https://rubygems.org"]; - sha256 = "1d13g6kipqqc9lmwz5b244pdwc97z15vcbnbq6n9rlf32bipdz4k"; + sha256 = "07vagjxdm5a6s103y8zkcnja6avpl8r196hrpiffmg7sk83dqdsm"; type = "gem"; }; - version = "3.10.2"; + version = "3.11.1"; }; rspec-support = { groups = ["default"]; platforms = []; source = { remotes = ["https://rubygems.org"]; - sha256 = "15j52parvb8cgvl6s0pbxi2ywxrv6x0764g222kz5flz0s4mycbl"; + sha256 = "1c01iicvrjk6vv744jgh0y4kk9d0kg2rd2ihdyzvg5p06xm2fpzq"; type = "gem"; }; - version = "3.10.2"; + version = "3.11.1"; }; } From 1186acdc3d056686187164fa7c32853a53efd44c Mon Sep 17 00:00:00 2001 From: Piotr Stachyra Date: Mon, 14 Nov 2022 17:41:04 +0100 Subject: [PATCH 2/7] extract shared e2e tests to separate suite file --- test/e2e/spec/e2e_shared_spec.rb | 644 +++++++++++++++++++++++++++++++ test/e2e/spec/e2e_spec.rb | 555 -------------------------- 2 files changed, 644 insertions(+), 555 deletions(-) create mode 100644 test/e2e/spec/e2e_shared_spec.rb diff --git a/test/e2e/spec/e2e_shared_spec.rb b/test/e2e/spec/e2e_shared_spec.rb new file mode 100644 index 00000000000..a59eba321a7 --- /dev/null +++ b/test/e2e/spec/e2e_shared_spec.rb @@ -0,0 +1,644 @@ +# frozen_string_literal: true + +RSpec.describe 'Cardano Wallet E2E tests - Shared wallets', :all, :e2e do + before(:all) do + # shelley wallets + @wid = create_fixture_wallet(:shelley) + @target_id = create_target_wallet(:shelley) + + # shared wallets + @wid_sha = create_target_wallet(:shared) + + @nightly_shared_wallets = [@wid_sha] + @nightly_shelley_wallets = [@wid, @target_id] + wait_for_all_shelley_wallets(@nightly_shelley_wallets) + wait_for_all_shared_wallets(@nightly_shared_wallets) + end + + after(:each) do + teardown + end + + after(:all) do + SHELLEY.stake_pools.quit(@target_id, PASS) + end + + describe 'E2E Shared' do + describe 'E2E Construct -> Sign -> Submit', :shared do + it 'I can get min_utxo_value when contructing tx' do + amt = 1 + tx_constructed = SHARED.transactions.construct(@wid_sha, payment_payload(amt)) + expect(tx_constructed.code).to eq 403 + expect(tx_constructed['code']).to eq 'utxo_too_small' + required_minimum = tx_constructed['info']['tx_output_lovelace_required_minimum']['quantity'] + + tx_constructed = SHARED.transactions.construct(@wid_sha, payment_payload(required_minimum)) + expect(tx_constructed).to be_correct_and_respond 202 + end + + it 'Single output transaction' do + amt = MIN_UTXO_VALUE_PURE_ADA * 2 + address = SHELLEY.addresses.list(@target_id)[1]['id'] + target_before = get_shelley_balances(@target_id) + src_before = get_shared_balances(@wid_sha) + + tx_constructed = SHARED.transactions.construct(@wid_sha, payment_payload(amt, address)) + expect(tx_constructed).to be_correct_and_respond 202 + expected_fee = tx_constructed['fee']['quantity'] + + # Can be decoded + tx_decoded = SHARED.transactions.decode(@wid_sha, tx_constructed['transaction']) + expect(tx_decoded).to be_correct_and_respond 202 + + expect(tx_decoded['id'].size).to be 64 + decoded_fee = tx_decoded['fee']['quantity'] + expect(expected_fee).to eq decoded_fee + # inputs are ours + expect(tx_decoded['inputs'].to_s).to include 'address' + expect(tx_decoded['inputs'].to_s).to include 'amount' + expect(tx_decoded['outputs']).not_to eq [] + expect(tx_decoded['script_validity']).to eq 'valid' + expect(tx_decoded['validity_interval']['invalid_before']).to eq({ 'quantity' => 0, 'unit' => 'slot' }) + expect(tx_decoded['validity_interval']['invalid_hereafter']['quantity']).to be > 0 + expect(tx_decoded['collateral']).to eq [] + expect(tx_decoded['collateral_outputs']).to eq [] + expect(tx_decoded['metadata']).to eq nil + expect(tx_decoded['deposits_taken']).to eq [] + expect(tx_decoded['deposits_returned']).to eq [] + expect(tx_decoded['withdrawals']).to eq [] + expect(tx_decoded['mint']).to eq({ 'tokens' => [] }) + expect(tx_decoded['burn']).to eq({ 'tokens' => [] }) + expect(tx_decoded['certificates']).to eq [] + + tx_signed = SHARED.transactions.sign(@wid_sha, PASS, tx_constructed['transaction']) + expect(tx_signed).to be_correct_and_respond 202 + + tx_submitted = SHARED.transactions.submit(@wid_sha, tx_signed['transaction']) + expect(tx_submitted).to be_correct_and_respond 202 + + tx_id = tx_submitted['id'] + # TODO: ADP-2224: change to wait_for_tx_in_ledger(@wid_sha, tx_id) + eventually "Funds are on target wallet: #{@target_id}" do + available = SHELLEY.wallets.get(@target_id)['balance']['available']['quantity'] + total = SHELLEY.wallets.get(@target_id)['balance']['total']['quantity'] + (available == amt + target_before['available']) && + (total == amt + target_before['total']) + end + + target_after = get_shelley_balances(@target_id) + src_after = get_shared_balances(@wid_sha) + + verify_ada_balance(src_after, src_before, + target_after, target_before, + amt, expected_fee) + # tx history + # TODO ADP-2224: check tx history on src wallet + # on target wallet + txt = SHELLEY.transactions.get(@target_id, tx_id) + tx_amount(txt, amt) + tx_fee(txt, 0) + tx_inputs(txt, present: true) + tx_outputs(txt, present: true) + tx_direction(txt, 'incoming') + tx_script_validity(txt, 'valid') + tx_status(txt, 'in_ledger') + tx_collateral(txt, present: false) + tx_collateral_outputs(txt, present: false) + tx_metadata(txt, nil) + tx_deposits(txt, deposit_taken: 0, deposit_returned: 0) + tx_withdrawals(txt, present: false) + tx_mint_burn(txt, mint: [], burn: []) + tx_extra_signatures(txt, present: false) + tx_script_integrity(txt, present: false) + tx_validity_interval_default(txt) + tx_certificates(txt, present: false) + end + + it 'Multi output transaction' do + amt = MIN_UTXO_VALUE_PURE_ADA + address = SHELLEY.addresses.list(@target_id)[1]['id'] + target_before = get_shelley_balances(@target_id) + src_before = get_shared_balances(@wid_sha) + + payment = [{ address: address, + amount: { quantity: amt, + unit: 'lovelace' } }, + { address: address, + amount: { quantity: amt, + unit: 'lovelace' } }] + tx_constructed = SHARED.transactions.construct(@wid_sha, payment) + expect(tx_constructed).to be_correct_and_respond 202 + expected_fee = tx_constructed['fee']['quantity'] + + # Can be decoded + tx_decoded = SHARED.transactions.decode(@wid_sha, tx_constructed['transaction']) + expect(tx_decoded).to be_correct_and_respond 202 + + expect(tx_decoded['id'].size).to be 64 + decoded_fee = tx_decoded['fee']['quantity'] + expect(expected_fee).to eq decoded_fee + # inputs are ours + expect(tx_decoded['inputs'].to_s).to include 'address' + expect(tx_decoded['inputs'].to_s).to include 'amount' + expect(tx_decoded['outputs']).not_to eq [] + expect(tx_decoded['script_validity']).to eq 'valid' + expect(tx_decoded['validity_interval']['invalid_before']).to eq({ 'quantity' => 0, 'unit' => 'slot' }) + expect(tx_decoded['validity_interval']['invalid_hereafter']['quantity']).to be > 0 + expect(tx_decoded['collateral']).to eq [] + expect(tx_decoded['collateral_outputs']).to eq [] + expect(tx_decoded['metadata']).to eq nil + expect(tx_decoded['deposits_taken']).to eq [] + expect(tx_decoded['deposits_returned']).to eq [] + expect(tx_decoded['withdrawals']).to eq [] + expect(tx_decoded['mint']).to eq({ 'tokens' => [] }) + expect(tx_decoded['burn']).to eq({ 'tokens' => [] }) + expect(tx_decoded['certificates']).to eq [] + + tx_signed = SHARED.transactions.sign(@wid_sha, PASS, tx_constructed['transaction']) + expect(tx_signed).to be_correct_and_respond 202 + + tx_submitted = SHARED.transactions.submit(@wid_sha, tx_signed['transaction']) + expect(tx_submitted).to be_correct_and_respond 202 + + tx_id = tx_submitted['id'] + # TODO: ADP-2224: change to wait_for_tx_in_ledger(@wid_sha, tx_id) + eventually "Funds are on target wallet: #{@target_id}" do + available = SHELLEY.wallets.get(@target_id)['balance']['available']['quantity'] + total = SHELLEY.wallets.get(@target_id)['balance']['total']['quantity'] + (available == (amt * 2) + target_before['available']) && + (total == (amt * 2) + target_before['total']) + end + + target_after = get_shelley_balances(@target_id) + src_after = get_shared_balances(@wid_sha) + + verify_ada_balance(src_after, src_before, + target_after, target_before, + amt * 2, expected_fee) + # tx history + # TODO ADP-2224: check tx history on src wallet + # on target wallet + txt = SHELLEY.transactions.get(@target_id, tx_id) + tx_amount(txt, amt * 2) + tx_fee(txt, 0) + tx_inputs(txt, present: true) + tx_outputs(txt, present: true) + tx_direction(txt, 'incoming') + tx_script_validity(txt, 'valid') + tx_status(txt, 'in_ledger') + tx_collateral(txt, present: false) + tx_collateral_outputs(txt, present: false) + tx_metadata(txt, nil) + tx_deposits(txt, deposit_taken: 0, deposit_returned: 0) + tx_withdrawals(txt, present: false) + tx_mint_burn(txt, mint: [], burn: []) + tx_extra_signatures(txt, present: false) + tx_script_integrity(txt, present: false) + tx_validity_interval_default(txt) + tx_certificates(txt, present: false) + end + + it 'Multi-assets transaction' do + amt = 1 + amt_ada = 1_600_000 + address = SHELLEY.addresses.list(@target_id)[1]['id'] + target_before = get_shelley_balances(@target_id) + src_before = get_shared_balances(@wid_sha) + + payment = [{ 'address' => address, + 'amount' => { 'quantity' => amt_ada, 'unit' => 'lovelace' }, + 'assets' => [{ 'policy_id' => ASSETS[0]['policy_id'], + 'asset_name' => ASSETS[0]['asset_name'], + 'quantity' => amt }, + { 'policy_id' => ASSETS[1]['policy_id'], + 'asset_name' => ASSETS[1]['asset_name'], + 'quantity' => amt }] }] + tx_constructed = SHARED.transactions.construct(@wid_sha, payment) + expect(tx_constructed).to be_correct_and_respond 202 + expected_fee = tx_constructed['fee']['quantity'] + + # Can be decoded + tx_decoded = SHARED.transactions.decode(@wid_sha, tx_constructed['transaction']) + expect(tx_decoded).to be_correct_and_respond 202 + + expect(tx_decoded['id'].size).to be 64 + decoded_fee = tx_decoded['fee']['quantity'] + expect(expected_fee).to eq decoded_fee + # inputs are ours + expect(tx_decoded['inputs'].to_s).to include 'address' + expect(tx_decoded['inputs'].to_s).to include 'amount' + expect(tx_decoded['outputs']).not_to eq [] + expect(tx_decoded['script_validity']).to eq 'valid' + expect(tx_decoded['validity_interval']['invalid_before']).to eq({ 'quantity' => 0, 'unit' => 'slot' }) + expect(tx_decoded['validity_interval']['invalid_hereafter']['quantity']).to be > 0 + expect(tx_decoded['collateral']).to eq [] + expect(tx_decoded['collateral_outputs']).to eq [] + expect(tx_decoded['metadata']).to eq nil + expect(tx_decoded['deposits_taken']).to eq [] + expect(tx_decoded['deposits_returned']).to eq [] + expect(tx_decoded['withdrawals']).to eq [] + expect(tx_decoded['mint']).to eq({ 'tokens' => [] }) + expect(tx_decoded['burn']).to eq({ 'tokens' => [] }) + expect(tx_decoded['certificates']).to eq [] + + tx_signed = SHARED.transactions.sign(@wid_sha, PASS, tx_constructed['transaction']) + expect(tx_signed).to be_correct_and_respond 202 + + # ADP-2221 [SharedWallets] FeeTooSmallUTxO when submitting transaction from Shared wallet + tx_submitted = SHARED.transactions.submit(@wid_sha, tx_signed['transaction']) + expect(tx_submitted).to be_correct_and_respond 202 + + tx_id = tx_submitted['id'] + # TODO: ADP-2224: change to wait_for_tx_in_ledger(@wid_sha, tx_id) + eventually "Funds are on target wallet: #{@target_id}" do + available = SHELLEY.wallets.get(@target_id)['balance']['available']['quantity'] + total = SHELLEY.wallets.get(@target_id)['balance']['total']['quantity'] + (available == amt_ada + target_before['available']) && + (total == amt_ada + target_before['total']) + end + + target_after = get_shelley_balances(@target_id) + src_after = get_shared_balances(@wid_sha) + + verify_ada_balance(src_after, src_before, + target_after, target_before, + amt_ada, expected_fee) + + verify_asset_balance(src_after, src_before, + target_after, target_before, + amt) + # tx history + # TODO ADP-2224: check tx history on src wallet + # on target wallet + txt = SHELLEY.transactions.get(@target_id, tx_id) + tx_amount(txt, amt_ada) + tx_fee(txt, 0) + tx_inputs(txt, present: true) + tx_outputs(txt, present: true) + tx_direction(txt, 'incoming') + tx_script_validity(txt, 'valid') + tx_status(txt, 'in_ledger') + tx_collateral(txt, present: false) + tx_collateral_outputs(txt, present: false) + tx_metadata(txt, nil) + tx_deposits(txt, deposit_taken: 0, deposit_returned: 0) + tx_withdrawals(txt, present: false) + tx_mint_burn(txt, mint: [], burn: []) + tx_extra_signatures(txt, present: false) + tx_script_integrity(txt, present: false) + tx_validity_interval_default(txt) + tx_certificates(txt, present: false) + end + + it 'Validity intervals' do + amt = MIN_UTXO_VALUE_PURE_ADA + address = SHELLEY.addresses.list(@target_id)[1]['id'] + target_before = get_shelley_balances(@target_id) + src_before = get_shared_balances(@wid_sha) + inv_before = 500 + inv_hereafter = 5_000_000_000 + validity_interval = { 'invalid_before' => { 'quantity' => inv_before, 'unit' => 'slot' }, + 'invalid_hereafter' => { 'quantity' => inv_hereafter, 'unit' => 'slot' } } + tx_constructed = SHARED.transactions.construct(@wid_sha, + payment_payload(amt, address), + nil, # withdrawal + nil, # metadata + nil, # delegations + nil, # mint_burn + validity_interval) + expect(tx_constructed).to be_correct_and_respond 202 + expected_fee = tx_constructed['fee']['quantity'] + + # Can be decoded + tx_decoded = SHARED.transactions.decode(@wid_sha, tx_constructed['transaction']) + expect(tx_decoded).to be_correct_and_respond 202 + + expect(tx_decoded['id'].size).to be 64 + decoded_fee = tx_decoded['fee']['quantity'] + expect(expected_fee).to eq decoded_fee + # inputs are ours + expect(tx_decoded['inputs'].to_s).to include 'address' + expect(tx_decoded['inputs'].to_s).to include 'amount' + expect(tx_decoded['outputs']).not_to eq [] + expect(tx_decoded['script_validity']).to eq 'valid' + expect(tx_decoded['validity_interval']['invalid_before']).to eq validity_interval['invalid_before'] + expect(tx_decoded['validity_interval']['invalid_hereafter']).to eq validity_interval['invalid_hereafter'] + expect(tx_decoded['collateral']).to eq [] + expect(tx_decoded['collateral_outputs']).to eq [] + expect(tx_decoded['metadata']).to eq nil + expect(tx_decoded['deposits_taken']).to eq [] + expect(tx_decoded['deposits_returned']).to eq [] + expect(tx_decoded['withdrawals']).to eq [] + expect(tx_decoded['mint']).to eq({ 'tokens' => [] }) + expect(tx_decoded['burn']).to eq({ 'tokens' => [] }) + expect(tx_decoded['certificates']).to eq [] + + tx_signed = SHARED.transactions.sign(@wid_sha, PASS, tx_constructed['transaction']) + expect(tx_signed).to be_correct_and_respond 202 + + # ADP-2221 [SharedWallets] FeeTooSmallUTxO when submitting transaction from Shared wallet + tx_submitted = SHARED.transactions.submit(@wid_sha, tx_signed['transaction']) + expect(tx_submitted).to be_correct_and_respond 202 + + tx_id = tx_submitted['id'] + # TODO: ADP-2224: change to wait_for_tx_in_ledger(@wid_sha, tx_id) + eventually "Funds are on target wallet: #{@target_id}" do + available = SHELLEY.wallets.get(@target_id)['balance']['available']['quantity'] + total = SHELLEY.wallets.get(@target_id)['balance']['total']['quantity'] + (available == amt + target_before['available']) && + (total == amt + target_before['total']) + end + + target_after = get_shelley_balances(@target_id) + src_after = get_shared_balances(@wid_sha) + + verify_ada_balance(src_after, src_before, + target_after, target_before, + amt, expected_fee) + # tx history + # TODO ADP-2224: check tx history on src wallet + # on target wallet + txt = SHELLEY.transactions.get(@target_id, tx_id) + tx_amount(txt, amt) + tx_fee(txt, 0) + tx_inputs(txt, present: true) + tx_outputs(txt, present: true) + tx_direction(txt, 'incoming') + tx_script_validity(txt, 'valid') + tx_status(txt, 'in_ledger') + tx_collateral(txt, present: false) + tx_collateral_outputs(txt, present: false) + tx_metadata(txt, nil) + tx_deposits(txt, deposit_taken: 0, deposit_returned: 0) + tx_withdrawals(txt, present: false) + tx_mint_burn(txt, mint: [], burn: []) + tx_extra_signatures(txt, present: false) + tx_script_integrity(txt, present: false) + tx_validity_interval(txt, invalid_before: inv_before, invalid_hereafter: inv_hereafter) + tx_certificates(txt, present: false) + end + + it 'Only metadata (without submitting)' do + # We can submit such tx, but cannot tell when tx is actually in ledger + # (as we cannot get tx history (ADP-2224)) + metadata = METADATA + # balance = get_shared_balances(@wid_sha) + tx_constructed = SHARED.transactions.construct(@wid_sha, + nil, # payments + nil, # withdrawal + metadata) + expect(tx_constructed).to be_correct_and_respond 202 + expected_fee = tx_constructed['fee']['quantity'] + + # Can be decoded + tx_decoded = SHARED.transactions.decode(@wid_sha, tx_constructed['transaction']) + expect(tx_decoded).to be_correct_and_respond 202 + + expect(tx_decoded['id'].size).to be 64 + decoded_fee = tx_decoded['fee']['quantity'] + expect(expected_fee).to eq decoded_fee + # inputs are ours + expect(tx_decoded['inputs'].to_s).to include 'address' + expect(tx_decoded['inputs'].to_s).to include 'amount' + expect(tx_decoded['outputs']).not_to eq [] + expect(tx_decoded['script_validity']).to eq 'valid' + expect(tx_decoded['validity_interval']['invalid_before']).to eq({ 'quantity' => 0, 'unit' => 'slot' }) + expect(tx_decoded['validity_interval']['invalid_hereafter']['quantity']).to be > 0 + expect(tx_decoded['collateral']).to eq [] + expect(tx_decoded['collateral_outputs']).to eq [] + expect(tx_decoded['metadata']).to eq metadata + expect(tx_decoded['deposits_taken']).to eq [] + expect(tx_decoded['deposits_returned']).to eq [] + expect(tx_decoded['withdrawals']).to eq [] + expect(tx_decoded['mint']).to eq({ 'tokens' => [] }) + expect(tx_decoded['burn']).to eq({ 'tokens' => [] }) + expect(tx_decoded['certificates']).to eq [] + + tx_signed = SHARED.transactions.sign(@wid_sha, PASS, tx_constructed['transaction']) + expect(tx_signed).to be_correct_and_respond 202 + + # TODO: ADP-2224: cannot tell when tx is actually in ledger, so this needs + # to be commented for now, because of potential race conditions with subsequent tests + # tx_submitted = SHARED.transactions.submit(@wid_sha, tx_signed["transaction"]) + # expect(tx_submitted).to be_correct_and_respond 202 + # tx_id = tx_submitted['id'] + + # TODO: ADP-2224: change to wait_for_tx_in_ledger(@wid_sha, tx_id) + # TODO ADP-2224: check tx history on src wallet and metadata is there + end + + it 'Delegation (without submitting)' do + # Delegation not yet implemented, only construct and sign in this tc + # balance = get_shared_balances(@wid_sha) + expected_deposit = CARDANO_CLI.protocol_params['stakeAddressDeposit'] + puts "Expected deposit #{expected_deposit}" + + # Pick up pool id to join + pools = SHELLEY.stake_pools + pool_id = pools.list({ stake: 1000 }).sample['id'] + + # Join pool + delegation = [{ + 'join' => { + 'pool' => pool_id, + 'stake_key_index' => '0H' + } + }] + + tx_constructed = SHARED.transactions.construct(@wid_sha, + nil, # payment + nil, # withdrawal + nil, # metadata + delegation, + nil, # mint_burn + nil) # validity_interval + # Check fee and deposit on joining + tx_decoded = SHARED.transactions.decode(@wid_sha, tx_constructed['transaction']) + expect(tx_decoded).to be_correct_and_respond 202 + + # TODO: although you can construct and decode delegation, deposit_taken /deposit_returned are not shown atm + # deposit_taken = tx_constructed['coin_selection']['deposits_taken'].first['quantity'] + # decoded_deposit_taken = tx_decoded['deposits_taken'].first['quantity'] + # expect(deposit_taken).to eq decoded_deposit_taken + # expect(deposit_taken).to eq expected_deposit + + expected_fee = tx_constructed['fee']['quantity'] + decoded_fee = tx_decoded['fee']['quantity'] + expect(decoded_fee).to eq expected_fee + # inputs are ours + expect(tx_decoded['inputs'].to_s).to include 'address' + expect(tx_decoded['inputs'].to_s).to include 'amount' + expect(tx_decoded['outputs']).not_to eq [] + expect(tx_decoded['script_validity']).to eq 'valid' + expect(tx_decoded['validity_interval']['invalid_before']).to eq({ 'quantity' => 0, 'unit' => 'slot' }) + expect(tx_decoded['validity_interval']['invalid_hereafter']['quantity']).to be > 0 + expect(tx_decoded['collateral']).to eq [] + expect(tx_decoded['collateral_outputs']).to eq [] + expect(tx_decoded['metadata']).to eq nil + expect(tx_decoded['deposits_taken']).to eq [] + expect(tx_decoded['deposits_returned']).to eq [] + expect(tx_decoded['withdrawals']).to eq [] + expect(tx_decoded['mint']).to eq({ 'tokens' => [] }) + expect(tx_decoded['burn']).to eq({ 'tokens' => [] }) + expect(tx_decoded['certificates']).to eq [] + + tx_signed = SHARED.transactions.sign(@wid_sha, PASS, tx_constructed['transaction']) + expect(tx_signed).to be_correct_and_respond 202 + end + + describe 'Minting and Burning' do + it 'Can mint and then burn (without submitting)' do + # Minting and Burning not yet implemented, only construct and sign in this tc + # src_before = get_shared_balances(@wid_sha) + policy_script1 = 'cosigner#0' + policy_script2 = { 'all' => ['cosigner#0'] } + policy_script3 = { 'any' => ['cosigner#0'] } + + # Minting: + mint = [mint(asset_name('Token1'), 1000, policy_script1), + mint(asset_name('Token2'), 1000, policy_script2), + mint('', 1000, policy_script3)] + + tx_constructed = SHARED.transactions.construct(@wid_sha, + nil, # payment + nil, # withdrawal + nil, # metadata + nil, # delegation + mint) + expect(tx_constructed).to be_correct_and_respond 202 + + tx_decoded = SHARED.transactions.decode(@wid_sha, tx_constructed['transaction']) + expect(tx_decoded).to be_correct_and_respond 202 + + expected_fee = tx_constructed['fee']['quantity'] + decoded_fee = tx_decoded['fee']['quantity'] + expect(expected_fee).to eq decoded_fee + # inputs are ours + expect(tx_decoded['inputs'].to_s).to include 'address' + expect(tx_decoded['inputs'].to_s).to include 'amount' + expect(tx_decoded['outputs']).not_to eq [] + expect(tx_decoded['script_validity']).to eq 'valid' + expect(tx_decoded['validity_interval']['invalid_before']).to eq({ 'quantity' => 0, 'unit' => 'slot' }) + expect(tx_decoded['validity_interval']['invalid_hereafter']['quantity']).to be > 0 + expect(tx_decoded['collateral']).to eq [] + expect(tx_decoded['collateral_outputs']).to eq [] + expect(tx_decoded['metadata']).to eq nil + expect(tx_decoded['deposits_taken']).to eq [] + expect(tx_decoded['deposits_returned']).to eq [] + expect(tx_decoded['withdrawals']).to eq [] + # TODO: mint / burn currently not decoded + expect(tx_decoded['mint']).to eq({ 'tokens' => [] }) + expect(tx_decoded['burn']).to eq({ 'tokens' => [] }) + expect(tx_decoded['certificates']).to eq [] + + tx_signed = SHARED.transactions.sign(@wid_sha, PASS, tx_constructed['transaction']) + expect(tx_signed).to be_correct_and_respond 202 + end + end + end + + it 'I can receive transaction to shared wallet' do + amt = 1 + amt_ada = 3_000_000 + address = SHARED.addresses.list(@wid_sha)[1]['id'] + target_before = get_shared_balances(@wid_sha) + src_before = get_shelley_balances(@wid) + + payload = [{ 'address' => address, + 'amount' => { 'quantity' => amt_ada, 'unit' => 'lovelace' }, + 'assets' => [{ 'policy_id' => ASSETS[0]['policy_id'], + 'asset_name' => ASSETS[0]['asset_name'], + 'quantity' => amt }, + { 'policy_id' => ASSETS[1]['policy_id'], + 'asset_name' => ASSETS[1]['asset_name'], + 'quantity' => amt }] }] + + tx_sent = SHELLEY.transactions.create(@wid, PASS, payload) + + expect(tx_sent).to be_correct_and_respond 202 + expect(tx_sent.to_s).to include 'pending' + wait_for_tx_in_ledger(@wid, tx_sent['id']) + + target_after = get_shared_balances(@wid_sha) + src_after = get_shelley_balances(@wid) + fee = SHELLEY.transactions.get(@wid, tx_sent['id'])['fee']['quantity'] + + verify_ada_balance(src_after, src_before, + target_after, target_before, + amt_ada, fee) + + verify_asset_balance(src_after, src_before, + target_after, target_before, + amt) + end + end + + describe 'E2E Migration' do + it 'I can migrate all funds back to fixture shared wallet' do + address = SHARED.addresses.list(@wid_sha)[0]['id'] + src_before = get_shelley_balances(@target_id) + target_before = get_shared_balances(@wid_sha) + + migration = SHELLEY.migrations.migrate(@target_id, PASS, [address]) + tx_ids = migration.map { |m| m['id'] } + fees = migration.map { |m| m['fee']['quantity'] }.sum + amounts = migration.map { |m| m['amount']['quantity'] }.sum - fees + tx_ids.each do |tx_id| + wait_for_tx_in_ledger(@target_id, tx_id) + end + src_after = get_shelley_balances(@target_id) + target_after = get_shared_balances(@wid_sha) + expected_src_balance = { 'total' => 0, + 'available' => 0, + 'rewards' => 0, + 'assets_total' => [], + 'assets_available' => [] } + + expect(src_after).to eq expected_src_balance + + verify_ada_balance(src_after, src_before, + target_after, target_before, + amounts, fees) + + tx_ids.each do |tx_id| + # examine the tx in history + # on src wallet + tx = SHELLEY.transactions.get(@target_id, tx_id) + tx_inputs(tx, present: true) + tx_outputs(tx, present: true) + tx_direction(tx, 'outgoing') + tx_script_validity(tx, 'valid') + tx_status(tx, 'in_ledger') + tx_collateral(tx, present: false) + tx_collateral_outputs(tx, present: false) + tx_metadata(tx, nil) + tx_deposits(tx, deposit_taken: 0, deposit_returned: 0) + tx_withdrawals(tx, present: false) + tx_mint_burn(tx, mint: [], burn: []) + tx_extra_signatures(tx, present: false) + tx_script_integrity(tx, present: false) + tx_validity_interval_default(tx) + tx_certificates(tx, present: false) + + # on target wallet + txt = SHARED.transactions.get(@wid_sha, tx_id) + tx_fee(txt, 0) + tx_inputs(txt, present: true) + tx_outputs(txt, present: true) + tx_direction(txt, 'incoming') + tx_script_validity(txt, 'valid') + tx_status(txt, 'in_ledger') + tx_collateral(txt, present: false) + tx_collateral_outputs(txt, present: false) + tx_metadata(txt, nil) + tx_deposits(txt, deposit_taken: 0, deposit_returned: 0) + tx_withdrawals(txt, present: false) + tx_mint_burn(txt, mint: [], burn: []) + tx_extra_signatures(txt, present: false) + tx_script_integrity(txt, present: false) + tx_validity_interval_default(txt) + tx_certificates(txt, present: false) + end + end + end +end diff --git a/test/e2e/spec/e2e_spec.rb b/test/e2e/spec/e2e_spec.rb index 9c9394f6654..b51b8ff4d38 100644 --- a/test/e2e/spec/e2e_spec.rb +++ b/test/e2e/spec/e2e_spec.rb @@ -10,14 +10,9 @@ @wid_rnd = create_fixture_wallet(:random) @wid_ic = create_fixture_wallet(:icarus) - # shared wallets - @wid_sha = create_target_wallet(:shared) - - @nightly_shared_wallets = [@wid_sha] @nighly_byron_wallets = [@wid_rnd, @wid_ic] @nightly_shelley_wallets = [@wid, @target_id] wait_for_all_shelley_wallets(@nightly_shelley_wallets) - wait_for_all_shared_wallets(@nightly_shared_wallets) wait_for_all_byron_wallets(@nighly_byron_wallets) end @@ -2191,556 +2186,6 @@ def fingerprint end end - describe 'E2E Shared' do - describe 'E2E Construct -> Sign -> Submit', :shared do - it 'I can get min_utxo_value when contructing tx' do - amt = 1 - tx_constructed = SHARED.transactions.construct(@wid_sha, payment_payload(amt)) - expect(tx_constructed.code).to eq 403 - expect(tx_constructed['code']).to eq 'utxo_too_small' - required_minimum = tx_constructed['info']['tx_output_lovelace_required_minimum']['quantity'] - - tx_constructed = SHARED.transactions.construct(@wid_sha, payment_payload(required_minimum)) - expect(tx_constructed).to be_correct_and_respond 202 - end - - it 'Single output transaction' do - amt = MIN_UTXO_VALUE_PURE_ADA - address = SHELLEY.addresses.list(@target_id)[1]['id'] - target_before = get_shelley_balances(@target_id) - src_before = get_shared_balances(@wid_sha) - - tx_constructed = SHARED.transactions.construct(@wid_sha, payment_payload(amt, address)) - expect(tx_constructed).to be_correct_and_respond 202 - expected_fee = tx_constructed['fee']['quantity'] - - # Can be decoded - tx_decoded = SHARED.transactions.decode(@wid_sha, tx_constructed['transaction']) - expect(tx_decoded).to be_correct_and_respond 202 - - expect(tx_decoded['id'].size).to be 64 - decoded_fee = tx_decoded['fee']['quantity'] - expect(expected_fee).to eq decoded_fee - # inputs are ours - expect(tx_decoded['inputs'].to_s).to include 'address' - expect(tx_decoded['inputs'].to_s).to include 'amount' - expect(tx_decoded['outputs']).not_to eq [] - expect(tx_decoded['script_validity']).to eq 'valid' - expect(tx_decoded['validity_interval']['invalid_before']).to eq({ 'quantity' => 0, 'unit' => 'slot' }) - expect(tx_decoded['validity_interval']['invalid_hereafter']['quantity']).to be > 0 - expect(tx_decoded['collateral']).to eq [] - expect(tx_decoded['collateral_outputs']).to eq [] - expect(tx_decoded['metadata']).to eq nil - expect(tx_decoded['deposits_taken']).to eq [] - expect(tx_decoded['deposits_returned']).to eq [] - expect(tx_decoded['withdrawals']).to eq [] - expect(tx_decoded['mint']).to eq({ 'tokens' => [] }) - expect(tx_decoded['burn']).to eq({ 'tokens' => [] }) - expect(tx_decoded['certificates']).to eq [] - - tx_signed = SHARED.transactions.sign(@wid_sha, PASS, tx_constructed['transaction']) - expect(tx_signed).to be_correct_and_respond 202 - - tx_submitted = SHARED.transactions.submit(@wid_sha, tx_signed['transaction']) - expect(tx_submitted).to be_correct_and_respond 202 - - tx_id = tx_submitted['id'] - # TODO: ADP-2224: change to wait_for_tx_in_ledger(@wid_sha, tx_id) - eventually "Funds are on target wallet: #{@target_id}" do - available = SHELLEY.wallets.get(@target_id)['balance']['available']['quantity'] - total = SHELLEY.wallets.get(@target_id)['balance']['total']['quantity'] - (available == amt + target_before['available']) && - (total == amt + target_before['total']) - end - - target_after = get_shelley_balances(@target_id) - src_after = get_shared_balances(@wid_sha) - - verify_ada_balance(src_after, src_before, - target_after, target_before, - amt, expected_fee) - # tx history - # TODO ADP-2224: check tx history on src wallet - # on target wallet - txt = SHELLEY.transactions.get(@target_id, tx_id) - tx_amount(txt, amt) - tx_fee(txt, 0) - tx_inputs(txt, present: true) - tx_outputs(txt, present: true) - tx_direction(txt, 'incoming') - tx_script_validity(txt, 'valid') - tx_status(txt, 'in_ledger') - tx_collateral(txt, present: false) - tx_collateral_outputs(txt, present: false) - tx_metadata(txt, nil) - tx_deposits(txt, deposit_taken: 0, deposit_returned: 0) - tx_withdrawals(txt, present: false) - tx_mint_burn(txt, mint: [], burn: []) - tx_extra_signatures(txt, present: false) - tx_script_integrity(txt, present: false) - tx_validity_interval_default(txt) - tx_certificates(txt, present: false) - end - - it 'Multi output transaction' do - amt = MIN_UTXO_VALUE_PURE_ADA - address = SHELLEY.addresses.list(@target_id)[1]['id'] - target_before = get_shelley_balances(@target_id) - src_before = get_shared_balances(@wid_sha) - - payment = [{ address: address, - amount: { quantity: amt, - unit: 'lovelace' } }, - { address: address, - amount: { quantity: amt, - unit: 'lovelace' } }] - tx_constructed = SHARED.transactions.construct(@wid_sha, payment) - expect(tx_constructed).to be_correct_and_respond 202 - expected_fee = tx_constructed['fee']['quantity'] - - # Can be decoded - tx_decoded = SHARED.transactions.decode(@wid_sha, tx_constructed['transaction']) - expect(tx_decoded).to be_correct_and_respond 202 - - expect(tx_decoded['id'].size).to be 64 - decoded_fee = tx_decoded['fee']['quantity'] - expect(expected_fee).to eq decoded_fee - # inputs are ours - expect(tx_decoded['inputs'].to_s).to include 'address' - expect(tx_decoded['inputs'].to_s).to include 'amount' - expect(tx_decoded['outputs']).not_to eq [] - expect(tx_decoded['script_validity']).to eq 'valid' - expect(tx_decoded['validity_interval']['invalid_before']).to eq({ 'quantity' => 0, 'unit' => 'slot' }) - expect(tx_decoded['validity_interval']['invalid_hereafter']['quantity']).to be > 0 - expect(tx_decoded['collateral']).to eq [] - expect(tx_decoded['collateral_outputs']).to eq [] - expect(tx_decoded['metadata']).to eq nil - expect(tx_decoded['deposits_taken']).to eq [] - expect(tx_decoded['deposits_returned']).to eq [] - expect(tx_decoded['withdrawals']).to eq [] - expect(tx_decoded['mint']).to eq({ 'tokens' => [] }) - expect(tx_decoded['burn']).to eq({ 'tokens' => [] }) - expect(tx_decoded['certificates']).to eq [] - - tx_signed = SHARED.transactions.sign(@wid_sha, PASS, tx_constructed['transaction']) - expect(tx_signed).to be_correct_and_respond 202 - - tx_submitted = SHARED.transactions.submit(@wid_sha, tx_signed['transaction']) - expect(tx_submitted).to be_correct_and_respond 202 - - tx_id = tx_submitted['id'] - # TODO: ADP-2224: change to wait_for_tx_in_ledger(@wid_sha, tx_id) - eventually "Funds are on target wallet: #{@target_id}" do - available = SHELLEY.wallets.get(@target_id)['balance']['available']['quantity'] - total = SHELLEY.wallets.get(@target_id)['balance']['total']['quantity'] - (available == (amt * 2) + target_before['available']) && - (total == (amt * 2) + target_before['total']) - end - - target_after = get_shelley_balances(@target_id) - src_after = get_shared_balances(@wid_sha) - - verify_ada_balance(src_after, src_before, - target_after, target_before, - amt * 2, expected_fee) - # tx history - # TODO ADP-2224: check tx history on src wallet - # on target wallet - txt = SHELLEY.transactions.get(@target_id, tx_id) - tx_amount(txt, amt * 2) - tx_fee(txt, 0) - tx_inputs(txt, present: true) - tx_outputs(txt, present: true) - tx_direction(txt, 'incoming') - tx_script_validity(txt, 'valid') - tx_status(txt, 'in_ledger') - tx_collateral(txt, present: false) - tx_collateral_outputs(txt, present: false) - tx_metadata(txt, nil) - tx_deposits(txt, deposit_taken: 0, deposit_returned: 0) - tx_withdrawals(txt, present: false) - tx_mint_burn(txt, mint: [], burn: []) - tx_extra_signatures(txt, present: false) - tx_script_integrity(txt, present: false) - tx_validity_interval_default(txt) - tx_certificates(txt, present: false) - end - - it 'Multi-assets transaction' do - amt = 1 - amt_ada = 1_600_000 - address = SHELLEY.addresses.list(@target_id)[1]['id'] - target_before = get_shelley_balances(@target_id) - src_before = get_shared_balances(@wid_sha) - - payment = [{ 'address' => address, - 'amount' => { 'quantity' => amt_ada, 'unit' => 'lovelace' }, - 'assets' => [{ 'policy_id' => ASSETS[0]['policy_id'], - 'asset_name' => ASSETS[0]['asset_name'], - 'quantity' => amt }, - { 'policy_id' => ASSETS[1]['policy_id'], - 'asset_name' => ASSETS[1]['asset_name'], - 'quantity' => amt }] }] - tx_constructed = SHARED.transactions.construct(@wid_sha, payment) - expect(tx_constructed).to be_correct_and_respond 202 - expected_fee = tx_constructed['fee']['quantity'] - - # Can be decoded - tx_decoded = SHARED.transactions.decode(@wid_sha, tx_constructed['transaction']) - expect(tx_decoded).to be_correct_and_respond 202 - - expect(tx_decoded['id'].size).to be 64 - decoded_fee = tx_decoded['fee']['quantity'] - expect(expected_fee).to eq decoded_fee - # inputs are ours - expect(tx_decoded['inputs'].to_s).to include 'address' - expect(tx_decoded['inputs'].to_s).to include 'amount' - expect(tx_decoded['outputs']).not_to eq [] - expect(tx_decoded['script_validity']).to eq 'valid' - expect(tx_decoded['validity_interval']['invalid_before']).to eq({ 'quantity' => 0, 'unit' => 'slot' }) - expect(tx_decoded['validity_interval']['invalid_hereafter']['quantity']).to be > 0 - expect(tx_decoded['collateral']).to eq [] - expect(tx_decoded['collateral_outputs']).to eq [] - expect(tx_decoded['metadata']).to eq nil - expect(tx_decoded['deposits_taken']).to eq [] - expect(tx_decoded['deposits_returned']).to eq [] - expect(tx_decoded['withdrawals']).to eq [] - expect(tx_decoded['mint']).to eq({ 'tokens' => [] }) - expect(tx_decoded['burn']).to eq({ 'tokens' => [] }) - expect(tx_decoded['certificates']).to eq [] - - tx_signed = SHARED.transactions.sign(@wid_sha, PASS, tx_constructed['transaction']) - expect(tx_signed).to be_correct_and_respond 202 - - # ADP-2221 [SharedWallets] FeeTooSmallUTxO when submitting transaction from Shared wallet - tx_submitted = SHARED.transactions.submit(@wid_sha, tx_signed['transaction']) - expect(tx_submitted).to be_correct_and_respond 202 - - tx_id = tx_submitted['id'] - # TODO: ADP-2224: change to wait_for_tx_in_ledger(@wid_sha, tx_id) - eventually "Funds are on target wallet: #{@target_id}" do - available = SHELLEY.wallets.get(@target_id)['balance']['available']['quantity'] - total = SHELLEY.wallets.get(@target_id)['balance']['total']['quantity'] - (available == amt_ada + target_before['available']) && - (total == amt_ada + target_before['total']) - end - - target_after = get_shelley_balances(@target_id) - src_after = get_shared_balances(@wid_sha) - - verify_ada_balance(src_after, src_before, - target_after, target_before, - amt_ada, expected_fee) - - verify_asset_balance(src_after, src_before, - target_after, target_before, - amt) - # tx history - # TODO ADP-2224: check tx history on src wallet - # on target wallet - txt = SHELLEY.transactions.get(@target_id, tx_id) - tx_amount(txt, amt_ada) - tx_fee(txt, 0) - tx_inputs(txt, present: true) - tx_outputs(txt, present: true) - tx_direction(txt, 'incoming') - tx_script_validity(txt, 'valid') - tx_status(txt, 'in_ledger') - tx_collateral(txt, present: false) - tx_collateral_outputs(txt, present: false) - tx_metadata(txt, nil) - tx_deposits(txt, deposit_taken: 0, deposit_returned: 0) - tx_withdrawals(txt, present: false) - tx_mint_burn(txt, mint: [], burn: []) - tx_extra_signatures(txt, present: false) - tx_script_integrity(txt, present: false) - tx_validity_interval_default(txt) - tx_certificates(txt, present: false) - end - - it 'Validity intervals' do - amt = MIN_UTXO_VALUE_PURE_ADA - address = SHELLEY.addresses.list(@target_id)[1]['id'] - target_before = get_shelley_balances(@target_id) - src_before = get_shared_balances(@wid_sha) - inv_before = 500 - inv_hereafter = 5_000_000_000 - validity_interval = { 'invalid_before' => { 'quantity' => inv_before, 'unit' => 'slot' }, - 'invalid_hereafter' => { 'quantity' => inv_hereafter, 'unit' => 'slot' } } - tx_constructed = SHARED.transactions.construct(@wid_sha, - payment_payload(amt, address), - nil, # withdrawal - nil, # metadata - nil, # delegations - nil, # mint_burn - validity_interval) - expect(tx_constructed).to be_correct_and_respond 202 - expected_fee = tx_constructed['fee']['quantity'] - - # Can be decoded - tx_decoded = SHARED.transactions.decode(@wid_sha, tx_constructed['transaction']) - expect(tx_decoded).to be_correct_and_respond 202 - - expect(tx_decoded['id'].size).to be 64 - decoded_fee = tx_decoded['fee']['quantity'] - expect(expected_fee).to eq decoded_fee - # inputs are ours - expect(tx_decoded['inputs'].to_s).to include 'address' - expect(tx_decoded['inputs'].to_s).to include 'amount' - expect(tx_decoded['outputs']).not_to eq [] - expect(tx_decoded['script_validity']).to eq 'valid' - expect(tx_decoded['validity_interval']['invalid_before']).to eq validity_interval['invalid_before'] - expect(tx_decoded['validity_interval']['invalid_hereafter']).to eq validity_interval['invalid_hereafter'] - expect(tx_decoded['collateral']).to eq [] - expect(tx_decoded['collateral_outputs']).to eq [] - expect(tx_decoded['metadata']).to eq nil - expect(tx_decoded['deposits_taken']).to eq [] - expect(tx_decoded['deposits_returned']).to eq [] - expect(tx_decoded['withdrawals']).to eq [] - expect(tx_decoded['mint']).to eq({ 'tokens' => [] }) - expect(tx_decoded['burn']).to eq({ 'tokens' => [] }) - expect(tx_decoded['certificates']).to eq [] - - tx_signed = SHARED.transactions.sign(@wid_sha, PASS, tx_constructed['transaction']) - expect(tx_signed).to be_correct_and_respond 202 - - # ADP-2221 [SharedWallets] FeeTooSmallUTxO when submitting transaction from Shared wallet - tx_submitted = SHARED.transactions.submit(@wid_sha, tx_signed['transaction']) - expect(tx_submitted).to be_correct_and_respond 202 - - tx_id = tx_submitted['id'] - # TODO: ADP-2224: change to wait_for_tx_in_ledger(@wid_sha, tx_id) - eventually "Funds are on target wallet: #{@target_id}" do - available = SHELLEY.wallets.get(@target_id)['balance']['available']['quantity'] - total = SHELLEY.wallets.get(@target_id)['balance']['total']['quantity'] - (available == amt + target_before['available']) && - (total == amt + target_before['total']) - end - - target_after = get_shelley_balances(@target_id) - src_after = get_shared_balances(@wid_sha) - - verify_ada_balance(src_after, src_before, - target_after, target_before, - amt, expected_fee) - # tx history - # TODO ADP-2224: check tx history on src wallet - # on target wallet - txt = SHELLEY.transactions.get(@target_id, tx_id) - tx_amount(txt, amt) - tx_fee(txt, 0) - tx_inputs(txt, present: true) - tx_outputs(txt, present: true) - tx_direction(txt, 'incoming') - tx_script_validity(txt, 'valid') - tx_status(txt, 'in_ledger') - tx_collateral(txt, present: false) - tx_collateral_outputs(txt, present: false) - tx_metadata(txt, nil) - tx_deposits(txt, deposit_taken: 0, deposit_returned: 0) - tx_withdrawals(txt, present: false) - tx_mint_burn(txt, mint: [], burn: []) - tx_extra_signatures(txt, present: false) - tx_script_integrity(txt, present: false) - tx_validity_interval(txt, invalid_before: inv_before, invalid_hereafter: inv_hereafter) - tx_certificates(txt, present: false) - end - - it 'Only metadata (without submitting)' do - # We can submit such tx, but cannot tell when tx is actually in ledger - # (as we cannot get tx history (ADP-2224)) - metadata = METADATA - # balance = get_shared_balances(@wid_sha) - tx_constructed = SHARED.transactions.construct(@wid_sha, - nil, # payments - nil, # withdrawal - metadata) - expect(tx_constructed).to be_correct_and_respond 202 - expected_fee = tx_constructed['fee']['quantity'] - - # Can be decoded - tx_decoded = SHARED.transactions.decode(@wid_sha, tx_constructed['transaction']) - expect(tx_decoded).to be_correct_and_respond 202 - - expect(tx_decoded['id'].size).to be 64 - decoded_fee = tx_decoded['fee']['quantity'] - expect(expected_fee).to eq decoded_fee - # inputs are ours - expect(tx_decoded['inputs'].to_s).to include 'address' - expect(tx_decoded['inputs'].to_s).to include 'amount' - expect(tx_decoded['outputs']).not_to eq [] - expect(tx_decoded['script_validity']).to eq 'valid' - expect(tx_decoded['validity_interval']['invalid_before']).to eq({ 'quantity' => 0, 'unit' => 'slot' }) - expect(tx_decoded['validity_interval']['invalid_hereafter']['quantity']).to be > 0 - expect(tx_decoded['collateral']).to eq [] - expect(tx_decoded['collateral_outputs']).to eq [] - expect(tx_decoded['metadata']).to eq metadata - expect(tx_decoded['deposits_taken']).to eq [] - expect(tx_decoded['deposits_returned']).to eq [] - expect(tx_decoded['withdrawals']).to eq [] - expect(tx_decoded['mint']).to eq({ 'tokens' => [] }) - expect(tx_decoded['burn']).to eq({ 'tokens' => [] }) - expect(tx_decoded['certificates']).to eq [] - - tx_signed = SHARED.transactions.sign(@wid_sha, PASS, tx_constructed['transaction']) - expect(tx_signed).to be_correct_and_respond 202 - - # TODO: ADP-2224: cannot tell when tx is actually in ledger, so this needs - # to be commented for now, because of potential race conditions with subsequent tests - # tx_submitted = SHARED.transactions.submit(@wid_sha, tx_signed["transaction"]) - # expect(tx_submitted).to be_correct_and_respond 202 - # tx_id = tx_submitted['id'] - - # TODO: ADP-2224: change to wait_for_tx_in_ledger(@wid_sha, tx_id) - # TODO ADP-2224: check tx history on src wallet and metadata is there - end - - it 'Delegation (without submitting)' do - # Delegation not yet implemented, only construct and sign in this tc - # balance = get_shared_balances(@wid_sha) - expected_deposit = CARDANO_CLI.protocol_params['stakeAddressDeposit'] - puts "Expected deposit #{expected_deposit}" - - # Pick up pool id to join - pools = SHELLEY.stake_pools - pool_id = pools.list({ stake: 1000 }).sample['id'] - - # Join pool - delegation = [{ - 'join' => { - 'pool' => pool_id, - 'stake_key_index' => '0H' - } - }] - - tx_constructed = SHARED.transactions.construct(@wid_sha, - nil, # payment - nil, # withdrawal - nil, # metadata - delegation, - nil, # mint_burn - nil) # validity_interval - # Check fee and deposit on joining - tx_decoded = SHARED.transactions.decode(@wid_sha, tx_constructed['transaction']) - expect(tx_decoded).to be_correct_and_respond 202 - - # TODO: although you can construct and decode delegation, deposit_taken /deposit_returned are not shown atm - # deposit_taken = tx_constructed['coin_selection']['deposits_taken'].first['quantity'] - # decoded_deposit_taken = tx_decoded['deposits_taken'].first['quantity'] - # expect(deposit_taken).to eq decoded_deposit_taken - # expect(deposit_taken).to eq expected_deposit - - expected_fee = tx_constructed['fee']['quantity'] - decoded_fee = tx_decoded['fee']['quantity'] - expect(decoded_fee).to eq expected_fee - # inputs are ours - expect(tx_decoded['inputs'].to_s).to include 'address' - expect(tx_decoded['inputs'].to_s).to include 'amount' - expect(tx_decoded['outputs']).not_to eq [] - expect(tx_decoded['script_validity']).to eq 'valid' - expect(tx_decoded['validity_interval']['invalid_before']).to eq({ 'quantity' => 0, 'unit' => 'slot' }) - expect(tx_decoded['validity_interval']['invalid_hereafter']['quantity']).to be > 0 - expect(tx_decoded['collateral']).to eq [] - expect(tx_decoded['collateral_outputs']).to eq [] - expect(tx_decoded['metadata']).to eq nil - expect(tx_decoded['deposits_taken']).to eq [] - expect(tx_decoded['deposits_returned']).to eq [] - expect(tx_decoded['withdrawals']).to eq [] - expect(tx_decoded['mint']).to eq({ 'tokens' => [] }) - expect(tx_decoded['burn']).to eq({ 'tokens' => [] }) - expect(tx_decoded['certificates']).to eq [] - - tx_signed = SHARED.transactions.sign(@wid_sha, PASS, tx_constructed['transaction']) - expect(tx_signed).to be_correct_and_respond 202 - end - - describe 'Minting and Burning' do - it 'Can mint and then burn (without submitting)' do - # Minting and Burning not yet implemented, only construct and sign in this tc - # src_before = get_shared_balances(@wid_sha) - policy_script1 = 'cosigner#0' - policy_script2 = { 'all' => ['cosigner#0'] } - policy_script3 = { 'any' => ['cosigner#0'] } - - # Minting: - mint = [mint(asset_name('Token1'), 1000, policy_script1), - mint(asset_name('Token2'), 1000, policy_script2), - mint('', 1000, policy_script3)] - - tx_constructed = SHARED.transactions.construct(@wid_sha, - nil, # payment - nil, # withdrawal - nil, # metadata - nil, # delegation - mint) - expect(tx_constructed).to be_correct_and_respond 202 - - tx_decoded = SHARED.transactions.decode(@wid_sha, tx_constructed['transaction']) - expect(tx_decoded).to be_correct_and_respond 202 - - expected_fee = tx_constructed['fee']['quantity'] - decoded_fee = tx_decoded['fee']['quantity'] - expect(expected_fee).to eq decoded_fee - # inputs are ours - expect(tx_decoded['inputs'].to_s).to include 'address' - expect(tx_decoded['inputs'].to_s).to include 'amount' - expect(tx_decoded['outputs']).not_to eq [] - expect(tx_decoded['script_validity']).to eq 'valid' - expect(tx_decoded['validity_interval']['invalid_before']).to eq({ 'quantity' => 0, 'unit' => 'slot' }) - expect(tx_decoded['validity_interval']['invalid_hereafter']['quantity']).to be > 0 - expect(tx_decoded['collateral']).to eq [] - expect(tx_decoded['collateral_outputs']).to eq [] - expect(tx_decoded['metadata']).to eq nil - expect(tx_decoded['deposits_taken']).to eq [] - expect(tx_decoded['deposits_returned']).to eq [] - expect(tx_decoded['withdrawals']).to eq [] - # TODO: mint / burn currently not decoded - expect(tx_decoded['mint']).to eq({ 'tokens' => [] }) - expect(tx_decoded['burn']).to eq({ 'tokens' => [] }) - expect(tx_decoded['certificates']).to eq [] - - tx_signed = SHARED.transactions.sign(@wid_sha, PASS, tx_constructed['transaction']) - expect(tx_signed).to be_correct_and_respond 202 - end - end - end - - it 'I can receive transaction to shared wallet' do - amt = 1 - amt_ada = 3_000_000 - address = SHARED.addresses.list(@wid_sha)[1]['id'] - target_before = get_shared_balances(@wid_sha) - src_before = get_shelley_balances(@wid) - - payload = [{ 'address' => address, - 'amount' => { 'quantity' => amt_ada, 'unit' => 'lovelace' }, - 'assets' => [{ 'policy_id' => ASSETS[0]['policy_id'], - 'asset_name' => ASSETS[0]['asset_name'], - 'quantity' => amt }, - { 'policy_id' => ASSETS[1]['policy_id'], - 'asset_name' => ASSETS[1]['asset_name'], - 'quantity' => amt }] }] - - tx_sent = SHELLEY.transactions.create(@wid, PASS, payload) - - expect(tx_sent).to be_correct_and_respond 202 - expect(tx_sent.to_s).to include 'pending' - wait_for_tx_in_ledger(@wid, tx_sent['id']) - - target_after = get_shared_balances(@wid_sha) - src_after = get_shelley_balances(@wid) - fee = SHELLEY.transactions.get(@wid, tx_sent['id'])['fee']['quantity'] - - verify_ada_balance(src_after, src_before, - target_after, target_before, - amt_ada, fee) - - verify_asset_balance(src_after, src_before, - target_after, target_before, - amt) - end - end - describe 'E2E Shelley' do describe 'Native Assets' do it 'I can list native assets' do From cccc4137159d49c20922730cac0b1a4c44516224 Mon Sep 17 00:00:00 2001 From: Piotr Stachyra Date: Tue, 15 Nov 2022 17:26:57 +0100 Subject: [PATCH 3/7] use list/get txs to examine transactions sent from shared wallets --- test/e2e/helpers/utils.rb | 14 +-- test/e2e/spec/e2e_shared_spec.rb | 168 ++++++++++++++++++++++--------- test/e2e/spec/spec_helper.rb | 28 +++--- 3 files changed, 144 insertions(+), 66 deletions(-) diff --git a/test/e2e/helpers/utils.rb b/test/e2e/helpers/utils.rb index 33a9150c928..2980bef9e13 100644 --- a/test/e2e/helpers/utils.rb +++ b/test/e2e/helpers/utils.rb @@ -83,20 +83,22 @@ def get_fixture_wallet_mnemonics(kind, type) fixture = ENV.fetch('TESTS_E2E_FIXTURES_FILE', nil) raise "File #{fixture} does not exist! (Hint: Template fixture file can be created with 'rake fixture_wallets_template'). Make sure to feed it with mnemonics of wallets with funds and assets." unless File.exist? fixture - wallets = JSON.parse File.read(fixture) - k = kind.to_s - t = type.to_s + wallets = from_json(fixture) if linux? - wallets['linux'][k][t] + wallets[:linux][kind][type] elsif mac? - wallets['macos'][k][t] + wallets[:macos][kind][type] elsif win? - wallets['windows'][k][t] + wallets[:windows][kind][type] else raise 'Unsupported platform!' end end + def from_json(file) + JSON.parse(File.read(file), { symbolize_names: true }) + end + def wget(url, file = nil) file ||= File.basename(url) resp = HTTParty.get(url) diff --git a/test/e2e/spec/e2e_shared_spec.rb b/test/e2e/spec/e2e_shared_spec.rb index a59eba321a7..a6b6d91b419 100644 --- a/test/e2e/spec/e2e_shared_spec.rb +++ b/test/e2e/spec/e2e_shared_spec.rb @@ -7,7 +7,9 @@ @target_id = create_target_wallet(:shelley) # shared wallets - @wid_sha = create_target_wallet(:shared) + @wid_sha = create_fixture_wallet(:shared) + # @wid_sha_cos0 = create_fixture_wallet(:shared, 0) + # @wid_sha_cos1 = create_fixture_wallet(:shared, 1) @nightly_shared_wallets = [@wid_sha] @nightly_shelley_wallets = [@wid, @target_id] @@ -77,13 +79,7 @@ expect(tx_submitted).to be_correct_and_respond 202 tx_id = tx_submitted['id'] - # TODO: ADP-2224: change to wait_for_tx_in_ledger(@wid_sha, tx_id) - eventually "Funds are on target wallet: #{@target_id}" do - available = SHELLEY.wallets.get(@target_id)['balance']['available']['quantity'] - total = SHELLEY.wallets.get(@target_id)['balance']['total']['quantity'] - (available == amt + target_before['available']) && - (total == amt + target_before['total']) - end + wait_for_tx_in_ledger(@wid_sha, tx_id, SHARED) target_after = get_shelley_balances(@target_id) src_after = get_shared_balances(@wid_sha) @@ -92,7 +88,26 @@ target_after, target_before, amt, expected_fee) # tx history - # TODO ADP-2224: check tx history on src wallet + # on src wallet + tx = SHARED.transactions.get(@wid_sha, tx_id) + tx_amount(tx, amt + expected_fee) + tx_fee(tx, expected_fee) + tx_inputs(tx, present: true) + tx_outputs(tx, present: true) + tx_direction(tx, 'outgoing') + tx_script_validity(tx, 'valid') + tx_status(tx, 'in_ledger') + tx_collateral(tx, present: false) + tx_collateral_outputs(tx, present: false) + tx_metadata(tx, nil) + tx_deposits(tx, deposit_taken: 0, deposit_returned: 0) + tx_withdrawals(tx, present: false) + tx_mint_burn(tx, mint: [], burn: []) + tx_extra_signatures(tx, present: false) + tx_script_integrity(tx, present: false) + tx_validity_interval_default(tx) + tx_certificates(tx, present: false) + # on target wallet txt = SHELLEY.transactions.get(@target_id, tx_id) tx_amount(txt, amt) @@ -161,13 +176,7 @@ expect(tx_submitted).to be_correct_and_respond 202 tx_id = tx_submitted['id'] - # TODO: ADP-2224: change to wait_for_tx_in_ledger(@wid_sha, tx_id) - eventually "Funds are on target wallet: #{@target_id}" do - available = SHELLEY.wallets.get(@target_id)['balance']['available']['quantity'] - total = SHELLEY.wallets.get(@target_id)['balance']['total']['quantity'] - (available == (amt * 2) + target_before['available']) && - (total == (amt * 2) + target_before['total']) - end + wait_for_tx_in_ledger(@wid_sha, tx_id, SHARED) target_after = get_shelley_balances(@target_id) src_after = get_shared_balances(@wid_sha) @@ -176,7 +185,26 @@ target_after, target_before, amt * 2, expected_fee) # tx history - # TODO ADP-2224: check tx history on src wallet + # on src wallet + tx = SHARED.transactions.get(@wid_sha, tx_id) + tx_amount(tx, (amt * 2) + expected_fee) + tx_fee(tx, expected_fee) + tx_inputs(tx, present: true) + tx_outputs(tx, present: true) + tx_direction(tx, 'outgoing') + tx_script_validity(tx, 'valid') + tx_status(tx, 'in_ledger') + tx_collateral(tx, present: false) + tx_collateral_outputs(tx, present: false) + tx_metadata(tx, nil) + tx_deposits(tx, deposit_taken: 0, deposit_returned: 0) + tx_withdrawals(tx, present: false) + tx_mint_burn(tx, mint: [], burn: []) + tx_extra_signatures(tx, present: false) + tx_script_integrity(tx, present: false) + tx_validity_interval_default(tx) + tx_certificates(tx, present: false) + # on target wallet txt = SHELLEY.transactions.get(@target_id, tx_id) tx_amount(txt, amt * 2) @@ -244,18 +272,11 @@ tx_signed = SHARED.transactions.sign(@wid_sha, PASS, tx_constructed['transaction']) expect(tx_signed).to be_correct_and_respond 202 - # ADP-2221 [SharedWallets] FeeTooSmallUTxO when submitting transaction from Shared wallet tx_submitted = SHARED.transactions.submit(@wid_sha, tx_signed['transaction']) expect(tx_submitted).to be_correct_and_respond 202 tx_id = tx_submitted['id'] - # TODO: ADP-2224: change to wait_for_tx_in_ledger(@wid_sha, tx_id) - eventually "Funds are on target wallet: #{@target_id}" do - available = SHELLEY.wallets.get(@target_id)['balance']['available']['quantity'] - total = SHELLEY.wallets.get(@target_id)['balance']['total']['quantity'] - (available == amt_ada + target_before['available']) && - (total == amt_ada + target_before['total']) - end + wait_for_tx_in_ledger(@wid_sha, tx_id, SHARED) target_after = get_shelley_balances(@target_id) src_after = get_shared_balances(@wid_sha) @@ -268,7 +289,26 @@ target_after, target_before, amt) # tx history - # TODO ADP-2224: check tx history on src wallet + # on src wallet + tx = SHARED.transactions.get(@wid_sha, tx_id) + tx_amount(tx, amt_ada + expected_fee) + tx_fee(tx, expected_fee) + tx_inputs(tx, present: true) + tx_outputs(tx, present: true) + tx_direction(tx, 'outgoing') + tx_script_validity(tx, 'valid') + tx_status(tx, 'in_ledger') + tx_collateral(tx, present: false) + tx_collateral_outputs(tx, present: false) + tx_metadata(tx, nil) + tx_deposits(tx, deposit_taken: 0, deposit_returned: 0) + tx_withdrawals(tx, present: false) + tx_mint_burn(tx, mint: [], burn: []) + tx_extra_signatures(tx, present: false) + tx_script_integrity(tx, present: false) + tx_validity_interval_default(tx) + tx_certificates(tx, present: false) + # on target wallet txt = SHELLEY.transactions.get(@target_id, tx_id) tx_amount(txt, amt_ada) @@ -336,18 +376,11 @@ tx_signed = SHARED.transactions.sign(@wid_sha, PASS, tx_constructed['transaction']) expect(tx_signed).to be_correct_and_respond 202 - # ADP-2221 [SharedWallets] FeeTooSmallUTxO when submitting transaction from Shared wallet tx_submitted = SHARED.transactions.submit(@wid_sha, tx_signed['transaction']) expect(tx_submitted).to be_correct_and_respond 202 tx_id = tx_submitted['id'] - # TODO: ADP-2224: change to wait_for_tx_in_ledger(@wid_sha, tx_id) - eventually "Funds are on target wallet: #{@target_id}" do - available = SHELLEY.wallets.get(@target_id)['balance']['available']['quantity'] - total = SHELLEY.wallets.get(@target_id)['balance']['total']['quantity'] - (available == amt + target_before['available']) && - (total == amt + target_before['total']) - end + wait_for_tx_in_ledger(@wid_sha, tx_id, SHARED) target_after = get_shelley_balances(@target_id) src_after = get_shared_balances(@wid_sha) @@ -356,7 +389,26 @@ target_after, target_before, amt, expected_fee) # tx history - # TODO ADP-2224: check tx history on src wallet + # on src wallet + tx = SHARED.transactions.get(@wid_sha, tx_id) + tx_amount(tx, amt + expected_fee) + tx_fee(tx, expected_fee) + tx_inputs(tx, present: true) + tx_outputs(tx, present: true) + tx_direction(tx, 'outgoing') + tx_script_validity(tx, 'valid') + tx_status(tx, 'in_ledger') + tx_collateral(tx, present: false) + tx_collateral_outputs(tx, present: false) + tx_metadata(tx, nil) + tx_deposits(tx, deposit_taken: 0, deposit_returned: 0) + tx_withdrawals(tx, present: false) + tx_mint_burn(tx, mint: [], burn: []) + tx_extra_signatures(tx, present: false) + tx_script_integrity(tx, present: false) + tx_validity_interval(tx, invalid_before: inv_before, invalid_hereafter: inv_hereafter) + tx_certificates(tx, present: false) + # on target wallet txt = SHELLEY.transactions.get(@target_id, tx_id) tx_amount(txt, amt) @@ -378,11 +430,9 @@ tx_certificates(txt, present: false) end - it 'Only metadata (without submitting)' do - # We can submit such tx, but cannot tell when tx is actually in ledger - # (as we cannot get tx history (ADP-2224)) + it 'Only metadata' do metadata = METADATA - # balance = get_shared_balances(@wid_sha) + balance = get_shared_balances(@wid_sha) tx_constructed = SHARED.transactions.construct(@wid_sha, nil, # payments nil, # withdrawal @@ -417,14 +467,40 @@ tx_signed = SHARED.transactions.sign(@wid_sha, PASS, tx_constructed['transaction']) expect(tx_signed).to be_correct_and_respond 202 - # TODO: ADP-2224: cannot tell when tx is actually in ledger, so this needs - # to be commented for now, because of potential race conditions with subsequent tests - # tx_submitted = SHARED.transactions.submit(@wid_sha, tx_signed["transaction"]) - # expect(tx_submitted).to be_correct_and_respond 202 - # tx_id = tx_submitted['id'] + tx_submitted = SHARED.transactions.submit(@wid_sha, tx_signed['transaction']) + expect(tx_submitted).to be_correct_and_respond 202 + signed_decoded = SHELLEY.transactions.decode(@wid, tx_signed['transaction']) + expect(signed_decoded['witness_count']['verification_key']).to be >= 1 + expect(expected_fee).to eq signed_decoded['fee']['quantity'] + + tx_id = tx_submitted['id'] + wait_for_tx_in_ledger(@wid_sha, tx_id, SHARED) + + # examine the tx in history + # on src wallet + tx = SHARED.transactions.get(@wid_sha, tx_id) + tx_amount(tx, expected_fee) + tx_fee(tx, expected_fee) + tx_inputs(tx, present: true) + tx_outputs(tx, present: true) + tx_direction(tx, 'outgoing') + tx_script_validity(tx, 'valid') + tx_status(tx, 'in_ledger') + tx_collateral(tx, present: false) + tx_collateral_outputs(tx, present: false) + tx_metadata(tx, metadata) + tx_deposits(tx, deposit_taken: 0, deposit_returned: 0) + tx_withdrawals(tx, present: false) + tx_mint_burn(tx, mint: [], burn: []) + tx_extra_signatures(tx, present: false) + tx_script_integrity(tx, present: false) + tx_validity_interval_default(tx) + tx_certificates(tx, present: false) - # TODO: ADP-2224: change to wait_for_tx_in_ledger(@wid_sha, tx_id) - # TODO ADP-2224: check tx history on src wallet and metadata is there + # verify balance is as expected + new_balance = get_shared_balances(@wid_sha) + expect(new_balance['available']).to eq(balance['available'] - expected_fee) + expect(new_balance['total']).to eq(balance['total'] - expected_fee) end it 'Delegation (without submitting)' do diff --git a/test/e2e/spec/spec_helper.rb b/test/e2e/spec/spec_helper.rb index 544b2f76e58..b1896a05bd2 100644 --- a/test/e2e/spec/spec_helper.rb +++ b/test/e2e/spec/spec_helper.rb @@ -286,13 +286,24 @@ def create_fixture_wallet(type) passphrase: PASS, mnemonic_sentence: get_fixture_wallet_mnemonics(:fixture, type.to_sym) } case type.to_sym - when :shelley, :shelley_light + when :shelley wallet = SHELLEY.wallets.create(payload) return_wallet_id(wallet) when :random, :icarus payload[:style] = type wallet = BYRON.wallets.create(payload) return_wallet_id(wallet) + when :shared + script_template = { 'cosigners' => + { 'cosigner#0' => 'self' }, + 'template' => + { 'all' => + ['cosigner#0'] } } + payload[:account_index] = '0H' + payload[:payment_script_template] = script_template + payload[:delegation_script_template] = script_template + wallet = SHARED.wallets.create(payload) + return_wallet_id(wallet) else raise "Unsupported wallet type: #{type}" end @@ -309,17 +320,6 @@ def create_target_wallet(type) when :shelley wallet = SHELLEY.wallets.create(payload) return_wallet_id(wallet) - when :shared - script_template = { 'cosigners' => - { 'cosigner#0' => 'self' }, - 'template' => - { 'all' => - ['cosigner#0'] } } - payload[:account_index] = '0H' - payload[:payment_script_template] = script_template - payload[:delegation_script_template] = script_template - wallet = SHARED.wallets.create(payload) - return_wallet_id(wallet) else raise "Unsupported wallet type: #{type}" end @@ -449,9 +449,9 @@ def verify_asset_balance(src_after, src_before, target_after, target_before, amt expect(src_avail_after).to eq src_avail_expected end -def wait_for_tx_in_ledger(wid, tx_id) +def wait_for_tx_in_ledger(wid, tx_id, wallet_api = SHELLEY) eventually "Tx #{tx_id} is in ledger" do - tx = SHELLEY.transactions.get(wid, tx_id) + tx = wallet_api.transactions.get(wid, tx_id) tx.code == 200 && tx['status'] == 'in_ledger' end end From 41e7aafece08c44d675cfea0c65170f6c2e239b1 Mon Sep 17 00:00:00 2001 From: Piotr Stachyra Date: Fri, 18 Nov 2022 16:55:41 +0100 Subject: [PATCH 4/7] add patched wallets --- test/e2e/helpers/utils.rb | 13 +++++++------ test/e2e/spec/e2e_shared_spec.rb | 16 ++++++++++++---- test/e2e/spec/e2e_spec.rb | 2 +- test/e2e/spec/spec_helper.rb | 19 +++++++++++++++---- 4 files changed, 35 insertions(+), 15 deletions(-) diff --git a/test/e2e/helpers/utils.rb b/test/e2e/helpers/utils.rb index 2980bef9e13..475d449e6a1 100644 --- a/test/e2e/helpers/utils.rb +++ b/test/e2e/helpers/utils.rb @@ -76,20 +76,21 @@ def absolute_path(path) end end - # Get wallet mnemonics from fixures file + # Get wallet mnemonics/payment template/delegation template from fixures file # @param kind [Symbol] :fixture or :target (fixture wallet with funds or target wallet) - # @param type [Symbol] wallet type = :shelley, :shared, :icarus, :random - def get_fixture_wallet_mnemonics(kind, type) + # @param wallet_type [Symbol] wallet type = :shelley, :shared, :icarus, :random + # @param info_type [Symbol] wallet type = :mnemonics, :payment_template, :delegation_template + def get_fixture_wallet(kind, wallet_type, info_type = :mnemonics) fixture = ENV.fetch('TESTS_E2E_FIXTURES_FILE', nil) raise "File #{fixture} does not exist! (Hint: Template fixture file can be created with 'rake fixture_wallets_template'). Make sure to feed it with mnemonics of wallets with funds and assets." unless File.exist? fixture wallets = from_json(fixture) if linux? - wallets[:linux][kind][type] + wallets[:linux][kind][wallet_type][info_type] elsif mac? - wallets[:macos][kind][type] + wallets[:macos][kind][wallet_type][info_type] elsif win? - wallets[:windows][kind][type] + wallets[:windows][kind][wallet_type][info_type] else raise 'Unsupported platform!' end diff --git a/test/e2e/spec/e2e_shared_spec.rb b/test/e2e/spec/e2e_shared_spec.rb index a6b6d91b419..acf0bffa35c 100644 --- a/test/e2e/spec/e2e_shared_spec.rb +++ b/test/e2e/spec/e2e_shared_spec.rb @@ -8,10 +8,18 @@ # shared wallets @wid_sha = create_fixture_wallet(:shared) - # @wid_sha_cos0 = create_fixture_wallet(:shared, 0) - # @wid_sha_cos1 = create_fixture_wallet(:shared, 1) - - @nightly_shared_wallets = [@wid_sha] + @wid_sha_cos0 = create_fixture_wallet(:shared_cosigner_0) + @wid_sha_cos1 = create_fixture_wallet(:shared_cosigner_1) + cos0 = shared_acc_pubkey(@wid_sha_cos0) + cos1 = shared_acc_pubkey(@wid_sha_cos1) + patch_incomplete_shared_wallet(@wid_sha_cos0, + { 'cosigner#1' => cos1 }, + { 'cosigner#1' => cos1 }) + patch_incomplete_shared_wallet(@wid_sha_cos1, + { 'cosigner#0' => cos0 }, + { 'cosigner#0' => cos0 }) + + @nightly_shared_wallets = [@wid_sha, @wid_sha_cos0, @wid_sha_cos1] @nightly_shelley_wallets = [@wid, @target_id] wait_for_all_shelley_wallets(@nightly_shelley_wallets) wait_for_all_shared_wallets(@nightly_shared_wallets) diff --git a/test/e2e/spec/e2e_spec.rb b/test/e2e/spec/e2e_spec.rb index b51b8ff4d38..a225e81d31a 100644 --- a/test/e2e/spec/e2e_spec.rb +++ b/test/e2e/spec/e2e_spec.rb @@ -2828,7 +2828,7 @@ def fingerprint describe 'Update passphrase' do it 'I can update passphrase with mnemonic and the wallet does not have to re-sync' do - mnemonics = get_fixture_wallet_mnemonics(:fixture, :shelley) + mnemonics = get_fixture_wallet(:fixture, :shelley, :mnemonics) upd = SHELLEY.wallets.update_passphrase(@wid, { mnemonic_sentence: mnemonics, new_passphrase: PASS }) expect(upd).to be_correct_and_respond 204 diff --git a/test/e2e/spec/spec_helper.rb b/test/e2e/spec/spec_helper.rb index b1896a05bd2..df279bc6b75 100644 --- a/test/e2e/spec/spec_helper.rb +++ b/test/e2e/spec/spec_helper.rb @@ -131,6 +131,11 @@ def create_incomplete_shared_wallet(m, acc_ix, acc_xpub) WalletFactory.create(:shared, payload)['id'] end +def shared_acc_pubkey(wallet_id) + key_bech32 = SHARED.keys.get_acc_public_key(wallet_id, { format: 'extended' }).parsed_response.delete_prefix('"').delete_suffix('"') + bech32_to_base16(key_bech32) +end + def patch_incomplete_shared_wallet(wid, payment_patch, deleg_patch) if payment_patch p_upd = SHARED.wallets.update_payment_script(wid, @@ -280,11 +285,11 @@ def return_wallet_id(create_wallet_response) ## # create fixture wallet or return it's id if it exists -# @param type [Symbol] :shelley, :shelley_light, :random, :icarus +# @param type [Symbol] :shelley, :shared, :shared_cosigner_0, :shared_cosigner_1, :random, :icarus def create_fixture_wallet(type) - payload = { name: 'Fixture wallet with funds', + payload = { name: "Fixture wallet with funds (#{type})", passphrase: PASS, - mnemonic_sentence: get_fixture_wallet_mnemonics(:fixture, type.to_sym) } + mnemonic_sentence: get_fixture_wallet(:fixture, type.to_sym, :mnemonics) } case type.to_sym when :shelley wallet = SHELLEY.wallets.create(payload) @@ -304,6 +309,12 @@ def create_fixture_wallet(type) payload[:delegation_script_template] = script_template wallet = SHARED.wallets.create(payload) return_wallet_id(wallet) + when :shared_cosigner_0, :shared_cosigner_1 + payload[:account_index] = '0H' + payload[:payment_script_template] = get_fixture_wallet(:fixture, type.to_sym, :payment_template) + payload[:delegation_script_template] = get_fixture_wallet(:fixture, type.to_sym, :delegation_template) + wallet = SHARED.wallets.create(payload) + return_wallet_id(wallet) else raise "Unsupported wallet type: #{type}" end @@ -315,7 +326,7 @@ def create_fixture_wallet(type) def create_target_wallet(type) payload = { name: 'Target wallet for txs', passphrase: PASS, - mnemonic_sentence: get_fixture_wallet_mnemonics(:target, type.to_sym) } + mnemonic_sentence: get_fixture_wallet(:target, type.to_sym, :mnemonics) } case type.to_sym when :shelley wallet = SHELLEY.wallets.create(payload) From d7f694714ed5b30959b3997759c51975c15ca8b7 Mon Sep 17 00:00:00 2001 From: Piotr Stachyra Date: Fri, 18 Nov 2022 16:56:04 +0100 Subject: [PATCH 5/7] update rakefile --- test/e2e/Rakefile | 38 +++++++++++++++++++------------------- 1 file changed, 19 insertions(+), 19 deletions(-) diff --git a/test/e2e/Rakefile b/test/e2e/Rakefile index c51668e71b7..16f5f5357c6 100644 --- a/test/e2e/Rakefile +++ b/test/e2e/Rakefile @@ -50,30 +50,30 @@ task :fixture_wallets_template do log ">> Creating #{FIXTURES_FILE}" utils = CardanoWallet.new.utils fixture_wallets = { linux: { - fixture: { shelley: utils.mnemonic_sentence(24), - icarus: utils.mnemonic_sentence(15), - random: utils.mnemonic_sentence(12), - shelley_light: utils.mnemonic_sentence(24) }, - target: { shelley: utils.mnemonic_sentence(24), - shared: utils.mnemonic_sentence(24) } + fixture: { shelley: { mnemonics: utils.mnemonic_sentence(24) }, + icarus: { mnemonics: utils.mnemonic_sentence(24) }, + random: { mnemonics: utils.mnemonic_sentence(24) }, + shared: { mnemonics: utils.mnemonic_sentence(24) }, + shared2: { mnemonics: utils.mnemonic_sentence(24) } }, + target: { shelley: { mnemonics: utils.mnemonic_sentence(24) } } }, macos: { - fixture: { shelley: utils.mnemonic_sentence(24), - icarus: utils.mnemonic_sentence(15), - random: utils.mnemonic_sentence(12), - shelley_light: utils.mnemonic_sentence(24) }, - target: { shelley: utils.mnemonic_sentence(24), - shared: utils.mnemonic_sentence(24) } + fixture: { shelley: { mnemonics: utils.mnemonic_sentence(24) }, + icarus: { mnemonics: utils.mnemonic_sentence(24) }, + random: { mnemonics: utils.mnemonic_sentence(24) }, + shared: { mnemonics: utils.mnemonic_sentence(24) }, + shared2: { mnemonics: utils.mnemonic_sentence(24) } }, + target: { shelley: { mnemonics: utils.mnemonic_sentence(24) } } }, windows: { - fixture: { shelley: utils.mnemonic_sentence(24), - icarus: utils.mnemonic_sentence(15), - random: utils.mnemonic_sentence(12), - shelley_light: utils.mnemonic_sentence(24) }, - target: { shelley: utils.mnemonic_sentence(24), - shared: utils.mnemonic_sentence(24) } + fixture: { shelley: { mnemonics: utils.mnemonic_sentence(24) }, + icarus: { mnemonics: utils.mnemonic_sentence(24) }, + random: { mnemonics: utils.mnemonic_sentence(24) }, + shared: { mnemonics: utils.mnemonic_sentence(24) }, + shared2: { mnemonics: utils.mnemonic_sentence(24) } }, + target: { shelley: { mnemonics: utils.mnemonic_sentence(24) } } }, - currency_contract_wallet: utils.mnemonic_sentence(24) } + currency_contract_wallet: { mnemonics: utils.mnemonic_sentence(24) } } if File.exist?(FIXTURES_FILE) err = " File #{FIXTURES_FILE} already exists! From af9b71c53d796bc737a525c2d3e8c10707d7bd59 Mon Sep 17 00:00:00 2001 From: Piotr Stachyra Date: Mon, 21 Nov 2022 19:54:51 +0100 Subject: [PATCH 6/7] e2e transactions with patched multi-sign wallets --- test/e2e/spec/e2e_shared_spec.rb | 675 ++++++++++++++++++++++++++++++- test/e2e/spec/spec_helper.rb | 74 +++- 2 files changed, 717 insertions(+), 32 deletions(-) diff --git a/test/e2e/spec/e2e_shared_spec.rb b/test/e2e/spec/e2e_shared_spec.rb index acf0bffa35c..d9c8bc62063 100644 --- a/test/e2e/spec/e2e_shared_spec.rb +++ b/test/e2e/spec/e2e_shared_spec.rb @@ -1,25 +1,23 @@ # frozen_string_literal: true -RSpec.describe 'Cardano Wallet E2E tests - Shared wallets', :all, :e2e do +RSpec.describe 'Cardano Wallet E2E tests - Shared wallets', :all, :e2e, :shared do before(:all) do # shelley wallets @wid = create_fixture_wallet(:shelley) @target_id = create_target_wallet(:shelley) # shared wallets - @wid_sha = create_fixture_wallet(:shared) - @wid_sha_cos0 = create_fixture_wallet(:shared_cosigner_0) - @wid_sha_cos1 = create_fixture_wallet(:shared_cosigner_1) - cos0 = shared_acc_pubkey(@wid_sha_cos0) - cos1 = shared_acc_pubkey(@wid_sha_cos1) - patch_incomplete_shared_wallet(@wid_sha_cos0, - { 'cosigner#1' => cos1 }, - { 'cosigner#1' => cos1 }) - patch_incomplete_shared_wallet(@wid_sha_cos1, - { 'cosigner#0' => cos0 }, - { 'cosigner#0' => cos0 }) - - @nightly_shared_wallets = [@wid_sha, @wid_sha_cos0, @wid_sha_cos1] + @wid_sha = create_fixture_wallet(:shared, :payment_cosigner0_all0, :delegation_cosigner0_all0) + @wid_sha_cos0_all = create_fixture_wallet(:shared, :payment_cosigner0_all, :delegation_cosigner0_all) + @wid_sha_cos1_all = create_fixture_wallet(:shared2, :payment_cosigner1_all, :delegation_cosigner1_all) + @wid_sha_cos0_any = create_fixture_wallet(:shared, :payment_cosigner0_any, :delegation_cosigner0_any) + cos0 = shared_acc_pubkey(@wid_sha_cos0_all) + cos1 = shared_acc_pubkey(@wid_sha_cos1_all) + patch_if_incomplete(@wid_sha_cos0_all, { 'cosigner#1' => cos1 }, { 'cosigner#1' => cos1 }) + patch_if_incomplete(@wid_sha_cos1_all, { 'cosigner#0' => cos0 }, { 'cosigner#0' => cos0 }) + patch_if_incomplete(@wid_sha_cos0_any, { 'cosigner#1' => cos1 }, { 'cosigner#1' => cos1 }) + + @nightly_shared_wallets = [@wid_sha, @wid_sha_cos0_all, @wid_sha_cos1_all, @wid_sha_cos0_any] @nightly_shelley_wallets = [@wid, @target_id] wait_for_all_shelley_wallets(@nightly_shelley_wallets) wait_for_all_shared_wallets(@nightly_shared_wallets) @@ -34,7 +32,652 @@ end describe 'E2E Shared' do - describe 'E2E Construct -> Sign -> Submit', :shared do + describe 'E2E Construct -> Sign -> Submit - multi signers' do + it 'Cannot submit if partially signed - one cosigner, all' do + amt = MIN_UTXO_VALUE_PURE_ADA * 2 + src_wid = @wid_sha_cos0_all + target_wid = @target_id + address = SHELLEY.addresses.list(target_wid)[1]['id'] + + tx_constructed = SHARED.transactions.construct(src_wid, payment_payload(amt, address)) + expect(tx_constructed).to be_correct_and_respond 202 + expected_fee = tx_constructed['fee']['quantity'] + + # Can be decoded + tx_decoded = SHARED.transactions.decode(src_wid, tx_constructed['transaction']) + expect(tx_decoded).to be_correct_and_respond 202 + + expect(tx_decoded['id'].size).to be 64 + decoded_fee = tx_decoded['fee']['quantity'] + expect(expected_fee).to eq decoded_fee + + # cosigner0 signs + tx_signed1 = SHARED.transactions.sign(src_wid, PASS, tx_constructed['transaction']) + expect(tx_signed1).to be_correct_and_respond 202 + + # cosigner0 submits + tx_submitted = SHARED.transactions.submit(src_wid, tx_signed1['transaction']) + expect(tx_submitted).to be_correct_and_respond 403 + expect(tx_submitted['code']).to eq 'missing_witnesses_in_transaction' + end + + it 'Cannot submit if tx is foreign - two cosigners, all' do + amt = MIN_UTXO_VALUE_PURE_ADA * 2 + src_wid = @wid_sha_cos0_all + cosigner_wid = @wid_sha_cos1_all + foreign_wid = @wid_sha + target_wid = @target_id + address = SHELLEY.addresses.list(target_wid)[1]['id'] + + tx_constructed = SHARED.transactions.construct(src_wid, payment_payload(amt, address)) + expect(tx_constructed).to be_correct_and_respond 202 + expected_fee = tx_constructed['fee']['quantity'] + + # Can be decoded + tx_decoded = SHARED.transactions.decode(src_wid, tx_constructed['transaction']) + expect(tx_decoded).to be_correct_and_respond 202 + + expect(tx_decoded['id'].size).to be 64 + decoded_fee = tx_decoded['fee']['quantity'] + expect(expected_fee).to eq decoded_fee + + # cosigner0 signs + tx_signed1 = SHARED.transactions.sign(src_wid, PASS, tx_constructed['transaction']) + expect(tx_signed1).to be_correct_and_respond 202 + + # cosigner1 signs + tx_signed = SHARED.transactions.sign(cosigner_wid, PASS, tx_constructed['transaction']) + expect(tx_signed).to be_correct_and_respond 202 + + # foreign submits + tx_submitted = SHARED.transactions.submit(foreign_wid, tx_signed['transaction']) + expect(tx_submitted).to be_correct_and_respond 403 + expect(tx_submitted['code']).to eq 'foreign_transaction' + end + + it 'Single output transaction - two cosigners, all' do + amt = MIN_UTXO_VALUE_PURE_ADA * 2 + src_wid = @wid_sha_cos0_all + cosigner_wid = @wid_sha_cos1_all + target_wid = @target_id + address = SHELLEY.addresses.list(target_wid)[1]['id'] + target_before = get_shelley_balances(target_wid) + src_before = get_shared_balances(src_wid) + + tx_constructed = SHARED.transactions.construct(src_wid, payment_payload(amt, address)) + expect(tx_constructed).to be_correct_and_respond 202 + expected_fee = tx_constructed['fee']['quantity'] + + # Can be decoded + tx_decoded = SHARED.transactions.decode(src_wid, tx_constructed['transaction']) + expect(tx_decoded).to be_correct_and_respond 202 + + expect(tx_decoded['id'].size).to be 64 + decoded_fee = tx_decoded['fee']['quantity'] + expect(expected_fee).to eq decoded_fee + + # cosigner0 signs + tx_signed1 = SHARED.transactions.sign(src_wid, PASS, tx_constructed['transaction']) + expect(tx_signed1).to be_correct_and_respond 202 + + # cosigner1 signs + tx_signed = SHARED.transactions.sign(cosigner_wid, PASS, tx_signed1['transaction']) + expect(tx_signed).to be_correct_and_respond 202 + + # cosigner0 submits + tx_submitted = SHARED.transactions.submit(src_wid, tx_signed['transaction']) + expect(tx_submitted).to be_correct_and_respond 202 + + tx_id = tx_submitted['id'] + wait_for_tx_in_ledger(src_wid, tx_id, SHARED) + + target_after = get_shelley_balances(target_wid) + src_after = get_shared_balances(src_wid) + + verify_ada_balance(src_after, src_before, + target_after, target_before, + amt, expected_fee) + # tx history + # on src wallet + tx = SHARED.transactions.get(src_wid, tx_id) + tx_amount(tx, amt + expected_fee) + tx_fee(tx, expected_fee) + tx_inputs(tx, present: true) + tx_outputs(tx, present: true) + tx_direction(tx, 'outgoing') + tx_script_validity(tx, 'valid') + tx_status(tx, 'in_ledger') + tx_collateral(tx, present: false) + tx_collateral_outputs(tx, present: false) + tx_metadata(tx, nil) + tx_deposits(tx, deposit_taken: 0, deposit_returned: 0) + tx_withdrawals(tx, present: false) + tx_mint_burn(tx, mint: [], burn: []) + tx_extra_signatures(tx, present: false) + tx_script_integrity(tx, present: false) + tx_validity_interval_default(tx) + tx_certificates(tx, present: false) + + # on co-signer wid + tx = SHARED.transactions.get(cosigner_wid, tx_id) + tx_amount(tx, amt + expected_fee) + tx_fee(tx, expected_fee) + tx_inputs(tx, present: true) + tx_outputs(tx, present: true) + tx_direction(tx, 'outgoing') + tx_script_validity(tx, 'valid') + tx_status(tx, 'in_ledger') + tx_collateral(tx, present: false) + tx_collateral_outputs(tx, present: false) + tx_metadata(tx, nil) + tx_deposits(tx, deposit_taken: 0, deposit_returned: 0) + tx_withdrawals(tx, present: false) + tx_mint_burn(tx, mint: [], burn: []) + tx_extra_signatures(tx, present: false) + tx_script_integrity(tx, present: false) + tx_validity_interval_default(tx) + tx_certificates(tx, present: false) + + # on target wallet + txt = SHELLEY.transactions.get(target_wid, tx_id) + tx_amount(txt, amt) + tx_fee(txt, 0) + tx_inputs(txt, present: true) + tx_outputs(txt, present: true) + tx_direction(txt, 'incoming') + tx_script_validity(txt, 'valid') + tx_status(txt, 'in_ledger') + tx_collateral(txt, present: false) + tx_collateral_outputs(txt, present: false) + tx_metadata(txt, nil) + tx_deposits(txt, deposit_taken: 0, deposit_returned: 0) + tx_withdrawals(txt, present: false) + tx_mint_burn(txt, mint: [], burn: []) + tx_extra_signatures(txt, present: false) + tx_script_integrity(txt, present: false) + tx_validity_interval_default(txt) + tx_certificates(txt, present: false) + end + + it 'Multi output transaction - two cosigners, all' do + amt = MIN_UTXO_VALUE_PURE_ADA + src_wid = @wid_sha_cos0_all + cosigner_wid = @wid_sha_cos1_all + target_wid = @target_id + address = SHELLEY.addresses.list(target_wid)[1]['id'] + target_before = get_shelley_balances(target_wid) + src_before = get_shared_balances(src_wid) + payment = [{ address: address, + amount: { quantity: amt, + unit: 'lovelace' } }, + { address: address, + amount: { quantity: amt, + unit: 'lovelace' } }] + + tx_constructed = SHARED.transactions.construct(src_wid, payment) + expect(tx_constructed).to be_correct_and_respond 202 + expected_fee = tx_constructed['fee']['quantity'] + + # Can be decoded + tx_decoded = SHARED.transactions.decode(src_wid, tx_constructed['transaction']) + expect(tx_decoded).to be_correct_and_respond 202 + + expect(tx_decoded['id'].size).to be 64 + decoded_fee = tx_decoded['fee']['quantity'] + expect(expected_fee).to eq decoded_fee + + # cosigner0 signs + tx_signed1 = SHARED.transactions.sign(src_wid, PASS, tx_constructed['transaction']) + expect(tx_signed1).to be_correct_and_respond 202 + + # cosigner1 signs + tx_signed = SHARED.transactions.sign(cosigner_wid, PASS, tx_signed1['transaction']) + expect(tx_signed).to be_correct_and_respond 202 + + # cosigner1 submits + tx_submitted = SHARED.transactions.submit(cosigner_wid, tx_signed['transaction']) + expect(tx_submitted).to be_correct_and_respond 202 + + tx_id = tx_submitted['id'] + wait_for_tx_in_ledger(src_wid, tx_id, SHARED) + + target_after = get_shelley_balances(target_wid) + src_after = get_shared_balances(src_wid) + + verify_ada_balance(src_after, src_before, + target_after, target_before, + amt * 2, expected_fee) + # tx history + # on src wallet + tx = SHARED.transactions.get(src_wid, tx_id) + tx_amount(tx, (amt * 2) + expected_fee) + tx_fee(tx, expected_fee) + tx_inputs(tx, present: true) + tx_outputs(tx, present: true) + tx_direction(tx, 'outgoing') + tx_script_validity(tx, 'valid') + tx_status(tx, 'in_ledger') + tx_collateral(tx, present: false) + tx_collateral_outputs(tx, present: false) + tx_metadata(tx, nil) + tx_deposits(tx, deposit_taken: 0, deposit_returned: 0) + tx_withdrawals(tx, present: false) + tx_mint_burn(tx, mint: [], burn: []) + tx_extra_signatures(tx, present: false) + tx_script_integrity(tx, present: false) + tx_validity_interval_default(tx) + tx_certificates(tx, present: false) + + # on co-signer wid + tx = SHARED.transactions.get(cosigner_wid, tx_id) + tx_amount(tx, (amt * 2) + expected_fee) + tx_fee(tx, expected_fee) + tx_inputs(tx, present: true) + tx_outputs(tx, present: true) + tx_direction(tx, 'outgoing') + tx_script_validity(tx, 'valid') + tx_status(tx, 'in_ledger') + tx_collateral(tx, present: false) + tx_collateral_outputs(tx, present: false) + tx_metadata(tx, nil) + tx_deposits(tx, deposit_taken: 0, deposit_returned: 0) + tx_withdrawals(tx, present: false) + tx_mint_burn(tx, mint: [], burn: []) + tx_extra_signatures(tx, present: false) + tx_script_integrity(tx, present: false) + tx_validity_interval_default(tx) + tx_certificates(tx, present: false) + + # on target wallet + txt = SHELLEY.transactions.get(target_wid, tx_id) + tx_amount(txt, amt * 2) + tx_fee(txt, 0) + tx_inputs(txt, present: true) + tx_outputs(txt, present: true) + tx_direction(txt, 'incoming') + tx_script_validity(txt, 'valid') + tx_status(txt, 'in_ledger') + tx_collateral(txt, present: false) + tx_collateral_outputs(txt, present: false) + tx_metadata(txt, nil) + tx_deposits(txt, deposit_taken: 0, deposit_returned: 0) + tx_withdrawals(txt, present: false) + tx_mint_burn(txt, mint: [], burn: []) + tx_extra_signatures(txt, present: false) + tx_script_integrity(txt, present: false) + tx_validity_interval_default(txt) + tx_certificates(txt, present: false) + end + + it 'Multi-assets transaction - two cosigners, all' do + amt = 1 + amt_ada = 1_600_000 + src_wid = @wid_sha_cos0_all + cosigner_wid = @wid_sha_cos1_all + target_wid = @target_id + address = SHELLEY.addresses.list(target_wid)[1]['id'] + target_before = get_shelley_balances(target_wid) + src_before = get_shared_balances(src_wid) + payment = [{ 'address' => address, + 'amount' => { 'quantity' => amt_ada, 'unit' => 'lovelace' }, + 'assets' => [{ 'policy_id' => ASSETS[0]['policy_id'], + 'asset_name' => ASSETS[0]['asset_name'], + 'quantity' => amt }, + { 'policy_id' => ASSETS[1]['policy_id'], + 'asset_name' => ASSETS[1]['asset_name'], + 'quantity' => amt }] }] + + tx_constructed = SHARED.transactions.construct(src_wid, payment) + expect(tx_constructed).to be_correct_and_respond 202 + expected_fee = tx_constructed['fee']['quantity'] + + # Can be decoded + tx_decoded = SHARED.transactions.decode(src_wid, tx_constructed['transaction']) + expect(tx_decoded).to be_correct_and_respond 202 + + expect(tx_decoded['id'].size).to be 64 + decoded_fee = tx_decoded['fee']['quantity'] + expect(expected_fee).to eq decoded_fee + + # cosigner0 signs + tx_signed1 = SHARED.transactions.sign(src_wid, PASS, tx_constructed['transaction']) + expect(tx_signed1).to be_correct_and_respond 202 + + # cosigner1 signs + tx_signed = SHARED.transactions.sign(cosigner_wid, PASS, tx_signed1['transaction']) + expect(tx_signed).to be_correct_and_respond 202 + + # cosigner0 submits + tx_submitted = SHARED.transactions.submit(src_wid, tx_signed['transaction']) + expect(tx_submitted).to be_correct_and_respond 202 + + tx_id = tx_submitted['id'] + wait_for_tx_in_ledger(src_wid, tx_id, SHARED) + + target_after = get_shelley_balances(target_wid) + src_after = get_shared_balances(src_wid) + + verify_ada_balance(src_after, src_before, + target_after, target_before, + amt_ada, expected_fee) + # tx history + # on src wallet + tx = SHARED.transactions.get(src_wid, tx_id) + tx_amount(tx, amt_ada + expected_fee) + tx_fee(tx, expected_fee) + tx_inputs(tx, present: true) + tx_outputs(tx, present: true) + tx_direction(tx, 'outgoing') + tx_script_validity(tx, 'valid') + tx_status(tx, 'in_ledger') + tx_collateral(tx, present: false) + tx_collateral_outputs(tx, present: false) + tx_metadata(tx, nil) + tx_deposits(tx, deposit_taken: 0, deposit_returned: 0) + tx_withdrawals(tx, present: false) + tx_mint_burn(tx, mint: [], burn: []) + tx_extra_signatures(tx, present: false) + tx_script_integrity(tx, present: false) + tx_validity_interval_default(tx) + tx_certificates(tx, present: false) + + # on co-signer wid + tx = SHARED.transactions.get(cosigner_wid, tx_id) + tx_amount(tx, amt_ada + expected_fee) + tx_fee(tx, expected_fee) + tx_inputs(tx, present: true) + tx_outputs(tx, present: true) + tx_direction(tx, 'outgoing') + tx_script_validity(tx, 'valid') + tx_status(tx, 'in_ledger') + tx_collateral(tx, present: false) + tx_collateral_outputs(tx, present: false) + tx_metadata(tx, nil) + tx_deposits(tx, deposit_taken: 0, deposit_returned: 0) + tx_withdrawals(tx, present: false) + tx_mint_burn(tx, mint: [], burn: []) + tx_extra_signatures(tx, present: false) + tx_script_integrity(tx, present: false) + tx_validity_interval_default(tx) + tx_certificates(tx, present: false) + + # on target wallet + txt = SHELLEY.transactions.get(target_wid, tx_id) + tx_amount(txt, amt_ada) + tx_fee(txt, 0) + tx_inputs(txt, present: true) + tx_outputs(txt, present: true) + tx_direction(txt, 'incoming') + tx_script_validity(txt, 'valid') + tx_status(txt, 'in_ledger') + tx_collateral(txt, present: false) + tx_collateral_outputs(txt, present: false) + tx_metadata(txt, nil) + tx_deposits(txt, deposit_taken: 0, deposit_returned: 0) + tx_withdrawals(txt, present: false) + tx_mint_burn(txt, mint: [], burn: []) + tx_extra_signatures(txt, present: false) + tx_script_integrity(txt, present: false) + tx_validity_interval_default(txt) + tx_certificates(txt, present: false) + end + + it 'Validity intervals - two cosigners, all' do + amt = MIN_UTXO_VALUE_PURE_ADA + src_wid = @wid_sha_cos0_all + cosigner_wid = @wid_sha_cos1_all + target_wid = @target_id + address = SHELLEY.addresses.list(target_wid)[1]['id'] + target_before = get_shelley_balances(target_wid) + src_before = get_shared_balances(src_wid) + inv_before = 500 + inv_hereafter = 5_000_000_000 + validity_interval = { 'invalid_before' => { 'quantity' => inv_before, 'unit' => 'slot' }, + 'invalid_hereafter' => { 'quantity' => inv_hereafter, 'unit' => 'slot' } } + tx_constructed = SHARED.transactions.construct(cosigner_wid, + payment_payload(amt, address), + nil, # withdrawal + nil, # metadata + nil, # delegations + nil, # mint_burn + validity_interval) + expect(tx_constructed).to be_correct_and_respond 202 + expected_fee = tx_constructed['fee']['quantity'] + + # Can be decoded + tx_decoded = SHARED.transactions.decode(src_wid, tx_constructed['transaction']) + expect(tx_decoded).to be_correct_and_respond 202 + + expect(tx_decoded['id'].size).to be 64 + decoded_fee = tx_decoded['fee']['quantity'] + expect(expected_fee).to eq decoded_fee + + # cosigner0 signs + tx_signed1 = SHARED.transactions.sign(src_wid, PASS, tx_constructed['transaction']) + expect(tx_signed1).to be_correct_and_respond 202 + + # cosigner1 signs + tx_signed = SHARED.transactions.sign(cosigner_wid, PASS, tx_signed1['transaction']) + expect(tx_signed).to be_correct_and_respond 202 + + # cosigner0 submits + tx_submitted = SHARED.transactions.submit(src_wid, tx_signed['transaction']) + expect(tx_submitted).to be_correct_and_respond 202 + + tx_id = tx_submitted['id'] + wait_for_tx_in_ledger(src_wid, tx_id, SHARED) + + target_after = get_shelley_balances(target_wid) + src_after = get_shared_balances(src_wid) + + verify_ada_balance(src_after, src_before, + target_after, target_before, + amt, expected_fee) + # tx history + # on src wallet + tx = SHARED.transactions.get(src_wid, tx_id) + tx_amount(tx, amt + expected_fee) + tx_fee(tx, expected_fee) + tx_inputs(tx, present: true) + tx_outputs(tx, present: true) + tx_direction(tx, 'outgoing') + tx_script_validity(tx, 'valid') + tx_status(tx, 'in_ledger') + tx_collateral(tx, present: false) + tx_collateral_outputs(tx, present: false) + tx_metadata(tx, nil) + tx_deposits(tx, deposit_taken: 0, deposit_returned: 0) + tx_withdrawals(tx, present: false) + tx_mint_burn(tx, mint: [], burn: []) + tx_extra_signatures(tx, present: false) + tx_script_integrity(tx, present: false) + tx_validity_interval(tx, invalid_before: inv_before, invalid_hereafter: inv_hereafter) + tx_certificates(tx, present: false) + + # on co-signer wid + tx = SHARED.transactions.get(cosigner_wid, tx_id) + tx_amount(tx, amt + expected_fee) + tx_fee(tx, expected_fee) + tx_inputs(tx, present: true) + tx_outputs(tx, present: true) + tx_direction(tx, 'outgoing') + tx_script_validity(tx, 'valid') + tx_status(tx, 'in_ledger') + tx_collateral(tx, present: false) + tx_collateral_outputs(tx, present: false) + tx_metadata(tx, nil) + tx_deposits(tx, deposit_taken: 0, deposit_returned: 0) + tx_withdrawals(tx, present: false) + tx_mint_burn(tx, mint: [], burn: []) + tx_extra_signatures(tx, present: false) + tx_script_integrity(tx, present: false) + tx_validity_interval(tx, invalid_before: inv_before, invalid_hereafter: inv_hereafter) + tx_certificates(tx, present: false) + + # on target wallet + txt = SHELLEY.transactions.get(target_wid, tx_id) + tx_amount(txt, amt) + tx_fee(txt, 0) + tx_inputs(txt, present: true) + tx_outputs(txt, present: true) + tx_direction(txt, 'incoming') + tx_script_validity(txt, 'valid') + tx_status(txt, 'in_ledger') + tx_collateral(txt, present: false) + tx_collateral_outputs(txt, present: false) + tx_metadata(txt, nil) + tx_deposits(txt, deposit_taken: 0, deposit_returned: 0) + tx_withdrawals(txt, present: false) + tx_mint_burn(txt, mint: [], burn: []) + tx_extra_signatures(txt, present: false) + tx_script_integrity(txt, present: false) + tx_validity_interval(txt, invalid_before: inv_before, invalid_hereafter: inv_hereafter) + tx_certificates(txt, present: false) + end + + it 'Only metadata - two cosigners, all' do + src_wid = @wid_sha_cos0_all + cosigner_wid = @wid_sha_cos1_all + metadata = METADATA + balance = get_shared_balances(src_wid) + + tx_constructed = SHARED.transactions.construct(src_wid, + nil, # payments + nil, # withdrawal + metadata) + expect(tx_constructed).to be_correct_and_respond 202 + expected_fee = tx_constructed['fee']['quantity'] + + # Can be decoded + tx_decoded = SHARED.transactions.decode(src_wid, tx_constructed['transaction']) + expect(tx_decoded).to be_correct_and_respond 202 + + expect(tx_decoded['id'].size).to be 64 + decoded_fee = tx_decoded['fee']['quantity'] + expect(expected_fee).to eq decoded_fee + + # cosigner0 signs + tx_signed1 = SHARED.transactions.sign(src_wid, PASS, tx_constructed['transaction']) + expect(tx_signed1).to be_correct_and_respond 202 + + # cosigner1 signs + tx_signed = SHARED.transactions.sign(cosigner_wid, PASS, tx_signed1['transaction']) + expect(tx_signed).to be_correct_and_respond 202 + + # cosigner0 submits + tx_submitted = SHARED.transactions.submit(src_wid, tx_signed['transaction']) + expect(tx_submitted).to be_correct_and_respond 202 + + tx_id = tx_submitted['id'] + wait_for_tx_in_ledger(src_wid, tx_id, SHARED) + + # examine the tx in history + # on src wallet + tx = SHARED.transactions.get(src_wid, tx_id) + tx_amount(tx, expected_fee) + tx_fee(tx, expected_fee) + tx_inputs(tx, present: true) + tx_outputs(tx, present: true) + tx_direction(tx, 'outgoing') + tx_script_validity(tx, 'valid') + tx_status(tx, 'in_ledger') + tx_collateral(tx, present: false) + tx_collateral_outputs(tx, present: false) + tx_metadata(tx, metadata) + tx_deposits(tx, deposit_taken: 0, deposit_returned: 0) + tx_withdrawals(tx, present: false) + tx_mint_burn(tx, mint: [], burn: []) + tx_extra_signatures(tx, present: false) + tx_script_integrity(tx, present: false) + tx_validity_interval_default(tx) + tx_certificates(tx, present: false) + + # verify balance is as expected + new_balance = get_shared_balances(src_wid) + expect(new_balance['available']).to eq(balance['available'] - expected_fee) + expect(new_balance['total']).to eq(balance['total'] - expected_fee) + end + + it 'Single output transaction - one cosigner, any' do + amt = MIN_UTXO_VALUE_PURE_ADA * 2 + src_wid = @wid_sha_cos0_any + target_wid = @target_id + address = SHELLEY.addresses.list(target_wid)[1]['id'] + target_before = get_shelley_balances(target_wid) + src_before = get_shared_balances(src_wid) + + tx_constructed = SHARED.transactions.construct(src_wid, payment_payload(amt, address)) + expect(tx_constructed).to be_correct_and_respond 202 + expected_fee = tx_constructed['fee']['quantity'] + + # Can be decoded + tx_decoded = SHARED.transactions.decode(src_wid, tx_constructed['transaction']) + expect(tx_decoded).to be_correct_and_respond 202 + + expect(tx_decoded['id'].size).to be 64 + decoded_fee = tx_decoded['fee']['quantity'] + expect(expected_fee).to eq decoded_fee + + # cosigner0 signs + tx_signed = SHARED.transactions.sign(src_wid, PASS, tx_constructed['transaction']) + expect(tx_signed).to be_correct_and_respond 202 + + # cosigner0 submits + tx_submitted = SHARED.transactions.submit(src_wid, tx_signed['transaction']) + expect(tx_submitted).to be_correct_and_respond 202 + + tx_id = tx_submitted['id'] + wait_for_tx_in_ledger(src_wid, tx_id, SHARED) + + target_after = get_shelley_balances(target_wid) + src_after = get_shared_balances(src_wid) + + verify_ada_balance(src_after, src_before, + target_after, target_before, + amt, expected_fee) + # tx history + # on src wallet + tx = SHARED.transactions.get(src_wid, tx_id) + tx_amount(tx, amt + expected_fee) + tx_fee(tx, expected_fee) + tx_inputs(tx, present: true) + tx_outputs(tx, present: true) + tx_direction(tx, 'outgoing') + tx_script_validity(tx, 'valid') + tx_status(tx, 'in_ledger') + tx_collateral(tx, present: false) + tx_collateral_outputs(tx, present: false) + tx_metadata(tx, nil) + tx_deposits(tx, deposit_taken: 0, deposit_returned: 0) + tx_withdrawals(tx, present: false) + tx_mint_burn(tx, mint: [], burn: []) + tx_extra_signatures(tx, present: false) + tx_script_integrity(tx, present: false) + tx_validity_interval_default(tx) + tx_certificates(tx, present: false) + + # on target wallet + txt = SHELLEY.transactions.get(target_wid, tx_id) + tx_amount(txt, amt) + tx_fee(txt, 0) + tx_inputs(txt, present: true) + tx_outputs(txt, present: true) + tx_direction(txt, 'incoming') + tx_script_validity(txt, 'valid') + tx_status(txt, 'in_ledger') + tx_collateral(txt, present: false) + tx_collateral_outputs(txt, present: false) + tx_metadata(txt, nil) + tx_deposits(txt, deposit_taken: 0, deposit_returned: 0) + tx_withdrawals(txt, present: false) + tx_mint_burn(txt, mint: [], burn: []) + tx_extra_signatures(txt, present: false) + tx_script_integrity(txt, present: false) + tx_validity_interval_default(txt) + tx_certificates(txt, present: false) + end + end + describe 'E2E Construct -> Sign -> Submit - single signer' do it 'I can get min_utxo_value when contructing tx' do amt = 1 tx_constructed = SHARED.transactions.construct(@wid_sha, payment_payload(amt)) @@ -477,7 +1120,7 @@ tx_submitted = SHARED.transactions.submit(@wid_sha, tx_signed['transaction']) expect(tx_submitted).to be_correct_and_respond 202 - signed_decoded = SHELLEY.transactions.decode(@wid, tx_signed['transaction']) + signed_decoded = SHARED.transactions.decode(@wid_sha, tx_signed['transaction']) expect(signed_decoded['witness_count']['verification_key']).to be >= 1 expect(expected_fee).to eq signed_decoded['fee']['quantity'] diff --git a/test/e2e/spec/spec_helper.rb b/test/e2e/spec/spec_helper.rb index df279bc6b75..f30d489321d 100644 --- a/test/e2e/spec/spec_helper.rb +++ b/test/e2e/spec/spec_helper.rb @@ -152,6 +152,34 @@ def patch_incomplete_shared_wallet(wid, payment_patch, deleg_patch) expect(d_upd).to be_correct_and_respond 200 end +def patch_if_incomplete(wid, payment_patch, deleg_patch) + if payment_patch + p_upd = SHARED.wallets.update_payment_script(wid, + payment_patch.keys.first, + payment_patch.values.first) + case p_upd.code + when 200 + expect(p_upd).to be_correct_and_respond 200 + when 403 + expect(p_upd).to be_correct_and_respond 403 + expect(p_upd.parsed_response['code']).to eq 'shared_wallet_not_pending' + end + end + + return unless deleg_patch + + d_upd = SHARED.wallets.update_delegation_script(wid, + deleg_patch.keys.first, + deleg_patch.values.first) + case d_upd.code + when 200 + expect(d_upd).to be_correct_and_respond 200 + when 403 + expect(d_upd).to be_correct_and_respond 403 + expect(d_upd.parsed_response['code']).to eq 'shared_wallet_not_pending' + end +end + def create_active_shared_wallet(m, acc_ix, acc_xpub) script_template = { 'cosigners' => { 'cosigner#0' => acc_xpub }, @@ -286,8 +314,10 @@ def return_wallet_id(create_wallet_response) ## # create fixture wallet or return it's id if it exists # @param type [Symbol] :shelley, :shared, :shared_cosigner_0, :shared_cosigner_1, :random, :icarus -def create_fixture_wallet(type) - payload = { name: "Fixture wallet with funds (#{type})", +# @param templates [Symbols] ':payment_cosigner{0,1}_{all,any,all0}', :delegation_cosigner{0,1}_{all,any,all0} +# rubocop:disable Metrics/CyclomaticComplexity +def create_fixture_wallet(type, *templates) + payload = { name: "Fixture wallet with funds (#{type}#{" #{templates}" unless templates.empty?}", passphrase: PASS, mnemonic_sentence: get_fixture_wallet(:fixture, type.to_sym, :mnemonics) } case type.to_sym @@ -298,27 +328,39 @@ def create_fixture_wallet(type) payload[:style] = type wallet = BYRON.wallets.create(payload) return_wallet_id(wallet) - when :shared - script_template = { 'cosigners' => - { 'cosigner#0' => 'self' }, - 'template' => - { 'all' => - ['cosigner#0'] } } - payload[:account_index] = '0H' - payload[:payment_script_template] = script_template - payload[:delegation_script_template] = script_template - wallet = SHARED.wallets.create(payload) - return_wallet_id(wallet) - when :shared_cosigner_0, :shared_cosigner_1 + when :shared, :shared2 + templates.each do |t| + case t + when :payment_cosigner0_all + payload[:payment_script_template] = { 'cosigners' => { 'cosigner#0' => 'self' }, 'template' => { 'all' => ['cosigner#0', 'cosigner#1'] } } + when :delegation_cosigner0_all + payload[:delegation_script_template] = { 'cosigners' => { 'cosigner#0' => 'self' }, 'template' => { 'all' => ['cosigner#0', 'cosigner#1'] } } + when :payment_cosigner1_all + payload[:payment_script_template] = { 'cosigners' => { 'cosigner#1' => 'self' }, 'template' => { 'all' => ['cosigner#0', 'cosigner#1'] } } + when :delegation_cosigner1_all + payload[:delegation_script_template] = { 'cosigners' => { 'cosigner#1' => 'self' }, 'template' => { 'all' => ['cosigner#0', 'cosigner#1'] } } + when :payment_cosigner0_any + payload[:payment_script_template] = { 'cosigners' => { 'cosigner#0' => 'self' }, 'template' => { 'any' => ['cosigner#0', 'cosigner#1'] } } + when :delegation_cosigner0_any + payload[:delegation_script_template] = { 'cosigners' => { 'cosigner#0' => 'self' }, 'template' => { 'any' => ['cosigner#0', 'cosigner#1'] } } + when :payment_cosigner1_any + payload[:payment_script_template] = { 'cosigners' => { 'cosigner#1' => 'self' }, 'template' => { 'any' => ['cosigner#0', 'cosigner#1'] } } + when :delegation_cosigner1_any + payload[:delegation_script_template] = { 'cosigners' => { 'cosigner#1' => 'self' }, 'template' => { 'any' => ['cosigner#0', 'cosigner#1'] } } + when :payment_cosigner0_all0 + payload[:payment_script_template] = { 'cosigners' => { 'cosigner#0' => 'self' }, 'template' => { 'all' => ['cosigner#0'] } } + when :delegation_cosigner0_all0 + payload[:delegation_script_template] = { 'cosigners' => { 'cosigner#0' => 'self' }, 'template' => { 'all' => ['cosigner#0'] } } + end + end payload[:account_index] = '0H' - payload[:payment_script_template] = get_fixture_wallet(:fixture, type.to_sym, :payment_template) - payload[:delegation_script_template] = get_fixture_wallet(:fixture, type.to_sym, :delegation_template) wallet = SHARED.wallets.create(payload) return_wallet_id(wallet) else raise "Unsupported wallet type: #{type}" end end +# rubocop:enable Metrics/CyclomaticComplexity ## # create target wallet or return it's id if it exists From 96948f8330effb5dd1cf05853a19e9a74d208c0a Mon Sep 17 00:00:00 2001 From: Piotr Stachyra Date: Tue, 22 Nov 2022 09:37:03 +0100 Subject: [PATCH 7/7] add fixture wallets --- test/e2e/fixtures/fixture_wallets.json.gpg | Bin 1942 -> 2150 bytes 1 file changed, 0 insertions(+), 0 deletions(-) diff --git a/test/e2e/fixtures/fixture_wallets.json.gpg b/test/e2e/fixtures/fixture_wallets.json.gpg index b4b14a1edcf0b08a556e2af180ef065b418f1d74..4ffeb068e987748115ac25fe6706230098266a1e 100644 GIT binary patch literal 2150 zcmV-s2$}bc4Fm@R0<}>iIw%xC#sAXl0i-6^RIt};-+`&B0)ZP-JtL)}a~>s{!Z!M2 zt}6(r2eZUIUI`FA!=%uRCJe@n+)_c-@;{aP&yC z${{-OjLz*#Wz$414Ih#xv^T!A{lw3wjZMQCRJ7&=EY2_G{jWU863lb0Fy};Wguc7H zUQ$-(be2WC-ZUMHK+oc$scc6%vO$k+W6uXOKT0Z@*LFQ;yDX(bA7CSg*eqSMs!CY2 zxU~MsvZqj=N%F|3kJfry`a~DIZy9F=7w_p97=Ap@Kh`&r9@hr@uEXMA#p`1Xm#QPJ z6s-1@iM)39J}iDu#)bK!9msb8IxNw@q7GiDE{6AQT*}L=pVbUHpR$a4Guq4?%CHXB zt;6D!Q2%h?zAU^v_mv9KQJt zXeC_9#RU_?(w@#HXqsikKzN+zmI4ks>aZBIB93dM>p&_Wrw$XEPZA7+Q`f|rq;ywY z@qSj=yN~j+-q~MlwC^+aKvvBdv=1o65~yv%lA2uRJ77j);FtUcH4UnU^3S%OkA9|3 zf+FV=F$d65@WNtJZoZn1sK(8YJXzLc;%6E>N{uVPWTMV9a6PUuKl=el-P1oli~vb3 zkyMj&BjdePW9URMUKgKg1-eWqH*sfmOf0g4HoT3=fAE|?2~$=Wy&>Hy&0J={p^#d; zDVgC@)pMN0QBV_VhXUg+iEi6P(Jg;-TD(ZuId|-xa!HXd*}%%>rEbpzTj0fKT+WjA z33kv!$~k9`28)K0sYGUX4bny-4DFqIg=K!-<;vpc+nwODNi)srb~QbDo*Np#F7@_W zdQCyZ3(vNZnKH$ zOc~9b?8XqW2by+Dh{k!GL@x%+pytoakHRL&Mw( z?)#AWKzp1&rbQ|)%)PC*j5vnty`Z@}F3W$v1uvNwY)qmI_z2D>1+OD>lP$l=tep`F zGTH{I!Ws%v2Se?Lf|3ZG&%Zg>B{nTh=0B#Ii;+R5!CgI;jNU9{{8F$~j>Bxcqq!bwVUn|n`JL}f=q3HV7|X&T8s z1G(8xvw3L1CP`$VizYunIbn+EYOmV%ZKI0+++$@)wYA9WYC=5efh#qyWkORz5!=D%(Ym_L*<)Vvf4y(Udi*AV7yDK%$BxoSqH-p$f}h4(6i1 zz|TvII9o1IivnX~sIt2$LiIDT%WHJAxne*1w!E96N4!X2Fd7o*E|EA5|K2P?um~}K z-P@H+q^u`BhVFw`0QaYg1#_RvWfj?Hd|}^X@ooC)fgEv1r+Pj?cfj~q@PfPT#1%o9 z6jyr~+Z1ok+YoV6>|hVN!Alk5NgzzY9% z8;$Ka`v>@w)O;<&rN6*b$FxfjxNb8;i|!TJ!CkEIKaEt8>DVEF{#Q8TJ!G+8K&1`4 zDD~uU2PiJ(nd$8YId!Rh85RGi87InXEsiWd`O=^K0K&6_;DT~i>#H8iVVlHfI_H_P zrSZVim=ym-De%PcE4E}SjtTZ0op;zKn05%1#mf>%21Zc<<(je?v(&40Oa8=Q$=TQO zAXk^}#z*>M5#^H>CBMsxT2O@?`CPDRM@ouV+nQq>iV|6)#Z)UlzZHyIG=7Q<74?O& zxmy)oeHrf?B2+j-6l$zJ&N5Y)Rr{jKD{f`MOCHHbyPMjE0#^SbE((aSh!68R3={;#{Q%_G#uv%g%}lU8nyaJV8I#l}MV9f8(K4%m z<3RaN%LSA|kpm}uVrr<7hFgssLW;6QE7Z7FWUl)s-jF$WTnQ~nRyLm2RI-@{6J|ej zrRMha1~VCJ7NMS*p9t4Cd3aA&5Lczz{)sRIBe!;iYo)yJe)gbY c7z|mMMG=ED)Cpm;YM9}9CiPD5Zw}+IBh9=IV*mgE literal 1942 zcmV;H2Wj|>4Fm@R0=APhI$ZLp^8eE60bCy%NJLo@Oe^c7OIBEOEmqtaNRSaR)%*|z zpK7mLJ+jon$9(k9l#~Va9=*WQ7xty;#*@8t=rw>4(8trf(DS@j>C2GuNq=F9mYc{c zYi@H=enO8z@?+-7g_;c-f%xJwHoO%1+R+M*m(GPx#bU8r66|f44dCJXDtSiIL%)ul z(A3%2jbbw>z(4eAEjj)f+?n(}ka6kE2lQF8 zJ6&pCUZZ=>c52#FG%sLLvDINgYgZn-XYThrHj^%@_LsdFj7EBsr1%1XJ>x7^Ls*+c zRu>IEEuBgQU%#{_6B5Q6CYz{&KQWNyX`hPdu&pq$8}}|Uf@=Szhi_4kwe{PHUOZ84 z0io1f5lhupOWHoYS!|YFQ%2b8by$vs%i&$#?MKF~!iO1;FbGfS{PW5rCCJ9_z$#{W zbp1Jd!(~+h{KLf)R3neQXtsP;X$F=gyK)L*g^;J;FZ=F$6zWouA)p8bpo*SBYv%igf2=a#fqn07H9*A5fFVVXz5a*?mMw7ZFA;|F=V``t}r04q`Y59;io8eU4D z6?bdUGxxs>AWS(_mBgHupxu^=5maaqIk$-UwyKO?ur)H>UQ0`WyRj;STs1J}m#JSX zF-fU9@d*M;tfrNk8(h->?pF23h~YtyOV~@ShKwd-=WR|Fun*Ynje8+7@4uR1t8xH} zt=IRYg{QbHl`*xopTbsVShk-lzXJEs_u486*?t9BZpk)9mtzn#k)9F02)gm^00KXT z*x_YkVgErSW{8A*=aA`wTY{WMrfk*nR2r2s1@>|tNcehX1nV}RU8wTX4I7yNwY1Zf ze&ZbQkqa8wM9&pn-ZeD%*|rI1qCQxZO1+w-fUN7GH)pjoG?C?4S`_OpWV7{&?{d?` z$ziwR&z||DiUIK%~Qp8!(FTv@8;o0I`8K9T6e`bqfBO8fpYN&X~<#A zfl#Z*&8wVt6%7sjjV}0JW_agmZ!Fkypm`^{SY9xw-M;@oX2as}ZJ>}35Bv^7b~a7* zwjSUP8um9Z99t9t6chrB(k_h<1dTiUwG!8BOdkDeWX8pb^`vCe>2Vncq}^7G985WxlbwJ zxK`))W8}PQEzWDEv9TuxO`0ps6wC~j1>fQ%utDT`6)u1Y@4jRkp100Wk2=mC^gbpP zU$@NoNG=KyuYWZmdU_@9hAIu_b`#IkV$B;I|9!8ck1@gg-pTVfiH#TTiern;YAUxN zs`o9{OI{_v0Vhpxsuow)*ry=WbHp6s;!lh_53+zHXL6c77OxR{zfpL(9^mef_uXTY zEGKq1v^I19qV&|U8-3Jonf_>io6~g_2=$A(i6JcUlOCP}!;nv7$(rmTN4%X~phz5o zR-d7KB8SWECx)heJPnwAU~zJ(k1x?d?`@QfWk8%Csxv+qN#>NdNCB*Haq$oY2*i5k zY;1Oz6gtP*FuhEZHoFO{nCy&)@_xFfLy0-##O_~yUN`j^c1y-*ZMBM!gL&6;3-5M5 z55U3`$sDKWXHf=%396QjOr!s1FN3(^jlFwiRn z%rRwCfMH-@lgo^yxxu%%!<6SmGrS7(fjAM#x=Scm#A}xM!)3tA!!xxD)+eme-p2pe zG4G+|&Pw1Qq$UhG(6Wx5C9l&^ovYnEw!9ycK%nFTU36I+E9l9=reh*fQ9Cwg!e(nE zy=+iO%YP_dD@~Srw#2>-xOa^$khJoim{E&GI!TXI!Zsv^< z%e&w_H!M(Z*^_6?_>+7M>jZ0+pBo;_y)8^^zRsYWDNyR(0JfhGV9FdY_zZ1EdvVH1 zA9A`^pIrjdTYEvc4s)|KT>TF4abtyDxaOw7L8-Q-eV?;1DO*HMx_?CQrnASdJpep) co}^SaZO?g~n92nPwJt(G!C~l;W7ASI-Jt}|(f|Me