Sharing Global State Across Contracts
It is often required for a smart contract to read the global state from another stateful smart contract. This may be because you need one contract to act as an oracle to multiple contracts or you want a global value in the current contract to be calculated partially on a value from another contract. This article explains how this can be done and what coding is required to achieve this capability. Note that you may also read the local state used by other contracts as well. This article only addresses accessing global state. For more information on reading and writing state in Algorand, see the developer documentation.
Reading Global State from Additional Contracts
Within the stateful smart contract framework, contracts can indeed read global state from additional contracts. This is done by using the ‘app_global_get_ex’ opcode in TEAL or the ‘App.globalGetEx’ method in PyTeal. These both take two parameters. This first parameter is an integer that represents the index into the foreign applications array. The second parameter is the specific key you are trying to read from the global state for the referenced stateful smart contract. The foreign applications array is just a list of available smart contracts that the current contract can read global state from and it can be set on every call to a stateful smart contract. Currently, the list of foreign applications can only contain two additional stateful smart contracts. While this may seem very limiting, it is important to realize that these two can be changed for each subsequent call to the smart contract. For example, transaction one may reference smart contracts one and two, and transaction two may reference smart contracts three and four.
To set the specific stateful smart contracts in the foreign applications array for a particular transaction, you can do the following.
With the ‘goal’ command line tool:
goal app call --app-id <current-contract-id> --from <address-of-caller> --foreign-app <foreign-contract-1> —foreign-app <foreign-contract-2> -d data
// appID1 and appID2 are integers let foreignApps = [appID1, appID2]; // Call stateful contract let transaction1 = algosdk.makeApplicationNoOpTxn(sender, params, appID, appArgs, appAccts, foreginApps);
# appID1 and appID2 are integers foreign_apps = [appid1,appid2] txn = transaction.ApplicationNoOpTxn(sender, params, index, app_args, foreign_apps)
Once the smart contracts are placed in the foreign application array you can then read them with either TEAL or PyTeal code. For example, suppose we have a master stateful smart contract with the following code.
Note the following examples should only be used for learning purposes and never in production as they do not contain any error checking.
#pragma version 2 // read global state byte "mastervalue" int 5000 app_global_put int 1 return
Assume that the above contract is deployed and its application ID is ‘12345’. This contract just stores a global value named “mastervalue” with a value of 5000. This value can be read from another contract by adding it to the foreign application array before making the call to the smart contract that wants to reference this master contract value.
goal app call --app-id <current-contract-id> --from <address-of-caller> --foreign-app 12345 -d data
This value can then be read using the
app_global_get_ex opcode. This opcode takes two parameters. The first is an integer value which represents an index into the foreign applications array. Note that the 0 position in the array, actually contains the current smart contract, so when we add to the foreign applications array, the first entry defaults to the one position. This is why we need to supply the
int 1 to the first parameter to the call. The second parameter contains the key that you want to read. The
app_gloabl_get_ex returns a
1 depending on whether the key is found. If the key is found this call also returns the value of the key.
#pragma version 2 // first application in the applications array // note that int 0 would reference the currently // running smart contract int 1 byte "mastervalue" app_global_get_ex bz failed int 5000 == bz failed int 1 return . . failed: int 0 return
In the above example, after making the call to the
app_global_get_ex opcode, we use the
bz branch opcode. This opcode pops the top value off the stack and if this value is
0, it will jump to the branch label. In this case
failed:. This will happen if the “mastervalue” key is not found in the referenced smart contract. If the value is a
1, the call will also return the value. After the
bz opcode the found value will now be at the top of the stack. In this example we compare it to the value 5000 and if that is the value read, we return success, else we return a failure.
The example smart contract can also be written in python using the PyTeal library.
from pyteal import * failed = Return(Int(0)) mastervalue = App.globalGetEx(Int(1), Bytes("mastervalue")) program = Seq([ mastervalue, If(Not(mastervalue.hasValue()), failed), If(mastervalue.value() != Int(5000), failed), Return(Int(1)), ]) print(compileTeal(program, Mode.Application))
This post covered just reading global state from additional smart contracts. Using Algorand smart contracts, developers can create many unique applications. For more information on building with Algorand smart contracts, see the developer documentation. For an overview of Algorands smart contract architecture and programming language see our recent developer office hours presentation. Also, be sure to sign up for upcoming developer office hours and join us on discord.