Job Registration & Update

This section describes the functional of the Agent contract on Job registration, update and removal.

Registration

To automate their smart contract execution, the Job provider should register the smart contract in the Agent and set the configuration of the Job.

The Job provider calls function registerJob(RegisterJobParams calldata params_, Resolver calldata resolver_, bytes calldata preDefinedCalldata_ ) to register a new job for execution.

Arguments

  • RegisterJobParams calldata params_: the parameters of the Job.

  • Resolver calldata resolver_: the address of the resolver for the Job type RESOLVER and the calldata to be supplied at each invocation thereof.

  • bytes calldata preDefinedCalldata_: the predefined calldata for the Job type PRE_DEFINED.

Assertions

  • For each address of the Job there can be maximum of type(uint24).max = 2**24 - 1 unique job ids. This number should never be reached in any sane situation:

  • The maximum deposit amount is type(uint88).max:

  • The Job address is specified and nonzero:

  • There are three available calldata sources (job types):

  • Reserved addresses are not allowed:

Jobs are enumerated by integer ids within each set of jobs with the same address. That is, if the job address already has 2 jobs, the new jobId = jobLastIds[params_.jobAddress] + 1 = 2. Afterwards, the jobLastIds of this address is reassigned to equal the new job's id.

Each new job is checked for having nonzero base fee cap maxBaseFeeGwei, and fixedReward_ and RewardPct_ are not zero simultaneously. In addition, checks are made to ensure that there is no mixing of job types: e.g., a resolver job may not have an interval specified, and, conversely, non-resolver jobs must have some interval length specified.

When all assertions are passed, a configuration byte for the job is made by bitwise addition of all flags multiplied by their specified Boolean values. Finally, a new instance of Job is created and stored in the mapping(bytes32 => Job) internal jobs.

Job Registration event

The function emits event RegisterJob(bytes32 indexed jobKey, address indexed jobAddress, uint256 indexed jobId, address owner, RegisterJobParams params).

Full job update

Should the Job provider desire to update his job, he ought to call function updateJob( bytes32 jobKey_, uint16 maxBaseFeeGwei_, uint16 rewardPct_, uint32 fixedReward_, uint256 jobMinCvp_, uint24 intervalSeconds_).

Arguments

  1. bytes32 jobKey_: a job key, which is a keccak256 hash of a concatenation of the job's address and id (in that order)

  2. uint16 maxBaseFeeGwei_: the upper bound on the base fee for gas to be used for computing the dynamic (gas-dependent) part of the keeper reward

  3. uint16 rewardPct_: the reward premium in percents; gives the dynamic (gas-dependent) reward

  4. uint32 fixedReward_: the fixed keeper reward. Mind that the fixed reward and the reward percentage may never be zero simultaneously (it is checked by _assertJobParams).

  5. uint256 jobMinCvp_: minimal CVP stake admissible for a keeper allowed to execute this job

  6. uint24 intervalSeconds_: interval for the job execution

Assertions

  1. _assertOnlyJobOwner asserts that the function caller is the owner of the job with the given key, i.e. prevents everyone but the owner from modifying the job

  2. _assertJobParamsasserts that the maxBaseFeeGwei_ is specified and that either rewardPct_ or fixedReward_ is nonzero, i.e. the base fee is capped, and at least some reward is set.

  3. _assertInterval asserts that either the job is not of resolver type, and the interval not zero, or the job is of resolver type, and the interval is zero. This has to do with the different mechanisms of checking eligibility of interval and resolver jobs.

After the checks are performed, the flags of config are updated if need be (e.g. if the demand for minimal stake was made where before there was none), the parameters of the job (as an element of the mapping jobs) are updated, and a corresponding event is emitted.

Job update event

The function emits JobUpdate(jobKey_, maxBaseFeeGwei_, rewardPct_, fixedReward_, jobMinCvp_, intervalSeconds_);

Partial job update

Setting the job resolver

Should the Job provider desire to set a resolver for his job, he ought to call the function setJobResolver(bytes32 jobKey_, Resolver calldata resolver_).

Arguments

  1. bytes32 jobKey_: a job key, which is a keccak256 hash of a concatenation of the job's address and id (in that order). Note: add this to the glossary for sure

  2. Resolver calldata resolver_: the Resolver object (a struct of address resolverAddress and bytes resolverCalldata, encoding the address of the resolver and the data passable at invocation thereof respectively)

Assertions

  1. _assertOnlyJobOwner asserts that the function caller is the owner of the job with the given key, i.e. prevents everyone but the owner from modifying the job

  2. _assertJobCalldataSource asserts that the calldata source of the job (i.e. its type) is indeed RESOLVER, i.e. that one does not set a resolver for an interval problem, thereby engendering ambiguity

  3. Just before setting the resolver address, a check is made that the address of the resolver supplied is not zero (i.e. not missing).

