Skip to main content

On-Chain Views

The feature specification can be found here.

note

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 Mainnet.

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.

On chain view

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.

Examples

# The view name will be "view1"
@sp.onchain_view()
def view1(self):
# sp.result is used to return the view result (the contract storage in this case)
sp.result(self.data)

# 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.")
sp.result(state)

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>).

Arguments:

ArgumentRequiredDescriptionExamples
view_nameYesThe name of the view being called."view1", "getState", "computeSomething"
contract_addressYesThe contract address where the view is defined.sp.address("KT1TezoooozzSmartPyzzSTATiCzzzwwBFA1")
argumentYesThe view argument.sp.unit, 10, "Some Text", sp.bytes("0x0123")
return_typeNo (It is optional)The view return type. Not required when the view is known by the compiler. (e.g. Defined in the same contract.)sp.TNat, "sp.TString"

Example

import smartpy as sp

# A contract that serves as storage and provides information to other contracts through an on-chain view
class Provider(sp.Contract):
def __init__(self, tokens):
self.init(tokens = tokens)

@sp.onchain_view()
def getTokenById(self, tokenID):
sp.verify(self.data.tokens.contains(tokenID), "Token doesn't exist")
sp.result(self.data.tokens[tokenID])

# Contract that will call the view defined in the consumer above
class Consumer(sp.Contract):
@sp.entry_point
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 <contract>.some_view().

The example below shows how to do it.

Example

import smartpy as sp

class MyContract(sp.Contract):
def __init__(self, param):
self.init(param)

@sp.onchain_view()
def state(self, param):
sp.verify(param < 5, "This is false: param >= 5")
sp.result(self.data * param)

@sp.add_test(name = "Test")
def test():
scenario = sp.test_scenario()
c1 = MyContract(1)
scenario += c1

""" Test views """

# Display the view result
scenario.show(c1.state(1))

# 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"))