oxheadalpha / smart-contracts

A library of smart contracts
MIT License
102 stars 31 forks source link

Calling `Balance_of` (or `get_balance`) from CameLIGO test #99

Open egarson opened 1 year ago

egarson commented 1 year ago

Hello, I want to call the Balance_of entrypoint for a standard FA2 contract in a CameLIGO unit test (or it's "implementation" get_balance). I am trying to formulate the correct balance_of_request.callback parameter, but the continuation-passing style is giving me trouble - I cannot conjure up the correct incantation. Below is the test in development. Notice that I typed all the parameters, and broke everything down completely, to isolate completely the error. The problem I have is only with the callback parameter ("Cannot unify ..."). I have tried a number of things, but I cannot get the right incantation. My last effort has been trying to pass a fun to the callback, because I thought it might require a fun going from balance_of_response list to contract. It is a unique signature amongst all the other ones with which I have successfully worked.

let test_call_get_balance =
    let addr,_,_ = Test.originate main {initial_storage with admin = admin} 0tez in
    let contract = (Test.to_contract addr) in
    let balance_request: balance_of_request = {owner = alice; token_id = 0n} in
    let requests: balance_of_request list = [balance_request] in
    // OR: let requests: balance_of_request list = [{owner = alice; token_id = 0n}] in
    // !!Trying to formulate balance_of_request.callback parameter here!!
    // This is "just" another attempt, I am not sure how to reconcile this with CPS
    let callback = fun(l: balance_of_response list): contract ->
        let _printList = Test.log("Got list: ", l) in
        contract
    in
    let balance_param: balance_of_param = ({ requests = requests; callback = callback}) in
    // And finally call get_balance (alternatively the Balance_of entrypoint)
    let balance_of_response: balance_of_response = get_balance(requests) in
    Test.log("Alice's balance is:", balance_of_response.balance)

What is the correct incantation such that I can get the above to pass? You can see what I'm trying to do, I hope? I will also take this opportunity to thank you very much for the significant value I have derived from this repository. Cheers! Edward Garson Developer Evangelist @ ECAD Labs

emishur commented 1 year ago

The callback type required is not a function but an other contract entry point balance_of_response list contract. You probably would need to originate yet another "balance inspector contract" (see inspector.mligo ); get its %response entry point using Ligo's Test.to_entrypoint to create a callback.

But I am not sure how Ligo test framework works with multi-contract tests. If it does not help you may ask Ligo team

egarson commented 1 year ago

Thank you very much for your speedy response. I will look into this. I missed inspector.mligo, to my surprise!

egarson commented 1 year ago

Hello, (Back at this after a short hiatus). I am unable to get the callback contract to work but I don't (yet) think it's a multi-contract problem. I am unsure how to wire up inspector and my contract, and I'm unable to get a handle to the %response entrypoint. I have copied the code to do so from inspector.mligo. The compiler allows me to assign to the variable inspector_contract, but when I run I get the error, Type "inspector_contract" not found at the line, let cb_opt: balance_of_response inspector_contract =. If I comment out from that line to the end of the test, and write Test.log("Inspector contract", inspector_contract) as the last line of the test, I can compile, and if I ligo run test I see the output ("Inspector contract", KT1address(None)). (Why is "(None)" appearing in my output? This may be the problem!). Here is what I've got now:

let test_call_get_balance =
    let main_addr,_,_ = Test.originate main {initial_storage with admin = admin} 0tez in
    let main_contract = Test.to_contract main_addr in
    let balance_request: balance_of_request = {owner = alice; token_id = 0n} in
    // Setup the inspector callback contract to receive (balance_of_response list)
    let inspector_storage: inspector_storage = (State []) in
    // Contract is imported as: `#import "inspector.mligo" "INSPECTOR"`
    let inspector_addr,_,_ = Test.originate INSPECTOR.main inspector_storage 0tez in
    // Ok: we get a bona fide address
    let _ = Test.log("Inspector addr", inspector_addr) in
    // And this assignment succeeds
    let inspector_contract = Test.to_contract inspector_addr in
    // This emits ("Inspector contract" , KT1AYqMzvqtZb4jNfDd1Wqdzd8JgD9pk2Rv3(None))" // N.B. (None)
    let _ = Test.log("Inspector contract", inspector_contract) in
    // Get a handle to the callback entrypoint in `inspector_contract`
    // Error! `Type "inspector_contract" not found`
    let cb_opt: balance_of_response list inspector_contract option =
        Tezos.get_entrypoint_opt "%response" inspector_contract in
    let cb = match cb_opt with
        | None -> (failwith "NO_RESPONSES_ENTRYPOINT")
        | Some callback_entrypoint -> callback_entrypoint
    in
    let bp: balance_of_param = ({ requests = requests; callback = cb}) in
    let q_op = match fa2 with
        | None -> (failwith "NO_BALANCE_OF_ENTRYPOINT" : operation)
        | Some balance -> Tezos.transaction bp 0mutez callback_entrypoint
    in
    Test.log("Alice's balance is:", balance)

I have added you as a collaborator to this private repository in case you need to see more information. I appreciate any help you may be able to offer to get the above test to pass! Cheers Edward

emishur commented 1 year ago
let cb_opt: balance_of_response list inspector_contract option =
        Tezos.get_entrypoint_opt "%response" inspector_contract

it. looks like wrong callback type. Shouldn't it be

let cb_opt: balance_of_response list contract option =
        Tezos.get_entrypoint_opt "%response" inspector_contract

?

egarson commented 1 year ago

Hmm, with that change I get, Cannot unify "contract (INSPECTOR.inspector_parameter) with "address"

emishur commented 1 year ago

Tezos.get_entrypoint_opt accepts an address as second parameter, not the contract reference. I guess the following should work:

let cb_opt: balance_of_response list contract option =
        Tezos.get_entrypoint_opt "%response" inspector_addr
egarson commented 1 year ago

With that change, I get Cannot unify "typed_address (INSPECTOR.inspector_parameter , INSPECTOR.inspector_storage)" with "address".

egarson commented 1 year ago

The new Marigold tutorial has a callback example at https://github.com/ligolang/contract-catalogue/blob/main/test/fa2/single_asset.test.mligo which I will try just now.

emishur commented 1 year ago

I think you cannot mix Test and Tezos modules in your test code. Try to use Test.to_entrypoint (which has signature string -> ('param, 'storage) typed_address -> 'e contract) instead of Tezos.get_entrypoint_opt

egarson commented 1 year ago

Ah... I will try that, thank you