Othentic
  • Introduction
    • Introducing Othentic Stack
    • Use Cases
  • AVS Framework
    • Abstract
    • Quick Start
    • Othentic CLI
      • Key Management
      • Contracts Deployment
      • Operator Registration
      • AVS Logic Implementation
      • Operator Deposit
      • Node Operators
      • Rewards Distribution
      • P2P Config
        • Custom P2P Messaging
        • P2P Auth Layer
        • Metrics and Monitoring
        • Logging
        • Persistent storage
        • Latency
      • CLI Command Reference
    • Smart Contracts
      • AVS Governance
      • Attestation Center
      • Hooks
        • Task Logic
        • Operator Management
        • Rewards Fee Calculator
      • OBLS
      • Othentic Registry
      • Message Handlers
    • Othentic Consensus
      • Abstract
      • Task & Task Definitions
      • Leader Election
      • Proof of Task
      • Execution Service
      • Validation Service
      • Voting Power
      • Rewards and Penalties
      • Internal Tasks
    • FAQ
    • Supported Networks
    • Explainers
      • Networking
      • Multichain
      • Production Guidelines
      • Operator Allowlisting
      • Governance Multisig
  • External
    • AVS Examples
  • GitHub
  • Othentic Hub
Powered by GitBook
On this page
  • Overview
  • Interface
  • Deployment & Setup
  • Use cases
  • Handle Multiple Data Types
  • Task Management
  • Updating arbitrary contracts
  • Cross chain messaging
  1. AVS Framework
  2. Smart Contracts
  3. Hooks

Task Logic

PreviousHooksNextOperator Management

Last updated 1 month ago

Overview

The Othentic Stack allows developers to write an AVSLogic contract, which provides pre and post execution hooks for task submission on-chain, and enables highly configurable and customized logic.

Interface

// SPDX-License-Identifier: BUSL-1.1
pragma solidity >=0.8.20;

/*______     __      __                              __      __ 
 /      \   /  |    /  |                            /  |    /  |
/$$$$$$  | _$$ |_   $$ |____    ______   _______   _$$ |_   $$/   _______ 
$$ |  $$ |/ $$   |  $$      \  /      \ /       \ / $$   |  /  | /       |
$$ |  $$ |$$$$$$/   $$$$$$$  |/$$$$$$  |$$$$$$$  |$$$$$$/   $$ |/$$$$$$$/ 
$$ |  $$ |  $$ | __ $$ |  $$ |$$    $$ |$$ |  $$ |  $$ | __ $$ |$$ |
$$ \__$$ |  $$ |/  |$$ |  $$ |$$$$$$$$/ $$ |  $$ |  $$ |/  |$$ |$$ \_____ 
$$    $$/   $$  $$/ $$ |  $$ |$$       |$$ |  $$ |  $$  $$/ $$ |$$       |
 $$$$$$/     $$$$/  $$/   $$/  $$$$$$$/ $$/   $$/    $$$$/  $$/  $$$$$$$/
*/
/**
 * @author Othentic Labs LTD.
 * @notice Terms of Service: https://www.othentic.xyz/terms-of-service
 */
interface IAvsLogic {
    function afterTaskSubmission(IAttestationCenter.TaskInfo calldata _taskInfo, bool _isApproved, bytes calldata _tpSignature, uint256[2] calldata _taSignature, uint256[] calldata _attestersIds) external;
    function beforeTaskSubmission(IAttestationCenter.TaskInfo calldata _taskInfo, bool _isApproved, bytes calldata _tpSignature, uint256[2] calldata _taSignature, uint256[] calldata _attestersIds) external;
}
function _submitTask(TaskInfo calldata _taskInfo, TaskSubmissionDetails memory _taskSubmissionDetails) internal {
        AttestationCenterStorageData storage _sd = _getStorage();
        IAvsLogic _avsLogic = _sd.avsLogic;
        bool _isAvsLogicSet = address(_avsLogic) != address(0);
        if (_isAvsLogicSet) {
            _avsLogic.beforeTaskSubmission(_taskInfo, _taskSubmissionDetails.isApproved, _taskSubmissionDetails.tpSignature, _taskSubmissionDetails.taSignature, _taskSubmissionDetails.attestersIds);;
        } 
        ...
        if (_isAvsLogicSet) {
            _avsLogic.afterTaskSubmission(_taskInfo, _taskSubmissionDetails.isApproved, _taskSubmissionDetails.tpSignature, _taskSubmissionDetails.taSignature, _taskSubmissionDetails.attestersIds);
        }
    }

