Handling async execution
To support cross-shard communication, =nil; supports smart contracts making async calls to one another directly in Solidity code.
Performing an async call
Consider a smart contract with the following code:
pragma solidity ^0.8.9;
import "@nilfoundation/smart-contracts/contracts/Nil.sol";
contract Caller {
using Nil for address;
event CallCompleted(address indexed dst);
function call(address dst) public payable {
dst.asyncCall(address(0), 0, abi.encodeWithSignature("funcName"));
emit CallCompleted(dst);
}
}
The asyncCall()
function makes it possible for the contract to call a function in another smart contract regardless of the shard where this contract is located. There are no additional actions required for making async calls: simply adding asyncCall()
and specifying its arguments is sufficient.
The asyncCall()
function is a shortcut for executing a precompiled contract responsible for managing async calls across the cluster.
To initiate an async call, the 'caller' contract must have sufficient funds to pay for initiating a new transaction.
asyncCall()
works even if the contract with the specified address is located on the same shard. The mechanism remains exactly the same as with cross-shard communications: the function will produce a new transaction, and the requested function in another contract will be executed whenever said transaction is processed by the shard. This usually occurs within the space of two-three blocks.
Examples
Consider two contracts deployed on two different shards in =nil;:
- Contract 1 (caller) is deployed on Shard 1
- Contract 2 (receiver) is deployed on Shard 2
There are two possible patterns for cross-shard communication between Contract 1 and Contract 2.
Calling a contract on another shard
In this pattern, Contract 1 simply calls a function in Contract 2 without receiving a result.
Contract 1 has the following structure:
pragma solidity ^0.8.9;
import "@nilfoundation/smart-contracts/contracts/Nil.sol";
contract Caller {
using Nil for address;
receive() external payable {}
function call(address dst) public {
Nil.asyncCall(
dst,
msg.sender,
0,
abi.encodeWithSignature("increment()")
);
}
function verifyExternal(
uint256,
bytes calldata
) external pure returns (bool) {
return true;
}
}
Contract 2 is theCounter
contract.
Whenever the call()
function is called inside Contract 1, a new outgoing transaction is spawned. When Shard 2 picks up this transaction and processes it, Contract 2 calls its increment()
function. After getValue()
is called, the result should display the total number of times the call()
function was executed by Contract 1.
Callback pattern
In this pattern, Contract 1 defines a special callback function and then uses the sendRequest()
method to perform an async request to another shard. Whenever the request executes, a response transaction is sent back to the caller. The callback function is then executed and the response value can be retrieved via the responseData
argument.
As its third argument, sendRequest()
accepts the amount of gas that will be reserved instantly and will later be used to process the execution of the callback function.
Contract 1 acts as a simple escrow mechanism. The submitForVerification()
function accepts the address of the validator and the addresses of the escrow participants. The function then sends a request to the validator while assigning resolve()
as the callback. Whenever the validator processes the request, Contract 1 can retrieve the returned data.
pragma solidity ^0.8.9;
import "@nilfoundation/smart-contracts/contracts/Nil.sol";
contract Escrow is NilBase {
using Nil for address;
mapping(address => uint256) private deposits;
function deposit() public payable {
deposits[msg.sender] += msg.value;
}
function submitForVerification(
address validator,
address participantOne,
address participantTwo
) public payable {
bytes memory context = abi.encodeWithSelector(
this.resolve.selector,
participantOne,
participantTwo,
msg.value
);
bytes memory callData = abi.encodeWithSignature(
"validate(address, address)",
participantOne,
participantTwo
);
Nil.sendRequest(
validator,
0,
Nil.ASYNC_REQUEST_MIN_GAS,
context,
callData
);
}
function resolve(
bool success,
bytes memory returnData,
bytes memory context
) public payable onlyResponse {
require(success, "Request failed!");
(address participantOne, address participantTwo, uint256 value) = abi
.decode(context, (address, address, uint256));
bool isValidated = abi.decode(returnData, (bool));
if (isValidated) {
deposits[participantOne] -= value;
deposits[participantTwo] += value;
}
}
function verifyExternal(
uint256 transactionHash,
bytes calldata authData
) external view returns (bool) {
return true;
}
}
Contract 2 acts as the validator for escrow resolution:
pragma solidity ^0.8.9;
import "@nilfoundation/smart-contracts/contracts/Nil.sol";
contract Validator {
using Nil for address;
function validate(
address participantOne,
address participantTwo
) public returns (bool) {
bool isValidated = (participantOne != participantTwo);
return isValidated;
}
}