Modifiers

Herein modifiers are described, including those that are not wrapped into their own distinct functions, instead being implemented as internal conditionals at execution.

ConfigFlags check

A few times the function ConfigFlags.check is invoked to check whether a certain flag is true. This is done by storing a predefined library of possible flags and taking bitwise AND between the given flag and the provided config byte.

ConfigFlags code

library ConfigFlags {
  function check(uint256 cfg, uint256 flag) internal pure returns (bool) {
    return (cfg & flag) != 0;
  }
}

Wrapped modifiers

Modifiers herein listed are wrapped in distinct functions.

_assertOnlyOwner

This is a modifier on the agent ownership (incorporated via inheritance from the well-known Ownable.sol) which is used to keep unauthorized users from performing important function calls (slashing, agent parameter modification, accumulated fee withdrawal).

function _assertOnlyOwner() internal view {
    if (msg.sender != owner()) {
      revert OnlyOwner();
    }
  }

Usage scope (functions in main contract)

function slash(uint256 keeperId_, address to_, uint256 currentAmount_, uint256 pendingAmount_) external
function withdrawFees(address payable to_) external
 function setAgentParams(uint256 minKeeperCvp_, uint256 timeoutSeconds_, uint256 feePpm_) external

_assertOnlyJobOwner

This is a modifier on job ownership (incorporated via a custom jobkey-owner mapping) used to prevent unauthorized users from changing the settings of the job or withdrawing credits from its balance. Mind that they are still allowed to deposit credits therein, though).

function _assertOnlyJobOwner(bytes32 jobKey_) internal view {
    if (msg.sender != jobOwners[jobKey_]) {
      revert OnlyJobOwner();
    }
  }

Usage scope (functions in main contract)

function updateJob(bytes32 jobKey_, uint16 maxBaseFeeGwei_, uint16 rewardPct_, uint32 fixedReward_, uint256 jobMinCvp_, uint24 intervalSeconds_) external
function setJobResolver(bytes32 jobKey_, Resolver calldata resolver_) external
function setJobPreDefinedCalldata(bytes32 jobKey_, bytes calldata preDefinedCalldata_) external
function setJobConfig(bytes32 jobKey_, bool isActive_, bool useJobOwnerCredits_, bool assertResolverSelector_) public virtual
function initiateJobTransfer(bytes32 jobKey_, address to_) external
function withdrawJobCredits(bytes32 jobKey_, address payable to_, uint256 amount_) external

_assertOnlyKeeperAdmin

This is a modifier that checks whether the calling user is an Admin of the supplied keeper (incorporated via a custom keeperId-admin mapping) in order to prevent unauthorized users to change the parameters of a keeper or withdraw some of the CVP stake. Mind that withdrawal of native-token compensation uses a laxer version of this modifier, described in the next subsection.

function _assertOnlyKeeperAdmin(uint256 keeperId_) internal view {
    if (msg.sender != keeperAdmins[keeperId_]) {
      revert OnlyKeeperAdmin();
    }
  }

Usage scope (functions in main contract)

function setWorkerAddress(uint256 keeperId_, address worker_) external
function initiateRedeem(uint256 keeperId_, uint256 amount_) external returns (uint256 pendingWithdrawalAfter)
function finalizeRedeem(uint256 keeperId_, address to_) external returns (uint256 redeemedCvp)

_assertOnlyKeeperAdminOrWorker

This is a modifier that checks whether the calling user is an Admin or Worker of the supplied keepers (incorporated via a custom keeperId-admin mapping and Keeper data structure, respectively; in both cases an object, address or Keeper struct, is associated via some mapping with the keeperId), being a laxer version of the preceding one. In the base contract, it has the only use of providing pre-compensation withdrawal checks.

function _assertOnlyKeeperAdminOrWorker(uint256 keeperId_) internal view {
    if (msg.sender != keeperAdmins[keeperId_] && msg.sender != keepers[keeperId_].worker) {
      revert OnlyKeeperAdminOrWorker();
    }
  }

Usage scope (functions in main contract)

function withdrawCompensation(uint256 keeperId_, address payable to_, uint256 amount_) external

_assertKeeperIdExists

This checks whether a keeper with a given ID exists at all by comparing the ID supplied with the ID of the latest registered keeper, since keepers are enumerated in order. Used to prevent staking to a non-existent keeper.

Usage scope (functions in main contract)

function stake(uint256 keeperId_, uint256 amount_) external

_assertWorkerNotAssigned

This checks that a worker with a given address is not already extant. Used to prevent setting two different keepers to have the same address.

Usage scope (functions in main contract)

function registerAsKeeper(address worker_, uint256 initialDepositAmount_) public virtual returns (uint256 keeperId)
function setWorkerAddress(uint256 keeperId_, address worker_) external

_assertNonZeroAmount

Asserts that the amount supplied is not zero. Used to cut off cases of attempts to invoke the deposition or withdrawal operator in a trivial setting.

Usage scope (functions in main contract)

function withdrawJobCredits(bytes32 jobKey_, address payable to_, uint256 amount_) external
function withdrawJobOwnerCredits(address payable to_, uint256 amount_) external
function stake(uint256 keeperId_, uint256 amount_) external
function initiateRedeem(uint256 keeperId_, uint256 amount_) external returns (uint256 pendingWithdrawalAfter)
function slash(uint256 keeperId_, address to_, uint256 currentAmount_, uint256 pendingAmount_) external

_assertNonZeroValue

Similar to the preceding assertion, but for functions with the payable property instead (i.e. differs in that the amount is not passed explicitly, but rather inferred from the message value). Used to perform the same check for payable functions (e.g. credit deposition in native tokens).

Usage scope (functions in main contract)

function depositJobCredits(bytes32 jobKey_) external virtual payable
function depositJobOwnerCredits(address for_) external payable

_assertJobCalldataSource

Asserts that the calldatasource field of the provided (by key) job is in concord with the specified source. Used to check that a job for which a type-specific property is set actually belongs to that type.

Usage scope (functions in main contract)

function setJobResolver(bytes32 jobKey_, Resolver calldata resolver_) external
function setJobPreDefinedCalldata(bytes32 jobKey_, bytes calldata preDefinedCalldata_) external

_assertJobParams