Deployment & Setup

After implementation, you can deploy the contract on the same L2 network as the AttestationCenter.

Make sure to deploy the Othentic contracts before deploying the AVS Logic.

Note that only the AVS Governance Multisig is authorized to call this function.

// SPDX-License-Identifier: UNLICENSED
pragma solidity ^0.8.13;

/*______     __      __                              __      __ 
 /      \   /  |    /  |                            /  |    /  |
/$$$$$$  | _$$ |_   $$ |____    ______   _______   _$$ |_   $$/   _______ 
$$ |  $$ |/ $$   |  $$      \  /      \ /       \ / $$   |  /  | /       |
$$ |  $$ |$$$$$$/   $$$$$$$  |/$$$$$$  |$$$$$$$  |$$$$$$/   $$ |/$$$$$$$/ 
$$ |  $$ |  $$ | __ $$ |  $$ |$$    $$ |$$ |  $$ |  $$ | __ $$ |$$ |
$$ \__$$ |  $$ |/  |$$ |  $$ |$$$$$$$$/ $$ |  $$ |  $$ |/  |$$ |$$ \_____ 
$$    $$/   $$  $$/ $$ |  $$ |$$       |$$ |  $$ |  $$  $$/ $$ |$$       |
 $$$$$$/     $$$$/  $$/   $$/  $$$$$$$/ $$/   $$/    $$$$/  $$/  $$$$$$$/
*/
/**
 * @author Othentic Labs LTD.
 * @notice Terms of Service: https://www.othentic.xyz/terms-of-service
 */

import {Script, console} from "forge-std/Script.sol";
import "../src/IAttestationCenter.sol";
import "../src/AvsLogic.sol";

// forge script AvsLogicScript --rpc-url $L2_RPC --private-key $PRIVATE_KEY
// --broadcast -vvvv --verify --etherscan-api-key $L2_ETHERSCAN_API_KEY --chain
// $L2_CHAIN --verifier-url $L2_VERIFIER_URL --sig="run(address)" $ATTESTATION_CENTER_ADDRESS
contract AvsLogicScript is Script {
    function setUp() public {}

    function run(address attestationCenter) public {
        vm.broadcast();
        AvsLogic avsLogic = new AvsLogic(attestationCenter);
        IAttestationCenter(attestationCenter).setAvsLogic(address(avsLogic));
    }
}

Now, the AVS contracts are set and ready to be used.


Use cases

Handle Multiple Data Types

Add custom logic based on the Type of the Task data . By using type-prefixed encoding, you can specify the data type, and the AVS logic hook can efficiently decode and process it accordingly.

Key Components:

  1. Type Identifier Prefix: First 4 bytes specify data type

  2. Payload Structure: Remaining bytes contain the encoded data

  3. Type-Specific Decoding: Switch logic based on data type

Usage

1

Define Supported Data Types

Define different data types you plan to use:

  • PRICE_TYPE: uint256

  • MESSAGE_TYPE: String

  • STATUS_TYPE: Tuple of (bool, address)

2

Encode Data with Type Prefix

Add data type identifier PRICE_TYPE at the beginning of the data field

const PRICE_TYPE = ethers.utils.keccak256(ethers.utils.toUtf8Bytes("PRICE")).slice(0, 10); // First 4 bytes
const encodedPrice = ethers.utils.defaultAbiCoder.encode(["uint256"], [1000]);

const data = ethers.utils.solidityPack(["bytes4", "bytes"], [PRICE_TYPE, encodedPrice]);
3

Decode and Process Data in AVS Hook

The afterTaskSubmission function extracts the first 4 bytes to determine the data type, then decodes the payload accordingly.

// Add data type identifier at the beginning of the data field
bytes4 constant PRICE_TYPE = bytes4(keccak256("PRICE"));
bytes4 constant MESSAGE_TYPE = bytes4(keccak256("MESSAGE"));
bytes4 constant STATUS_TYPE = bytes4(keccak256("STATUS"));

function afterTaskSubmission() {
    ....
    // Read the first 4 bytes to determine data type
    bytes4 dataType = bytes4(_taskInfo.data[0:4]);
    
    // Slice off the type identifier
    bytes memory payload = _taskInfo.data[4:];

    if (dataType == PRICE_TYPE) {
        // Handle price update
        uint256 price = abi.decode(payload, (uint256));
        _updatePrice(price);
        emit PriceUpdated(price);
    } 
    else if (dataType == MESSAGE_TYPE) {
        // Handle string message
        string memory message = abi.decode(payload, (string));
        _logMessage(message);
        emit MessageLogged(message);
    }
    else if (dataType == STATUS_TYPE) {
        // Handle boolean status with address
        (bool status, address validator) = abi.decode(payload, (bool, address));
        _updateValidatorStatus(validator, status);
        emit ValidatorStatusUpdated(validator, status);
    }
    else {
        revert UnsupportedDataType();
    }
}

