1

I have a scenario that works in local smartpy tests, but once deployed to testnet the same call responds with an obscure "OpenVariant" error.

There is a check that happens inside an otherwise pretty standard transfer entrypoint.

This check fetches a value from another contracts off-chain view and makes a decision based on that result.

Example code:

def transfer(self, params)
    # skipping boilerplate code ...

    # this checks the remote contract for info on the wallet
    hasBlockerItem = sp.view(
        'has_item',            # off-chain view
        self.data.provider,    # configurable view provider
        sp.record(
            owner=tx.to_,      # wallet to check
            token_id=sp.nat(4) # blocker token id is 4 in this case
        ),
        sp.TBool
    ).open_some()

    sp.trace(hasBlockerItem)   # False

    # this works in tests but once deployed an unexpected error is received on this line
    sp.if (hasBlockerItem == True):
        sp.verify (self.is_admin(sp.sender), 'FA2_RECEIVER_BLOCKED')

    # rest of boilerplate code...

this is the generated michaelson as seen inside an explorer (bcd):

PUSH bool True ;
DUP 5 ;
GET 5 ;
PUSH nat 4 ;
DUP 4 ;
CAR ;
PAIR ;
VIEW "has_item" bool ;
IF_NONE { PUSH int 53 ; FAILWITH } {} ;
IF
  { DUP 4 ;
    CAR ;
    CAR ;
    CAR ;
    SENDER ;
    COMPARE ;
    EQ ;
    IF
      { PUSH bool True }
      { SELF_ADDRESS ; SENDER ; COMPARE ; EQ } ;
    IF
      {}
      { PUSH string "FA2_RECEIVER_BLOCKED" ;
        FAILWITH } }
{} ;

This is the generated michaelson with comments preserved:

