anchor.diff:         % poi tests
   
          fp_insufficent_tokens,
  +
  +       % FP resets locked_until timer
  +       fp_settle_too_soon,
  +
          fp_register_name,
  -
  -       % FP resets locked_until timer
  -       fp_settle_too_soon
  +       fp_register_oracle,
  +       fp_oracle_query,
  +       fp_oracle_extend,
  +       fp_oracle_respond
         ]}
       ].
   
  
positive.code:          fp_register_name/1,
  
           % FP resets locked_until timer
           fp_settle_too_soon/1
          ]).
  
  -include_lib("common_test/include/ct.hrl").
  
negative.code:     Test(11, 20, 30),
      ok.
  
  %% test that a force progress transaction can NOT produce an on-chain oracle
  %% query via a contract
  fp_oracle_query(Cfg) ->
      ProduceCallData =
          fun(_Pubkey, _Privkey, Oracle, _OraclePrivkey, _QueryId, _ContractId, QueryFee) ->
              <<IntOracleId:256>> = Oracle,
              RelativeTTL = {variant, 0, [10]},
              Args = {IntOracleId, <<"Very much of a question">>, QueryFee, RelativeTTL, RelativeTTL},
              CalldataType = {tuple, [string, aeso_abi:get_type(Args)]},
              ?TEST_LOG("Oracle createQuery function arguments ~p", [Args]),
              aeso_data:to_binary({CalldataType,
                           {list_to_binary(atom_to_list(createQuery)),
                            Args}})
          end,
      fp_oracle_action(Cfg, ProduceCallData).
  
  %% test that a force progress transaction can NOT respond an oracle 
  %% query via a contract
  fp_oracle_respond(Cfg) ->
      ProduceCallData =
          fun(_Pubkey, _Privkey, Oracle, OraclePrivkey, QueryId, ContractId, _QueryFee) ->
              ?TEST_LOG("Oracle ~p", [Oracle]),
              ?TEST_LOG("QueryId ~p", [QueryId]),
              <<IntOracleId:256>> = Oracle,
              <<IntQueryId:256>> = QueryId,
              BinToSign = <<QueryId/binary, ContractId/binary>>,
              SigBin = <<Word1:256, Word2:256>> =
                  enacl:sign_detached(aec_governance:add_network_id(BinToSign),
                                      OraclePrivkey),
              ?TEST_LOG("Signature binary ~p", [SigBin]),
              Sig = {Word1, Word2},
              Args = {IntOracleId, IntQueryId, Sig, 
                      aeso_data:to_binary(42, 0)},
              CalldataType = {tuple, [string, aeso_abi:get_type(Args)]},
              ?TEST_LOG("Oracle respond function arguments ~p", [Args]),
              aeso_data:to_binary({CalldataType,
                           {list_to_binary(atom_to_list(respond)),
                            Args}})
          end,
      fp_oracle_action(Cfg, ProduceCallData).
  
  %% test that a force progress transaction can NOT extend an oracle 
  %% via a contract
  fp_oracle_extend(Cfg) ->
      ProduceCallData =
          fun(_Pubkey, _Privkey, Oracle, OraclePrivkey, _QueryId, ContractId, _QueryFee) ->
              <<IntOracleId:256>> = Oracle,
              BinToSign = <<Oracle/binary, ContractId/binary>>,
              RelativeTTL = {variant, 0, [10]},
              SigBin = <<Word1:256, Word2:256>> =
                  enacl:sign_detached(aec_governance:add_network_id(BinToSign),
                                      OraclePrivkey),
              ?TEST_LOG("Signature binary ~p", [SigBin]),
              Sig = {Word1, Word2},
              Args = {IntOracleId, Sig, RelativeTTL},
              CalldataType = {tuple, [string, aeso_abi:get_type(Args)]},
              ?TEST_LOG("Oracle respond function arguments ~p", [Args]),
              aeso_data:to_binary({CalldataType,
                           {list_to_binary(atom_to_list(extendOracle)),
                            Args}})
          end,
      fp_oracle_action(Cfg, ProduceCallData).
  
  fp_oracle_action(Cfg, ProduceCallData) ->
      StateHashSize = aec_base58c:byte_size_for_type(state),
      StateHash = <<42:StateHashSize/unit:8>>,
      Round = 42,
      FPRound = Round + 10,
      ContractName = "oracles",
      QueryFee = 1,
      
      % test contract on-chain
      % this validates that the contract and the fucntion are indeed callable
      % on-chain and they produce the expected oracle action 
      ?TEST_LOG("Create contract ~p.aes on-chain", [ContractName]),
      run(#{cfg => Cfg},
          [ % create account for being contract owner
            set_prop(height, 10),
            fun(#{} = Props) ->
                S0 = aesc_test_utils:new_state(),
                {NewAcc, S} = aesc_test_utils:setup_new_account(S0),
                S1 = aesc_test_utils:set_account_balance(NewAcc, 10000000, S),
                PrivKey = aesc_test_utils:priv_key(NewAcc, S1),
                ?TEST_LOG("Owner: pubkey ~p, privkey: ~p", [NewAcc, PrivKey]),
                Props#{state => S1, onchain_contract_owner_pubkey => NewAcc,
                                    onchain_contract_owner_privkey => PrivKey}
            end,
            % create contract on-chain
            fun(#{onchain_contract_owner_pubkey := PubKey,
                  state := S0} = Props) ->
              ContractString = aeso_test_utils:read_contract(ContractName),
              BinCode = aeso_compiler:from_string(ContractString, []),
              CallData = aect_sophia:create_call(BinCode, <<"init">>, <<"()">>),
              Nonce = 1,
              {ok, ContractCreateTx} =
                  aect_create_tx:new(
                      #{owner_id    => aec_id:create(account, PubKey),
                        nonce       => Nonce,
                        code        => BinCode,
                        vm_version  => ?VM_VERSION,
                        deposit     => 1,
                        amount      => 1,
                        gas         => 123456,
                        gas_price   => 1,
                        call_data   => CallData,
                        fee         => 1}),
              ?TEST_LOG("Contract create tx ~p", [ContractCreateTx]),
              OnChainTrees = aesc_test_utils:trees(S0), 
              TxEnv = tx_env(#{height => 3}),
              {ok, _} = aetx:check(ContractCreateTx, OnChainTrees,
                                    TxEnv),
              {ok, OnChainTrees1} = aetx:process(ContractCreateTx,
                                                  OnChainTrees,
                                                  TxEnv),
              S1 = aesc_test_utils:set_trees(OnChainTrees1, S0),
              ContractId = aect_contracts:compute_contract_pubkey(PubKey, Nonce),
              ?TEST_LOG("Contract created on-chain, id ~p", [ContractId]),
              Props#{state => S1,
                      onchain_contract_id => ContractId,
                      code => BinCode}
            end,
            % create oracle
            register_new_oracle(aeso_data:to_binary(string, 0),
                                aeso_data:to_binary(word, 0),
                                QueryFee),
            oracle_query(aeso_data:to_binary(<<"Some question">>, 0), 10),
            % call contract on-chain
            fun(#{onchain_contract_owner_pubkey := OPubKey,
                  onchain_contract_owner_privkey := OPrivKey,
                  onchain_contract_id := ContractId,
                  oracle := Oracle,
                  state := S0,
                  query_id := QueryID} = Props) ->
              Nonce = 2,
              OraclePrivkey = aesc_test_utils:priv_key(Oracle, S0),
              CallData = ProduceCallData(OPubKey, OPrivKey, Oracle,
                                         OraclePrivkey, QueryID, ContractId, QueryFee),
              ?TEST_LOG("CallData ~p", [CallData]),
              true = is_binary(CallData),
              {ok, CallTx} =
                  aect_call_tx:new(
                      #{caller_id   => aec_id:create(account, OPubKey),
                        nonce       => Nonce,
                        contract_id => aec_id:create(contract,
                                                      ContractId),
                        vm_version  => ?VM_VERSION,
                        amount      => 1,
                        gas         => 123456,
                        gas_price   => 1,
                        call_data   => CallData,
                        fee         => 1}),
              ?TEST_LOG("Contract call tx ~p", [CallTx]),
              OnChainTrees = aesc_test_utils:trees(S0), 
              TxEnv = tx_env(#{height => 4}),
              {ok, _} = aetx:check(CallTx, OnChainTrees,
                                    TxEnv),
              {ok, OnChainTrees1} = aetx:process(CallTx,
                                                  OnChainTrees,
                                                  TxEnv),
              CallId = aect_call:id(OPubKey,
                                    Nonce,
                                    ContractId),
              Calls = aec_trees:calls(OnChainTrees1),
              {value, Call} =
                  aect_call_state_tree:lookup_call(ContractId, CallId,
                                                    Calls),
              ok = aect_call:return_type(Call),
              S1 = aesc_test_utils:set_trees(OnChainTrees1, S0),
              Props#{state => S1}
            end]),
      ?TEST_LOG("Oracle action succeded on-chain, proceeding with off-chain tests", []),
      Test =
          fun(Owner, Forcer) ->
              ?TEST_LOG("Oracle off-chain action, owner is ~p, forcer is ~p",
                        [Owner, Forcer]),
              ContractCreateRound = 10,
              run(#{cfg => Cfg},
                  [ % test contract on-chain:
                    % create account for being contract owner
                    positive(fun create_channel_/2),
                    register_new_oracle(aeso_data:to_binary(string, 0),
                                        aeso_data:to_binary(word, 0),
                                        QueryFee),
                    % store state on-chain via snapshot
                    set_from(initiator),
                    set_prop(round, 42),
                    set_prop(state_hash, StateHash),
                    positive(fun snapshot_solo_/2),
                    fun(#{state := S,
                          channel_pubkey := ChannelPubKey} = Props) ->
                        Channel = aesc_test_utils:get_channel(ChannelPubKey, S),
                        Round = aesc_channels:round(Channel),
                        StateHash = aesc_channels:state_hash(Channel),
                        Props
                    end,
                    % create contract off-chain
                    create_trees_if_not_present(),
  
                    set_from(Owner, owner, owner_privkey),
                    create_contract_in_trees(_Round    = ContractCreateRound,
                                            _Contract = ContractName,
                                            _InitArgs = <<"()">>,
                                            _Deposit  = 2),
                    oracle_query(aeso_data:to_binary(<<"Some question">>, 0), 10),
                    % force progress contract on-chain
                    fun(#{contract_id   := ContractId,
                          oracle        := Oracle,
                          from_pubkey   := Pubkey,
                          from_privkey  := Privkey,
                          state         := S,
                          query_id      := QueryID} = Props) ->
                        OraclePrivkey = aesc_test_utils:priv_key(Oracle, S),
                        CallData = ProduceCallData(Pubkey, Privkey, Oracle,
                                                   OraclePrivkey,
                                                   QueryID,
                                                   ContractId, QueryFee),
                        (force_call_contract_first_with_calldata(Forcer,
                                              CallData, FPRound))(Props)
                    end,
                    % ensure all gas is consumed and channel is updated
                    fun(#{state := S,
                          channel_pubkey := ChannelPubKey,
                          signed_force_progress := SignedForceProgressTx,
                          solo_payload := #{update    := Update,
                                          state_hash := ExpectedStateHash,
                                          round      := ExpectedRound}} = Props) ->
                        {_ContractId, Caller} = aesc_offchain_update:extract_call(Update),
                        TxHashContractPubkey = aesc_utils:tx_hash_to_contract_pubkey(
                                              aetx_sign:hash(SignedForceProgressTx)),
                        CallId = aect_call:id(Caller,
                                              FPRound,
                                              TxHashContractPubkey),
                        Call = aect_test_utils:get_call(TxHashContractPubkey, CallId,
                                                        S),
                        ?TEST_LOG("Off-chain call ~p", [Call]),
                        {_, GasPrice, GasLimit} = aesc_offchain_update:extract_amounts(Update),
                        %% assert all gas was consumed
                        GasLimit = aect_call:gas_used(Call),
                        GasPrice = aect_call:gas_price(Call),
                        %% the default catch all reason for error
                        <<"not_allowed_off_chain">> = aect_call:return_value(Call),
                        error = aect_call:return_type(Call),
  
                        %% expected channel states
                        Channel = aesc_test_utils:get_channel(ChannelPubKey, S),
                        FPRound = aesc_channels:round(Channel),
                        FPRound = ExpectedRound,
                        ExpectedStateHash = aesc_channels:state_hash(Channel),
                        Props
                    end])
          end,
      [Test(Owner, Forcer) || Owner  <- ?ROLES,
                              Forcer <- ?ROLES],
      ok.
  
  
  fp_register_oracle(Cfg) ->
      StateHashSize = aec_base58c:byte_size_for_type(state),
      StateHash = <<42:StateHashSize/unit:8>>,
      Round = 42,
      FPRound = Round + 10,
      SignAddress =
          fun(Oracle, PrivK, ContractId) ->
              BinToSign = <<Oracle/binary, ContractId/binary>>,
              SigBin = <<Word1:256, Word2:256>> =
                  enacl:sign_detached(aec_governance:add_network_id(BinToSign), PrivK),
              ?TEST_LOG("Signature binary ~p", [SigBin]),
              {Word1, Word2}
          end,
      RegisterCallData =
          fun(OPubKey, Sig) ->
              <<IntPubKey:256>> = OPubKey,
              RelativeTTL = {variant, 0, [10]},
              RegisterArgs = {IntPubKey, Sig, 2, RelativeTTL},
              CalldataType = {tuple, [string, aeso_abi:get_type(RegisterArgs)]},
              ?TEST_LOG("Oracle register function arguments ~p", [RegisterArgs]),
              aeso_data:to_binary({CalldataType,
                           {list_to_binary(atom_to_list(registerOracle)),
                            RegisterArgs}})
          end,
      ContractName = "oracles",
      
      % test contract on-chain
      % this validates that the contract and the fucntion are indeed callable
      % on-chain and it registers an oracle on-chain 
      ?TEST_LOG("Create contract ~p.aes on-chain", [ContractName]),
      run(#{cfg => Cfg},
          [ % create account for being contract owner
            fun(#{} = Props) ->
                S0 = aesc_test_utils:new_state(),
                {NewAcc, S} = aesc_test_utils:setup_new_account(S0),
                S1 = aesc_test_utils:set_account_balance(NewAcc, 10000000, S),
                PrivKey = aesc_test_utils:priv_key(NewAcc, S1),
                ?TEST_LOG("Owner: pubkey ~p, privkey: ~p", [NewAcc, PrivKey]),
                Props#{state => S1, onchain_contract_owner_pubkey => NewAcc,
                                    onchain_contract_owner_privkey => PrivKey}
            end,
            % create contract on-chain
            fun(#{onchain_contract_owner_pubkey := PubKey,
                  state := S0} = Props) ->
              ContractString = aeso_test_utils:read_contract(ContractName),
              BinCode = aeso_compiler:from_string(ContractString, []),
              CallData = aect_sophia:create_call(BinCode, <<"init">>, <<"()">>),
              Nonce = 1,
              {ok, ContractCreateTx} =
                  aect_create_tx:new(
                      #{owner_id    => aec_id:create(account, PubKey),
                        nonce       => Nonce,
                        code        => BinCode,
                        vm_version  => ?VM_VERSION,
                        deposit     => 1,
                        amount      => 1,
                        gas         => 123456,
                        gas_price   => 1,
                        call_data   => CallData,
                        fee         => 1}),
              ?TEST_LOG("Contract create tx ~p", [ContractCreateTx]),
              OnChainTrees = aesc_test_utils:trees(S0), 
              TxEnv = tx_env(#{height => 3}),
              {ok, _} = aetx:check(ContractCreateTx, OnChainTrees,
                                    TxEnv),
              {ok, OnChainTrees1} = aetx:process(ContractCreateTx,
                                                  OnChainTrees,
                                                  TxEnv),
              S1 = aesc_test_utils:set_trees(OnChainTrees1, S0),
              ContractId = aect_contracts:compute_contract_pubkey(PubKey, Nonce),
              ?TEST_LOG("Contract created on-chain, id ~p", [ContractId]),
              Props#{state => S1,
                      onchain_contract_id => ContractId,
                      code => BinCode}
            end,
            % call contract on-chain
            fun(#{onchain_contract_owner_pubkey := OPubKey,
                  onchain_contract_owner_privkey := OPrivKey,
                  onchain_contract_id := ContractId,
                  state := S0} = Props) ->
              Nonce = 2,
              Sig = SignAddress(OPubKey, OPrivKey, ContractId),
              CallData = RegisterCallData(OPubKey, Sig),
              ?TEST_LOG("CallData ~p", [CallData]),
              true = is_binary(CallData),
              {ok, CallTx} =
                  aect_call_tx:new(
                      #{caller_id   => aec_id:create(account, OPubKey),
                        nonce       => Nonce,
                        contract_id => aec_id:create(contract,
                                                      ContractId),
                        vm_version  => ?VM_VERSION,
                        amount      => 1,
                        gas         => 123456,
                        gas_price   => 1,
                        call_data   => CallData,
                        fee         => 1}),
              ?TEST_LOG("Contract call tx ~p", [CallTx]),
              OnChainTrees = aesc_test_utils:trees(S0), 
              TxEnv = tx_env(#{height => 4}),
              {ok, _} = aetx:check(CallTx, OnChainTrees,
                                    TxEnv),
              {ok, OnChainTrees1} = aetx:process(CallTx,
                                                  OnChainTrees,
                                                  TxEnv),
              CallId = aect_call:id(OPubKey,
                                    Nonce,
                                    ContractId),
              Calls = aec_trees:calls(OnChainTrees1),
              {value, Call} =
                  aect_call_state_tree:lookup_call(ContractId, CallId,
                                                    Calls),
              ok = aect_call:return_type(Call),
              S1 = aesc_test_utils:set_trees(OnChainTrees1, S0),
              Props#{state => S1}
            end]),
      ?TEST_LOG("Oracle registered on-chain, proceeding with off-chain tests", []),
      Test =
          fun(Owner, Forcer) ->
              ?TEST_LOG("Oracle register off-chain, owner is ~p, forcer is ~p",
                        [Owner, Forcer]),
              ContractCreateRound = 10,
              run(#{cfg => Cfg},
                  [ % test contract on-chain:
                    % create account for being contract owner
                    positive(fun create_channel_/2),
                    % store state on-chain via snapshot
                    set_from(initiator),
                    set_prop(round, 42),
                    set_prop(state_hash, StateHash),
                    positive(fun snapshot_solo_/2),
                    fun(#{state := S,
                          channel_pubkey := ChannelPubKey} = Props) ->
                        Channel = aesc_test_utils:get_channel(ChannelPubKey, S),
                        Round = aesc_channels:round(Channel),
                        StateHash = aesc_channels:state_hash(Channel),
                        Props
                    end,
                    % create contract off-chain
                    create_trees_if_not_present(),
                    set_from(Owner, owner, owner_privkey),
                    create_contract_in_trees(_Round    = ContractCreateRound,
                                            _Contract = ContractName,
                                            _InitArgs = <<"()">>,
                                            _Deposit  = 2),
                    % force progress contract on-chain
                    fun(#{contract_id := ContractId,
                          from_pubkey := Pubkey,
                          from_privkey := Privkey} = Props) ->
                        Sig = SignAddress(Pubkey, Privkey, ContractId),
                        Account = aeu_hex:hexstring_encode(Pubkey),
                        CallData = RegisterCallData(Pubkey, Sig),
                        (force_call_contract_first_with_calldata(Forcer,
                                              CallData, FPRound))(Props)
                    end,
                    % ensure all gas is consumed and channel is updated
                    fun(#{state := S,
                          channel_pubkey := ChannelPubKey,
                          signed_force_progress := SignedForceProgressTx,
                          solo_payload := #{update    := Update,
                                          state_hash := ExpectedStateHash,
                                          round      := ExpectedRound}} = Props) ->
                        {_ContractId, Caller} = aesc_offchain_update:extract_call(Update),
                        TxHashContractPubkey = aesc_utils:tx_hash_to_contract_pubkey(
                                              aetx_sign:hash(SignedForceProgressTx)),
                        CallId = aect_call:id(Caller,
                                              FPRound,
                                              TxHashContractPubkey),
                        Call = aect_test_utils:get_call(TxHashContractPubkey, CallId,
                                                        S),
                        ?TEST_LOG("Off-chain call ~p", [Call]),
                        {_, GasPrice, GasLimit} = aesc_offchain_update:extract_amounts(Update),
                        %% assert all gas was consumed
                        GasLimit = aect_call:gas_used(Call),
                        GasPrice = aect_call:gas_price(Call),
                        %% the default catch all reason for error
                        <<"not_allowed_off_chain">> = aect_call:return_value(Call),
                        error = aect_call:return_type(Call),
  
                        %% expected channel states
                        Channel = aesc_test_utils:get_channel(ChannelPubKey, S),
                        FPRound = aesc_channels:round(Channel),
                        FPRound = ExpectedRound,
                        ExpectedStateHash = aesc_channels:state_hash(Channel),
                        Props
                    end])
          end,
      [Test(Owner, Forcer) || Owner  <- ?ROLES,
                              Forcer <- ?ROLES],
      ok.
  
  create_contract_poi_and_payload(Round, ContractRound, Owner) ->
      create_contract_poi_and_payload(Round, ContractRound, Owner, #{}).
  