Task Management

In certain cases, it may be necessary to track and manage tasks even after their completion. For example, you can have an array of task results, allowing anyone to retrieve task history and data.

This functionality can be implemented using the AVS Logic afterTaskSubmission function to store task details on-chain, which other contracts can access via a getter function.

  • Random Number Generator: After completing a task, the generated random number is stored in the AvsLogic contract through afterTaskSubmission, making it available for retrieval.

  • EigenDA: A task is considered complete when the confirmBatch function is called. This function records batch details in a contract mapping, ensuring they are persistently stored.

Note that the function also checks the quorum and emits an event, but these steps have already been executed in the submitTask function.


Updating arbitrary contracts

Depending on your design, your AVS may follow an IFTTT (If This Then That) architecture, requiring on-chain post-execution tasks.

Usage

If the AVS functions as an Oracle service, it may fetch the latest query data. Once operators submit the final result and the task is processed, you might need to update a separate Oracle contract with the latest data.

function afterTaskSubmission(IAttestationCenter.TaskInfo calldata _taskInfo) external {
    uint256 price = abi.decode(_taskInfo.data, (uint256));
    priceOracleContract.updatePrice(price);
}

Use AvsLogic hook to implement this by directly interacting with the contract, ensuring its state reflects the most recent task data.


Cross chain messaging

The AVS Logic hook enables seamless communication between different chains by dispatching messages after task execution. This ensures that data or task verification results can be reflected on another chain.

Usage

AVS tasks are executed off-chain, and task verification remains chain-agnostic and can take place on any blockchain. To maintain verification trails on a different chain, you can utilize the AVS Logic Hook to send a cross-chain message upon task completion.

For instance, if a task is executed on Base, the AVS Logic Hook can transmit a cross-chain message to notify the target blockchain. Additionally, if a particular chain is not natively supported by the Stack, this hook enables cross-chain messaging to ensure the task completion event is successfully relayed.


function afterTaskSubmission(
    IAttestationCenter.TaskInfo calldata _taskInfo,
    bool _isApproved,
    bytes calldata _tpSignature,
    uint256[2] calldata _taSignature,
    uint256[] calldata _attestersIds
) external payable {
    if (!_isApproved) return; // Only process approved tasks

    // Decode the structured data
    (bytes32 txUUID, uint256 agentId, uint256 timestamp, string memory status, string memory reason) =
        abi.decode(_taskInfo.data, (bytes32, uint256, uint256, string, string));

    // Emit event with extracted data
    emit TaskDataParsed(txUUID, agentId, timestamp, status, reason);

    // Encode message for LayerZero
    bytes memory message = abi.encode(
        string(_taskInfo.proofOfTask),
        txUUID,
        agentId,
        timestamp,
        status,
        reason
    );

    // Estimate the gas fee for sending the message
    (uint256 estimatedFee, ) = lzEndpoint.estimateFees(destinationChainId, address(this), message, false, "");

    require(address(this).balance >= estimatedFee, "Insufficient funds for cross-chain message");

    // Send the message via LayerZero
    lzEndpoint.lzSend{value: estimatedFee}(
        destinationChainId,        // Target LayerZero chain ID
        abi.encodePacked(policyCoordinator), // Recipient address on target chain
        message,                    // Encoded payload
        payable(msg.sender),        // Refund address
        address(0),                 // No ZRO token payment
        bytes("")                    // Empty adapterParams (default settings)
    );

    emit CrosschainTaskDataSent(
        block.timestamp,  // Using block timestamp as message ID
        string(_taskInfo.proofOfTask),
        _taskInfo.data,
        _taskInfo.taskDefinitionId
    );
}

The defines two functions: beforeTaskSubmission and afterTaskSubmission. As their names suggest, these functions allow developers to implement custom AVS logic before and after task submission within the contract.

These functions are called in the function in AttestationCenter which is called by the aggregator.

After deployment, you must call the function on the AttestationCenter and provide the address of the AvsLogic contract as the parameter, as shown in the example below.

interface
submitTask
setAvsLogic
AVS Logic Hook