Function modifiers
The =nil; Solidity library provides several function modifiers that simplify the processing of external messages and async calls.
onlyInternal
and onlyExternal
=nil; adds the following function modifiers:
onlyInternal
onlyExternal
onlyResponse
If a function has the onlyInternal
modifier, it can only be called by another contract regardless of where this contract is located. A function with the onlyExternal
modifier behaves the opposite way: it must always be called via an external message (e.g., from a user).
The onlyResponse
modifier limits makes it so a function can only be called as callback function when processing the response to sendRequest()
.
It is technically possible to call a function marked with onlyExternal
/onlyInternal
modifier with message whose type is opposite to the type in the modifier. However, such a call will produce an error.
To use the onlyInternal
, onlyExternal
, and onlyResponse
function modifiers, the contract must use NilBase
as its base contract.
Examples
onlyInternal
and onlyExternal
Consider the below examples that simulate on-chain voting.
Contract 1 is a 'ballot' contract that is deployed for every voter:
import "@nilfoundation/smart-contracts/contracts/Nil.sol";
contract Ballot is NilBase {
bytes pubkey;
constructor(bytes memory _pubkey) payable {
pubkey = _pubkey;
}
function vote(address election, string candidate) external onlyExternal {
int feeCredit = 500_000;
bool deploy = false;
uint value = 0;
election.asyncCall(
address(this), feeCredit, value,
abi.encodeWithSignature("requestVote(string)", candidate));
}
function verifyExternal(
uint256 hash,
bytes memory authData
) external view returns (bool) {
return Nil.validateSignature(pubkey, hash, authData);
}
}
Contract 1 contains the voter's public key. The vote()
function can only be called externally by the voter for whom the ballot contract was deployed. This is made possible by the verifyExternal()
function that validates the caller's signature.
Contract 2 records votes:
import "@nilfoundation/smart-contracts/contracts/Nil.sol";
contract Election {
using Nil for address;
mapping(address => string) public votes;
mapping(address => bool) private voters; // Filled by the contract owner
function requestVote(string candidate) public {
address voter = msg.sender;
if (voters[voter] != true) {
return;
}
votes[voter] = candidate;
}
}
Note that Contract 2 lacks the verifyExternal()
function and, therefore, cannot accept external messages. This means that requestVote()
does not need to have the onlyInternal
modifier. Vote verification in Contract 2 can only be initiated by another contract.
Contract 1 makes an async call to Contract 2 requesting vote registration. If the caller is a valid voter, the votes
map is updated to include the new vote for the specified candidate
. Careful use of modifiers and the verifyExternal()
function means that the voting system cannot be abused: votes cannot be requested directly, and additional verification occurs at the level of the election contract.
onlyResponse
The following escrow contract uses onlyResponse
to limit access to its resolve()
function:
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 messageHash,
bytes calldata authData
) external view returns (bool) {
return true;
}
}