When all checks are passed, the mapping mapping(bytes32 => Resolver) internal resolvers, which stores the resolvers indexed by job keys, sets the supplied resolver for the given job key.

Resolver setting event

Having set or updated the resolver, the function emits the event SetJobResolver(jobKey_, resolver_.resolverAddress, resolver_.resolverCalldata).

Setting predefined job calldata

Should the Job provider desire to provide calldata for his job of the appropriate calldata source type, he ought to call the function setJobPreDefinedCalldata(bytes32 jobKey_, bytes calldata preDefinedCalldata_).

Arguments

  1. bytes32 jobKey_: a job key, which is a keccak256 hash of a concatenation of the job's address and id (in that order).

  2. bytes calldata preDefinedCalldata_: the calldata to set as predefined for the job

Assertions

  1. _assertOnlyJobOwner asserts that the function caller is the owner of the job with the given key, i.e. prevents everyone but the owner from modifying the job

  2. _assertJobCalldataSource asserts that the calldata source of the job (i.e. its type) is indeed PRE_DEFINED

When all checks are passed, the mapping mapping(bytes32 => bytes) internal preDefinedCalldatas, which stores the bytearrays of pre-defined calldata indexed by job keys, sets the supplied calldata for the given job key.

Calldata setting event

Having set or updated the calldata , the function emits the event SetJobPreDefinedCalldata(jobKey_, preDefinedCalldata_).

Setting job config byte

Should the Job provider desire to modify his job's config byte, he ought to call the function setJobConfig( bytes32 jobKey_, bool isActive_, bool useJobOwnerCredits_, bool assertResolverSelector_ ).

Arguments

  1. bytes32 jobKey_: a job key, which is a keccak256 hash of a concatenation of the job's address and id (in that order).

  2. bool isActive_: indicates whether the job is active (i.e. scheduled for execution) under the new config

  3. bool useJobOwnerCredits_: indicates whether to use the credits belonging to job owner himself instead of credits belonging to a job to reward keepers under the new config

  4. bool assertResolverSelector_: indicates whether to enforce the selector of a resolver job function matching between the calldata passed by the Keeper and the job binary representation. This may be desirable because under the hood, resolver jobs work like PRE_DEFINED jobs, except the calldata is provided by the Keeper. It would therefore be possible for a malicious Keeper to supply a different selector, which is not checkable unless this flag is set to True.

Assertions

  1. _assertOnlyJobOwner asserts that the function caller is the owner of the job with the given key, i.e. prevents everyone but the owner from modifying the job

When all checks are passed, a new config value is generated, initially being zero. It then has all the flags corresponding to parameters for which a True value is passed added to it bitwise. The flags do not mix because they are given by distinct powers of 2. Then the binary job data is retrieved, bitwise multiplied with a mask that zeroes out the last (config) byte, and has the new config data added to it bitwise, in essence replacing the old trailing (config) byte with the new config byte. Finally, the job data is rewritten in memory.

Job config update event

Having updated the config byte, the function emits the event SetJobConfig(jobKey_, isActive_, useJobOwnerCredits_, assertResolverSelector_).

Transferring the job owner status to a new address

Initialising the transfer

Should the Job provider desire to transfer his job to a new owner, he ought to call the function initiateJobTransfer(bytes32 jobKey_, address to_).

Arguments

  1. bytes32 jobKey_: a job key, which is a keccak256 hash of a concatenation of the job's address and id (in that order). It corresponds to the job being transferred.

  2. address to_: specifies the owner status recipient address

Assertions

  1. _assertOnlyJobOwner asserts that the function caller is the owner of the job with the given key, i.e. prevents everyone but the owner from modifying the job

When all checks are passed, the pending owner address (the one specified) is entered to the mapping mapping(bytes32 => address) internal jobPendingTransfers with a key given by the job key.

Transfer initiation event

Having added the recipient address to the pending address mapping, the function emits the event InitiateJobTransfer(jobKey_, msg.sender, to_).

Accepting the transfer

Upon transfer ownership initiation, it may be finalised when the recipient calls the function acceptJobTransfer(bytes32 jobKey_).

Arguments

  1. bytes32 jobKey_: a job key, which is a keccak256 hash of a concatenation of the job's address and id (in that order). It corresponds to the job being transferred.

Assertions

  1. An assertion is made that the sender's address matches the pending owner address (i.e. the address retrievable from the mapping mapping(bytes32 => address) internal jobPendingTransfers by the jobKey_), that is to say, that the intended recipient calls this function and no one else

When all checks are passed, the pending owner address is removed from the jobPendingTransfers (to be precise, the element indexed by the jobKey_ is removed therefrom, but the effect is the same), and the owner of the job indexed by jobKey_ is reset to be the pending (now actual) owner.

Transfer initiation event

Having finalised the job transfer, the function emits the event AcceptJobTransfer(jobKey_, msg.sender), where msg.sender, obviously, corresponds to the new job owner.

Last updated