diff --git a/.github/workflows/ci.yml.liquid b/.github/workflows/ci.yml.liquid index a577c5c..5640d48 100644 --- a/.github/workflows/ci.yml.liquid +++ b/.github/workflows/ci.yml.liquid @@ -19,13 +19,14 @@ jobs: - name: Checkout Code uses: actions/checkout@v4 - - uses: dtolnay/rust-toolchain@nightly + - name: Install Rust + uses: actions-rs/toolchain@v1 with: - toolchain: nightly-2024-10-13 + toolchain: nightly-2025-10-20 components: rustfmt - name: Check Formatting - run: cargo fmt -- --check + run: cargo +nightly-2025-10-20 fmt -- --check lint: timeout-minutes: 120 @@ -46,9 +47,10 @@ jobs: - name: Install Solidity Dependencies run: forge soldeer update -d - - uses: dtolnay/rust-toolchain@nightly + - name: Install Rust + uses: actions-rs/toolchain@v1 with: - toolchain: nightly-2024-10-13 + toolchain: nightly-2025-10-20 components: clippy - uses: swatinem/rust-cache@v2 @@ -82,9 +84,10 @@ jobs: - name: Install Solidity Dependencies run: forge soldeer update -d - - uses: dtolnay/rust-toolchain@nightly + - name: Install Rust + uses: actions-rs/toolchain@v1 with: - toolchain: nightly-2024-10-13 + toolchain: nightly-2025-10-20 components: clippy - uses: swatinem/rust-cache@v2 diff --git a/.github/workflows/verify-template.yml b/.github/workflows/verify-template.yml index 35d0478..f69a514 100644 --- a/.github/workflows/verify-template.yml +++ b/.github/workflows/verify-template.yml @@ -22,7 +22,7 @@ jobs: --define project-description="An example blueprint" --define project-homepage="https://tangle.tools" --define flakes=true - --define docker=true + --define container=true --define base-image="rustlang/rust:nightly" --define ci=true --define rust-ci=true @@ -33,9 +33,12 @@ jobs: version: nightly - name: Show forge version run: forge --version - - uses: dtolnay/rust-toolchain@stable + - name: Install Rust + uses: actions-rs/toolchain@v1 with: - toolchain: stable + toolchain: nightly-2025-10-20 + - name: Install protobuf compiler + run: sudo apt-get update && sudo apt-get install -y protobuf-compiler - run: | cp -r $PROJECT_NAME ${{ runner.temp }}/ cd ${{ runner.temp }}/$PROJECT_NAME diff --git a/Cargo.toml b/Cargo.toml index 17d2f77..a82d14a 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -10,16 +10,20 @@ repository = "https://github.com/{{gh-username}}/{{project-name}}" readme = "README.md" categories = ["cryptography", "cryptography::cryptocurrencies"] keywords = ["tangle", "blueprint", "avs"] -rust-version = "1.81" +rust-version = "1.88" [dependencies] -# Gadget -blueprint-sdk = { git = "https://github.com/tangle-network/gadget.git", default-features = false, features = ["std", "eigenlayer", "evm", "macros", "build"] } +blueprint-sdk = { version = "0.1.0-alpha.19", default-features = false, features = ["std", "eigenlayer", "evm", "macros", "build", "testing"], git = "https://github.com/tangle-network/blueprint.git" } serde = { version = "1.0.208", features = ["derive"] } +jsonrpc-core = { version = "18.0.0", default-features = false } +jsonrpc-http-server = { version = "18.0.0", default-features = false } +thiserror = { version = "2.0.17", default-features = false } +reqwest = { version = "0.12.22", default-features = false } +color-eyre = { version = "0.6", default-features = false } [build-dependencies] -blueprint-sdk = { git = "https://github.com/tangle-network/gadget.git", default-features = false, features = ["std", "build"] } +blueprint-sdk = { version = "0.1.0-alpha.19", default-features = false, features = ["std", "eigenlayer", "evm", "macros", "build", "testing"], git = "https://github.com/tangle-network/blueprint.git" } [features] default = ["std"] diff --git a/Dockerfile b/Dockerfile index 8d711e9..2fd5104 100644 --- a/Dockerfile +++ b/Dockerfile @@ -21,7 +21,7 @@ LABEL org.opencontainers.image.description="{{project-description}}" LABEL org.opencontainers.image.source="https://github.com/{{gh-username}}/{{project-name}}" LABEL org.opencontainers.image.licenses="MIT OR Apache-2.0" -ENV RUST_LOG="gadget=info" +ENV RUST_LOG="blueprint_sdk=info" ENV BIND_ADDR="0.0.0.0" ENV BIND_PORT=9632 ENV BLUEPRINT_ID=0 diff --git a/build.rs b/build.rs index 2460465..d20e17d 100644 --- a/build.rs +++ b/build.rs @@ -1,5 +1,5 @@ fn main() { let contract_dirs: Vec<&str> = vec!["./contracts"]; - blueprint_sdk::build::utils::soldeer_update(); - blueprint_sdk::build::utils::build_contracts(contract_dirs); + blueprint_sdk::build::soldeer_update(); + blueprint_sdk::build::build_contracts(contract_dirs); } diff --git a/contracts/src/ITangleTaskManager.sol b/contracts/src/ITangleTaskManager.sol index 90440ab..fa8a770 100644 --- a/contracts/src/ITangleTaskManager.sol +++ b/contracts/src/ITangleTaskManager.sol @@ -1,32 +1,18 @@ // SPDX-License-Identifier: UNLICENSED pragma solidity >=0.8.13; -import "eigenlayer-middleware/src/libraries/BN254.sol"; - interface ITangleTaskManager { // EVENTS event NewTaskCreated(uint32 indexed taskIndex, Task task); - event TaskResponded( - TaskResponse taskResponse, - TaskResponseMetadata taskResponseMetadata - ); + event TaskResponded(TaskResponse taskResponse, TaskResponseMetadata taskResponseMetadata); event TaskCompleted(uint32 indexed taskIndex); - event TaskChallengedSuccessfully( - uint32 indexed taskIndex, - address indexed challenger - ); - - event TaskChallengedUnsuccessfully( - uint32 indexed taskIndex, - address indexed challenger - ); - // STRUCTS struct Task { - uint256 numberToBeSquared; + // TODO: Replace your task params + bytes message; uint32 taskCreatedBlock; // task submitter decides on the criteria for a task to be completed // note that this does not mean the task was "correctly" answered (i.e. the number was squared correctly) @@ -44,21 +30,23 @@ interface ITangleTaskManager { // Can be obtained by the operator from the event NewTaskCreated. uint32 referenceTaskIndex; // This is just the response that the operator has to compute by itself. - uint256 numberSquared; + // TODO: Replace with your expected param following Task + bytes message; } // Extra information related to taskResponse, which is filled inside the contract. // It thus cannot be signed by operators, so we keep it in a separate struct than TaskResponse // This metadata is needed by the challenger, so we emit it in the TaskResponded event struct TaskResponseMetadata { - uint32 taskResponsedBlock; + uint32 taskRespondedBlock; bytes32 hashOfNonSigners; } // FUNCTIONS // NOTE: this function creates new task. function createNewTask( - uint256 numberToBeSquared, + // TODO: Replace param following Task + bytes calldata message, uint32 quorumThresholdPercentage, bytes calldata quorumNumbers ) external; @@ -66,14 +54,6 @@ interface ITangleTaskManager { /// @notice Returns the current 'taskNumber' for the middleware function taskNumber() external view returns (uint32); - // // NOTE: this function raises challenge to existing tasks. - function raiseAndResolveChallenge( - Task calldata task, - TaskResponse calldata taskResponse, - TaskResponseMetadata calldata taskResponseMetadata, - BN254.G1Point[] memory pubkeysOfNonSigningOperators - ) external; - /// @notice Returns the TASK_RESPONSE_WINDOW_BLOCK function getTaskResponseWindowBlock() external view returns (uint32); } diff --git a/contracts/src/TangleServiceManager.sol b/contracts/src/TangleServiceManager.sol index f9a0bad..53dc464 100644 --- a/contracts/src/TangleServiceManager.sol +++ b/contracts/src/TangleServiceManager.sol @@ -1,17 +1,18 @@ // SPDX-License-Identifier: UNLICENSED pragma solidity >=0.8.13; -import "eigenlayer-contracts/src/contracts/libraries/BytesLib.sol"; import "contracts/src/ITangleTaskManager.sol"; -import "eigenlayer-middleware/src/ServiceManagerBase.sol"; +import "@eigenlayer-middleware/src/ServiceManagerBase.sol"; +import {IAllocationManager} from "@eigenlayer/contracts/interfaces/IAllocationManager.sol"; +import {IRewardsCoordinator} from "@eigenlayer/contracts/interfaces/IRewardsCoordinator.sol"; +import {ISlashingRegistryCoordinator} from +"@eigenlayer-middleware/src/interfaces/ISlashingRegistryCoordinator.sol"; /** * @title Primary entrypoint for procuring services from Hello. * @author Layr Labs, Inc. */ contract TangleServiceManager is ServiceManagerBase { - using BytesLib for bytes; - ITangleTaskManager public immutable TangleTaskManager; @@ -27,15 +28,19 @@ contract TangleServiceManager is ServiceManagerBase { constructor( IAVSDirectory _avsDirectory, IRewardsCoordinator _rewardsCoordinator, - IRegistryCoordinator _registryCoordinator, + ISlashingRegistryCoordinator _registryCoordinator, IStakeRegistry _stakeRegistry, + IPermissionController _permissionController, + IAllocationManager _allocationManager, ITangleTaskManager _TangleTaskManager ) ServiceManagerBase( _avsDirectory, _rewardsCoordinator, _registryCoordinator, - _stakeRegistry + _stakeRegistry, + _permissionController, + _allocationManager ) { TangleTaskManager = _TangleTaskManager; diff --git a/contracts/src/TangleTaskManager.sol b/contracts/src/TangleTaskManager.sol index bb6e649..f4fcef1 100644 --- a/contracts/src/TangleTaskManager.sol +++ b/contracts/src/TangleTaskManager.sol @@ -1,15 +1,21 @@ // SPDX-License-Identifier: UNLICENSED pragma solidity >=0.8.13; + import "@openzeppelin-upgrades/contracts/proxy/utils/Initializable.sol"; import "@openzeppelin-upgrades/contracts/access/OwnableUpgradeable.sol"; -import "eigenlayer-contracts/src/contracts/permissions/Pausable.sol"; -import "eigenlayer-middleware/src/interfaces/IServiceManager.sol"; -import {BLSApkRegistry} from "eigenlayer-middleware/src/BLSApkRegistry.sol"; -import {RegistryCoordinator} from "eigenlayer-middleware/src/RegistryCoordinator.sol"; -import {BLSSignatureChecker, IRegistryCoordinator} from "eigenlayer-middleware/src/BLSSignatureChecker.sol"; -import {OperatorStateRetriever} from "eigenlayer-middleware/src/OperatorStateRetriever.sol"; -import "eigenlayer-middleware/src/libraries/BN254.sol"; +import "@eigenlayer/contracts/permissions/Pausable.sol"; +import "@eigenlayer-middleware/src/interfaces/IServiceManager.sol"; +import {BLSApkRegistry} from "@eigenlayer-middleware/src/BLSApkRegistry.sol"; +import {ISlashingRegistryCoordinator} from + "@eigenlayer-middleware/src/interfaces/ISlashingRegistryCoordinator.sol"; +import {BLSSignatureChecker} from "@eigenlayer-middleware/src/BLSSignatureChecker.sol"; +import {OperatorStateRetriever} from "@eigenlayer-middleware/src/OperatorStateRetriever.sol"; +import {InstantSlasher} from "@eigenlayer-middleware/src/slashers/InstantSlasher.sol"; +import "@eigenlayer-middleware/src/libraries/BN254.sol"; +import {IStrategy} from "@eigenlayer/contracts/interfaces/IStrategy.sol"; +import {IAllocationManagerTypes} from "@eigenlayer/contracts/interfaces/IAllocationManager.sol"; +import {OperatorSet} from "@eigenlayer/contracts/libraries/OperatorSetLib.sol"; import "contracts/src/ITangleTaskManager.sol"; contract TangleTaskManager is @@ -18,7 +24,7 @@ contract TangleTaskManager is Pausable, BLSSignatureChecker, OperatorStateRetriever, -ITangleTaskManager + ITangleTaskManager { using BN254 for BN254.G1Point; @@ -27,6 +33,7 @@ ITangleTaskManager uint32 public immutable TASK_RESPONSE_WINDOW_BLOCK; uint32 public constant TASK_CHALLENGE_WINDOW_BLOCK = 100; uint256 internal constant _THRESHOLD_DENOMINATOR = 100; + uint256 public constant WADS_TO_SLASH = 100_000_000_000_000_000; // 10% /* STORAGE */ // The latest task index @@ -41,11 +48,10 @@ ITangleTaskManager // mapping of task indices to hash of abi.encode(taskResponse, taskResponseMetadata) mapping(uint32 => bytes32) public allTaskResponses; - mapping(uint32 => bool) public taskSuccesfullyChallenged; - address public aggregator; address public generator; + /* MODIFIERS */ modifier onlyAggregator() { require(msg.sender == aggregator, "Aggregator must be the caller"); @@ -60,19 +66,18 @@ ITangleTaskManager } constructor( - IRegistryCoordinator _registryCoordinator, + ISlashingRegistryCoordinator _registryCoordinator, + IPauserRegistry _pauserRegistry, uint32 _taskResponseWindowBlock - ) BLSSignatureChecker(_registryCoordinator) { + ) BLSSignatureChecker(_registryCoordinator) Pausable(_pauserRegistry) { TASK_RESPONSE_WINDOW_BLOCK = _taskResponseWindowBlock; } function initialize( - IPauserRegistry _pauserRegistry, - address initialOwner, address _aggregator, - address _generator + address _generator, + address initialOwner ) public initializer { - _initializePauser(_pauserRegistry, UNPAUSE_ALL); _transferOwnership(initialOwner); aggregator = _aggregator; generator = _generator; @@ -81,13 +86,14 @@ ITangleTaskManager /* FUNCTIONS */ // NOTE: this function creates new task, assigns it a taskId function createNewTask( - uint256 numberToBeSquared, + // TODO: replace with your Task params + bytes calldata message, uint32 quorumThresholdPercentage, bytes calldata quorumNumbers ) external onlyTaskGenerator { // create a new task struct Task memory newTask; - newTask.numberToBeSquared = numberToBeSquared; + newTask.message = message; newTask.taskCreatedBlock = uint32(block.number); newTask.quorumThresholdPercentage = quorumThresholdPercentage; newTask.quorumNumbers = quorumNumbers; @@ -170,92 +176,6 @@ ITangleTaskManager return latestTaskNum; } - // NOTE: this function enables a challenger to raise and resolve a challenge. - // TODO: require challenger to pay a bond for raising a challenge - // TODO(samlaf): should we check that quorumNumbers is same as the one recorded in the task? - function raiseAndResolveChallenge( - Task calldata task, - TaskResponse calldata taskResponse, - TaskResponseMetadata calldata taskResponseMetadata, - BN254.G1Point[] memory pubkeysOfNonSigningOperators - ) external { - uint32 referenceTaskIndex = taskResponse.referenceTaskIndex; - uint256 numberToBeSquared = task.numberToBeSquared; - // some logical checks - require( - allTaskResponses[referenceTaskIndex] != bytes32(0), - "Task hasn't been responded to yet" - ); - require( - allTaskResponses[referenceTaskIndex] == - keccak256(abi.encode(taskResponse, taskResponseMetadata)), - "Task response does not match the one recorded in the contract" - ); - require( - taskSuccesfullyChallenged[referenceTaskIndex] == false, - "The response to this task has already been challenged successfully." - ); - - require( - uint32(block.number) <= - taskResponseMetadata.taskResponsedBlock + - TASK_CHALLENGE_WINDOW_BLOCK, - "The challenge period for this task has already expired." - ); - - // logic for checking whether challenge is valid or not - uint256 actualSquaredOutput = numberToBeSquared * numberToBeSquared; - bool isResponseCorrect = (actualSquaredOutput == - taskResponse.numberSquared); - - // if response was correct, no slashing happens so we return - if (isResponseCorrect == true) { - emit TaskChallengedUnsuccessfully(referenceTaskIndex, msg.sender); - return; - } - - // get the list of hash of pubkeys of operators who weren't part of the task response submitted by the aggregator - bytes32[] memory hashesOfPubkeysOfNonSigningOperators = new bytes32[]( - pubkeysOfNonSigningOperators.length - ); - for (uint i = 0; i < pubkeysOfNonSigningOperators.length; i++) { - hashesOfPubkeysOfNonSigningOperators[ - i - ] = pubkeysOfNonSigningOperators[i].hashG1Point(); - } - - // verify whether the pubkeys of "claimed" non-signers supplied by challenger are actually non-signers as recorded before - // when the aggregator responded to the task - // currently inlined, as the MiddlewareUtils.computeSignatoryRecordHash function was removed from BLSSignatureChecker - // in this PR: https://github.com/Layr-Labs/eigenlayer-contracts/commit/c836178bf57adaedff37262dff1def18310f3dce#diff-8ab29af002b60fc80e3d6564e37419017c804ae4e788f4c5ff468ce2249b4386L155-L158 - // TODO(samlaf): contracts team will add this function back in the BLSSignatureChecker, which we should use to prevent potential bugs from code duplication - bytes32 signatoryRecordHash = keccak256( - abi.encodePacked( - task.taskCreatedBlock, - hashesOfPubkeysOfNonSigningOperators - ) - ); - require( - signatoryRecordHash == taskResponseMetadata.hashOfNonSigners, - "The pubkeys of non-signing operators supplied by the challenger are not correct." - ); - - // get the address of operators who didn't sign - address[] memory addresssOfNonSigningOperators = new address[]( - pubkeysOfNonSigningOperators.length - ); - for (uint i = 0; i < pubkeysOfNonSigningOperators.length; i++) { - addresssOfNonSigningOperators[i] = BLSApkRegistry( - address(blsApkRegistry) - ).pubkeyHashToOperator(hashesOfPubkeysOfNonSigningOperators[i]); - } - - // the task response has been challenged successfully - taskSuccesfullyChallenged[referenceTaskIndex] = true; - - emit TaskChallengedSuccessfully(referenceTaskIndex, msg.sender); - } - function getTaskResponseWindowBlock() external view returns (uint32) { return TASK_RESPONSE_WINDOW_BLOCK; } diff --git a/foundry.toml b/foundry.toml index c1e1690..94a69d7 100644 --- a/foundry.toml +++ b/foundry.toml @@ -7,6 +7,7 @@ cache_path = "contracts/cache" broadcast = "contracts/broadcast" libs = ["dependencies"] auto_detect_remappings = true +via-ir = true [soldeer] recursive_deps = true @@ -14,7 +15,7 @@ remappings_location = "txt" remappings_version = false [dependencies] -eigenlayer-middleware = { version = "0.5.4", git = "https://github.com/Layr-Labs/eigenlayer-middleware.git", tag = "v0.5.4-mainnet-rewards-v2" } -forge-std = { version = "1.9.4" } +eigenlayer-middleware = { version = "1.3.1", git = "https://github.com/Layr-Labs/eigenlayer-middleware", rev = "fd26169c7f988a53ad9ca1e745f40754afed4ed4" } +forge-std = { version = "1.9.6" } # See more config options https://github.com/foundry-rs/foundry/blob/master/crates/config/README.md#all-options diff --git a/hooks/pre.rhai b/hooks/pre.rhai index dc16e6b..490cf3e 100644 --- a/hooks/pre.rhai +++ b/hooks/pre.rhai @@ -33,12 +33,12 @@ if !check_bool("flakes") { file::delete("./.envrc"); } -if !variable::is_set("docker") { - let docker = variable::prompt("Do you want to generate a Dockerfile for your gadget?", true); - variable::set("docker", docker); +if !variable::is_set("container") { + let container = variable::prompt("Do you want to generate a Dockerfile for your gadget?", true); + variable::set("container", container); } -if check_bool("docker") { +if check_bool("container") { if !variable::is_set("base-image") { let base_image = variable::prompt("What base image should be used?", "rustlang/rust:nightly"); variable::set("base-image", base_image); diff --git a/remappings.txt b/remappings.txt index 6ac4867..88ec8a6 100644 --- a/remappings.txt +++ b/remappings.txt @@ -1,2 +1,3 @@ -eigenlayer-middleware/=dependencies/eigenlayer-middleware-0.5.4/ -forge-std/=dependencies/forge-std-1.9.4 +@eigenlayer-middleware/=dependencies/eigenlayer-middleware-1.3.1/ +@eigenlayer/contracts/=dependencies/eigenlayer-middleware-1.3.1/lib/eigenlayer-contracts/src/contracts +forge-std/=dependencies/forge-std-1.9.6 diff --git a/rust-toolchain.toml b/rust-toolchain.toml index d358d90..07ccef3 100644 --- a/rust-toolchain.toml +++ b/rust-toolchain.toml @@ -2,7 +2,7 @@ # Currently we are using this specific nightly version since we rely on the # rustdoc API which is not yet stabilized. We will keep updating this version # as we go along. -channel = "nightly-2025-01-30" +channel = "nightly-2025-06-26" components = ["rustfmt", "clippy", "rust-src"] targets = ["wasm32-unknown-unknown"] profile = "minimal" diff --git a/settings.env b/settings.env index 84b47d7..d57312e 100644 --- a/settings.env +++ b/settings.env @@ -1,8 +1,12 @@ -REGISTRY_COORDINATOR_ADDRESS=c3e53f4d16ae77db1c982e75a937b9f60fe63690 -OPERATOR_STATE_RETRIEVER_ADDRESS=1613beb3b2c4f22ee086b2b38c1476a3ce7f78e8 -DELEGATION_MANAGER_ADDRESS=dc64a140aa3e981100a9beca4e685f962f0cf6c9 -STRATEGY_MANAGER_ADDRESS=5fc8d32690cc91d4c39d9d3abcbd16989f875707 -SERVICE_MANAGER_ADDRESS=67d269191c92caf3cd7723f116c85e6e9bf55933 -STAKE_REGISTRY_ADDRESS=5fc8d32690cc91d4c39d9d3abcbd16989f875707 -AVS_DIRECTORY_ADDRESS=0000000000000000000000000000000000000000 -REWARDS_COORDINATOR_ADDRESS=0000000000000000000000000000000000000000 \ No newline at end of file +REGISTRY_COORDINATOR_ADDRESS=fd471836031dc5108809d173a067e8486b9047a3 +OPERATOR_STATE_RETRIEVER_ADDRESS=922d6956c99e12dfeb3224dea977d0939758a1fe +DELEGATION_MANAGER_ADDRESS=cf7ed3acca5a467e9e704c703e8d87f634fb0fc9 +STRATEGY_MANAGER_ADDRESS=a513e6e4b8f2a923d98304ec87f64353c4d5c853 +STRATEGY_ADDRESS=1613beb3b2c4f22ee086b2b38c1476a3ce7f78e8 +SERVICE_MANAGER_ADDRESS=2bdcc0de6be1f7d2ee689a0342d76f52e8efaba3 +STAKE_REGISTRY_ADDRESS=7bc06c482dead17c0e297afbc32f6e63d3846650 +AVS_DIRECTORY_ADDRESS=b7f8bc63bbcad18155201308c8f3540b07f84f5e +REWARDS_COORDINATOR_ADDRESS=0dcd1bf9a1b36ce34237eeafef220932846bcd82 +PERMISSION_CONTROLLER_ADDRESS=322813fd9a801c5507c9de605d63cea4f2ce6c44 +PAUSE_REGISTRY_ADDRESS=9a9f2ccfde556a7e9ff0848998aa4a0cfd8863ae +ALLOCATION_MANAGER_ADDRESS=8a791620dd6260079bf849dc5567adc3f2fdc318 \ No newline at end of file diff --git a/soldeer.lock b/soldeer.lock index 9111e9a..aa07cb5 100644 --- a/soldeer.lock +++ b/soldeer.lock @@ -1,12 +1,12 @@ [[dependencies]] name = "eigenlayer-middleware" -version = "0.5.4" -git = "https://github.com/Layr-Labs/eigenlayer-middleware.git" -rev = "2475ab8b8c7698e69bb18f3a19e0c518381f24df" +version = "1.3.1" +git = "https://github.com/Layr-Labs/eigenlayer-middleware" +rev = "fd26169c7f988a53ad9ca1e745f40754afed4ed4" [[dependencies]] name = "forge-std" -version = "1.9.4" -url = "https://soldeer-revisions.s3.amazonaws.com/forge-std/1_9_4_25-10-2024_14:36:59_forge-std-1.9.zip" -checksum = "b5be24beb5e4dab5e42221b2ad1288b64c826bee5ee71b6159ba93ffe86f14d4" -integrity = "3874463846ab995a6a9a88412913cacec6144f7605daa1af57c2d8bf3f210b13" +version = "1.9.6" +url = "https://soldeer-revisions.s3.amazonaws.com/forge-std/1_9_6_01-02-2025_20:49:10_forge-std-1.9.zip" +checksum = "55f341818321b3f925161a72fd0dcd62e4a0a4b66785a7a932bf2bfaf96fb9d1" +integrity = "e9ecdc364d152157431e5df5aa041ffddbe9bb1c1ad81634b1e72df9e23814e8" diff --git a/src/error.rs b/src/error.rs new file mode 100644 index 0000000..9239b8e --- /dev/null +++ b/src/error.rs @@ -0,0 +1,6 @@ +#[expect(clippy::large_enum_variant, reason = "SDK error is large currently")] +#[derive(Debug, thiserror::Error)] +pub enum TaskError { + #[error("Task: {0}")] + Task(String), +} diff --git a/src/lib.rs b/src/lib.rs index fd856bf..bc9a7cf 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,17 +1,17 @@ +pub mod error; + +use crate::error::TaskError; +use crate::TangleTaskManager::NewTaskCreated; use blueprint_sdk::alloy::primitives::{address, Address}; -use blueprint_sdk::alloy::rpc::types::Log; +use blueprint_sdk::alloy::sol_types::{SolEvent, SolValue}; use blueprint_sdk::alloy::sol; -use blueprint_sdk::config::GadgetConfiguration; -use blueprint_sdk::event_listeners::evm::EvmContractEventListener; -use blueprint_sdk::job; -use blueprint_sdk::logging::info; -use blueprint_sdk::macros::load_abi; -use blueprint_sdk::std::convert::Infallible; -use blueprint_sdk::std::sync::LazyLock; +use blueprint_sdk::info; +use blueprint_sdk::runner::config::BlueprintEnvironment; +use blueprint_sdk::extract::Context; +use blueprint_sdk::evm::extract::BlockEvents; use serde::{Deserialize, Serialize}; - -type ProcessorError = - blueprint_sdk::event_listeners::core::Error; +use std::env; +use std::sync::LazyLock; sol!( #[allow(missing_docs)] @@ -21,56 +21,60 @@ sol!( "contracts/out/TangleTaskManager.sol/TangleTaskManager.json" ); -load_abi!( - TANGLE_TASK_MANAGER_ABI_STRING, - "contracts/out/TangleTaskManager.sol/TangleTaskManager.json" -); - pub static TASK_MANAGER_ADDRESS: LazyLock
= LazyLock::new(|| { - std::env::var("TASK_MANAGER_ADDRESS") + env::var("TASK_MANAGER_ADDRESS") .map(|addr| addr.parse().expect("Invalid TASK_MANAGER_ADDRESS")) .unwrap_or_else(|_| address!("0000000000000000000000000000000000000000")) }); +pub static PRIVATE_KEY: LazyLock = LazyLock::new(|| { + env::var("PRIVATE_KEY").unwrap_or_else(|_| { + "ac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80".to_string() + }) +}); + +// TODO: Replace with your context name #[derive(Clone)] pub struct ExampleContext { - pub config: GadgetConfiguration, + pub std_config: BlueprintEnvironment, } -/// Returns "Hello, {who}!" -#[job( - id = 0, - params(who), - event_listener( - listener = EvmContractEventListener, - instance = TangleTaskManager, - abi = TANGLE_TASK_MANAGER_ABI_STRING, - pre_processor = example_pre_processor, - ), -)] -pub fn say_hello(context: ExampleContext, who: String) -> Result { - blueprint_sdk::logging::trace!("Successfully ran job function!"); +/// Example task that responds to a task created event +/// This function is triggered by the NewTaskCreated event emitted by the TangleTaskManager contract +/// This function response to greeting `Task.message` +#[blueprint_sdk::macros::debug_job] +pub async fn example_task( + Context(_ctx): Context, + BlockEvents(events): BlockEvents, +) -> Result<(), TaskError> { info!("Successfully ran job function!"); - Ok(format!("Hello, {who}!")) -} -/// Example pre-processor for handling inbound events -async fn example_pre_processor( - (_event, log): (TangleTaskManager::NewTaskCreated, Log), -) -> Result, ProcessorError> { - let who = log.address(); - Ok(Some((who.to_string(),))) -} + let task_created_events = events.iter().filter_map(|log| { + NewTaskCreated::decode_log(&log.inner) + .map(|event| event.data) + .ok() + }); + + for task_created in task_created_events { + let task = task_created.task; + let task_index = task_created.taskIndex; -#[cfg(test)] -mod tests { - use super::*; + info!("Task created: {}", task_index); - #[test] - fn it_works() { - let config = GadgetConfiguration::default(); - let context = ExampleContext { config }; - let result = say_hello(context, "Alice".into()).unwrap(); - assert_eq!(result, "Hello, Alice!"); + let message_bytes = &task.message; + let greeting = std::str::from_utf8(message_bytes) + .unwrap_or("") + .to_string(); + info!("Greeting: {}", greeting); + + // Calculate the square + let greeting_result = format!("Hello, {}!", greeting); + info!("Greeting result: {}", greeting_result); + + // Properly encode the result as a uint256 instead of a string + let message = SolValue::abi_encode(&greeting_result.as_bytes()); + info!("Result message: {:?}", message); } + + Ok(()) } diff --git a/src/main.rs b/src/main.rs index 7a68fdb..a5be712 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,40 +1,56 @@ use {{project-name | snake_case}} as blueprint; -use blueprint::{TangleTaskManager, TASK_MANAGER_ADDRESS}; -use blueprint_sdk::alloy::primitives::{address, Address, U256}; -use blueprint_sdk::logging::{info, warn}; -use blueprint_sdk::macros::main; -use blueprint_sdk::runners::core::runner::BlueprintRunner; -use blueprint_sdk::runners::eigenlayer::bls::EigenlayerBLSConfig; -use blueprint_sdk::utils::evm::get_provider_http; - -#[main(env)] -async fn main() { - // Create your service context - // Here you can pass any configuration or context that your service needs. - let context = blueprint::ExampleContext { - config: env.clone(), - }; - // Get the provider - let rpc_endpoint = env.http_rpc_endpoint.clone(); - let provider = get_provider_http(&rpc_endpoint); +use blueprint_sdk::alloy::network::EthereumWallet; +use blueprint_sdk::alloy::primitives::{address, Address, Bytes}; +use blueprint_sdk::alloy::signers::local::PrivateKeySigner; +use blueprint_sdk::evm::producer::{PollingConfig, PollingProducer}; +use blueprint_sdk::evm::util::get_wallet_provider_http; +use blueprint_sdk::runner::config::BlueprintEnvironment; +use blueprint_sdk::runner::eigenlayer::bls::EigenlayerBLSConfig; +use blueprint_sdk::runner::BlueprintRunner; +use blueprint_sdk::testing::utils::setup_log; +use blueprint_sdk::{info, warn, tokio, Router}; +use std::sync::Arc; +use std::time::Duration; +use blueprint::TangleTaskManager; +use blueprint::{PRIVATE_KEY, TASK_MANAGER_ADDRESS}; +// TODO: Replace with your context name +use blueprint::ExampleContext; +use blueprint::example_task; + +#[tokio::main] +async fn main() -> Result<(), blueprint_sdk::Error> { + setup_log(); + let env = BlueprintEnvironment::load()?; + + // TODO: Replace with your context name + let context = ExampleContext { + std_config: env.clone(), + }; + let signer: PrivateKeySigner = PRIVATE_KEY.parse().expect("failed to generate wallet "); + let wallet = EthereumWallet::from(signer); + let provider = get_wallet_provider_http(env.http_rpc_endpoint.clone(), wallet.clone()); // Create an instance of your task manager - let contract = TangleTaskManager::new(*TASK_MANAGER_ADDRESS, provider); + let contract = TangleTaskManager::new(*TASK_MANAGER_ADDRESS, provider.clone()); - // Create the event handler from the job - let say_hello_job = blueprint::SayHelloEventHandler::new(contract, context.clone()); + // Create producer for task events + let task_producer = PollingProducer::new( + Arc::new(provider), + // PollingConfig::default().poll_interval(Duration::from_secs(1)), + PollingConfig::from_current().step(1).confirmations(1u64).poll_interval(Duration::from_secs(1)), + ) + .await + .map_err(|e| blueprint_sdk::Error::Other(e.to_string()))?; // Spawn a task to create a task - this is just for testing/example purposes info!("Spawning a task to create a task on the contract..."); - blueprint_sdk::tokio::spawn(async move { - let provider = get_provider_http(&rpc_endpoint); - let contract = TangleTaskManager::new(*TASK_MANAGER_ADDRESS, provider); + tokio::spawn(async move { loop { - blueprint_sdk::tokio::time::sleep(std::time::Duration::from_secs(5)).await; + tokio::time::sleep(Duration::from_secs(5)).await; // We use the Anvil Account #4 as the Task generator address let task = contract - .createNewTask(U256::from(5), 100u32, vec![0].into()) + .createNewTask(Bytes::from_static(b"World"), 100u32, vec![0].into()) .from(address!("15d34AAf54267DB7D7c367839AAf71A00a2C6A65")); let receipt = task.send().await.unwrap().get_receipt().await.unwrap(); if receipt.status() { @@ -46,9 +62,19 @@ async fn main() { }); info!("Starting the event watcher ..."); - let eigen_config = EigenlayerBLSConfig::new(Address::default(), Address::default()); - BlueprintRunner::new(eigen_config, env) - .job(say_hello_job) + let eigen_config = EigenlayerBLSConfig::new(Address::default(), Address::default()) + .with_exit_after_register(false); + BlueprintRunner::builder(eigen_config, env) + .router( + // TODO: Update your task + Router::new() + .always(example_task) + .with_context(context), + ) + .producer(task_producer) + .with_shutdown_handler(async { + info!("Shutting down task manager service"); + }) .run() .await?;