External Storage Access
It is common for one smart contract to want to reason about the state of another external smart contract. For example, a Decentralized Exchange contract typically requires reading and modifying the state (balances!) owned by the external contracts of the tokens that are being traded through the exchange.
In imperative smart contract languages like Solidity, reasoning about external state is typically done by calling some methods in the external contract that access or modify that contract's state. In Pint however, the storage variables of a contract are public and can be accessed from outside the contract using the contract's interface.
Interface Instance
While a contract's interface contains all the public symbols that can be externally accessed, it does not specify the address of the contract. The address of a contract is 256-bit value that uniquely identify the contract on the blockchain. Specifying the address is required because multiple contracts with different addresses may share the same interface.
In order to specify which external contract to actually interact with, we need an interface instance. Consider the counter example that we presented earlier and its interface that looks like this:
interface Counter {
storage {
counter: int,
}
predicate Initialize;
predicate Increment;
}
Assume that a contract with that interface has been deployed and has the following address:
const ContractID: b256 = 0x0003000300030003000300030003000300030003000300030003000300030003;
In order to interact with that particular contract, we would first declare an interface instance as follows:
interface CounterInstance = Counter(ContractID);
External Storage Access Using the Interface Instance
Now that we have an instance of the interface, we can use it to create a path to the external storage variable. For example,
state counter = CounterInstance::storage::counter;
Note that the path CounterInstance::storage::counter
has three parts:
- The name of the interface instance
CounterInstance
that indicates which instance we would like to access. Recall that there could be multiple interface instances, with different addresses, for the sameinterface
, hence the need to start the path with the name of the interface instance and not the name of interface itself. - The keyword
storage
to indicate that we're accessing thestorage
block ofCounterInstance
. counter
, the name of the storage variable we want to access.
Once we have assigned the external storage expression to a state
variable, we can then use that
variable as we usually do.
Similarly to local storage access expressions, the expression CounterInstance::storage::counter
can only be used on the right-hand side of a state
declaration.
Note: The
mut
keyword cannot be added to external storage accesses. External storage locations belong to the external contract that owns and it's up to the predicates in that contract to control their mutability.