Asserts that the provided maxBaseFeeGwei_ is nonzero, and that the fixed and dynamic parts of the reward (given correspondingly by the parameters fixed_reward_ and rewardPcts_) is not zero, i.e., the components thereof are not zero simultaneously.

Usage scope (functions in main contract)

function registerJob(RegisterJobParams calldata params_, Resolver calldata resolver_, bytes calldata preDefinedCalldata_) public payable virtual returns (bytes32 jobKey, uint256 jobId)
function updateJob(bytes32 jobKey_, uint16 maxBaseFeeGwei_, uint16 rewardPct_, uint32 fixedReward_, uint256 jobMinCvp_, uint24 intervalSeconds_) external

_assertInterval

Asserts that the provided (by key) job is either of RESOLVER type AND does not have intervals or not of RESOLVER type AND has nonzero interval size specified.

Usage scope (functions in main contract)

function registerJob(RegisterJobParams calldata params_, Resolver calldata resolver_, bytes calldata preDefinedCalldata_) public payable virtual returns (bytes32 jobKey, uint256 jobId)
function updateJob(bytes32 jobKey_, uint16 maxBaseFeeGwei_, uint16 rewardPct_, uint32 fixedReward_, uint256 jobMinCvp_, uint24 intervalSeconds_) external

Ad-hoc modifiers

Modifiers listed hereafter are not wrapped in distinct functions of their own, being instead implemented as ad-hoc logic inside of the functions that utilise them, and are listed under the provisional names given by us.

_msg_sender_is_pending_owner_assertion

Asserts that the user other than a pending owner can not accept the job transfer.

Code of the assertion

if (msg.sender != jobPendingTransfers[jobKey_]) {
      revert OnlyPendingOwner();
    }

Usage scope (functions in main contract)

function acceptJobTransfer(bytes32 jobKey_, address to_) external

_fee_adjusted_credits_after_deposit_do_not_overflow_uint88_assertion

Asserts that after the fee (specified by the Agent parameter feePpm) has been deducted, the final balance after depositing additional tokens does not overflow the type used to store it (uint88).

Code of the assertion

//return the fee in accordance with the Agent parameter and the amount remaining after deduction thereof
(uint256 fee, uint256 amount) = _calculateDepositFee();
uint256 creditsAfter = jobs[jobKey_].credits + amount;
if (creditsAfter > type(uint88).max) {
    revert CreditsDepositOverflow();
}

Usage scope (functions in main contract)

function _processJobCreditsDeposit(bytes32 jobKey_) internal
Function invoking this internal routine
function depositJobCredits(bytes32 jobKey_) external virtual payable

_initial_credits_deposit_does_not_overflow_uint88_assertion

Asserts that for the newly registered job, the initial token balance does not overflow the type used to store it (uint88).

Code of the assertion

//revert if the proposed job credit amount is not storable in uint88
if (msg.value > type(uint88).max) {
      revert CreditsDepositOverflow();
    }

Usage scope (functions in main contract)

function registerJob( RegisterJobParams calldata params_, Resolver calldata resolver_, bytes calldata preDefinedCalldata_ ) public payable virtual

_job_id_no_uint24_overflow_assertion

Asserts that after the ID of a newly registered job does not overflow the uint24 variable used to store it. Employed at registration, though it is unlikely that any owner ever registers type(uint24).max jobs.

Code of the assertion

if (jobId > type(uint24).max) {
      revert JobIdOverflow();
    }

Usage scope (functions in main contract)

function registerJob(
    RegisterJobParams calldata params_,
    Resolver calldata resolver_,
    bytes calldata preDefinedCalldata_
  ) public payable virtual returns (bytes32 jobKey, uint256 jobId)

_credits_deposit_overflow_assertion

Asserts that the initial job credit deposit does not overflow the uint88 value used to store it. Registration analogue of other deposit checkers.

Code of the assertion

if (jobId > type(uint24).max) {
      revert JobIdOverflow();
    }

Usage scope (functions in main contract)

function registerJob(
    RegisterJobParams calldata params_,
    Resolver calldata resolver_,
    bytes calldata preDefinedCalldata_
  ) public payable virtual returns (bytes32 jobKey, uint256 jobId)

_job_address_exists_assertion

Asserts that the job address provided in the registration parameters is extant and nonzero.

Code of the assertion

if (params_.jobAddress == address(0)) {
      revert MissingJobAddress();
    }

Usage scope (functions in main contract)

function registerJob(
    RegisterJobParams calldata params_,
    Resolver calldata resolver_,
    bytes calldata preDefinedCalldata_
  ) public payable virtual returns (bytes32 jobKey, uint256 jobId)

_calldata_source_within_0_2_assertion

Asserts that the specified calldata source is within the [0;2] range, since they are encoded by the first three nonnegative integers (SELECTOR, PRE_DEFINED, RESOLVER in that exact order).

Code of the assertion

if (params_.calldataSource > 2) {
      revert InvalidCalldataSource();
    }

Usage scope (functions in main contract)

function registerJob(
    RegisterJobParams calldata params_,
    Resolver calldata resolver_,
    bytes calldata preDefinedCalldata_
  ) public payable virtual returns (bytes32 jobKey, uint256 jobId)

_job_address_not_cvp_or_agent_assertion

Asserts that the specified job address does not correspond to the CVP token or the Agent instance in order to prevent possible arbuses.

Code of the assertion

if (params_.jobAddress == address(CVP) || params_.jobAddress == address(this)) {
      revert InvalidJobAddress();
    }

Usage scope (functions in main contract)

function registerJob(
    RegisterJobParams calldata params_,
    Resolver calldata resolver_,
    bytes calldata preDefinedCalldata_
  ) public payable virtual returns (bytes32 jobKey, uint256 jobId)

_sender_is_authorised_as_keeper_assertion

Asserts that the message sender that invokes execute_44g58pv is the keeper authorised to do so (checked via bytes reserved for technical variables in execute calldata). Enforces compliance with the selection made.

Code of the assertion

assembly ("memory-safe") {
 // load jobAddress, cfg, and keeperId from calldata to the stack
 actualKeeperId := shr(232, calldataload(28))
}
Keeper memory keeper = keepers[actualKeeperId];
if (keeper.worker != msg.sender) {
  revert KeeperWorkerNotAuthorized();
}

Usage scope (functions in main contract)

function execute_44g58pv() external

_executing_keeper_stake_sufficient_assertion

