Contract Source Code:
<i class='far fa-question-circle text-muted ms-2' data-bs-trigger='hover' data-bs-toggle='tooltip' data-bs-html='true' data-bs-title='Click on the check box to select individual contract to compare. Only 1 contract can be selected from each side.'></i>
// SPDX-License-Identifier: AGPL-3.0-only
pragma solidity 0.8.26;
import {Auth} from "src/Auth.sol";
import {MessagesLib} from "src/libraries/MessagesLib.sol";
import {BytesLib} from "src/libraries/BytesLib.sol";
import {IRoot, IRecoverable} from "src/interfaces/IRoot.sol";
import {IAuth} from "src/interfaces/IAuth.sol";
/// @title Root
/// @notice Core contract that is a ward on all other deployed contracts.
/// @dev Pausing can happen instantaneously, but relying on other contracts
/// is restricted to the timelock set by the delay.
contract Root is Auth, IRoot {
using BytesLib for bytes;
/// @dev To prevent filing a delay that would block any updates indefinitely
uint256 internal constant MAX_DELAY = 4 weeks;
address public immutable escrow;
/// @inheritdoc IRoot
bool public paused;
/// @inheritdoc IRoot
uint256 public delay;
/// @inheritdoc IRoot
mapping(address => uint256) public endorsements;
/// @inheritdoc IRoot
mapping(address relyTarget => uint256 timestamp) public schedule;
constructor(address _escrow, uint256 _delay, address deployer) Auth(deployer) {
require(_delay <= MAX_DELAY, "Root/delay-too-long");
escrow = _escrow;
delay = _delay;
}
// --- Administration ---
/// @inheritdoc IRoot
function file(bytes32 what, uint256 data) external auth {
if (what == "delay") {
require(data <= MAX_DELAY, "Root/delay-too-long");
delay = data;
} else {
revert("Root/file-unrecognized-param");
}
emit File(what, data);
}
/// --- Endorsements ---
/// @inheritdoc IRoot
function endorse(address user) external auth {
endorsements[user] = 1;
emit Endorse(user);
}
/// @inheritdoc IRoot
function veto(address user) external auth {
endorsements[user] = 0;
emit Veto(user);
}
/// @inheritdoc IRoot
function endorsed(address user) public view returns (bool) {
return endorsements[user] == 1;
}
// --- Pause management ---
/// @inheritdoc IRoot
function pause() external auth {
paused = true;
emit Pause();
}
/// @inheritdoc IRoot
function unpause() external auth {
paused = false;
emit Unpause();
}
/// --- Timelocked ward management ---
/// @inheritdoc IRoot
function scheduleRely(address target) public auth {
schedule[target] = block.timestamp + delay;
emit ScheduleRely(target, schedule[target]);
}
/// @inheritdoc IRoot
function cancelRely(address target) public auth {
require(schedule[target] != 0, "Root/target-not-scheduled");
schedule[target] = 0;
emit CancelRely(target);
}
/// @inheritdoc IRoot
function executeScheduledRely(address target) external {
require(schedule[target] != 0, "Root/target-not-scheduled");
require(schedule[target] <= block.timestamp, "Root/target-not-ready");
wards[target] = 1;
emit Rely(target);
schedule[target] = 0;
}
/// --- Incoming message handling ---
/// @inheritdoc IRoot
function handle(bytes calldata message) public auth {
MessagesLib.Call call = MessagesLib.messageType(message);
if (call == MessagesLib.Call.ScheduleUpgrade) {
scheduleRely(message.toAddress(1));
} else if (call == MessagesLib.Call.CancelUpgrade) {
cancelRely(message.toAddress(1));
} else if (call == MessagesLib.Call.RecoverTokens) {
(address target, address token, address to, uint256 amount) =
(message.toAddress(1), message.toAddress(33), message.toAddress(65), message.toUint256(97));
recoverTokens(target, token, to, amount);
} else {
revert("Root/invalid-message");
}
}
/// --- External contract ward management ---
/// @inheritdoc IRoot
function relyContract(address target, address user) external auth {
IAuth(target).rely(user);
emit RelyContract(target, user);
}
/// @inheritdoc IRoot
function denyContract(address target, address user) external auth {
IAuth(target).deny(user);
emit DenyContract(target, user);
}
/// --- Token recovery ---
/// @inheritdoc IRoot
function recoverTokens(address target, address token, address to, uint256 amount) public auth {
IRecoverable(target).recoverTokens(token, to, amount);
emit RecoverTokens(target, token, to, amount);
}
} <i class='far fa-question-circle text-muted ms-2' data-bs-trigger='hover' data-bs-toggle='tooltip' data-bs-html='true' data-bs-title='Click on the check box to select individual contract to compare. Only 1 contract can be selected from each side.'></i>
// SPDX-License-Identifier: AGPL-3.0-only
pragma solidity 0.8.26;
import {IAuth} from "src/interfaces/IAuth.sol";
/// @title Auth
/// @notice Simple authentication pattern
/// @author Based on code from https://github.com/makerdao/dss
abstract contract Auth is IAuth {
/// @inheritdoc IAuth
mapping(address => uint256) public wards;
constructor(address initialWard) {
wards[initialWard] = 1;
emit Rely(initialWard);
}
/// @dev Check if the msg.sender has permissions
modifier auth() {
require(wards[msg.sender] == 1, "Auth/not-authorized");
_;
}
/// @inheritdoc IAuth
function rely(address user) external auth {
wards[user] = 1;
emit Rely(user);
}
/// @inheritdoc IAuth
function deny(address user) external auth {
wards[user] = 0;
emit Deny(user);
}
} <i class='far fa-question-circle text-muted ms-2' data-bs-trigger='hover' data-bs-toggle='tooltip' data-bs-html='true' data-bs-title='Click on the check box to select individual contract to compare. Only 1 contract can be selected from each side.'></i>
// SPDX-License-Identifier: AGPL-3.0-only
pragma solidity 0.8.26;
import {BytesLib} from "src/libraries/BytesLib.sol";
/// @title MessagesLib
/// @dev Library for encoding and decoding messages.
library MessagesLib {
using BytesLib for bytes;
enum Call {
/// 0 - An invalid message
Invalid,
// --- Gateway ---
/// 1 - Proof
MessageProof,
/// 2 - Initiate Message Recovery
InitiateMessageRecovery,
/// 3 - Dispute Message Recovery
DisputeMessageRecovery,
/// 4 - Batch Messages
Batch,
// --- Root ---
/// 5 - Schedule an upgrade contract to be granted admin rights
ScheduleUpgrade,
/// 6 - Cancel a previously scheduled upgrade
CancelUpgrade,
/// 7 - Recover tokens sent to the wrong contract
RecoverTokens,
// --- Gas service ---
/// 8 - Update Centrifuge gas price
UpdateCentrifugeGasPrice,
// --- Pool Manager ---
/// 9 - Add an asset id -> EVM address mapping
AddAsset,
/// 10 - Add Pool
AddPool,
/// 11 - Add a Pool's Tranche Token
AddTranche,
/// 12 - Allow an asset to be used as an asset for investing in pools
AllowAsset,
/// 13 - Disallow an asset to be used as an asset for investing in pools
DisallowAsset,
/// 14 - Update the price of a Tranche Token
UpdateTranchePrice,
/// 15 - Update tranche token metadata
UpdateTrancheMetadata,
/// 16 - Update Tranche Hook
UpdateTrancheHook,
/// 17 - A transfer of assets
TransferAssets,
/// 18 - A transfer of tranche tokens
TransferTrancheTokens,
/// 19 - Update a user restriction
UpdateRestriction,
/// --- Investment Manager ---
/// 20 - Increase an investment order by a given amount
DepositRequest,
/// 21 - Increase a Redeem order by a given amount
RedeemRequest,
/// 22 - Executed Collect Invest
FulfilledDepositRequest,
/// 23 - Executed Collect Redeem
FulfilledRedeemRequest,
/// 24 - Cancel an investment order
CancelDepositRequest,
/// 25 - Cancel a redeem order
CancelRedeemRequest,
/// 26 - Executed Decrease Invest Order
FulfilledCancelDepositRequest,
/// 27 - Executed Decrease Redeem Order
FulfilledCancelRedeemRequest,
/// 28 - Request redeem investor
TriggerRedeemRequest
}
function messageType(bytes memory _msg) internal pure returns (Call _call) {
_call = Call(_msg.toUint8(0));
}
} <i class='far fa-question-circle text-muted ms-2' data-bs-trigger='hover' data-bs-toggle='tooltip' data-bs-html='true' data-bs-title='Click on the check box to select individual contract to compare. Only 1 contract can be selected from each side.'></i>
// SPDX-License-Identifier: AGPL-3.0-only
pragma solidity 0.8.26;
/// @title Bytes Lib
/// @dev Bytes tightly packed arrays utility library for ethereum contracts written in Solidity.
/// The library lets you concatenate, slice and type cast bytes arrays both in memory and storage.
/// @author Modified from Solidity Bytes Arrays Utils v0.8.0
library BytesLib {
function slice(bytes memory _bytes, uint256 _start, uint256 _length) internal pure returns (bytes memory) {
require(_length + 31 >= _length, "slice_overflow");
require(_bytes.length >= _start + _length, "slice_outOfBounds");
bytes memory tempBytes;
assembly {
switch iszero(_length)
case 0 {
// Get a location of some free memory and store it in tempBytes as
// Solidity does for memory variables.
tempBytes := mload(0x40)
// The first word of the slice result is potentially a partial
// word read from the original array. To read it, we calculate
// the length of that partial word and start copying that many
// bytes into the array. The first word we copy will start with
// data we don't care about, but the last `lengthmod` bytes will
// land at the beginning of the contents of the new array. When
// we're done copying, we overwrite the full first word with
// the actual length of the slice.
let lengthmod := and(_length, 31)
// The multiplication in the next line is necessary
// because when slicing multiples of 32 bytes (lengthmod == 0)
// the following copy loop was copying the origin's length
// and then ending prematurely not copying everything it should.
let mc := add(add(tempBytes, lengthmod), mul(0x20, iszero(lengthmod)))
let end := add(mc, _length)
for {
// The multiplication in the next line has the same exact purpose
// as the one above.
let cc := add(add(add(_bytes, lengthmod), mul(0x20, iszero(lengthmod))), _start)
} lt(mc, end) {
mc := add(mc, 0x20)
cc := add(cc, 0x20)
} { mstore(mc, mload(cc)) }
mstore(tempBytes, _length)
//update free-memory pointer
//allocating the array padded to 32 bytes like the compiler does now
mstore(0x40, and(add(mc, 31), not(31)))
}
//if we want a zero-length slice let's just return a zero-length array
default {
tempBytes := mload(0x40)
//zero out the 32 bytes slice we are about to return
//we need to do it because Solidity does not garbage collect
mstore(tempBytes, 0)
mstore(0x40, add(tempBytes, 0x20))
}
}
return tempBytes;
}
function toAddress(bytes memory _bytes, uint256 _start) internal pure returns (address) {
require(_bytes.length >= _start + 20, "toAddress_outOfBounds");
address tempAddress;
assembly {
tempAddress := div(mload(add(add(_bytes, 0x20), _start)), 0x1000000000000000000000000)
}
return tempAddress;
}
function toUint8(bytes memory _bytes, uint256 _start) internal pure returns (uint8) {
require(_bytes.length >= _start + 1, "toUint8_outOfBounds");
uint8 tempUint;
assembly {
tempUint := mload(add(add(_bytes, 0x1), _start))
}
return tempUint;
}
function toUint16(bytes memory _bytes, uint256 _start) internal pure returns (uint16) {
require(_bytes.length >= _start + 2, "toUint16_outOfBounds");
uint16 tempUint;
assembly {
tempUint := mload(add(add(_bytes, 0x2), _start))
}
return tempUint;
}
function toUint64(bytes memory _bytes, uint256 _start) internal pure returns (uint64) {
require(_bytes.length >= _start + 8, "toUint64_outOfBounds");
uint64 tempUint;
assembly {
tempUint := mload(add(add(_bytes, 0x8), _start))
}
return tempUint;
}
function toUint128(bytes memory _bytes, uint256 _start) internal pure returns (uint128) {
require(_bytes.length >= _start + 16, "toUint128_outOfBounds");
uint128 tempUint;
assembly {
tempUint := mload(add(add(_bytes, 0x10), _start))
}
return tempUint;
}
function toUint256(bytes memory _bytes, uint256 _start) internal pure returns (uint256) {
require(_bytes.length >= _start + 32, "toUint256_outOfBounds");
uint256 tempUint;
assembly {
tempUint := mload(add(add(_bytes, 0x20), _start))
}
return tempUint;
}
function toBytes32(bytes memory _bytes, uint256 _start) internal pure returns (bytes32) {
require(_bytes.length >= _start + 32, "toBytes32_outOfBounds");
bytes32 tempBytes32;
assembly {
tempBytes32 := mload(add(add(_bytes, 0x20), _start))
}
return tempBytes32;
}
function toBytes16(bytes memory _bytes, uint256 _start) internal pure returns (bytes16) {
require(_bytes.length >= _start + 16, "toBytes16_outOfBounds");
bytes16 tempBytes16;
assembly {
tempBytes16 := mload(add(add(_bytes, 0x20), _start))
}
return tempBytes16;
}
} <i class='far fa-question-circle text-muted ms-2' data-bs-trigger='hover' data-bs-toggle='tooltip' data-bs-html='true' data-bs-title='Click on the check box to select individual contract to compare. Only 1 contract can be selected from each side.'></i>
// SPDX-License-Identifier: AGPL-3.0-only
pragma solidity >=0.5.0;
import {IMessageHandler} from "src/interfaces/gateway/IGateway.sol";
interface IRecoverable {
/// @notice Used to recover any ERC-20 token.
/// @dev This method is called only by authorized entities
/// @param token It could be 0xeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee
/// to recover locked native ETH or any ERC20 compatible token.
/// @param to Receiver of the funds
/// @param amount Amount to send to the receiver.
function recoverTokens(address token, address to, uint256 amount) external;
}
interface IRoot is IMessageHandler {
// --- Events ---
event File(bytes32 indexed what, uint256 data);
event Pause();
event Unpause();
event ScheduleRely(address indexed target, uint256 indexed scheduledTime);
event CancelRely(address indexed target);
event RelyContract(address indexed target, address indexed user);
event DenyContract(address indexed target, address indexed user);
event RecoverTokens(address indexed target, address indexed token, address indexed to, uint256 amount);
event Endorse(address indexed user);
event Veto(address indexed user);
/// @notice Returns whether the root is paused
function paused() external view returns (bool);
/// @notice Returns the current timelock for adding new wards
function delay() external view returns (uint256);
/// @notice Trusted contracts within the system
function endorsements(address target) external view returns (uint256);
/// @notice Returns when `relyTarget` has passed the timelock
function schedule(address relyTarget) external view returns (uint256 timestamp);
// --- Administration ---
/// @notice Updates a contract parameter
/// @param what Accepts a bytes32 representation of 'delay'
function file(bytes32 what, uint256 data) external;
/// --- Endorsements ---
/// @notice Endorses the `user`
/// @dev Endorsed users are trusted contracts in the system. They are allowed to bypass
/// token restrictions (e.g. the Escrow can automatically receive tranche tokens by being endorsed), and
/// can automatically set operators in ERC-7540 vaults (e.g. the CentrifugeRouter) is always an operator.
function endorse(address user) external;
/// @notice Removes the endorsed user
function veto(address user) external;
/// @notice Returns whether the user is endorsed
function endorsed(address user) external view returns (bool);
// --- Pause management ---
/// @notice Pause any contracts that depend on `Root.paused()`
function pause() external;
/// @notice Unpause any contracts that depend on `Root.paused()`
function unpause() external;
/// --- Timelocked ward management ---
/// @notice Schedule relying a new ward after the delay has passed
function scheduleRely(address target) external;
/// @notice Cancel a pending scheduled rely
function cancelRely(address target) external;
/// @notice Execute a scheduled rely
/// @dev Can be triggered by anyone since the scheduling is protected
function executeScheduledRely(address target) external;
/// --- Incoming message handling ---
function handle(bytes calldata message) external;
/// --- External contract ward management ---
/// @notice Make an address a ward on any contract that Root is a ward on
function relyContract(address target, address user) external;
/// @notice Removes an address as a ward on any contract that Root is a ward on
function denyContract(address target, address user) external;
/// --- Token Recovery ---
/// @notice Allows Governance to recover tokens sent to the wrong contract by mistake
function recoverTokens(address target, address token, address to, uint256 amount) external;
} <i class='far fa-question-circle text-muted ms-2' data-bs-trigger='hover' data-bs-toggle='tooltip' data-bs-html='true' data-bs-title='Click on the check box to select individual contract to compare. Only 1 contract can be selected from each side.'></i>
// SPDX-License-Identifier: AGPL-3.0-only
pragma solidity >=0.5.0;
interface IAuth {
event Rely(address indexed user);
event Deny(address indexed user);
/// @notice Returns whether the target is a ward (has admin access)
function wards(address target) external view returns (uint256);
/// @notice Make user a ward (give them admin access)
function rely(address user) external;
/// @notice Remove user as a ward (remove admin access)
function deny(address user) external;
} <i class='far fa-question-circle text-muted ms-2' data-bs-trigger='hover' data-bs-toggle='tooltip' data-bs-html='true' data-bs-title='Click on the check box to select individual contract to compare. Only 1 contract can be selected from each side.'></i>
// SPDX-License-Identifier: AGPL-3.0-only
pragma solidity 0.8.26;
uint8 constant MAX_ADAPTER_COUNT = 8;
interface IGateway {
/// @dev Each adapter struct is packed with the quorum to reduce SLOADs on handle
struct Adapter {
/// @notice Starts at 1 and maps to id - 1 as the index on the adapters array
uint8 id;
/// @notice Number of votes required for a message to be executed
uint8 quorum;
/// @notice Each time the quorum is decreased, a new session starts which invalidates old votes
uint64 activeSessionId;
}
struct Message {
/// @dev Counts are stored as integers (instead of boolean values) to accommodate duplicate
/// messages (e.g. two investments from the same user with the same amount) being
/// processed in parallel. The entire struct is packed in a single bytes32 slot.
/// Max uint16 = 65,535 so at most 65,535 duplicate messages can be processed in parallel.
uint16[MAX_ADAPTER_COUNT] votes;
/// @notice Each time adapters are updated, a new session starts which invalidates old votes
uint64 sessionId;
bytes pendingMessage;
}
// --- Events ---
event ProcessMessage(bytes message, address adapter);
event ProcessProof(bytes32 messageHash, address adapter);
event ExecuteMessage(bytes message, address adapter);
event SendMessage(bytes message);
event RecoverMessage(address adapter, bytes message);
event RecoverProof(address adapter, bytes32 messageHash);
event InitiateMessageRecovery(bytes32 messageHash, address adapter);
event DisputeMessageRecovery(bytes32 messageHash, address adapter);
event ExecuteMessageRecovery(bytes message, address adapter);
event File(bytes32 indexed what, address[] adapters);
event File(bytes32 indexed what, address instance);
event File(bytes32 indexed what, uint8 messageId, address manager);
event File(bytes32 indexed what, address caller, bool isAllowed);
event ReceiveNativeTokens(address indexed sender, uint256 amount);
/// @notice Returns the address of the adapter at the given id.
function adapters(uint256 id) external view returns (address);
/// @notice Returns the address of the contract that handles the given message id.
function messageHandlers(uint8 messageId) external view returns (address);
/// @notice Returns the timestamp when the given recovery can be executed.
function recoveries(address adapter, bytes32 messageHash) external view returns (uint256 timestamp);
// --- Administration ---
/// @notice Used to update an array of addresses ( state variable ) on very rare occasions.
/// @dev Currently it is used to update the supported adapters.
/// @param what The name of the variable to be updated.
/// @param value New addresses.
function file(bytes32 what, address[] calldata value) external;
/// @notice Used to update an address ( state variable ) on very rare occasions.
/// @dev Currently used to update addresses of contract instances.
/// @param what The name of the variable to be updated.
/// @param data New address.
function file(bytes32 what, address data) external;
/// @notice Used to update a mapping ( state variables ) on very rare occasions.
/// @dev Currently used to update any custom handlers for a specific message type.
/// data1 is the message id from MessagesLib.Call and data2 could be any
/// custom instance of a contract that will handle that call.
/// @param what The name of the variable to be updated.
/// @param data1 The key of the mapping.
/// @param data2 The value of the mapping
function file(bytes32 what, uint8 data1, address data2) external;
/// @notice Used to update a mapping ( state variables ) on very rare occasions.
/// @dev Manages who is allowed to call `this.topUp`
///
/// @param what The name of the variable to be updated - `payers`
/// @param caller Address of the payer allowed to top-up
/// @param isAllower Whether the `caller` is allowed to top-up or not
function file(bytes32 what, address caller, bool isAllower) external;
// --- Incoming ---
/// @notice Handles incoming messages, proofs, and recoveries.
/// @dev Assumes adapters ensure messages cannot be confirmed more than once.
/// @param payload Incoming message from the Centrifuge Chain passed through adapters.
function handle(bytes calldata payload) external;
/// @notice Governance on Centrifuge Chain can initiate message recovery. After the challenge period,
/// the recovery can be executed. If a malign adapter initiates message recovery, governance on
/// Centrifuge Chain can dispute and immediately cancel the recovery, using any other valid adapter.
/// @param adapter Adapter that the recovery was targeting
/// @param messageHash Hash of the message being disputed
function disputeMessageRecovery(address adapter, bytes32 messageHash) external;
/// @notice Governance on Centrifuge Chain can initiate message recovery. After the challenge period,
/// the recovery can be executed. If a malign adapter initiates message recovery, governance on
/// Centrifuge Chain can dispute and immediately cancel the recovery, using any other valid adapter.
///
/// Only 1 recovery can be outstanding per message hash. If multiple adapters fail at the same time,
/// these will need to be recovered serially (increasing the challenge period for each failed adapter).
/// @param adapter Adapter's address that the recovery is targeting
/// @param message Hash of the message to be recovered
function executeMessageRecovery(address adapter, bytes calldata message) external;
// --- Outgoing ---
/// @notice Sends outgoing messages to the Centrifuge Chain.
/// @dev Sends 1 message to the first adapter with the full message,
/// and n-1 messages to the other adapters with proofs (hash of message).
/// This ensures message uniqueness (can only be executed on the destination once).
/// Source could be either Centrifuge router or EoA or any contract
/// that calls the ERC7540Vault contract directly.
/// @param message Message to be send. Either the message itself or a hash value of it ( proof ).
/// @param source Entry point of the transaction.
/// Used to determine whether it is eligible for TX cost payment.
function send(bytes calldata message, address source) external payable;
/// @notice Prepays for the TX cost for sending through the adapters
/// and Centrifuge Chain
/// @dev It can be called only through endorsed contracts.
/// Currently being called from Centrifuge Router only.
/// In order to prepay, the method MUST be called with `msg.value`.
/// Called is assumed to have called IGateway.estimate before calling this.
function topUp() external payable;
// --- Helpers ---
/// @notice A view method of the current quorum.abi
/// @dev Quorum shows the amount of votes needed in order for a message to be dispatched further.
/// The quorum is taken from the first adapter.
/// Current quorum is the amount of all adapters.
/// return Needed amount
function quorum() external view returns (uint8);
/// @notice Gets the current active routers session id.
/// @dev When the adapters are updated with new ones,
/// each new set of adapters has their own sessionId.
/// Currently it uses sessionId of the previous set and
/// increments it by 1. The idea of an activeSessionId is
/// to invalidate any incoming messages from previously used adapters.
function activeSessionId() external view returns (uint64);
/// @notice Counts how many times each incoming messages has been received per adapter.
/// @dev It supports parallel messages ( duplicates ). That means that the incoming messages could be
/// the result of two or more independ request from the user of the same type.
/// i.e. Same user would like to deposit same underlying asset with the same amount more then once.
/// @param messageHash The hash value of the incoming message.
function votes(bytes32 messageHash) external view returns (uint16[MAX_ADAPTER_COUNT] memory);
/// @notice Used to calculate overall cost for bridging a payload on the first adapter and settling
/// on the destination chain and bridging its payload proofs on n-1 adapter
/// and settling on the destination chain.
/// @param payload Used in gas cost calculations.
/// @dev Currenly the payload is not taken into consideration.
/// @return perAdapter An array of cost values per adapter. Each value is how much it's going to cost
/// for a message / proof to be passed through one router and executed on Centrifuge Chain
/// @return total Total cost for sending one message and corresponding proofs on through all adapters
function estimate(bytes calldata payload) external view returns (uint256[] memory perAdapter, uint256 total);
/// @notice Used to check current state of the `caller` and whether they are allowed to call
/// `this.topUp` or not.
/// @param caller Address to check
/// @return isAllowed Whether the `caller` `isAllowed to call `this.topUp()`
function payers(address caller) external view returns (bool isAllowed);
}
interface IMessageHandler {
/// @notice Handling incoming messages from Centrifuge Chain.
/// @param message Incoming message
function handle(bytes memory message) external;
}