Copy of Task Reward and Gas Compensation

All payment is carried out by the Agent contract. Herein we specify the modes of interaction with it for such purposes.

Flashbots realisation

Reward amount cases and computation

In Flashbots realisation, all compensation and reward computation is handled by one internal function _calculateCompensation sharing functionality with a public pure function calculateCompensationPure.

_calculateCompensation arguments and logic

Function signature:

function _calculateCompensation(
    //boolean identifier of the job being executed successfully
    bool ok_,
    //job binary data
    uint256 job_,
    //executor ID
    uint256 keeperId_,
    //gas price to use in computation of the dynamic reward portion; obtained by taking the minimum of the Job-specified maximal admissible gas price and the current block base gas price. 
    //Do bear in mind that in cases of the block base gas price exceeding the one specified as maximal by the Job owner the job execution will revert unless the keeper passes in his execution config byte the appropriate flag, assenting to potentially receiving deficient recompense compared to what he may expect. If no such keeper is found in the entire Flashbots pool, the task is not executed in that round. Do refer to the Flags page for further information. 
    uint256 gasPrice_,
    //gas used for execution
    uint256 gasUsed_
  ) internal view virtual returns (uint256)

Function logic in Flashbots implementation:

function _calculateCompensation(
    ...
)
{
    //the fixedReward data occupies the bit positions 64-95 (size of 32 bits) in the job binary representation, hence obtained by left-shifting by 64, bringing the fixedReward in the leading position, and right-shifting by 224, retaining only 32 leading bits of the data, which is exactly fixedReward
    uint256 fixedReward = (job_ << 64) >> 224;
    //the rewardPct data occipies the bit positions 96-111 (size of 16 bits) in the job binary representation, hence obtained by left-shifting by 96, bringing the rewardPct in the leading position, and right-shifting by 240, retaining only 16 leading bits of the data, which is exactly rewardPct
    uint256 rewardPct = (job_ << 96) >> 240;
    //invoke the general compensation calculator
    return calculateCompensationPure(rewardPct, fixedReward, gasPrice_, gasUsed_);
}
function calculateCompensationPure(
    uint256 rewardPct_,
    uint256 fixedReward_,
    uint256 blockBaseFee_,
    uint256 gasUsed_
  ) public pure returns (uint256) {
    unchecked {
      //returns the sum of a fixed part (given by the job-defined fixedReward_ multiplied by a fixed multiplier given by the FIXED_PAYMENT_MULTIPLIER constant (with value of 1e15) for the purposes of convenience) and a dynamic part (given as a percentage coverage, from 0 to 56535 percents, of the total gas cost expenditure: gas usage and overhead are translated into the gas cost at either the current block gas fee level or the maximal gas fee admissible by the job, whichever is smalelr, and multiplied by rewardPct_, which is a job-defined parameter). Mind that one of rewardPct_ or fixedReward_ may be zero, but never both. The gas overhead value is set to 40.000. 
      return (gasUsed_ + _getJobGasOverhead()) * blockBaseFee_ * rewardPct_ / 100
             + fixedReward_ * FIXED_PAYMENT_MULTIPLIER;
    }
  }

Compensation computation usage cases

The function _calculateCompensation is invoked in the payout block of the execute_44g58pv, immediately after the execution part proper. Do note that the call of this function is in no way conditioned on the success of execution of the job being automated: there are no conditionals involved in either the function itself or the parameters of its call, and it is always invoked, irrespectively of the ok result which indicates the success of the call to the desired function.

For this reason, do note that all executions that are posted to the blockchain and do not revert the main function in Flashbots realisation are rewarded in full. In fact, no partial reward (e..g gas compensation) exists in Flashbots realisation; since the _afterExecutionReverted function in Flashbots realisation always produces and propagates a revert (unlike in RanDAO realisation, where it only produces a revert when the function is of a Resolver type, but has no slashing initiated: since for Resolver type functions we do not known the executability status a priori, we use slashing initiation as an indicator thereof, judging the slashers to be optimal actors; for interval jobs, checks are performed by considering the interval proper earlier in the function), no unsuccessful attempts to execute the underlying job may receive any compensation (it would be reverted by this hook). This is justified because Flashbots provide costless reversion, and there is no gas expenditure to compensate. CHECK THE CORRECTNESS OF THIS INTERPRETATION OF LOGIC WITH KOLYAN

Reward payment options

Having computed the reward by invoking _calculateCompensation, execute_44g58pv either directly transfers the rewards in native chain tokens to the worker address or increases the corresponding value in the appropriate mapping.

The first option is whether job or job owner credits are employed to disburse rewards. This is controlled on a per-job basis by means of the job config byte, wherein the penultimate bit corresponds to the flag switching the reward source between the two listed here. The flag assuming the value of True makes the rewards be disbursed from the job owner account, which may be advantageous for those who desire to automate a great amount of jobs, particularly inexpensive ones, since it affords superior convenience. Greater detail on this is afforded on pages Flags, Job (Old), and Job Registration & Update.

