Reading onchain state
Read Example​
SOCKET supports reading public variables and functions from the underlying chains. To understand how this is done, lets extend the SuperToken example that was introduced in our guide and expanded to have a transfer
function here.
On SuperTokenAppGateway
, we will create a checkBalance
function. This function will read the user's balance
of a given instance and confirm the user has the funds before performing the transfer.
interface ISuperToken {
function balanceOf(address owner) external;
}
The balanceOf
function signature is similar to a standard token contract but,
- its visibility is not restricted to
view
; - it does not have any return values.
This needs to be followed for all calls that return values to be read. This means if you have any interfaces that need to be leveraged by the offchainVM they will need to be changed to be compatible with the async
/forwarder
system on the offchainVM.
The transfer
function created here had an issue that would be prone to happen. It was not checking the user's balance on the source chain before executing the transfer. Below is a read example where we read and validate the user's balance on the source chain before attempting to transfer the funds.
Click to expand MyTokenAppGateway
changes
contract MyTokenAppGateway is AppGatewayBase {
(...)
/**
* @notice Validates user's token balance for a cross-chain transaction
* @param data Encoded user order and async transaction ID
* @param returnData Balance data returned from the source chain
* @dev Checks if user has sufficient balance to complete the bridge transaction
* @custom:modifier onlyPromises Ensures the function can only be called by the promises system
*/
function checkBalance(bytes memory data, bytes memory returnData) external onlyPromises {
(uint256 amount, bytes32 asyncId) = abi.decode(data, (uint256, bytes32));
uint256 balance = abi.decode(returnData, (uint256));
if (balance < amount) {
_revertTx(asyncId);
return;
}
}
/**
* @notice Initiates a cross-chain token bridge transaction
* @param amount Amount to transfer
* @return asyncId Unique identifier for the asynchronous cross-chain transaction
* @dev Handles token bridging logic across different chains
*/
function transfer(uint256 amount, address srcForwarder, address dstForwarder)
external
async
returns (bytes32 asyncId)
{
// Check user balance on src chain
_readCallOn();
// Request to forwarder and deploys immutable promise contract and stores it
ISuperToken(srcForwarder).balanceOf(msg.sender);
IPromise(srcForwarder).then(this.checkBalance.selector, abi.encode(amount, asyncId));
_readCallOff();
ISuperToken(srcForwarder).burn(msg.sender, amount);
ISuperToken(dstForwarder).mint(msg.sender, amount);
}
}
Let's break down the key points:
- The
transfer
function uses theasync
modifier, which is required for onchain interactions, whether they are read or write operations. - The
_readCallOn()
and_readCallOff()
functions are used to tell SOCKET that a particular section only needs to read data from another chain, rather than executing an onchain transaction. - In multi-chain operations, data cannot be read synchronously. Instead, it follows this pattern:
- Make the onchain call (
balanceOf
) - Set up a promise with a callback function using
IPromise(srcForwarder).then()
- The callback function (
checkBalance
) receives the data asynchronously
- Make the onchain call (
- The
then
function on the forwarder (cast asIPromise
) takes:- First parameter: The callback function's selector (
this.checkBalance.selector
) - Second parameter: Encoded data needed by the callback (
abi.encode(amount, asyncId)
).
- First parameter: The callback function's selector (
- The callback function
checkBalance
:- Uses the
onlyPromises
modifier to ensure it's only called by the promises system - Takes two parameters:
data
: The encoded data from the original function (amount and asyncId)returnData
: The actual data returned from the source chain (balance)
- Uses the
- When interacting with contracts on other chains, interface definitions need to be modified:
- Remove visibility modifiers (like
view
) - Remove return value declarations from the function signature
- Remove visibility modifiers (like
Go here to see how promises work.
How to revert async transactions?​
When working with async transactions in SOCKET, there might be cases where you need to revert an ongoing transaction in a callback based on certain conditions. SOCKET provides utilities to handle such scenarios through the _revertTx
function.
- The
_revertTx(asyncId)
function is called when the balance check fails to stop triggering burn and mint.- If
_revertTx(asyncId)
is not called, burn and mint are executed as expected. - The
asyncId
is obtained using_getCurrentAsyncId()
and passed to callback using data.
- If
The _revertTx
function is only available in contracts that inherit from AppGatewayBase
.