The feature specification can be found here.
On-chain views are currently only supported in the
Hangzhou test network. The protocol proposal is presently under an election process to become the new protocol of
On-chain views are a new feature included in
Hangzhou protocol. They are synchronous calls, meaning that the result is immediately available on the stack of the caller contract, which will make the contract development more effortless.
Views are a mechanism for contract calls that:
- Are read-only: they may depend on the storage of the contract declaring the view but cannot modify it nor emit operations;
- Can call other views;
- Take arguments as input in addition to the contract storage;
- Return a result as output;
- Are synchronous: the result is immediately available on the stack of the caller contract.
In other words, the execution of a view is included in the operation of caller’s contract, but accesses the storage of the declarer’s contract, in read-only mode. Thus, in terms of execution, views are more like lambda functions rather than contract entrypoints.
See reference On-Chain Views template.
Defining an on-chain view
On-chain views are defined using the
@sp.onchain_view( name = <name> ) decorator inside the contract class.
By default, the
view name is equal to the method name, where
<name> is an optional argument and can be used to set a view name explicitly.
# The view name will be "view1"
# sp.result is used to return the view result (the contract storage in this case)
# The view name will be "getState", it is being set explicitly
@sp.onchain_view(name = "getState")
def view2(self, param):
state = self.data.state[param];
sp.verify(state == 2, "The state is not equal to 2.")
Calling an on-chain view
On-chain views are called with
sp.view(<view_name>, <contract_address>, <argument>, t = <return_type>), and their output is of type sp.TOption(<return_type>).
|view_name||Yes||The name of the view being called.|
|contract_address||Yes||The contract address where the view is defined.|
|argument||Yes||The view argument.|
|return_type||No (It is optional)||The view return type. Not required when the view is known by the compiler. (e.g. Defined in the same contract.)|
import smartpy as sp
# A contract that serves as storage and provides information to other contracts through an on-chain view
def __init__(self, tokens):
self.init(tokens = tokens)
def getTokenById(self, tokenID):
sp.verify(self.data.tokens.contains(tokenID), "Token doesn't exist")
# Contract that will call the view defined in the consumer above
def checkToken(self, params):
token = sp.view("getTokenById", params.providerAddress, params.tokenID, t = sp.TRecord(balance = sp.TNat)).open_some("Invalid view");
sp.verify(token.balance >= 10, "Token balance is lower than 10")
Testing an on-chain view
Views can be called from test scenarios the same way as entry points
The example below shows how to do it.
import smartpy as sp
def __init__(self, param):
def state(self, param):
sp.verify(param < 5, "This is false: param >= 5")
sp.result(self.data * param)
@sp.add_test(name = "Test")
scenario = sp.test_scenario()
c1 = MyContract(1)
scenario += c1
""" Test views """
# Display the view result
# Assert the view result
scenario.verify(c1.state(2) == 2)
# Assert call failures
scenario.verify(sp.is_failing(c1.state(6))); # Expected to fail
scenario.verify(~ sp.is_failing(c1.state(1))); # Not expected to fail
# Assert exception result
# catch_exception returns an option:
# sp.none if the call succeeds
# sp.some(<exception>) if the call fails
e = sp.catch_exception(c1.state(7), t = sp.TString)
scenario.verify(e == sp.some("This is false: param >= 5"))