# if sp.view("has_item", self.data.provider, sp.record(owner = tx.to_, token_id = 4), sp.TBool).open_some() == True: # pair address (pair nat nat) : pair address (list (pair address (pair nat nat))) : list (pair address (list (pair address (pair nat nat)))) : pair (pair (pair address nat) (pair (big_map nat (pair (pair address bool) (pair address bool))) (pair (big_map (pair address nat) nat) (big_map string bytes)))) (pair (pair nat (pair (big_map (pair address (pair address nat)) unit) string)) (pair address (pair (big_map nat (pair nat (map string bytes))) (big_map nat nat))))
PUSH bool True; # bool : pair address (pair nat nat) : pair address (list (pair address (pair nat nat))) : list (pair address (list (pair address (pair nat nat)))) : pair (pair (pair address nat) (pair (big_map nat (pair (pair address bool) (pair address bool))) (pair (big_map (pair address nat) nat) (big_map string bytes)))) (pair (pair nat (pair (big_map (pair address (pair address nat)) unit) string)) (pair address (pair (big_map nat (pair nat (map string bytes))) (big_map nat nat))))
DUP 5;      # pair (pair (pair address nat) (pair (big_map nat (pair (pair address bool) (pair address bool))) (pair (big_map (pair address nat) nat) (big_map string bytes)))) (pair (pair nat (pair (big_map (pair address (pair address nat)) unit) string)) (pair address (pair (big_map nat (pair nat (map string bytes))) (big_map nat nat)))) : bool : pair address (pair nat nat) : pair address (list (pair address (pair nat nat))) : list (pair address (list (pair address (pair nat nat)))) : pair (pair (pair address nat) (pair (big_map nat (pair (pair address bool) (pair address bool))) (pair (big_map (pair address nat) nat) (big_map string bytes)))) (pair (pair nat (pair (big_map (pair address (pair address nat)) unit) string)) (pair address (pair (big_map nat (pair nat (map string bytes))) (big_map nat nat))))
GET 5;      # address : bool : pair address (pair nat nat) : pair address (list (pair address (pair nat nat))) : list (pair address (list (pair address (pair nat nat)))) : pair (pair (pair address nat) (pair (big_map nat (pair (pair address bool) (pair address bool))) (pair (big_map (pair address nat) nat) (big_map string bytes)))) (pair (pair nat (pair (big_map (pair address (pair address nat)) unit) string)) (pair address (pair (big_map nat (pair nat (map string bytes))) (big_map nat nat))))
PUSH nat 4; # nat : address : bool : pair address (pair nat nat) : pair address (list (pair address (pair nat nat))) : list (pair address (list (pair address (pair nat nat)))) : pair (pair (pair address nat) (pair (big_map nat (pair (pair address bool) (pair address bool))) (pair (big_map (pair address nat) nat) (big_map string bytes)))) (pair (pair nat (pair (big_map (pair address (pair address nat)) unit) string)) (pair address (pair (big_map nat (pair nat (map string bytes))) (big_map nat nat))))
DUP 4;      # pair address (pair nat nat) : nat : address : bool : pair address (pair nat nat) : pair address (list (pair address (pair nat nat))) : list (pair address (list (pair address (pair nat nat)))) : pair (pair (pair address nat) (pair (big_map nat (pair (pair address bool) (pair address bool))) (pair (big_map (pair address nat) nat) (big_map string bytes)))) (pair (pair nat (pair (big_map (pair address (pair address nat)) unit) string)) (pair address (pair (big_map nat (pair nat (map string bytes))) (big_map nat nat))))
CAR;        # address : nat : address : bool : pair address (pair nat nat) : pair address (list (pair address (pair nat nat))) : list (pair address (list (pair address (pair nat nat)))) : pair (pair (pair address nat) (pair (big_map nat (pair (pair address bool) (pair address bool))) (pair (big_map (pair address nat) nat) (big_map string bytes)))) (pair (pair nat (pair (big_map (pair address (pair address nat)) unit) string)) (pair address (pair (big_map nat (pair nat (map string bytes))) (big_map nat nat))))
PAIR;       # pair address nat : address : bool : pair address (pair nat nat) : pair address (list (pair address (pair nat nat))) : list (pair address (list (pair address (pair nat nat)))) : pair (pair (pair address nat) (pair (big_map nat (pair (pair address bool) (pair address bool))) (pair (big_map (pair address nat) nat) (big_map string bytes)))) (pair (pair nat (pair (big_map (pair address (pair address nat)) unit) string)) (pair address (pair (big_map nat (pair nat (map string bytes))) (big_map nat nat))))
VIEW "has_item" bool; # option bool : bool : pair address (pair nat nat) : pair address (list (pair address (pair nat nat))) : list (pair address (list (pair address (pair nat nat)))) : pair (pair (pair address nat) (pair (big_map nat (pair (pair address bool) (pair address bool))) (pair (big_map (pair address nat) nat) (big_map string bytes)))) (pair (pair nat (pair (big_map (pair address (pair address nat)) unit) string)) (pair address (pair (big_map nat (pair nat (map string bytes))) (big_map nat nat))))
IF_NONE
  {
    PUSH int 53; # int : bool : pair address (pair nat nat) : pair address (list (pair address (pair nat nat))) : list (pair address (list (pair address (pair nat nat)))) : pair (pair (pair address nat) (pair (big_map nat (pair (pair address bool) (pair address bool))) (pair (big_map (pair address nat) nat) (big_map string bytes)))) (pair (pair nat (pair (big_map (pair address (pair address nat)) unit) string)) (pair address (pair (big_map nat (pair nat (map string bytes))) (big_map nat nat))))
    FAILWITH;   # FAILED
  }
  {}; # @some : bool : pair address (pair nat nat) : pair address (list (pair address (pair nat nat))) : list (pair address (list (pair address (pair nat nat)))) : pair (pair (pair address nat) (pair (big_map nat (pair (pair address bool) (pair address bool))) (pair (big_map (pair address nat) nat) (big_map string bytes)))) (pair (pair nat (pair (big_map (pair address (pair address nat)) unit) string)) (pair address (pair (big_map nat (pair nat (map string bytes))) (big_map nat nat))))
