From d9a11caeb8a37f1b8590ac77965bb21983f9c123 Mon Sep 17 00:00:00 2001 From: Quyen Date: Mon, 4 Jun 2018 14:36:44 +0200 Subject: [PATCH] Tests: add tests for transfer and origination operations --- src/proto_alpha/lib_protocol/test/main.ml | 6 - .../lib_protocol/test/origination.ml | 327 ++++++++++++ src/proto_alpha/lib_protocol/test/transfer.ml | 479 ++++++++++++++++++ 3 files changed, 806 insertions(+), 6 deletions(-) create mode 100644 src/proto_alpha/lib_protocol/test/origination.ml create mode 100644 src/proto_alpha/lib_protocol/test/transfer.ml diff --git a/src/proto_alpha/lib_protocol/test/main.ml b/src/proto_alpha/lib_protocol/test/main.ml index bac9f389e..5e4c4a210 100644 --- a/src/proto_alpha/lib_protocol/test/main.ml +++ b/src/proto_alpha/lib_protocol/test/main.ml @@ -11,10 +11,4 @@ let () = Alcotest.run "protocol_alpha" [ "transfer", Transfer.tests ; "origination", Origination.tests ; - "baking", Baking.tests ; - "activation", Activation.tests ; - "seed", Seed.tests ; - "endorsement", Endorsement.tests ; - "double endorsement", Double_endorsement.tests ; - "double baking", Double_baking.tests ; ] diff --git a/src/proto_alpha/lib_protocol/test/origination.ml b/src/proto_alpha/lib_protocol/test/origination.ml new file mode 100644 index 000000000..22fe3b93a --- /dev/null +++ b/src/proto_alpha/lib_protocol/test/origination.ml @@ -0,0 +1,327 @@ +(**************************************************************************) +(* *) +(* Copyright (c) 2014 - 2018. *) +(* Dynamic Ledger Solutions, Inc. *) +(* *) +(* All rights reserved. No warranty, explicit or implicit, provided. *) +(* *) +(**************************************************************************) + +open Proto_alpha +open Test_tez + +let ten_tez = Tez.of_int 10 + +(* create a source contract, use it to create an originate contract + with fee and credit as parameters (default to zero tez), this originate + contract can also be spendable and/or delegatable. *) +let register_origination ?(fee=Tez.zero) ?(credit=Tez.zero) ?spendable ?delegatable () = + Context.init 1 >>=? fun (b, contracts) -> + let contract = List.hd contracts in + Op.origination (B b) contract ~fee ~credit ?spendable ?delegatable + >>=? fun (operation, originated) -> + Block.bake ~operation b >>=? fun b -> + return (b, contract, originated) + + +(* like register_origination but additionally tests that + - the source contract has payed all the fees + - the originated has been credited correctly *) +let test_origination_balances ~loc ?(fee=Tez.zero) ?(credit=Tez.zero) + ?spendable ?delegatable () = + Context.init 1 >>=? fun (b, contracts) -> + let contract = List.hd contracts in + + Context.Contract.balance (B b) contract >>=? fun balance -> + Op.origination (B b) contract ~fee ~credit ?spendable ?delegatable >>=? fun (operation, new_contract) -> + + (* The possible fees are: a given credit, an origination burn fee of 1 tez, + a fee that is paid when creating an originate contract *) + Context.get_constants (B b) >>=? fun {parametric = {origination_burn ; + block_security_deposit}} -> + Lwt.return ( + Tez.(+?) credit block_security_deposit >>? + Tez.(+?) fee >>? + Tez.(+?) origination_burn ) >>=? fun total_fee -> + + Block.bake ~operation b >>=? fun b -> + + (* check that after the block has been baked the source contract + was debited all the fees *) + Assert.balance_was_debited ~loc (B b) contract balance total_fee + >>=? fun _ -> + + (* check the balance of the originate contract is equal to credit *) + Assert.balance_is ~loc (B b) new_contract credit + + +(* transfer an amount of tez (with no fee) and check the source and + destination balances. *) +let transfer_and_check_balances b source dest amount = + Context.Contract.balance (B b) source >>=? fun balance -> + Op.transaction (B b) source dest amount >>=? fun operation -> + Block.bake ~operation b >>=? fun b -> + Assert.balance_was_debited ~loc:__LOC__ (B b) source balance amount >>=? fun _ -> + return b + +(******************************************************) +(* Tests *) + +let balances_simple () = test_origination_balances ~loc:__LOC__ () + +let balances_credit () = + test_origination_balances ~loc:__LOC__ ~credit:ten_tez () + +let balances_credit_fee () = + test_origination_balances ~loc:__LOC__ ~credit:(Tez.of_int 2) ~fee:ten_tez () + +let balances_credit_unspendable () = + test_origination_balances ~loc:__LOC__ ~credit:Tez.one ~spendable:false () + +let balances_undelegatable () = + test_origination_balances ~loc:__LOC__ ~delegatable:false () + + +(* create an originate contract with a credit, then use this contract to + transfer some tez back into the source contract, change the delegate + contract to the endorser account *) + +let regular () = + register_origination ~credit:ten_tez () >>=? fun (b, contract, new_contract) -> + transfer_and_check_balances b new_contract contract Tez.one_cent >>=? fun _ -> + + (* Delegatable *) + Context.get_endorser (B b) 0 >>=? fun account -> + Op.delegation (B b) new_contract (Some account) >>=? fun operation -> + Block.bake ~operation b >>=? fun _ -> + return () + +(* ask source contract to pay a fee when originating a contract *) + +let pay_fee () = + register_origination ~credit:(Tez.of_int 2) ~fee:ten_tez () >>=? fun (b, contract, new_contract) -> + transfer_and_check_balances b new_contract contract (Tez.of_int 2) >>=? fun _ -> + return () + +(******************************************************) +(* Errors *) + +(* the originate contract is marked as unspendable. Then ask this + contract to transfer, it will raise an error *) + +let unspendable () = + register_origination ~credit:Tez.one ~spendable:false () >>=? fun (b, contract, new_contract) -> + Op.transaction (B b) new_contract contract Tez.one_cent >>=? fun operation -> + Block.bake ~operation b >>= fun e -> + let unspendable = function + | Proto_alpha.Contract_storage.Unspendable_contract _ -> true + | _ -> false in + Assert.proto_error ~loc:__LOC__ e unspendable + +(* the originate contract is marked as undelegatable. Then do the delegation + for this contract, it will raise an error *) + +let undelegatable fee () = + register_origination ~delegatable:false () >>=? fun (b, _, new_contract) -> + Context.get_endorser (B b) 0 >>=? fun account -> + Incremental.begin_construction b >>=? fun i -> + Context.Contract.balance (I i) new_contract >>=? fun balance -> + (* FIXME need Context.Contract.delegate: cf. delegation tests + Context.Contract.delegate (I i) new_contract >>=? fun delegate -> *) + Op.delegation ~fee (I i) new_contract (Some account) >>=? fun operation -> + if fee > balance then + (* fees cannot be paid *) + begin + Incremental.add_operation i operation >>= fun res -> + let not_enough_money = function + | Proto_alpha.Contract_storage.Balance_too_low _ -> true + | _ -> false + in + Assert.proto_error ~loc:__LOC__ res not_enough_money + end + else + (* delegation is processed ; but delegate does not change *) + begin + Incremental.add_operation i operation >>=? fun i -> + (* new contracts loses the fee *) + Assert.balance_was_debited ~loc:__LOC__ (I i) new_contract balance fee + (* TODO delegate has not changed : wait for delegation tests and Context.Contract.delegate + >>=? fun () -> + Context.Contract.delegate (I i) new_contract >>=? fun new_delegate -> + Assert.equal_account ~loc:__LOC__ delegate new_delegate + *) + end + +let credit fee () = + register_origination ~credit:Tez.zero () >>=? fun (b, contract, new_contract) -> + Incremental.begin_construction b >>=? fun i -> + Context.Contract.balance (I i) contract >>=? fun balance -> + Context.Contract.balance (I i) new_contract >>=? fun new_balance -> + (* the source contract does not have enough tez to transfer *) + Op.transaction ~fee (I i) new_contract contract Tez.one_cent >>=? fun operation -> + if fee > new_balance then + begin + Incremental.add_operation i operation >>= fun res -> + let not_enough_money = function + | Proto_alpha.Contract_storage.Balance_too_low _ -> true + | _ -> false + in + Assert.proto_error ~loc:__LOC__ res not_enough_money + end + else + begin + Incremental.add_operation i operation >>=? fun i -> + (* new contracts loses the fee *) + Assert.balance_was_debited ~loc:__LOC__ (I i) new_contract new_balance fee >>=? fun () -> + (* contract is not credited *) + Assert.balance_was_credited ~loc:__LOC__ (I i) contract balance Tez.zero + end + +(* same as register_origination but for an incremental *) +let register_origination_inc ~credit () = + Context.init 1 >>=? fun (b, contracts) -> + let contract = List.hd contracts in + Incremental.begin_construction b >>=? fun inc -> + Op.origination (I inc) ~credit contract >>=? fun (operation, new_contract) -> + Incremental.add_operation inc operation >>=? fun inc -> + return (inc, new_contract) + +(* Using the originate contract to create another + originate contract *) + +let origination_contract_from_origination_contract_not_enough_fund fee () = + let amount = Tez.one in + register_origination_inc ~credit:amount () >>=? fun (inc, contract) -> + (* contract's balance is not enough to afford origination burn *) + Op.origination ~fee (I inc) ~credit:amount contract >>=? fun (operation, orig_contract) -> + Incremental.add_operation inc operation >>=? fun inc -> + Context.Contract.balance (I inc) contract >>=? fun balance_aft -> + (* contract was debited of [fee] but not of origination burn *) + Assert.balance_was_debited ~loc:__LOC__ (I inc) contract balance_aft fee >>=? fun () -> + (* orig_contract does not exist *) + Context.Contract.balance (I inc) orig_contract >>= fun res -> + Assert.error ~loc:__LOC__ res begin function + | RPC_context.Not_found _ -> true + | _ -> false + end + +(* create an originate contract where the contract + does not have enough tez to pay for the fee *) +let not_tez_in_contract_to_pay_fee () = + Context.init 2 >>=? fun (b, contracts) -> + let contract_1 = List.nth contracts 0 in + let contract_2 = List.nth contracts 1 in + Incremental.begin_construction b >>=? fun inc -> + (* transfer everything but one tez from 1 to 2 and check balance of 1 *) + Context.Contract.balance (I inc) contract_1 >>=? fun balance -> + Lwt.return @@ Tez.(-?) balance Tez.one >>=? fun amount -> + Op.transaction (I inc) contract_1 contract_2 amount >>=? fun operation -> + Incremental.add_operation inc operation >>=? fun inc -> + Assert.balance_was_debited ~loc:__LOC__ (I inc) contract_1 balance amount + >>=? fun _ -> + + (* use this source contract to create an originate contract where it requires + to pay a fee and add an amount of credit into this new contract *) + Op.origination (I inc) ~fee:ten_tez ~credit:Tez.one contract_1 >>=? fun (op, _) -> + Incremental.add_operation inc op >>= fun inc -> + Assert.proto_error ~loc:__LOC__ inc begin function + | Contract_storage.Balance_too_low _ -> true + | _ -> false + end + +(******************************************************) +(* change the manager/delegate of this account to the account + of endorser *) + +let register_contract_get_ownership slot () = + Context.init 1 >>=? fun (b, contracts) -> + let contract = List.hd contracts in + Incremental.begin_construction b >>=? fun inc -> + Context.get_endorser (I inc) slot >>=? fun account_endorser -> + return (inc, contract, account_endorser) + +let change_manager () = + register_contract_get_ownership 0 () >>=? fun (inc, contract, account_endorser) -> + Op.origination ~manager:account_endorser (I inc) ~credit:Tez.one contract >>=? fun (op, _) -> + Incremental.add_operation inc op >>=? fun inc -> + Incremental.finalize_block inc >>=? fun _ -> + return () + +let change_delegate () = + register_contract_get_ownership 0 () >>=? fun (inc, contract, account_endorser) -> + Op.origination ~delegate:account_endorser (I inc) ~credit:Tez.one contract >>=? fun (op, _) -> + Incremental.add_operation inc op >>=? fun inc -> + Incremental.finalize_block inc >>=? fun _ -> + return () + +(******************************************************) +(* create a multiple originate contracts and + ask contract to pay the fee +*) + +let n_originations n ?credit ?fee ?spendable ?delegatable () = + fold_left_s (fun new_contracts _ -> + register_origination ?fee ?credit ?spendable ?delegatable () >>=? fun (_, _, new_contract) -> + let contracts = new_contract :: new_contracts in + return contracts + ) [] (1 -- n) + +let multiple_originations () = + n_originations 100 ~credit:(Tez.of_int 2) ~fee:ten_tez () >>=? fun _ -> + return () + +(******************************************************) +(* cannot originate two contracts with the same context's counter *) + +let counter () = + Context.init 1 >>=? fun (b, contracts) -> + let contract = List.hd contracts in + Incremental.begin_construction b >>=? fun inc -> + Op.origination (I inc) ~credit:Tez.one contract >>=? fun (op1, _) -> + Op.origination (I inc) ~credit:Tez.one contract >>=? fun (op2, _) -> + Incremental.add_operation inc op1 >>=? fun inc -> + Incremental.add_operation inc op2 >>= fun res -> + Assert.proto_error ~loc:__LOC__ res begin function + | Contract_storage.Counter_in_the_past _ -> true + | _ -> false + end + +(******************************************************) +(* create an originate contract from an originate contract *) + +let origination_contract_from_origination_contract () = + register_origination_inc ~credit:ten_tez () >>=? fun (inc, new_contract) -> + Op.origination (I inc) ~credit:Tez.one new_contract >>=? fun (op2, _) -> + Incremental.add_operation inc op2 >>=? fun inc -> + Incremental.finalize_block inc >>=? fun _ -> + return () + +(******************************************************) + +let tests = [ + Test.tztest "balances_simple" `Quick balances_simple ; + Test.tztest "balances_credit" `Quick balances_credit ; + Test.tztest "balances_credit_fee" `Quick balances_credit_fee ; + Test.tztest "balances_credit_unspendable" `Quick balances_credit_unspendable ; + Test.tztest "balances_undelegatable" `Quick balances_undelegatable ; + + Test.tztest "regular" `Quick regular ; + Test.tztest "pay_fee" `Quick pay_fee; + + Test.tztest "unspendable" `Quick unspendable ; + Test.tztest "undelegatable (no fee)" `Quick (undelegatable Tez.zero); + Test.tztest "undelegatable (with fee)" `Quick (undelegatable Tez.one); + Test.tztest "credit" `Quick (credit Tez.one) ; + Test.tztest "create origination from origination not enough fund" `Quick (origination_contract_from_origination_contract_not_enough_fund Tez.zero); + Test.tztest "not enough tez in contract to pay fee" `Quick not_tez_in_contract_to_pay_fee; + + Test.tztest "change manager" `Quick change_manager; + Test.tztest "change delegate" `Quick change_delegate; + + Test.tztest "multiple originations" `Quick multiple_originations; + + Test.tztest "counter" `Quick counter; + + Test.tztest "create origination from origination" `Quick + origination_contract_from_origination_contract; +] diff --git a/src/proto_alpha/lib_protocol/test/transfer.ml b/src/proto_alpha/lib_protocol/test/transfer.ml new file mode 100644 index 000000000..45d1851c7 --- /dev/null +++ b/src/proto_alpha/lib_protocol/test/transfer.ml @@ -0,0 +1,479 @@ +(**************************************************************************) +(* *) +(* Copyright (c) 2014 - 2018. *) +(* Dynamic Ledger Solutions, Inc. *) +(* *) +(* All rights reserved. No warranty, explicit or implicit, provided. *) +(* *) +(**************************************************************************) +open Proto_alpha +open Alpha_context +open Test_utils +open Test_tez + +(*********************************************************************) +(* Utility functions *) +(*********************************************************************) + +(** 1- Transfer the amount of tez from a source contract to a + destination contract with or without the fee of transfer. + 2- Check the equivalent of the balance of the source/destination + contract before and after the transfer *) +let transfer_and_check_balances ~loc b ?(fee=Tez.zero) src dst amount = + Tez.(+?) fee amount >>?= fun amount_fee -> + Context.Contract.balance (I b) src >>=? fun bal_src -> + Context.Contract.balance (I b) dst >>=? fun bal_dst -> + Op.transaction (I b) ~fee src dst amount >>=? fun op -> + Incremental.add_operation b op >>=? fun b -> + Assert.balance_was_debited ~loc (I b) src bal_src amount_fee >>=? fun () -> + Assert.balance_was_credited ~loc (I b) dst bal_dst amount >>=? fun () -> + return (b, op) + +(** 1- Transfer the amount of tez from/to a contract itself, with or + without fee of transfer. + 2- Check the equivalent of the balance of the contract before + and after transfer *) +let transfer_to_itself_and_check_balances ~loc b ?(fee=Tez.zero) contract amount = + Context.Contract.balance (I b) contract >>=? fun bal -> + Op.transaction (I b) ~fee contract contract amount >>=? fun op -> + Incremental.add_operation b op >>=? fun b -> + Assert.balance_was_debited ~loc (I b) contract bal fee >>=? fun () -> + return (b, op) + +(** Apply a transfer n times *) +let n_transactions n b ?fee source dest amount = + fold_left_s (fun b _ -> + transfer_and_check_balances ~loc:__LOC__ b ?fee source dest amount >>=? fun (b,_) -> + return b) + b (1 -- n) + +let ten_tez = Tez.of_int 10 + +let max_tez = + match Tez.of_mutez Int64.max_int with + | None -> assert false + | Some p -> p + +(*********************************************************************) +(* Tests *) +(*********************************************************************) + +let register_two_contracts () = + Context.init 2 >>=? fun (b, contracts) -> + let contract_1 = List.nth contracts 0 in + let contract_2 = List.nth contracts 1 in + return (b, contract_1, contract_2) + +(** 1- Create a block and two contracts/accounts; + 2- Add a single transfer into this block; + 3- Bake this block. *) +let single_transfer ?fee amount = + register_two_contracts () >>=? fun (b, contract_1, contract_2) -> + Incremental.begin_construction b >>=? fun b -> + transfer_and_check_balances ~loc:__LOC__ ?fee b contract_1 contract_2 amount >>=? fun (b,_) -> + Incremental.finalize_block b >>=? fun _ -> + return () + +(** single transfer without fee *) +let block_with_a_single_transfer () = + single_transfer Tez.one + +(** single transfer without fee *) +let transfer_zero_tez () = + single_transfer Tez.zero + +(** single transfer with fee *) +let block_with_a_single_transfer_with_fee () = + single_transfer ~fee:Tez.one Tez.one + +(** 1- Create a block, and a single contract/account; + 2- Add the originate operation into this block; + 3- Add a transfer from a contract to a contract created by + originate operation, that requires to pay a fee of transfer; + 4- Bake this block. *) +let block_originate_and_transfer_with_fee () = + Context.init 1 >>=? fun (b, contracts) -> + let contract = List.nth contracts 0 in + Incremental.begin_construction b >>=? fun b -> + Op.origination (I b) ~fee:ten_tez contract >>=? fun (operation, new_contract) -> + Incremental.add_operation b operation >>=? fun b -> + transfer_and_check_balances ~loc:__LOC__ b ~fee:ten_tez contract new_contract ten_tez >>=? fun (b, _) -> + Incremental.finalize_block b >>=? fun _ -> + return () + +(** 1- Create a block, and two contracts/accounts; + 2- Add a transfer from a current balance of a source contract + into this block; + 3- Bake this block. *) +let block_transfer_from_contract_balance () = + register_two_contracts () >>=? fun (b, contract_1, contract_2) -> + Incremental.begin_construction b >>=? fun b -> + Context.Contract.balance (I b) contract_1 >>=? fun balance -> + transfer_and_check_balances ~loc:__LOC__ b contract_1 contract_2 balance >>=? fun (b,_) -> + Incremental.finalize_block b >>=? fun _ -> + return () + +(** 1- Create a block and a single contract/account; + 2- Add a transfer to a contract itself without fee into this block; + 3- Add a transfer to a contract itself with fee into this block; + 4- Bake this block. *) +let block_transfers_without_with_fee_to_self () = + Context.init 1 >>=? fun (b, contracts) -> + let contract = List.nth contracts 0 in + Incremental.begin_construction b >>=? fun b -> + transfer_to_itself_and_check_balances ~loc:__LOC__ b contract ten_tez + >>=? fun (b, _) -> + transfer_to_itself_and_check_balances ~loc:__LOC__ b ~fee:ten_tez contract ten_tez + >>=? fun (b, _) -> + Incremental.finalize_block b >>=? fun _ -> + return () + +(** 1- Create a block, two contracts/accounts; + 2- Add three transfers into the block; + 3- Do a transfer without adding it to the block; + 4- Bake the block with three transfers. *) +let four_transfers_bake_three_transfers () = + register_two_contracts () >>=? fun (b, contract_1, contract_2) -> + Incremental.begin_construction b >>=? fun b -> + n_transactions 3 b contract_1 contract_2 ten_tez >>=? fun b -> + Op.transaction (I b) contract_1 contract_2 ten_tez >>=? fun _ -> + Incremental.finalize_block b >>=? fun _ -> + return () + +(** 1- Create a contract from a bootstrap account; + 2- Create two implicit contracts; + 3- Build a block from genesis; + 4- Add a transfer with fee for transfer from a bootstrap countract into an + implicit contract into this block; + 5- Add a transfer with fee for transfer, between two implicit contracts + into this block; + 6- Bake this block. *) +let transfer_from_implicit_to_implicit_contract () = + Context.init 1 >>=? fun (b, contracts) -> + let bootstrap_contract = List.nth contracts 0 in + let account_a = Account.new_account () in + let account_b = Account.new_account () in + Incremental.begin_construction b >>=? fun b -> + let src = Contract.implicit_contract account_a.Account.pkh in + transfer_and_check_balances ~loc:__LOC__ ~fee:ten_tez b + bootstrap_contract src (Tez.of_int 20) >>=? fun (b, _) -> + let dest = Contract.implicit_contract account_b.pkh in + transfer_and_check_balances ~loc:__LOC__ ~fee:(Tez.of_int 3) b + src dest ten_tez >>=? fun (b, _) -> + Incremental.finalize_block b >>=? fun _ -> + return () + +(** 1- Create a block, contract from bootstrap accounts, contract from originate; + 2- Add a transfer from the bootstract contract into the implicit contract; + 3- Add a transfer from the impicit contract to the originate contract; + 4- Bake this block. *) +let transfer_from_implicit_to_originated_contract () = + Context.init 1 >>=? fun (b, contracts) -> + let bootstrap_contract = List.nth contracts 0 in + let contract = List.nth contracts 0 in + let account = Account.new_account () in + let src = Contract.implicit_contract account.Account.pkh in + Incremental.begin_construction b >>=? fun b -> + transfer_and_check_balances ~loc:__LOC__ b bootstrap_contract src ten_tez + >>=? fun (b, _) -> + Op.origination (I b) contract >>=? fun (operation, new_contract) -> + Incremental.add_operation b operation >>=? fun b -> + transfer_and_check_balances ~loc:__LOC__ b src new_contract Alpha_context.Tez.one + >>=? fun (b, _) -> + Incremental.finalize_block b >>=? fun _ -> + return () + +(** 1- Create a block with 2 contracts; + 2- Originate 2 contracts from the previous ones; + 2- Add a transfer between the two originated contracts; + 3- Bake this block. *) +let transfer_from_originated_to_originated () = + register_two_contracts () >>=? fun (b, contract_1, contract_2) -> + Incremental.begin_construction b >>=? fun b -> + Op.origination (I b) contract_1 >>=? fun (operation, orig_contract_1) -> + Incremental.add_operation b operation >>=? fun b -> + Op.origination (I b) contract_2 >>=? fun (operation, orig_contract_2) -> + Incremental.add_operation b operation >>=? fun b -> + transfer_and_check_balances ~loc:__LOC__ b + orig_contract_1 orig_contract_2 Alpha_context.Tez.one >>=? fun (b,_) -> + Incremental.finalize_block b >>=? fun _ -> + return () + +(** 1- Create a block, an originate contract, an impicit contract, a contract + from bootstrap; + 2- Add a transfer from the originate contract to impicit contract; + 3- Bake this block. *) +let transfer_from_originated_to_implicit () = + Context.init 1 >>=? fun (b, contracts) -> + let contract_1 = List.nth contracts 0 in + let account = Account.new_account () in + let src = Contract.implicit_contract account.pkh in + Incremental.begin_construction b >>=? fun b -> + Op.origination (I b) contract_1 >>=? fun (operation, new_contract) -> + Incremental.add_operation b operation >>=? fun b -> + transfer_and_check_balances ~loc:__LOC__ b new_contract src Alpha_context.Tez.one + >>=? fun (b, _) -> + Incremental.finalize_block b >>=? fun _ -> + return () + +(* Slow tests case *) + +let multiple_transfer n ?fee amount = + register_two_contracts () >>=? fun (b, contract_1, contract_2) -> + Incremental.begin_construction b >>=? fun b -> + n_transactions n b ?fee contract_1 contract_2 amount >>=? fun b -> + Incremental.finalize_block b >>=? fun _ -> + return () + +(** 1- Create a block with two contracts; + 2- Apply 100 transfers. *) +let block_with_multiple_transfers () = + multiple_transfer 99 (Tez.of_int 1000) + +(** 1- Create a block with two contracts; + 2- Apply 100 transfers with 10tz fee. *) +let block_with_multiple_transfers_pay_fee () = + multiple_transfer 10 ~fee:ten_tez (Tez.of_int 1000) + +(** 1- Create a block with 8 contracts; + 2- Apply multiple transfers without fees; + 3- Apply multiple transfers with fees. *) +(* TODO : increase the number of operations and add a `Slow tag to it in `tests` *) +let block_with_multiple_transfers_with_without_fee () = + Context.init 8 >>=? fun (b, contracts) -> + let contracts = Array.of_list contracts in + Incremental.begin_construction b >>=? fun b -> + let hundred = Tez.of_int 100 in + let ten = Tez.of_int 10 in + let twenty = Tez.of_int 20 in + n_transactions 10 b contracts.(0) contracts.(1) Tez.one >>=? fun b -> + n_transactions 30 b contracts.(1) contracts.(2) hundred >>=? fun b -> + n_transactions 30 b contracts.(1) contracts.(3) hundred >>=? fun b -> + n_transactions 30 b contracts.(4) contracts.(3) hundred >>=? fun b -> + n_transactions 20 b contracts.(0) contracts.(1) hundred >>=? fun b -> + n_transactions 10 b contracts.(1) contracts.(3) hundred >>=? fun b -> + n_transactions 10 b contracts.(1) contracts.(3) hundred >>=? fun b -> + + n_transactions 20 ~fee:ten b contracts.(3) contracts.(4) ten >>=? fun b -> + n_transactions 10 ~fee:twenty b contracts.(4) contracts.(5) ten >>=? fun b -> + n_transactions 70 ~fee:twenty b contracts.(6) contracts.(0) twenty >>=? fun b -> + n_transactions 550 ~fee:twenty b contracts.(6) contracts.(4) twenty >>=? fun b -> + n_transactions 50 ~fee:ten b contracts.(7) contracts.(5) twenty >>=? fun b -> + n_transactions 30 ~fee:ten b contracts.(0) contracts.(7) hundred >>=? fun b -> + n_transactions 20 ~fee:ten b contracts.(1) contracts.(0) twenty >>=? fun b -> + + Incremental.finalize_block b >>=? fun _ -> + return () + +(** 1- Create a block with 8 contracts; + 2- Bake 10 blocks with a transfer each time. *) +let build_a_chain () = + register_two_contracts () >>=? fun (b, contract_1, contract_2) -> + let ten = Tez.of_int 10 in + fold_left_s (fun b _ -> + Incremental.begin_construction b >>=? fun b -> + transfer_and_check_balances ~loc:__LOC__ b contract_1 contract_2 ten + >>=? fun (b, _) -> + Incremental.finalize_block b + ) b (1 -- 10) >>=? fun _ -> + return () + +(*********************************************************************) +(* Expected error test cases *) +(*********************************************************************) + +(** 1- Create a block; + 2- transfer an amount of tez that is bigger than the balance of the source + contract; + 3a- If fee is smaller than the balance: + Transfer is accepted but not processed. Fees are taken. + 3b- If fee higher than the balance: raises an `Balance_too_low` error. +*) +let balance_too_low fee () = + register_two_contracts () >>=? fun (b, contract_1, contract_2) -> + Incremental.begin_construction b >>=? fun i -> + Context.Contract.balance (I i) contract_1 >>=? fun balance1 -> + Context.Contract.balance (I i) contract_2 >>=? fun balance2 -> + Op.transaction ~fee (I i) contract_1 contract_2 max_tez >>=? fun op -> + if fee > balance1 then begin + Incremental.add_operation i op >>= fun res -> + Assert.proto_error ~loc:__LOC__ res begin function + | Contract_storage.Balance_too_low _ -> true + | _ -> false + end + end + else begin + Incremental.add_operation i op >>=? fun i -> + (* contract_1 loses the fees *) + Assert.balance_was_debited ~loc:__LOC__ (I i) contract_1 balance1 fee >>=? fun () -> + (* contract_2 is not credited *) + Assert.balance_was_credited ~loc:__LOC__ (I i) contract_2 balance2 Tez.zero + end + +(** 1- Create a block, and three contracts/accounts; + 2- Add a transfer that at the end the balance of a contract is + zero into this block; + 3- Add another transfer that send tez from a zero balance contract; + 4- Catch the expected error: Balance_too_low. *) +let balance_too_low_two_transfers fee () = + Context.init 3 >>=? fun (b, contracts) -> + let contract_1 = List.nth contracts 0 in + let contract_2 = List.nth contracts 1 in + let contract_3 = List.nth contracts 2 in + Incremental.begin_construction b >>=? fun i -> + Context.Contract.balance (I i) contract_1 >>=? fun balance -> + Tez.(/?) balance 3L >>?= fun res -> + Tez.( *?) res 2L >>?= fun two_third_of_balance -> + transfer_and_check_balances ~loc:__LOC__ i + contract_1 contract_2 two_third_of_balance >>=? fun (i, _) -> + Context.Contract.balance (I i) contract_1 >>=? fun balance1 -> + Context.Contract.balance (I i) contract_3 >>=? fun balance3 -> + Op.transaction ~fee (I i) contract_1 contract_3 + two_third_of_balance >>=? fun operation -> + Incremental.add_operation i operation >>=? fun i -> + (* contract_1 loses the fees *) + Assert.balance_was_debited ~loc:__LOC__ (I i) contract_1 balance1 fee >>=? fun () -> + (* contract_3 is not credited *) + Assert.balance_was_credited ~loc:__LOC__ (I i) contract_3 balance3 Tez.zero + +(** 1- Create a block; + 2- Do two transfers one after another; + 3- Add two transfers into the block sequently; + 4- Catch the expected error: Counter_in_the_past. *) +let invalid_counter () = + register_two_contracts () >>=? fun (b, contract_1, contract_2) -> + Incremental.begin_construction b >>=? fun b -> + Op.transaction (I b) contract_1 contract_2 + Tez.one >>=? fun op1 -> + Op.transaction (I b) contract_1 contract_2 + Tez.one >>=? fun op2 -> + Incremental.add_operation b op1 >>=? fun b -> + Incremental.add_operation b op2 >>= fun b -> + Assert.proto_error ~loc:__LOC__ b begin function + | Contract_storage.Counter_in_the_past _ -> true + | _ -> false + end + +(** 1- Create a block; + 2- Add a transfer into this block; + 3- Make another transfer, but did not add this transfer into the block, + instead add the previous transfer again into this block; + 4- Catch the expected error: Counter_in_the_past. *) +let add_the_same_operation_twice () = + register_two_contracts () >>=? fun (b, contract_1, contract_2) -> + Incremental.begin_construction b >>=? fun b -> + transfer_and_check_balances ~loc:__LOC__ b contract_1 contract_2 ten_tez + >>=? fun (b, op_transfer) -> + Op.transaction (I b) contract_1 contract_2 ten_tez >>=? fun _ -> + Incremental.add_operation b op_transfer >>= fun b -> + Assert.proto_error ~loc:__LOC__ b begin function + | Contract_storage.Counter_in_the_past _ -> true + | _ -> false + end + +(** 1- Create a block; + 2- Originate an unspendable new contract; + 3- Make a transfer from this contract; + 4- Catch the expected error: Unspendable_contract. *) +let unspendable_contract () = + register_two_contracts () >>=? fun (b, contract_1, contract_2) -> + Incremental.begin_construction b >>=? fun b -> + Op.origination ~spendable:false (I b) contract_1 >>=? fun (operation, unspendable_contract) -> + Incremental.add_operation b operation >>=? fun b -> + Op.transaction (I b) unspendable_contract contract_2 Alpha_context.Tez.one_cent >>=? fun operation -> + Incremental.add_operation b operation >>= fun res -> + Assert.proto_error ~loc:__LOC__ res begin function + | Contract_storage.Unspendable_contract _ -> true + | _ -> false + end + +(** Checking that the sender of a transaction is the actual + manager of the contract. + Ownership of sender manager key (in case of a contract) *) +let ownership_sender () = + register_two_contracts () >>=? fun (b, contract_1, contract_2) -> + Incremental.begin_construction b >>=? fun b -> + (* get the manager of the contract_1 as a sender *) + Context.Contract.manager (I b) contract_1 >>=? fun manager -> + (* create an implicit_contract *) + let imcontract_1 = Alpha_context.Contract.implicit_contract manager.pkh in + transfer_and_check_balances ~loc:__LOC__ b imcontract_1 contract_2 Tez.one + >>=? fun (b,_) -> + Incremental.finalize_block b >>=? fun _ -> + return () + +(*********************************************************************) +(** Random transfer *) + +(** Return a pair of minimum and maximum random number *) +let random_range (min, max) = + let interv = max - min + 1 in + let init = + Random.self_init (); + (Random.int interv) + min + in init + +(** Return a random contract *) +let random_contract contract_array = + let i = Random.int (Array.length contract_array) in + contract_array.(i) + +(** Transfer by randomly choose amount 10 contracts, and randomly + choose the amount in the source contract *) +let random_transfer () = + Context.init 10 >>=? fun (b, contracts) -> + let contracts = Array.of_list contracts in + Incremental.begin_construction b >>=? fun b -> + let source = random_contract contracts in + let dest = random_contract contracts in + Context.Contract.balance (I b) source >>=? fun amount -> + begin + if source = dest + then + transfer_to_itself_and_check_balances ~loc:__LOC__ b source amount + else + transfer_and_check_balances ~loc:__LOC__ b source dest amount + end >>=? fun (b,_) -> + Incremental.finalize_block b >>=? fun _ -> + return () + +(** Transfer random transactions *) +let random_multi_transactions () = + let n = random_range (1, 100) in + multiple_transfer n (Tez.of_int 100) + +(*********************************************************************) + +let tests = [ + Test.tztest "block with a single transfer" `Quick block_with_a_single_transfer ; + Test.tztest "transfer zero tez" `Quick transfer_zero_tez ; + Test.tztest "block with a single transfer with fee" `Quick block_with_a_single_transfer_with_fee ; + Test.tztest "block originate and transfer with fee" `Quick block_originate_and_transfer_with_fee ; + Test.tztest "block transfer from contract balance" `Quick block_transfer_from_contract_balance ; + Test.tztest "block transfers without and with fee to itself" `Quick block_transfers_without_with_fee_to_self ; + Test.tztest "four transfers but bake three transfers" `Quick four_transfers_bake_three_transfers ; + Test.tztest "transfer from an implicit to implicit contract " `Quick transfer_from_implicit_to_implicit_contract ; + Test.tztest "transfer from an impicit to an originated contract" `Quick transfer_from_implicit_to_originated_contract ; + Test.tztest "transfer from an originated to an originated contract" `Quick transfer_from_originated_to_originated ; + Test.tztest "transfer from an originated to an implicit contract" `Quick transfer_from_originated_to_implicit ; + Test.tztest "ownership sender" `Quick ownership_sender ; + + (* Slow tests *) + Test.tztest "block with multiple transfers" `Quick block_with_multiple_transfers ; + Test.tztest "block with multiple transfer paying fee" `Quick block_with_multiple_transfers_pay_fee ; + Test.tztest "block with multiple transfer without paying fee" `Quick block_with_multiple_transfers_with_without_fee ; + Test.tztest "build a chain" `Quick build_a_chain ; + + (* Erroneous *) + Test.tztest "balance too low" `Quick (balance_too_low Tez.zero); + Test.tztest "balance too low" `Quick (balance_too_low Tez.one); + Test.tztest "balance too low with two transfers" `Quick (balance_too_low_two_transfers Tez.zero); + Test.tztest "balance too low with two transfers" `Quick (balance_too_low_two_transfers Tez.one); + Test.tztest "invalid_counter" `Quick invalid_counter ; + Test.tztest "add the same operation twice" `Quick add_the_same_operation_twice ; + Test.tztest "unspendable contract" `Quick unspendable_contract ; + + (* Random tests *) + Test.tztest "random transfer" `Quick random_transfer ; + Test.tztest "random multi transfer" `Quick random_multi_transactions ; +]