Job/Job owner payment code snippet

 if (ConfigFlags.check(binJob, CFG_USE_JOB_OWNER_CREDITS)) {
          // use job owner credits
          _useJobOwnerCredits(ok, jobKey, compensation);
        } else {
          // use job credits
          uint256 creditsBefore = (binJob << 128) >> 168;
          if (creditsBefore < compensation) {
            if (ok) {
              revert InsufficientJobCredits(creditsBefore, compensation);
            } else {
              compensation = creditsBefore;
            }
          }

The second option is whether to increment the keeper's compensation value stored in the eponymous mapping with his ID as a key or pass the tokens directly to the message sender's address, i.e., the Keeper's worker address. The former option may afford superior efficiency, since it allows to transfer all accured compensation in one transaction, saving on gas. This option is configured by the Keeper himself in the execution config byte: the penultimate bit value thereof toggles this behaviour.

Accrue/send payment code snippet

if (ConfigFlags.check(cfg, FLAG_ACCRUE_REWARD)) {
          compensations[actualKeeperId] += compensation;
        } else {
          payable(msg.sender).transfer(compensation);
        }

Accrued compensation may be withdrawn by a special function. Do refer to Keeper (Old) and Flags for superior clarity.

RanDAO realisation

In RanDAO realisation, only hooks and compensation computations are overloaded, and the main function execute_44g58pv is retained exactly as it is in the Flashbots realisation, as it is not virtual.

Reward amount cases and computation

The function calculateCompensationPure is no longer used; all computation are done directly within _calculateCompensation, since calculateCompensationPure was not made virtual and thus remains specific to the Flashbots realisation.

_calculateCompensation arguments and logic

Function signature:

function _calculateCompensation(
    //boolean identifier of the job being executed successfully
    bool ok_,
    //job binary data
    uint256 job_,
    //executor ID
    uint256 keeperId_,
    //gas price to use in computation of the dynamic reward portion; in RanDAO implementation overloaded to always read type(uint256).max. The reason for this behaviour is a strict system of keeper choice; we may not force keepers to be recomponsed suboptimally if the Job owner sets a meagre limit, but if they do not accept poor recomponse, they will be slashed. For this reason, RanDAO version bereaves the Job owner of the ability to limit the block base gas price for the purposes of computing the amount to be disbursed. 
    uint256 gasPrice_,
    //gas used for execution
    uint256 gasUsed_
  ) internal view override returns (uint256)

Function logic in RanDAO implementation:

function _calculateCompensation(
    ...
)
{
    //if the function call failed, merely compensate the gas spent. Since we no longer have the luxury of costless reverts, this is a necessary step. 
    if (!ok_) {
      return gasUsed_ * gasPrice_;
    }
    //cap the keeper stake for the purposes of computing the reward (if needed)
    if (_jobMaxCvpStake > 0  && _jobMaxCvpStake < stake) {
      stake = _jobMaxCvpStake;
    }
    //cap the keeper stake at an Agent-wide level for the purposes of computing the reward (if needed)
    if (_rdConfig.agentMaxCvpStake > 0 && _rdConfig.agentMaxCvpStake < stake) {
      stake = _rdConfig.agentMaxCvpStake;
    }
    //the fixed (i.e. gas-independent) reward is given in RanDAO implementation as a fixed scaling of the (capped if needed) stake of the executing keeper, which incentivises staking greater amounts. The scaling coefficient is global and is given by the Agent config rdConfig. This coefficient has the meaning of controlling the characteristic scale of the keeper reward growth. Since the keeper selection is no longer of the form of a gas auction, the gas overhead term is dropped from the dynamic (gas-dependent) component, which is now computed as a certain portion of the total gas expense. This portion is now also governed by a global coefficient and not a job-specific one; the coefficient is expressed in basis points to allow for finer control. Do note that now the fixed part of the reward is always present because the stakeDivisor value is obviously bounded from above. 
    return (gasPrice_ * gasUsed_ * _rdConfig.jobCompensationMultiplierBps / 10_000) +
      (stake / _rdConfig.stakeDivisor);
}

Compensation computation usage cases

RanDAO implementation shares the main execution function with the Flashbots implementation, and the hooks have no bearing on computing rewards (instead, they deal with the matters of slashing). Therefore, the cases are entirely similar to the Flashbots implementation, with the primary difference being that now the amount compensated is actually conditioned on the success of execution of the job in a different way: if the job succeeds, the full reward is paid, but reverted jobs pay only the gas cost as recompense. This can be seen as a generalisation of Flashbots realisation model, with the latter simply always having gas usage of reverted transactions be zero.

Reward payment options

Since the payout block is shared together with the execution function, these are entirely coincident with those of the Flashbots implementation.

Last updated