COMPARE;    # int : pair address (pair nat nat) : pair address (list (pair address (pair nat nat))) : list (pair address (list (pair address (pair nat nat)))) : pair (pair (pair address nat) (pair (big_map nat (pair (pair address bool) (pair address bool))) (pair (big_map (pair address nat) nat) (big_map string bytes)))) (pair (pair nat (pair (big_map (pair address (pair address nat)) unit) string)) (pair address (pair (big_map nat (pair nat (map string bytes))) (big_map nat nat))))
EQ;         # bool : pair address (pair nat nat) : pair address (list (pair address (pair nat nat))) : list (pair address (list (pair address (pair nat nat)))) : pair (pair (pair address nat) (pair (big_map nat (pair (pair address bool) (pair address bool))) (pair (big_map (pair address nat) nat) (big_map string bytes)))) (pair (pair nat (pair (big_map (pair address (pair address nat)) unit) string)) (pair address (pair (big_map nat (pair nat (map string bytes))) (big_map nat nat))))
IF
  {
    # sp.verify((sp.sender == self.data.admin) | (sp.sender == sp.self_address), 'FA2_RECEIVER_BLOCKED') # pair address (pair nat nat) : pair address (list (pair address (pair nat nat))) : list (pair address (list (pair address (pair nat nat)))) : pair (pair (pair address nat) (pair (big_map nat (pair (pair address bool) (pair address bool))) (pair (big_map (pair address nat) nat) (big_map string bytes)))) (pair (pair nat (pair (big_map (pair address (pair address nat)) unit) string)) (pair address (pair (big_map nat (pair nat (map string bytes))) (big_map nat nat))))
    DUP 4;      # pair (pair (pair address nat) (pair (big_map nat (pair (pair address bool) (pair address bool))) (pair (big_map (pair address nat) nat) (big_map string bytes)))) (pair (pair nat (pair (big_map (pair address (pair address nat)) unit) string)) (pair address (pair (big_map nat (pair nat (map string bytes))) (big_map nat nat)))) : pair address (pair nat nat) : pair address (list (pair address (pair nat nat))) : list (pair address (list (pair address (pair nat nat)))) : pair (pair (pair address nat) (pair (big_map nat (pair (pair address bool) (pair address bool))) (pair (big_map (pair address nat) nat) (big_map string bytes)))) (pair (pair nat (pair (big_map (pair address (pair address nat)) unit) string)) (pair address (pair (big_map nat (pair nat (map string bytes))) (big_map nat nat))))
    CAR;        # pair (pair address nat) (pair (big_map nat (pair (pair address bool) (pair address bool))) (pair (big_map (pair address nat) nat) (big_map string bytes))) : pair address (pair nat nat) : pair address (list (pair address (pair nat nat))) : list (pair address (list (pair address (pair nat nat)))) : pair (pair (pair address nat) (pair (big_map nat (pair (pair address bool) (pair address bool))) (pair (big_map (pair address nat) nat) (big_map string bytes)))) (pair (pair nat (pair (big_map (pair address (pair address nat)) unit) string)) (pair address (pair (big_map nat (pair nat (map string bytes))) (big_map nat nat))))
    CAR;        # pair address nat : pair address (pair nat nat) : pair address (list (pair address (pair nat nat))) : list (pair address (list (pair address (pair nat nat)))) : pair (pair (pair address nat) (pair (big_map nat (pair (pair address bool) (pair address bool))) (pair (big_map (pair address nat) nat) (big_map string bytes)))) (pair (pair nat (pair (big_map (pair address (pair address nat)) unit) string)) (pair address (pair (big_map nat (pair nat (map string bytes))) (big_map nat nat))))
    CAR;        # address : pair address (pair nat nat) : pair address (list (pair address (pair nat nat))) : list (pair address (list (pair address (pair nat nat)))) : pair (pair (pair address nat) (pair (big_map nat (pair (pair address bool) (pair address bool))) (pair (big_map (pair address nat) nat) (big_map string bytes)))) (pair (pair nat (pair (big_map (pair address (pair address nat)) unit) string)) (pair address (pair (big_map nat (pair nat (map string bytes))) (big_map nat nat))))
    SENDER;     # @sender : address : pair address (pair nat nat) : pair address (list (pair address (pair nat nat))) : list (pair address (list (pair address (pair nat nat)))) : pair (pair (pair address nat) (pair (big_map nat (pair (pair address bool) (pair address bool))) (pair (big_map (pair address nat) nat) (big_map string bytes)))) (pair (pair nat (pair (big_map (pair address (pair address nat)) unit) string)) (pair address (pair (big_map nat (pair nat (map string bytes))) (big_map nat nat))))
    COMPARE;    # int : pair address (pair nat nat) : pair address (list (pair address (pair nat nat))) : list (pair address (list (pair address (pair nat nat)))) : pair (pair (pair address nat) (pair (big_map nat (pair (pair address bool) (pair address bool))) (pair (big_map (pair address nat) nat) (big_map string bytes)))) (pair (pair nat (pair (big_map (pair address (pair address nat)) unit) string)) (pair address (pair (big_map nat (pair nat (map string bytes))) (big_map nat nat))))
    EQ;         # bool : pair address (pair nat nat) : pair address (list (pair address (pair nat nat))) : list (pair address (list (pair address (pair nat nat)))) : pair (pair (pair address nat) (pair (big_map nat (pair (pair address bool) (pair address bool))) (pair (big_map (pair address nat) nat) (big_map string bytes)))) (pair (pair nat (pair (big_map (pair address (pair address nat)) unit) string)) (pair address (pair (big_map nat (pair nat (map string bytes))) (big_map nat nat))))
    IF
      {
        PUSH bool True; # bool : pair address (pair nat nat) : pair address (list (pair address (pair nat nat))) : list (pair address (list (pair address (pair nat nat)))) : pair (pair (pair address nat) (pair (big_map nat (pair (pair address bool) (pair address bool))) (pair (big_map (pair address nat) nat) (big_map string bytes)))) (pair (pair nat (pair (big_map (pair address (pair address nat)) unit) string)) (pair address (pair (big_map nat (pair nat (map string bytes))) (big_map nat nat))))
       }
       {
         SELF_ADDRESS; # @self : pair address (pair nat nat) : pair address (list (pair address (pair nat nat))) : list (pair address (list (pair address (pair nat nat)))) : pair (pair (pair address nat) (pair (big_map nat (pair (pair address bool) (pair address bool))) (pair (big_map (pair address nat) nat) (big_map string bytes)))) (pair (pair nat (pair (big_map (pair address (pair address nat)) unit) string)) (pair address (pair (big_map nat (pair nat (map string bytes))) (big_map nat nat))))
         SENDER;     # @sender : @self : pair address (pair nat nat) : pair address (list (pair address (pair nat nat))) : list (pair address (list (pair address (pair nat nat)))) : pair (pair (pair address nat) (pair (big_map nat (pair (pair address bool) (pair address bool))) (pair (big_map (pair address nat) nat) (big_map string bytes)))) (pair (pair nat (pair (big_map (pair address (pair address nat)) unit) string)) (pair address (pair (big_map nat (pair nat (map string bytes))) (big_map nat nat))))
         COMPARE;    # int : pair address (pair nat nat) : pair address (list (pair address (pair nat nat))) : list (pair address (list (pair address (pair nat nat)))) : pair (pair (pair address nat) (pair (big_map nat (pair (pair address bool) (pair address bool))) (pair (big_map (pair address nat) nat) (big_map string bytes)))) (pair (pair nat (pair (big_map (pair address (pair address nat)) unit) string)) (pair address (pair (big_map nat (pair nat (map string bytes))) (big_map nat nat))))
         EQ;         # bool : pair address (pair nat nat) : pair address (list (pair address (pair nat nat))) : list (pair address (list (pair address (pair nat nat)))) : pair (pair (pair address nat) (pair (big_map nat (pair (pair address bool) (pair address bool))) (pair (big_map (pair address nat) nat) (big_map string bytes)))) (pair (pair nat (pair (big_map (pair address (pair address nat)) unit) string)) (pair address (pair (big_map nat (pair nat (map string bytes))) (big_map nat nat))))
       }; # bool : pair address (pair nat nat) : pair address (list (pair address (pair nat nat))) : list (pair address (list (pair address (pair nat nat)))) : pair (pair (pair address nat) (pair (big_map nat (pair (pair address bool) (pair address bool))) (pair (big_map (pair address nat) nat) (big_map string bytes)))) (pair (pair nat (pair (big_map (pair address (pair address nat)) unit) string)) (pair address (pair (big_map nat (pair nat (map string bytes))) (big_map nat nat))))
      IF
        {}
        {
          PUSH string "FA2_RECEIVER_BLOCKED"; # string : pair address (pair nat nat) : pair address (list (pair address (pair nat nat))) : list (pair address (list (pair address (pair nat nat)))) : pair (pair (pair address nat) (pair (big_map nat (pair (pair address bool) (pair address bool))) (pair (big_map (pair address nat) nat) (big_map string bytes)))) (pair (pair nat (pair (big_map (pair address (pair address nat)) unit) string)) (pair address (pair (big_map nat (pair nat (map string bytes))) (big_map nat nat))))
          FAILWITH;   # FAILED
        }; # pair address (pair nat nat) : pair address (list (pair address (pair nat nat))) : list (pair address (list (pair address (pair nat nat)))) : pair (pair (pair address nat) (pair (big_map nat (pair (pair address bool) (pair address bool))) (pair (big_map (pair address nat) nat) (big_map string bytes)))) (pair (pair nat (pair (big_map (pair address (pair address nat)) unit) string)) (pair address (pair (big_map nat (pair nat (map string bytes))) (big_map nat nat))))
      }
      {}; # pair address (pair nat nat) : pair address (list (pair address (pair nat nat))) : list (pair address (list (pair address (pair nat nat)))) : pair (pair (pair address nat) (pair (big_map nat (pair (pair address bool) (pair address bool))) (pair (big_map (pair address nat) nat) (big_map string bytes)))) (pair (pair nat (pair (big_map (pair address (pair address nat)) unit) string)) (pair address (pair (big_map nat (pair nat (map string bytes))) (big_map nat nat))))

