IPcl.runOnPcl
runOnPcl(address contractAddress, bytes calldata data, uint256 value) external returns (bytes memory) Executes a call to a target contract within the PCL's regulated context. This function is the entry point to the 'Regulated Track'. It first triggers all applicable global and contract-specific policy checks based on the sender, target contract, and call data. If all checks pass, it then executes the provided call data on the target contract using a low-level call. If any check fails, the transaction reverts with a specific error.
Parameters
| Name | Type | Required | Description |
|---|---|---|---|
contractAddress | address | ✓ | The address of the target contract to call after PCL checks pass. |
data | bytes | ✓ | The ABI-encoded calldata for the function to be executed on the target contract. |
value | uint256 | ✓ | The amount of OKRW to send with the call (msg.value). |
Returns
bytes memory The return data from the successful execution of the target contract call.
Errors
| Code | Name | Description |
|---|---|---|
InDenylist | InDenylist | Reverts if the sender's address is found in an active denylist policy. |
EasAttestationRequired | EasAttestationRequired | Reverts if the sender lacks a required EAS attestation (e.g., for KYC). |
VolumeAboveMaxLimit | VolumeAboveMaxLimit | Reverts if the transaction amount exceeds a configured volume limit. |
Examples
Wrapping an ERC20 Transfer
This example shows a token contract that overrides its public `transfer` function. Instead of executing the logic directly, it calls `runOnPcl`, targeting itself (`address(this)`) and providing the encoded calldata for its own `internal` transfer logic. This ensures no transfer can occur without passing the PCL checks registered for this token.
import { IERC20 } from "@openzeppelin/contracts/token/ERC20/IERC20.sol";
import { IPcl, PCL_CONTRACT } from "./IPcl.sol";
contract CompliantToken is IERC20 {
// ... other ERC20 implementation ...
function _transfer(address from, address to, uint256 amount) internal {
// ... core transfer logic ...
}
// Public transfer function enforces PCL checks
function transfer(address to, uint256 amount) public override returns (bool) {
// Encode the call to the internal transfer function
bytes memory data = abi.encodeWithSelector(
this._transfer.selector,
msg.sender,
to,
amount
);
// Execute via PCL. This contract calls itself through the PCL gate.
PCL_CONTRACT.runOnPcl(address(this), data, 0);
return true;
}
} Calling another contract with error handling
This example demonstrates how to use a `try/catch` block to handle potential reversions from `runOnPcl`. It specifically checks for the `InDenylist` custom error and provides a more user-friendly message.
import { IPcl, PCL_CONTRACT } from "./IPcl.sol";
contract PclCaller {
function callCompliantService(address service, uint256 value) external {
bytes memory data = abi.encodeWithSignature("deposit(uint256)", value);
try PCL_CONTRACT.runOnPcl(service, data, 0) {
// Success
} catch (bytes memory reason) {
// Attempt to decode a PCL custom error
if (reason.length == 36) { // selector + 1 argument (e.g., address)
bytes4 selector = bytes4(reason[:4]);
if (selector == IPcl.InDenylist.selector) {
address sender = abi.decode(reason[4:], (address));
// Handle denylist error specifically
revert("Caller is on the denylist");
}
}
// Revert with the original reason if it's not a known PCL error
revert(string(reason));
}
}
}