Asserts that the message sender that invokes execute_44g58pv has at least the minimal stake demanded by the global level set by the Agent. Ensures a keeper has stake to slash even if the job does not specify any demands in this regard.

Code of the assertion

assembly ("memory-safe") {
 // load jobAddress, cfg, and keeperId from calldata to the stack
 actualKeeperId := shr(232, calldataload(28))
}
Keeper memory keeper = keepers[actualKeeperId];
if (keeper.cvpStake < minKeeperCvp) {
  revert InsufficientKeeperStake();
}

Usage scope (functions in main contract)

function execute_44g58pv() external

_job_is_active_assertion

Asserts that the invoked job is active and therefore potentially executable.

Code of the assertion

assembly ("memory-safe") {
    // size of (address(bytes20)+id(uint24/bytes3))
    let size := 23
    
    // keccack256(address+id(uint24)) to memory to generate jobKey
    calldatacopy(0, 4, size)
    jobKey := keccak256(0, size)
}
uint256 binJob = getJobRaw(jobKey);
if (!ConfigFlags.check(binJob, CFG_ACTIVE)) {
    revert InactiveJob(jobKey);

Usage scope (functions in main contract)

function execute_44g58pv() external

_keeper_satisfies_job_stake_demand_assertion

Asserts that the keeper invoking execute_44g58pv has sufficient amount of stake with respect to the demands made by the job, given such demands.

Code of the assertion

assembly ("memory-safe") {
    // size of (address(bytes20)+id(uint24/bytes3))
    let size := 23
    
    // keccack256(address+id(uint24)) to memory to generate jobKey
    calldatacopy(0, 4, size)
    jobKey := keccak256(0, size)
}
assembly ("memory-safe") {
 // load jobAddress, cfg, and keeperId from calldata to the stack
 actualKeeperId := shr(232, calldataload(28))
}
Keeper memory keeper = keepers[actualKeeperId];
uint256 binJob = getJobRaw(jobKey);
if (ConfigFlags.check(binJob, CFG_CHECK_KEEPER_MIN_CVP_DEPOSIT) && keepers[actualKeeperId].cvpStake < jobMinKeeperCvp[jobKey]) {
      revert InsufficientJobScopedKeeperStake();

Usage scope (functions in main contract)

function execute_44g58pv() external

_execution_interval_reached_assertion

Asserts that if the job being executed is of one of two interval types, at least a full interval duration has elapsed since this job was last executed.

Code of the assertion

assembly ("memory-safe") {
    // size of (address(bytes20)+id(uint24/bytes3))
    let size := 23
    
    // keccack256(address+id(uint24)) to memory to generate jobKey
    calldatacopy(0, 4, size)
    jobKey := keccak256(0, size)
}
uint256 binJob = getJobRaw(jobKey);
{ 
    //the interval size occupies bytes 4-6, or bits 32-55. For this reason we first shift left by 32, making interval size the leading bytes, and then shift right by 232, retaining thereby only the first 24 bits (or exactly 3 bytes)
    uint256 intervalSeconds = (binJob << 32) >> 232;
    
    if (intervalSeconds > 0) {
        //since lastExecutionAt is the leading byte and has field size bytes4 (uint32), we shift right by 224 and retain only the leading 32 bits, or 4 bytes
        uint256 lastExecutionAt = binJob >> 224;
        if (lastExecutionAt > 0) {
            uint256 nextExecutionAt;
            unchecked {
                nextExecutionAt = lastExecutionAt + intervalSeconds;
            }
            if (nextExecutionAt > block.timestamp) {
                revert IntervalNotReached(lastExecutionAt, intervalSeconds, block.timestamp);
            }
        }
    }
}

Usage scope (functions in main contract)

function execute_44g58pv() external

_msg_sender_is_EoA_assertion

Asserts that the message sender invoking execute_44g58pv is an externally-owned address (i.e. not an automatic contract).

Code of the assertion

//if the sender is not an EoA, the origin will be the EoA which started the chain of transactions that led to the sender's activation and code execution and thereby the message transmission
if (msg.sender != tx.origin) {
      revert NonEOASender();
    }

Usage scope (functions in main contract)

function execute_44g58pv() external

_invalid_source_type_catcher

The _calldata_source_within_0_2_assertion only makes sure the calldata numerical value is betwen 0 and 2. It will normally coincide with one of the three defined job types (SELECTOR, PRE_DEFINED, RESOLVER), but in a hypothetical case when this does not occur for any reason whatsoever, we need a catcher to raise an error.

Code of the assertion

assembly ("memory-safe") {
    // size of (address(bytes20)+id(uint24/bytes3))
    let size := 23
    
    // keccack256(address+id(uint24)) to memory to generate jobKey
    calldatacopy(0, 4, size)
    jobKey := keccak256(0, size)
}
uint256 binJob = getJobRaw(jobKey);
CalldataSourceType calldataSource = CalldataSourceType((binJob << 56) >> 248);
if (calldataSource == CalldataSourceType.SELECTOR) {
    ...
} else if (calldataSource == CalldataSourceType.PRE_DEFINED) {
        ...
    } else if (calldataSource == CalldataSourceType.RESOLVER) {
            ...
        } else {
                //catcher that should never be reached under normal operation
                revert InvalidCalldataSource();
            }     
    

Usage scope (functions in main contract)

function execute_44g58pv() external

_job_has_enough_credits_assertion

Asserts that the job has enough credits to cover the keeper's reward for executing it.

Code of the assertion

compensation = _calculateCompensation(ok, binJob, actualKeeperId, min, gasUsed);
uint256 creditsBefore = (binJob << 128) >> 168;
if (creditsBefore < compensation) {
    if (ok) {
        revert InsufficientJobCredits(creditsBefore, compensation);
        }
    }

Usage scope (functions in main contract)

function execute_44g58pv() external

_block_base_fee_within_limits_or_accept_exceeding_assertion

This modifier asserts that the block base gas fee is either not in excess of the job-specified limit or accepted anyway by the appropriately configured flag FLAG_ACCEPT_MAX_BASE_FEE_LIMIT passed along with the execute call in the config calldata byte.

Code of the assertion

uint256 maxBaseFee;
    unchecked {
      maxBaseFee = ((binJob_ << 112) >> 240)  * 1 gwei;
    }
    if (block.basefee > maxBaseFee && !ConfigFlags.check(cfg_, FLAG_ACCEPT_MAX_BASE_FEE_LIMIT)) {
      revert BaseFeeGtGasPrice(block.basefee, maxBaseFee);
    }
    return maxBaseFee;
  }

Usage scope (functions in main contract)

function _checkBaseFee(uint256 binJob_, uint256 cfg_) internal view virtual returns (uint256)

_job_revert_handler

In some cases, job reverts will give traceback messages. Occasionally, however, they will give no traceback, and both occasion need to be handled properly. This code provides such a functionality. It is not properly a modifier, since it does not condition execution of anything and merely serves as an error handler, but we include it here on the basis of collecting on one page all revert-capable routines.

Code of the assertion

if (executionResponse_.length == 0) {
      revert JobCallRevertedWithoutDetails();
    } else {
      assembly ("memory-safe") {
        revert(add(32, executionResponse_), mload(executionResponse_))
      }
    }

Usage scope (functions in main contract)

function _afterExecutionReverted(
    bytes32 jobKey_,
    CalldataSourceType calldataSource_,
    uint256 keeperId_,
    bytes memory executionResponse_
  ) internal virtual
Function invoking this internal routine
function execute_44g58pv() external

_job_owner_has_enough_credits_assertion

Asserts that the job owner has enough credits to cover the keeper's reward for executing it. Is only invoked when the CFG_USE_JOB_OWNER_CREDITS job config flag is set to True.

Code of the assertion

uint256 jobOwnerCreditsBefore = jobOwnerCredits[jobOwners[jobKey_]];
    if (jobOwnerCreditsBefore < compensation_) {
      if (ok_) {
        revert InsufficientJobOwnerCredits(jobOwnerCreditsBefore, compensation_);

Usage scope (functions in main contract)

function _useJobOwnerCredits(bool ok_, bytes32 jobKey_, uint256 compensation_) internal
Function invoking this internal routine
function execute_44g58pv() external

_job_owner_has_enough_credits_assertion

Asserts that the job owner has enough credits to cover the keeper's reward for executing it. Is only invoked when the CFG_USE_JOB_OWNER_CREDITS job config flag is set to True.

Code of the assertion

uint256 jobOwnerCreditsBefore = jobOwnerCredits[jobOwners[jobKey_]];
    if (jobOwnerCreditsBefore < compensation_) {
      if (ok_) {
        revert InsufficientJobOwnerCredits(jobOwnerCreditsBefore, compensation_);

Usage scope (functions in main contract)

function _useJobOwnerCredits(bool ok_, bytes32 jobKey_, uint256 compensation_) internal

_resolver_address_extant_assertion

Asserts that the address of the supplied resolver is extant (i.e. nonzero).

Code of the assertion

if (resolver_.resolverAddress == address(0)) {
      revert MissingResolverAddress();
    }

Usage scope (functions in main contract)

function _setJobResolver(bytes32 jobKey_, Resolver calldata resolver_) internal
Function invoking this internal routine
function setJobResolver(bytes32 jobKey_, Resolver calldata resolver_) external

_target_job_has_owner_assertion

Asserts that the job for which credits are deposited has an owner (i.e. the owner is not a zero address).

Code of the assertion

if (jobOwners[jobKey_] == address(0)) {
      revert JobWithoutOwner();
    }

Usage scope (functions in main contract)

function _setJobResolver(bytes32 jobKey_, Resolver calldata resolver_) internal

_sufficient_credits_to_withdraw

Asserts that the job/job owner has enough credits (in native chain tokens) to withdraw the specifeid amount.

Code of the assertion

if (creditsBefore < amount_) {
      revert CreditsWithdrawalUnderflow();
    }

Usage scope (functions in main contract)

function withdrawJobCredits(
    bytes32 jobKey_,
    address payable to_,
    uint256 amount_
  ) external
function withdrawJobOwnerCredits(address payable to_, uint256 amount_) external

_minKeeperCvp_assertion

Asserts that the registering keeper has a sufficient initial deposit value to stake at least the minKeeperCvp value demanded by the Agent parameter of the same name.

Code of the assertion

if (initialDepositAmount_ < minKeeperCvp) {
      revert InsufficientAmount();
    }

Usage scope (functions in main contract)

function registerAsKeeper(address worker_, uint256 initialDepositAmount_) public virtual returns (uint256 keeperId)

_amount_not_exceeds_available_assertion

Asserts that a keeper has accrued enough compensation in native chain tokens to withdraw the specified amount.

Code of the assertion

uint256 available = compensations[keeperId_];
if (amount_ > available) {
    revert WithdrawAmountExceedsAvailable(amount_, available);
}

Usage scope (functions in main contract)

function withdrawCompensation(uint256 keeperId_, address payable to_, uint256 amount_) external

_stake_amount_does_not_overflow_assertion

Asserts that, should the provided stake amount be added to the extant stake, the resultant value shall not exceed the maximal value admitted by the type uint88, which is used to store stakes.

Code of the assertion

uint256 amountAfter = keepers[keeperId_].cvpStake + amount_;
if (amountAfter > type(uint88).max) {
  revert StakeAmountOverflow();
}

Usage scope (functions in main contract)

function _stake(uint256 keeperId_, uint256 amount_) internal
Function invoking this internal routine
function stake(uint256 keeperId_, uint256 amount_) external

_amount_sufficient_to_compensate_slashed_stake_assertion

Asserts that the amount of CVP stake the keeper wishes to withdraw is at least sufficient to compensate the totality of the slashed stake penalty he has accrued by now.

Code of the assertion

uint256 slashedStakeOfBefore = slashedStakeOf[keeperId_];
if (amount_ < slashedStakeOfBefore) {
    revert InsufficientAmountToCoverSlashedStake(amount_, slashedStakeOfBefore);
}

Usage scope (functions in main contract)

function initiateRedeem(uint256 keeperId_, uint256 amount_) external returns (uint256 pendingWithdrawalAfter)

_amount_not_exceeds_stake_assertion

Asserts that the amount of CVP stake the keeper wishes to withdraw is not in excess of the totality of his stake.

Code of the assertion

uint256 stakeOfBefore = keepers[keeperId_].cvpStake;
uint256 slashedStakeOfBefore = slashedStakeOf[keeperId_];
uint256 totalStakeBefore = stakeOfBefore + slashedStakeOfBefore;
if (amount_ > totalStakeBefore) {
    revert AmountGtStake(amount_, stakeOfBefore, slashedStakeOfBefore);
}

Usage scope (functions in main contract)

function initiateRedeem(uint256 keeperId_, uint256 amount_) external returns (uint256 pendingWithdrawalAfter)

_withdrawal_time_limit_elapsed_assertion

Asserts that the time delay between the keeper calling initiateRedeem and finalizeRedeem is not exceeded by the Agent-specified constant pendingWithdrawalTimeoutSeconds, i.e. that at least the minimum demanded time has elapsed since the stake redemption was initiated.

Code of the assertion

if (pendingWithdrawalEndsAt[keeperId_] > block.timestamp) {
    revert WithdrawalTimoutNotReached();
}

Usage scope (functions in main contract)

function finalizeRedeem(uint256 keeperId_, address to_) external returns (uint256 redeemedCvp)

_pending_withdrawal_extant_assertion

Asserts that the keeper whose stake is being withdrawn actually has any stake pending withdrawal; i.e. that it is not attempted to finalise redemption of zero stake.

Code of the assertion

redeemedCvp = pendingWithdrawalAmounts[keeperId_];
if (redeemedCvp == 0) {
  revert NoPendingWithdrawal();
}

Usage scope (functions in main contract)

function finalizeRedeem(uint256 keeperId_, address to_) external returns (uint256 redeemedCvp)

_timeout_not_in_excess_of_constant_global_cap_assert

Asserts that the timeout parameter pendingWithdrawalTimeoutSeconds provided by the Agent instance owner is not in excess of a constant global cap on this value MAX_PENDING_WITHDRAWAL_TIMEOUT_SECONDS. This parameter is responsible for setting the minimal time delay between initiating and finalising redemption of staked CVP tokens by keepers.

Code of the assertion

if (timeoutSeconds_ > MAX_PENDING_WITHDRAWAL_TIMEOUT_SECONDS) {
    revert TimeoutTooBig();
}

Usage scope (functions in main contract)

function _setAgentParams(
    uint256 minKeeperCvp_,
    uint256 timeoutSeconds_,
    uint256 feePpm_
  ) internal
Function invoking this internal routine
function setAgentParams(
    uint256 minKeeperCvp_,
    uint256 timeoutSeconds_,
    uint256 feePpm_
  ) external

__feeppm_not_in_excess_of_constant_global_cap_assert

Asserts that the timeout parameter feePpm provided by the Agent instance owner is not in excess of a constant global cap on this value MAX_FEE_PPM. This parameter governs the fee the Agent exacts from all

Code of the assertion

if (feePpm_ > MAX_FEE_PPM) {
    revert FeeTooBig();
}

Usage scope (functions in main contract)

function _setAgentParams(
    uint256 minKeeperCvp_,
    uint256 timeoutSeconds_,
    uint256 feePpm_
  ) internal
Function invoking this internal routine
function setAgentParams(
    uint256 minKeeperCvp_,
    uint256 timeoutSeconds_,
    uint256 feePpm_
  ) external

_calldata_passed_to_resolver_job_assert

Asserts that if the job is of the RESOLVER type, the calldata passed to execute_44g58pv contains nonempty calldata after byte number 31 to be passed to the function being automated.

Code of the assertion

assembly ("memory-safe") {
        let cdInCdSize := calldatasize()
        // calldata offset is 31
        let beforeCdSize := 31
        let ptr := mload(0x40)
        if lt(cdInCdSize, beforeCdSize) {
          // revert MissingInputCalldata()
          mstore(ptr, 0x47a0bafb00000000000000000000000000000000000000000000000000000000)
          revert(ptr, 4)
        }

Usage scope (functions in main contract)

function execute_44g58pv() external

_selector_check_assert

Asserts that, provided the corresponding flag (CFG_ASSERT_RESOLVER_SELECTOR, which toggles checks of resolver selector coincidence) is specified, the resolver selector in the keeper-provided calldata matches the resolver selector contained in the Job binary representation.

Code of the assertion

// CFG_ASSERT_RESOLVER_SELECTOR = 0x04
        if and(binJob, 0x04) {
          if iszero(eq(
            // actual
            shl(224, shr(224, calldataload(31))),
            // expected
            shl(224, shr(8, binJob))
          )) {
            // revert SelectorCheckFailed()
            mstore(ptr, 0x84fb827500000000000000000000000000000000000000000000000000000000)
            revert(ptr, 4)
          }
        }

Usage scope (functions in main contract)

function execute_44g58pv() external

RanDAO realisation-specific modifiers

All RanDAO realisation-specific modifiers are ad-hoc.

_enough_stake_to_slash_assertion

Asserts that a keeper has enough stake to be slashed. This should always be the case, since the fixed stake slash amount is capped at 50% of the minimal Agent-wide admissible stake, and the dynamic part must not exceed 50% of the actual keeper stake (imposed via a cap on bps).

Code of the assertion

uint256 dynamicSlashAmount = eKeeper.cvpStake * uint256(rdConfig.slashingFeeBps) / 10_000;
      uint256 fixedSlashAmount = uint256(rdConfig.slashingFeeFixedCVP) * 1 ether;
      // NOTICE: totalSlashAmount can't be >= uint88
      uint88 totalSlashAmount = uint88(fixedSlashAmount + dynamicSlashAmount);
      if (totalSlashAmount > eKeeper.cvpStake) {
        // Actually this block should not be reached, so this is just in case
        revert InsufficientKeeperStakeToSlash(jobKey_, expectedKeeperId, eKeeper.cvpStake, totalSlashAmount);
      }

Usage scope (functions in RanDAO contract)

function _afterExecutionSucceeded(bytes32 jobKey_, uint256 actualKeeperId_, uint256 binJob_) internal override

_interval_job_slashing_timestamp_reached_assertion

Asserts that when a slasher attempts to execute an interval job (which necessarily leads to the assigned keeper being slashed), he does so when the grace period has elapsed. Resolver jobs have their own assertion.

Code of the assertion

uint256 nextExecutionTimeoutAt;
      uint256 _lastExecutionAt = lastExecutionAt;
      if (_lastExecutionAt == 0) {
        _lastExecutionAt = jobCreatedAt[jobKey_];
      }
      unchecked {
        nextExecutionTimeoutAt = _lastExecutionAt + intervalSeconds + rdConfig.period1;
      }
      // if it is to early to slash this job
      if (block.timestamp < nextExecutionTimeoutAt) {
        revert OnlyNextKeeper(nextKeeperId, lastExecutionAt, intervalSeconds, rdConfig.period1, block.timestamp);
      }

Usage scope (functions in RanDAO contract)

function _beforeExecute(bytes32 jobKey_, uint256 actualKeeperId_, uint256 binJob_) internal view override

_resolver_job_slashing_timestamp_reached_assertion

Asserts that when a slasher attempts to execute a resolver job (which necessarily leads to the assigned keeper being slashed), he does so when the grace period has elapsed. Interval jobs have their own assertion.

Code of the assertion

uint256 _jobSlashingPossibleAfter = jobSlashingPossibleAfter[jobKey_];
      if (_jobSlashingPossibleAfter > block.timestamp) {
        revert TooEarlyForSlashing(block.timestamp, jobSlashingPossibleAfter[jobKey_]);
      }

Usage scope (functions in RanDAO contract)

function _beforeExecute(bytes32 jobKey_, uint256 actualKeeperId_, uint256 binJob_) internal view override

_resolver_job_cannot_release_before_slashing_init_assertion

Asserts that a keeper of a resolver job shall not be released (i.e. unassigned from execution) while slashing is not initialised (unless by job owner, in which case release is unconditional).

Code of the assertion

uint256 _jobSlashingPossibleAfter = jobSlashingPossibleAfter[jobKey_];
      if (_jobSlashingPossibleAfter != 0) {
          //...//
        }
      // if no slashing initiated
      } else {
        revert CantRelease();
      }

Usage scope (functions in RanDAO contract)

function releaseJob(bytes32 jobKey_) external

_keeper_activation_timeout_elapsed_assertion

Asserts that a keeper shall not be activated unless a specified Agent-wide time interval has passed since his activation was initialised.

Code of the assertion

uint256 availableAt = keeperActivationCanBeFinalizedAt[keeperId_];
    if (availableAt > block.timestamp) {
      revert TooEarlyForActivationFinalization(block.timestamp, availableAt);
    }

Usage scope (functions in RanDAO contract)

function finalizeKeeperActivation(uint256 keeperId_) external

_lockup_period_expired_assertion

Asserts that a keeper of a job shall not be released (i.e. unassigned from execution) until the lock-up period (defined as the sum of period1 and period2, where period1 corresponds to the grace period, period2 corresponds to the slashing reservation period, also called admissibility period, and both are set by the Agent owner as global parameters) has elapsed since the execution was made possible (for interval jobs) or slashing was initialised (for resolver jobs; largely equivalent to execution being made possible if slashers are rational). Once again, does not apply to release by job owner, which is unconditional.

Code of the assertion

//interval case
uint256 period2EndsAt = lastExecutionAt + rdConfig.period1 + rdConfig.period2;
      if (period2EndsAt > block.timestamp) {
        revert TooEarlyToRelease(jobKey_, period2EndsAt);
      } 
//resolver case
uint256 _jobSlashingPossibleAfter = jobSlashingPossibleAfter[jobKey_];
      if (_jobSlashingPossibleAfter != 0) {
        uint256 period2EndsAt = _jobSlashingPossibleAfter + rdConfig.period2;
        if (period2EndsAt > block.timestamp) {
          revert TooEarlyToRelease(jobKey_, period2EndsAt);
        }

Usage scope (functions in RanDAO contract)

function releaseJob(bytes32 jobKey_) external

_revert_with_false

Technical modifier made to obtain a function that always reverts to perform checks without introducing state changes (checkCouldBeExecuted). Corresponds to the case of the job not being executable at call.

Code of the assertion

(bool ok, bytes memory result) = jobAddress_.call(jobCalldata_);
    if (ok) {
      //...//
    } else {
      revert JobCheckCanNotBeExecuted(result);
    }

Usage scope (functions in RanDAO contract)

function checkCouldBeExecuted(address jobAddress_, bytes memory jobCalldata_) external

_revert_with_true

Technical modifier made to obtain a function that always reverts to perform checks without introducing state changes (checkCouldBeExecuted). Corresponds to the case of the job being executable at call.

Code of the assertion

(bool ok, bytes memory result) = jobAddress_.call(jobCalldata_);
    if (ok) {
      revert JobCheckCanBeExecuted();
    } else {
      //...//
    }

Usage scope (functions in RanDAO contract)

function checkCouldBeExecuted(address jobAddress_, bytes memory jobCalldata_) external

_slashing_period_elapsed_assertion

Asserts that slashing will not be reinitialised until the admissibility period of the previous slashing instance thereof has expired. Has the meaning of giving the slasher who initiates slashing a period during which he is the sole actor capable of slashing the keeper, in the same vein as the keeper is given a grace period for execution, since re-initation does not necessarily assign the same slasher.

Code of the assertion

uint256 _jobSlashingPossibleAfter = jobSlashingPossibleAfter[jobKey];
    // if is already initiated
    if (_jobSlashingPossibleAfter != 0 &&
      // but not overdue yet
      (_jobSlashingPossibleAfter + rdConfig.period2) > block.timestamp
      ) {
      revert TooEarlyToReinitiateSlashing();
    }

Usage scope (functions in RanDAO contract)

function initiateSlashing(
    address jobAddress_,
    uint256 jobId_,
    uint256 slasherKeeperId_,
    bool useResolver_,
    bytes memory jobCalldata_
  ) external

_resolver_terminated_successfully_assertion

Asserts that when slashing is initiated, the resolver job for which it is done can actually be executed (in actuality, this check is done in two stage, and this is the first one, ensuring the resolver did not revert).

Code of the assertion

(bool ok, bytes memory result) = resolver.resolverAddress.call(resolver.resolverCalldata);
if (!ok) {
  revert JobCheckResolverError(result);
}

Usage scope (functions in RanDAO contract)

function initiateSlashing(
    address jobAddress_,
    uint256 jobId_,
    uint256 slasherKeeperId_,
    bool useResolver_,
    bytes memory jobCalldata_
  ) external

_resolver_returned_true_assertion

Asserts that when slashing is initiated, the resolver job for which it is done can actually be executed (in actuality, this check is done in two stage, and this is the second one, ensuring th resolver returns True).

Code of the assertion

(bool ok, bytes memory result) = resolver.resolverAddress.call(resolver.resolverCalldata);
if (!ok) {
  //...//
}
(bool canExecute,) = abi.decode(result, (bool, bytes));
if (!canExecute) {
  revert JobCheckResolverReturnedFalse();

Usage scope (functions in RanDAO contract)

function initiateSlashing(
    address jobAddress_,
    uint256 jobId_,
    uint256 slasherKeeperId_,
    bool useResolver_,
    bytes memory jobCalldata_
  ) external

_no_initiating_for_interval_jobs

Asserts that slashing is not initated for interval jobs, since for them it has no meaning (their execution possibility timestamps are known in advance).

Code of the assertion

uint256 intervalSeconds = (binJob << 32) >> 232;
      if (intervalSeconds != 0) {
        revert NonIntervalJob();
      }

Usage scope (functions in RanDAO contract)

function initiateSlashing(
    address jobAddress_,
    uint256 jobId_,
    uint256 slasherKeeperId_,
    bool useResolver_,
    bytes memory jobCalldata_
  ) external

_unexpected_selector_handler

Normally checkCouldBeExecuted reverts either with JobCheckCanBeExecuted or with JobCheckCanNotBeExecuted. However, in the unlikely case a third error is returned, differing in selector from both, this handler throws an exception.

Code of the assertion

(bool ok, bytes memory result) = address(this).call(
        abi.encodeWithSelector(PPAgentV2Randao.checkCouldBeExecuted.selector, jobAddress_, jobCalldata_)
      );
 bytes4 selector = bytes4(result);
if (selector == PPAgentV2Randao.JobCheckCanNotBeExecuted.selector) {
   //...//
  }
} else if (selector != PPAgentV2Randao.JobCheckCanBeExecuted.selector) {
  revert InitiateSlashingUnexpectedError();

Usage scope (functions in RanDAO contract)

function initiateSlashing(
    address jobAddress_,
    uint256 jobId_,
    uint256 slasherKeeperId_,
    bool useResolver_,
    bytes memory jobCalldata_
  ) external

_unexpected_nonrevert_safeguard

Normally checkCouldBeExecuted reverts either with JobCheckCanBeExecuted or with JobCheckCanNotBeExecuted. However, in the unlikely case it does not revert at all, this handler throws an exception.

Code of the assertion

(bool ok, bytes memory result) = address(this).call(
        abi.encodeWithSelector(PPAgentV2Randao.checkCouldBeExecuted.selector, jobAddress_, jobCalldata_)
      );
if (ok) {
  revert UnexpectedCodeBlock();
}

Usage scope (functions in RanDAO contract)

function initiateSlashing(
    address jobAddress_,
    uint256 jobId_,
    uint256 slasherKeeperId_,
    bool useResolver_,
    bytes memory jobCalldata_
  ) external

_keeper_not_inactive_assertion

Ensures that an already inactive keeper is not deactivated.

Code of the assertion

if (!keepers[keeperId_].isActive) {
      revert KeeperIsAlreadyInactive();
    }

Usage scope (functions in RanDAO contract)

function disableKeeper(uint256 keeperId_) external

_keeper_not_active_assertion

Ensures that an already active keeper is not activated.

Code of the assertion

if (keepers[keeperId_].isActive) {
      revert KeeperIsAlreadyActive();
    }

Usage scope (functions in RanDAO contract)

function initiateKeeperActivation(uint256 keeperId_) external

_keeper_no_slasher_assertion

Ensures that at slashing initiation the initiating slasher (who is then stored as the only slasher able to execute the job at keeper failure) is not the assigned keeper (i.e. no one can slash themselves).

Code of the assertion

if (jobNextKeeperId[jobKey] == slasherKeeperId_) {
      revert KeeperCantSlash();

Usage scope (functions in RanDAO contract)

function initiateSlashing(
    address jobAddress_,
    uint256 jobId_,
    uint256 slasherKeeperId_,
    bool useResolver_,
    bytes memory jobCalldata_
  ) external

_handle_revert_no_slashing

Ensures that at revert of a resolver job the keeper is not released if slashing has not yet been initiated. This has the meaning of blocking a keeper's ability to be released from a job they do not wish to execute by calling it when it is not yet executable and obtaining release through a revert handler.

Code of the assertion

 if (calldataSource_ == CalldataSourceType.RESOLVER &&
      jobReservedSlasherId[jobKey_] == 0 && jobSlashingPossibleAfter[jobKey_] == 0) {
      revert SlashingNotInitiatedExecutionReverted();
    }

Usage scope (functions in RanDAO contract)

function _afterExecutionReverted(
    bytes32 jobKey_,
    CalldataSourceType calldataSource_,
    uint256 keeperId_,
    bytes memory executionResponse_
  ) internal

_slashing_initiated_assertion

Ensures that when a slasher calls a resolver job, slashing was initiated beforehand. Overlaps with _is_reserved_slasher_assertion, since the reserved slasher is a zero address until slashing is initiated.

Code of the assertion

 if (calldataSource_ == CalldataSourceType.RESOLVER &&
      jobReservedSlasherId[jobKey_] == 0 && jobSlashingPossibleAfter[jobKey_] == 0) {
      revert SlashingNotInitiatedExecutionReverted();
    }

Usage scope (functions in RanDAO contract)

function _afterExecutionReverted(
    bytes32 jobKey_,
    CalldataSourceType calldataSource_,
    uint256 keeperId_,
    bytes memory executionResponse_
  ) internal

_slashing_initiated_assertion

Ensures that when a slasher calls a resolver job, slashing was initiated beforehand. Overlaps with _is_reserved_slasher_assertion, since the reserved slasher is a zero address until slashing is initiated.

Code of the assertion

if (intervalSeconds == 0 && nextKeeperId != actualKeeperId_) {
      uint256 _jobSlashingPossibleAfter = jobSlashingPossibleAfter[jobKey_];
      if (_jobSlashingPossibleAfter == 0) {
        revert SlashingNotInitiated();
      }

Usage scope (functions in RanDAO contract)

function _beforeExecute(bytes32 jobKey_, uint256 actualKeeperId_, uint256 binJob_) internal view

_resolver_job_slashing_timestamp_reached_assertion

Ensures that when a slasher calls a resolver job, he does so after slashing has been made possible (i.e. the grace period elapsed)

Code of the assertion

if (_jobSlashingPossibleAfter > block.timestamp) {
        revert TooEarlyForSlashing(block.timestamp, jobSlashingPossibleAfter[jobKey_]);
      }

Usage scope (functions in RanDAO contract)

function _beforeExecute(bytes32 jobKey_, uint256 actualKeeperId_, uint256 binJob_) internal view

_is_reserved_slasher_assertion

Ensures that when a slasher calls a resolver job, he is the slasher earmarked for doing so (i.e. he is the one who initiated slashing in the first place).

Code of the assertion

uint256 _jobReservedSlasherId = jobReservedSlasherId[jobKey_];
      if (_jobReservedSlasherId != actualKeeperId_) {
        revert OnlyReservedSlasher(_jobReservedSlasherId);
      }

Usage scope (functions in RanDAO contract)

function _beforeExecute(bytes32 jobKey_, uint256 actualKeeperId_, uint256 binJob_) internal view

_is_current_slasher_assertion

Ensures that when a slasher calls an interval job or initiates resolver job slashing, he is the slasher earmarked for doing so at the current time (i.e. the getSlasherIdByBlock returns his ID). Distinct from the preceding modifier because there are no reserved slashers for interval jobs (no initiation process, either) and at slashing initialisation the reserved slasher is a null value.

Code of the assertion

uint256 currentSlasherId = getCurrentSlasherId(jobKey_);
      if (actualKeeperId_ != currentSlasherId) {
        revert OnlyCurrentSlasher(currentSlasherId);
      }

Usage scope (functions in RanDAO contract)

function _beforeExecute(bytes32 jobKey_, uint256 actualKeeperId_, uint256 binJob_) internal view

_keeper_releasable_assertion

Ensures that when an attempt is made to disable the keeper or initiate redemption of part of the stake thereof, the keeper can be released (i.e. has no jobs pending).

Code of the assertion

uint256 len = keeperLocksByJob[keeperId_].length();
    if (len > 0) {
      revert KeeperIsAssignedToJobs(len);
    }

Usage scope (functions in RanDAO contract)

function _ensureCanReleaseKeeper(uint256 keeperId_) internal view
Internal function is invoked by
function disableKeeper(uint256 keeperId_) external
function _beforeInitiateRedeem(uint256 keeperId_) internal view

_keeper_active_assertion

Ensures that when an attempt is made to initiate slashing, the slasher is an active keeper.

Code of the assertion

Keeper memory keeper = keepers[slasherKeeperId_];
if (!keeper.isActive) {
        revert InactiveKeeper();
      }

Usage scope (functions in RanDAO contract)

function initiateSlashing(
    address jobAddress_,
    uint256 jobId_,
    uint256 slasherKeeperId_,
    bool useResolver_,
    bytes memory jobCalldata_
  ) external

_fixed_reward_coeff_finite

Ensures that when an attempt is made to change the inverse of the global stake scaling coefficient for the purposes of computing compensation (see Copy of Task Reward and Gas Compensation), it is nonzero.

Code of the assertion

if (rdConfig_.stakeDivisor == 0) {
      revert InvalidStakeDivisor();
    }

Usage scope (functions in RanDAO contract)

function _setRdConfig(RandaoConfig memory rdConfig_) internal

_slashing_bps_not_too_great_assertion

Ensures that when an attempt is made to change the global dynamic slash coefficient in basis points of the current stake (see Slashing), it is not in excess of 50%.

Code of the assertion

if (rdConfig_.slashingFeeBps > 5000) {
      revert SlashingBpsGt5000Bps();
    }

Usage scope (functions in RanDAO contract)

function _setRdConfig(RandaoConfig memory rdConfig_) internal

_slashing_fee_not_too_great_assertion

Ensures that when an attempt is made to change the global fixed slashing fee (see Slashing), it is not in excess of 50% of the Agent-wide minimal Keeper stake. Together with the preceding modifier ensure that a keeper is slashed for at most 100% of their stake.

Code of the assertion

if (rdConfig_.slashingFeeFixedCVP > (minKeeperCvp / 2)) {
      revert InvalidSlashingFeeFixedCVP();
    }

Usage scope (functions in RanDAO contract)

function _setRdConfig(RandaoConfig memory rdConfig_) internal

_admissibility_period_long_enough_assertion

Ensures that when an attempt is made to change the global parameter period2, which corresponds to the length of the time interval for which a job is not considered overdue (and e.g. cannot have slashing re-initiated; during that time the reserved/current keeper is the only person allowed to perform slashing), it is at least 15 seconds, so at least one block plus some reserve.

Code of the assertion

if (rdConfig_.period2 < 15 seconds) {
      revert InvalidPeriod2();
    }

Usage scope (functions in RanDAO contract)

function _setRdConfig(RandaoConfig memory rdConfig_) internal

_grace_period_long_enough_assertion

Ensures that when an attempt is made to change the global parameter period1, which corresponds to the length of the grace period during which execution (and therefore slashing, if we assume Slashers are optimal actors) is possible, but cannot be done yet to give the keeper a chance to execute the job, it is at least 15 seconds, so at least one block plus some reserve.

Code of the assertion

if (rdConfig_.period1 < 15 seconds) {
      revert InvalidPeriod1();
    }

Usage scope (functions in RanDAO contract)

function _setRdConfig(RandaoConfig memory rdConfig_) internal

_slashing_period_long_enough_assertion

Ensures that when an attempt is made to change the global parameter slashingEpochBlocks, which corresponds to the length of the slashing epoch, defined as the time interval during which the only sources of slasher variability are the job keys and possible shrinkage of the active keeper set, it is at least 3 blocks.

Code of the assertion

if (rdConfig_.slashingEpochBlocks < 3) {
      revert SlashingEpochBlocksTooLow();
    }

Usage scope (functions in RanDAO contract)

function _setRdConfig(RandaoConfig memory rdConfig_) internal

_job_has_no_keeper_assigned_assertion

Ensures that when an attempt is made by a job owner to assign a keeper for a job, the job does not have a keeper assigned already.

Code of the assertion

uint256 assignedKeeperId = jobNextKeeperId[jobKey];
      if (assignedKeeperId != 0) {
        revert JobHasKeeperAssigned(assignedKeeperId);
      }

Usage scope (functions in RanDAO contract)

function assignKeeper(bytes32[] calldata jobKeys_) external

Last updated