(*****************************************************************************) (* *) (* Open Source License *) (* Copyright (c) 2018 Dynamic Ledger Solutions, Inc. *) (* *) (* Permission is hereby granted, free of charge, to any person obtaining a *) (* copy of this software and associated documentation files (the "Software"),*) (* to deal in the Software without restriction, including without limitation *) (* the rights to use, copy, modify, merge, publish, distribute, sublicense, *) (* and/or sell copies of the Software, and to permit persons to whom the *) (* Software is furnished to do so, subject to the following conditions: *) (* *) (* The above copyright notice and this permission notice shall be included *) (* in all copies or substantial portions of the Software. *) (* *) (* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR*) (* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, *) (* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL *) (* THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER*) (* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING *) (* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER *) (* DEALINGS IN THE SOFTWARE. *) (* *) (*****************************************************************************) (** The activation operation creates an implicit contract from a registered commitment present in the context. It is parametrized by a public key hash (pkh) and a secret. The commitments are composed of : - a blinded pkh that can be revealed by the secret ; - an amount. The commitments and the secrets are generated from /scripts/create_genesis/create_genenis.py and should be coherent. *) open Protocol open Alpha_context open Test_utils open Test_tez (* Generated commitments and secrets *) (* Commitments are hard-coded in {Tezos_proto_alpha_parameters.Default_parameters} *) (* let commitments = * List.map (fun (bpkh, a) -> * Commitment_repr.{ * blinded_public_key_hash=Blinded_public_key_hash.of_b58check_exn bpkh ; * amount = Tez_repr.of_mutez_exn (Int64.of_string a)} * ) * [ ( "btz1bRL4X5BWo2Fj4EsBdUwexXqgTf75uf1qa", "23932454669343" ) ; * ( "btz1SxjV1syBgftgKy721czKi3arVkVwYUFSv", "72954577464032" ) ; * ( "btz1LtoNCjiW23txBTenALaf5H6NKF1L3c1gw", "217487035428349" ) ; * ( "btz1SUd3mMhEBcWudrn8u361MVAec4WYCcFoy", "4092742372031" ) ; * ( "btz1MvBXf4orko1tsGmzkjLbpYSgnwUjEe81r", "17590039016550" ) ; * ( "btz1LoDZ3zsjgG3k3cqTpUMc9bsXbchu9qMXT", "26322312350555" ) ; * ( "btz1RMfq456hFV5AeDiZcQuZhoMv2dMpb9hpP", "244951387881443" ) ; * ( "btz1Y9roTh4A7PsMBkp8AgdVFrqUDNaBE59y1", "80065050465525" ) ; * ( "btz1Q1N2ePwhVw5ED3aaRVek6EBzYs1GDkSVD", "3569618927693" ) ; * ( "btz1VFFVsVMYHd5WfaDTAt92BeQYGK8Ri4eLy", "9034781424478" ) ; * ] *) type secret_account = { account : public_key_hash ; activation_code : Blinded_public_key_hash.activation_code ; amount : Tez.t ; } let secrets () = (* Exported from proto_alpha client - TODO : remove when relocated to lib_crypto *) let read_key mnemonic email password = match Bip39.of_words mnemonic with | None -> assert false | Some t -> (* TODO: unicode normalization (NFKD)... *) let passphrase = MBytes.(concat "" [ of_string email ; of_string password ; ]) in let sk = Bip39.to_seed ~passphrase t in let sk = MBytes.sub sk 0 32 in let sk : Signature.Secret_key.t = Ed25519 (Data_encoding.Binary.of_bytes_exn Ed25519.Secret_key.encoding sk) in let pk = Signature.Secret_key.to_public_key sk in let pkh = Signature.Public_key.hash pk in (pkh, pk, sk) in List.map (fun (mnemonic, secret, amount, pkh, password, email) -> let (pkh', pk, sk) = read_key mnemonic email password in let pkh = Signature.Public_key_hash.of_b58check_exn pkh in assert (Signature.Public_key_hash.equal pkh pkh'); let account = Account.{ pkh ; pk ; sk } in Account.add_account account ; { account = account.pkh ; activation_code = Blinded_public_key_hash.activation_code_of_hex secret ; amount = Option.unopt_exn (Invalid_argument "tez conversion") (Tez.of_mutez (Int64.of_string amount)) }) [ (["envelope"; "hospital"; "mind"; "sunset"; "cancel"; "muscle"; "leisure"; "thumb"; "wine"; "market"; "exit"; "lucky"; "style"; "picnic"; "success"], "0f39ed0b656509c2ecec4771712d9cddefe2afac", "23932454669343", "tz1MawerETND6bqJqx8GV3YHUrvMBCDasRBF", "z0eZHQQGKt", "cjgfoqmk.wpxnvnup@tezos.example.org" ); (["flag"; "quote"; "will"; "valley"; "mouse"; "chat"; "hold"; "prosper"; "silk"; "tent"; "cruel"; "cause"; "demise"; "bottom"; "practice"], "41f98b15efc63fa893d61d7d6eee4a2ce9427ac4", "72954577464032", "tz1X4maqF9tC1Yn4jULjHRAyzjAtc25Z68TX", "MHErskWPE6", "oklmcktr.ztljnpzc@tezos.example.org" ); (["library"; "away"; "inside"; "paper"; "wise"; "focus"; "sweet"; "expose"; "require"; "change"; "stove"; "planet"; "zone"; "reflect"; "finger"], "411dfef031eeecc506de71c9df9f8e44297cf5ba", "217487035428348", "tz1SWBY7rWMutEuWS54Pt33MkzAS6eWkUuTc", "0AO6BzQNfN", "ctgnkvqm.kvtiybky@tezos.example.org" ); (["cruel"; "fluid"; "damage"; "demand"; "mimic"; "above"; "village"; "alpha"; "vendor"; "staff"; "absent"; "uniform"; "fire"; "asthma"; "milk"], "08d7d355bc3391d12d140780b39717d9f46fcf87", "4092742372031", "tz1amUjiZaevaxQy5wKn4SSRvVoERCip3nZS", "9kbZ7fR6im", "bnyxxzqr.tdszcvqb@tezos.example.org" ) ; (["opera"; "divorce"; "easy"; "myself"; "idea"; "aim"; "dash"; "scout"; "case"; "resource"; "vote"; "humor"; "ticket"; "client"; "edge"], "9b7cad042fba557618bdc4b62837c5f125b50e56", "17590039016550", "tz1Zaee3QBtD4ErY1SzqUvyYTrENrExu6yQM", "suxT5H09yY", "iilkhohu.otnyuvna@tezos.example.org" ) ; (["token"; "similar"; "ginger"; "tongue"; "gun"; "sort"; "piano"; "month"; "hotel"; "vote"; "undo"; "success"; "hobby"; "shell"; "cart"], "124c0ca217f11ffc6c7b76a743d867c8932e5afd", "26322312350555", "tz1geDUUhfXK1EMj7VQdRjug1MoFe6gHWnCU", "4odVdLykaa", "kwhlglvr.slriitzy@tezos.example.org" ) ; (["shield"; "warrior"; "gorilla"; "birth"; "steak"; "neither"; "feel"; "only"; "liberty"; "float"; "oven"; "extend"; "pulse"; "suffer"; "vapor"], "ac7a2125beea68caf5266a647f24dce9fea018a7", "244951387881443", "tz1h3nY7jcZciJgAwRhWcrEwqfVp7VQoffur", "A6yeMqBFG8", "lvrmlbyj.yczltcxn@tezos.example.org" ) ; (["waste"; "open"; "scan"; "tip"; "subway"; "dance"; "rent"; "copper"; "garlic"; "laundry"; "defense"; "clerk"; "another"; "staff"; "liar"], "2b3e94be133a960fa0ef87f6c0922c19f9d87ca2", "80065050465525", "tz1VzL4Xrb3fL3ckvqCWy6bdGMzU2w9eoRqs", "oVZqpq60sk", "rfodmrha.zzdndvyk@tezos.example.org" ) ; (["fiber"; "next"; "property"; "cradle"; "silk"; "obey"; "gossip"; "push"; "key"; "second"; "across"; "minimum"; "nice"; "boil"; "age"], "dac31640199f2babc157aadc0021cd71128ca9ea", "3569618927693", "tz1RUHg536oRKhPLFfttcB5gSWAhh4E9TWjX", "FfytQTTVbu", "owecikdy.gxnyttya@tezos.example.org" ) ; (["print"; "labor"; "budget"; "speak"; "poem"; "diet"; "chunk"; "eternal"; "book"; "saddle"; "pioneer"; "ankle"; "happy"; "only"; "exclude"], "bb841227f250a066eb8429e56937ad504d7b34dd", "9034781424478", "tz1M1LFbgctcPWxstrao9aLr2ECW1fV4pH5u", "zknAl3lrX2", "ettilrvh.zsrqrbud@tezos.example.org" ) ; ] let activation_init () = Context.init ~with_commitments:true 1 >>=? fun (b, cs) -> secrets () |> fun ss -> return (b, cs, ss) let simple_init_with_commitments () = activation_init () >>=? fun (blk, _contracts, _secrets) -> Block.bake blk >>=? fun _ -> return_unit (** A single activation *) let single_activation () = activation_init () >>=? fun (blk, _contracts, secrets) -> let { account ; activation_code ; amount=expected_amount ; _ } as _first_one = List.hd secrets in (* Contract does not exist *) Assert.balance_is ~loc:__LOC__ (B blk) (Contract.implicit_contract account) Tez.zero >>=? fun () -> Op.activation (B blk) account activation_code >>=? fun operation -> Block.bake ~operation blk >>=? fun blk -> (* Contract does exist *) Assert.balance_is ~loc:__LOC__ (B blk) (Contract.implicit_contract account) expected_amount (** 10 activations, one per bake *) let multi_activation_1 () = activation_init () >>=? fun (blk, _contracts, secrets) -> Error_monad.fold_left_s (fun blk { account ; activation_code ; amount = expected_amount ; _ } -> Op.activation (B blk) account activation_code >>=? fun operation -> Block.bake ~operation blk >>=? fun blk -> Assert.balance_is ~loc:__LOC__ (B blk) (Contract.implicit_contract account) expected_amount >>=? fun () -> return blk ) blk secrets >>=? fun _ -> return_unit (** All in one bake *) let multi_activation_2 () = activation_init () >>=? fun (blk, _contracts, secrets) -> Error_monad.fold_left_s (fun ops { account ; activation_code ; _ } -> Op.activation (B blk) account activation_code >>=? fun op -> return (op::ops) ) [] secrets >>=? fun ops -> Block.bake ~operations:ops blk >>=? fun blk -> Error_monad.iter_s (fun { account ; amount = expected_amount ; _ } -> (* Contract does exist *) Assert.balance_is ~loc:__LOC__ (B blk) (Contract.implicit_contract account) expected_amount ) secrets (** Transfer with activated account *) let activation_and_transfer () = activation_init () >>=? fun (blk, contracts, secrets) -> let { account ; activation_code ; _ } as _first_one = List.hd secrets in let bootstrap_contract = List.hd contracts in let first_contract = Contract.implicit_contract account in Op.activation (B blk) account activation_code >>=? fun operation -> Block.bake ~operation blk >>=? fun blk -> Context.Contract.balance (B blk) bootstrap_contract >>=? fun amount -> Tez.(/?) amount 2L >>?= fun half_amount -> Context.Contract.balance (B blk) first_contract >>=? fun activated_amount_before -> Op.transaction (B blk) bootstrap_contract first_contract half_amount >>=? fun operation -> Block.bake ~operation blk >>=? fun blk -> Assert.balance_was_credited ~loc:__LOC__ (B blk) (Contract.implicit_contract account) activated_amount_before half_amount (** Transfer to an unactivated account and then activating it *) let transfer_to_unactivated_then_activate () = activation_init () >>=? fun (blk, contracts, secrets) -> let { account ; activation_code ; amount } as _first_one = List.hd secrets in let bootstrap_contract = List.hd contracts in let unactivated_commitment_contract = Contract.implicit_contract account in Context.Contract.balance (B blk) bootstrap_contract >>=? fun b_amount -> Tez.(/?) b_amount 2L >>?= fun b_half_amount -> Incremental.begin_construction blk >>=? fun inc -> Op.transaction (I inc) bootstrap_contract unactivated_commitment_contract b_half_amount >>=? fun op -> Incremental.add_operation inc op >>=? fun inc -> Op.activation (I inc) account activation_code >>=? fun op' -> Incremental.add_operation inc op' >>=? fun inc -> Incremental.finalize_block inc >>=? fun blk2 -> Assert.balance_was_credited ~loc:__LOC__ (B blk2) (Contract.implicit_contract account) amount b_half_amount (****************************************************************) (* The following test scenarios are supposed to raise errors. *) (****************************************************************) (** Invalid pkh activation : expected to fail as the context does not contain any commitment *) let invalid_activation_with_no_commitments () = Context.init 1 >>=? fun (blk, _) -> let secrets = secrets () in let { account ; activation_code ; _ } as _first_one = List.hd secrets in Op.activation (B blk) account activation_code >>=? fun operation -> Block.bake ~operation blk >>= fun res -> Assert.proto_error ~loc:__LOC__ res begin function | Apply.Invalid_activation _ -> true | _ -> false end (** Wrong activation : wrong secret given in the operation *) let invalid_activation_wrong_secret () = activation_init () >>=? fun (blk, _, secrets) -> let { account ; _ } as _first_one = List.nth secrets 0 in let { activation_code ; _ } as _second_one = List.nth secrets 1 in Op.activation (B blk) account activation_code >>=? fun operation -> Block.bake ~operation blk >>= fun res -> Assert.proto_error ~loc:__LOC__ res begin function | Apply.Invalid_activation _ -> true | _ -> false end (** Invalid pkh activation : expected to fail as the context does not contain an associated commitment *) let invalid_activation_inexistent_pkh () = activation_init () >>=? fun (blk, _, secrets) -> let { activation_code ; _ } as _first_one = List.hd secrets in let inexistent_pkh = Signature.Public_key_hash.of_b58check_exn "tz1PeQHGKPWSpNoozvxgqLN9TFsj6rDqNV3o" in Op.activation (B blk) inexistent_pkh activation_code >>=? fun operation -> Block.bake ~operation blk >>= fun res -> Assert.proto_error ~loc:__LOC__ res begin function | Apply.Invalid_activation _ -> true | _ -> false end (** Invalid pkh activation : expected to fail as the commitment has already been claimed *) let invalid_double_activation () = activation_init () >>=? fun (blk, _, secrets) -> let { account ; activation_code ; _ } as _first_one = List.hd secrets in Incremental.begin_construction blk >>=? fun inc -> Op.activation (I inc) account activation_code >>=? fun op -> Incremental.add_operation inc op >>=? fun inc -> Op.activation (I inc) account activation_code >>=? fun op' -> Incremental.add_operation inc op' >>= fun res -> Assert.proto_error ~loc:__LOC__ res begin function | Apply.Invalid_activation _ -> true | _ -> false end (** Transfer from an unactivated commitment account *) let invalid_transfer_from_unactived_account () = activation_init () >>=? fun (blk, contracts, secrets) -> let { account ; _ } as _first_one = List.hd secrets in let bootstrap_contract = List.hd contracts in let unactivated_commitment_contract = Contract.implicit_contract account in (* No activation *) Op.transaction (B blk) unactivated_commitment_contract bootstrap_contract Tez.one >>=? fun operation -> Block.bake ~operation blk >>= fun res -> Assert.proto_error ~loc:__LOC__ res begin function | Contract_storage.Empty_implicit_contract pkh -> if pkh = account then true else false | _ -> false end let tests = [ Test.tztest "init with commitments" `Quick simple_init_with_commitments ; Test.tztest "single activation" `Quick single_activation ; Test.tztest "multi-activation one-by-one" `Quick multi_activation_1 ; Test.tztest "multi-activation all at a time" `Quick multi_activation_2 ; Test.tztest "activation and transfer" `Quick activation_and_transfer ; Test.tztest "transfer to unactivated account then activate" `Quick transfer_to_unactivated_then_activate ; Test.tztest "invalid activation with no commitments" `Quick invalid_activation_with_no_commitments ; Test.tztest "invalid activation with commitments" `Quick invalid_activation_inexistent_pkh ; Test.tztest "invalid double activation" `Quick invalid_double_activation ; Test.tztest "wrong activation code" `Quick invalid_activation_wrong_secret ; Test.tztest "invalid transfer from unactivated account" `Quick invalid_transfer_from_unactived_account ]