This is the error the line triggers once deployed (the contract_handle is that of the originated contract, not the remote view provider)

[
  {
    "kind": "temporary",
    "id": "proto.011-PtHangz2.michelson_v1.runtime_error",
    "contract_handle": "KT1XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX"
  },
  {
    "kind": "temporary",
    "id": "proto.011-PtHangz2.michelson_v1.script_rejected",
    "location": 1574,
    "with": {
      "prim": "Pair",
      "args": [
        {
          "string": "OpenVariant"
        },
        {
          "int": "53"
        }
      ]
    }
  }
]

this is the actual remote contract method inside the provider contract

@sp.offchain_view(pure = True)
def has_item(self, req):
    sp.set_type(
        req, sp.TRecord(
            owner = sp.TAddress,
            token_id = sp.TNat
        ).layout(("owner", "token_id")))
    user = self.ledger_key.make(req.owner, req.token_id)
    sp.verify(self.data.token_metadata.contains(req.token_id), message = self.error_message.token_undefined())
    sp.result(self.data.ledger.contains(user) & (self.data.ledger[user].balance > 0))

This is the stub used by the tests. The type is set and the response is bool (same input / output as the code above)

class ViewProviderNotHasItem(sp.Contract):
    def __init__(self):
        self.init(value = 0)

    @sp.offchain_view(pure = True)
    def has_item(self, req):
        sp.set_type(
            req, sp.TRecord(
                owner = sp.TAddress,
                token_id = sp.TNat
            ).layout(("owner", "token_id")))

        sp.result(False)

