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?