this is the test that passes

def transfer_test():
    alice = sp.test_account("alice")
    admin = sp.test_account("admin")

    # stub the remote contract view call
    view_provider = ViewProviderNotHasItem()

    contract = MyFA2(
        provider = view_provider.address,
        admin = admin.address,
    )

    scenario = sp.test_scenario()
    scenario += contract
    scenario += view_provider

    # this creates token id 0
    contract.mint().run(sender=admin)
    
    # passes in test, fails in reality
    contract.transfer(
        sp.list([
            sp.record(
                from_=contract.address,
                txs=sp.list([
                    sp.record(
                        to_=alice.address,
                        amount=1,
                        token_id=0,
                    ),
                ]),
            ),
        ]),
    ).run(sender=admin)

Why am I receiving this "OpenVariant" error, what does this error actually mean, and why doesn't this get caught in the unit test?

2 Answers 2

1

There’s a 99% probability that the types don’t match between what the view in your target declares and what your calling contract expects (either input or output). You can check Michelson code in an explorer. If it works in tests and not in the test net, I also doubt that you originated the exact contracts that you’re testing. You can also try to specify types in your sp.view arguments to force them.

7
  • i edited the question above - i split the stub out of the test and provided the exact remote code so you can compare. the types match
    – 0x10
    Feb 12, 2022 at 4:17
  • i'm also specifying the types in the view parameters in the hasBlockerItem = sp.view code (first code block)
    – 0x10
    Feb 12, 2022 at 4:18
  • i added both the michelson shown in an explorer and the original michelson with comments preserved to the question
    – 0x10
    Feb 12, 2022 at 4:58
  • 1
    @sp.offchain_view(pure = True) def has_item(self, req): This is an offchain view in your example. This doesn't get originated, only onchain view are (this is the single difference). Is the view really originated? Does it appear in the contract code in explorers? New guess: you didn't originate the view.
    – FFF
    Feb 12, 2022 at 13:48
  • 1
    Yes. Contracts cannot call anything outside of the chain directly. We use oracles for that.
    – FFF
    Feb 13, 2022 at 10:35
0

Even though the tests pass it is not actually possible for another contract to use the result of an off-chain view.

If you want to use the result in another contract it must be an on-chain view.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge that you have read and understand our privacy policy and code of conduct.

Not the answer you're looking for? Browse other questions tagged or ask your own question.