From a62ec34769bcbb8efa3476e71662d7ff2dfca45c Mon Sep 17 00:00:00 2001 From: alrxy Date: Thu, 12 Dec 2024 15:20:01 +0400 Subject: [PATCH 1/2] feat: self register sqrt task middleware example --- README.md | 2 +- .../SelfRegisterSqrtTaskMiddleware.sol | 179 ++++++++++++++++++ 2 files changed, 180 insertions(+), 1 deletion(-) create mode 100644 src/examples/sqrt-task-network/SelfRegisterSqrtTaskMiddleware.sol diff --git a/README.md b/README.md index 4170f13..74390b8 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,4 @@ -# Middleware Development Guide [**!!!WIP!!!**] +# Middleware Development Guide **Warning: The SDK is a work in progress and is currently under audits. Use with caution.** diff --git a/src/examples/sqrt-task-network/SelfRegisterSqrtTaskMiddleware.sol b/src/examples/sqrt-task-network/SelfRegisterSqrtTaskMiddleware.sol new file mode 100644 index 0000000..99a101b --- /dev/null +++ b/src/examples/sqrt-task-network/SelfRegisterSqrtTaskMiddleware.sol @@ -0,0 +1,179 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.25; + +import {IVault} from "@symbiotic/interfaces/vault/IVault.sol"; +import {IBaseDelegator} from "@symbiotic/interfaces/delegator/IBaseDelegator.sol"; +import {Subnetwork} from "@symbiotic/contracts/libraries/Subnetwork.sol"; + +import {Math} from "@openzeppelin/contracts/utils/math/Math.sol"; +import {SignatureChecker} from "@openzeppelin/contracts/utils/cryptography/SignatureChecker.sol"; + +import {BaseMiddleware} from "../../middleware/BaseMiddleware.sol"; +import {SharedVaults} from "../../extensions/SharedVaults.sol"; +import {SelfRegisterOperators} from "../../extensions/operators/SelfRegisterOperators.sol"; + +import {ECDSASig} from "../../extensions/managers/sigs/ECDSASig.sol"; +import {OwnableAccessManager} from "../../extensions/managers/access/OwnableAccessManager.sol"; +import {KeyManager256} from "../../extensions/managers/keys/KeyManager256.sol"; +import {TimestampCapture} from "../../extensions/managers/capture-timestamps/TimestampCapture.sol"; +import {EqualStakePower} from "../../extensions/managers/stake-powers/EqualStakePower.sol"; + +contract SelfRegisterSqrtTaskMiddleware is + SharedVaults, + SelfRegisterOperators, + ECDSASig, + KeyManager256, + OwnableAccessManager, + TimestampCapture, + EqualStakePower +{ + using Subnetwork for address; + using Math for uint256; + + error InvalidHints(); + error TaskCompleted(); + + event CreateTask(uint256 indexed taskIndex); + event CompleteTask(uint256 indexed taskIndex, bool isValidAnswer); + + struct Task { + uint48 captureTimestamp; + uint256 value; + address operator; + bool completed; + } + + bytes32 private constant COMPLETE_TASK_TYPEHASH = keccak256("CompleteTask(uint256 taskIndex,uint256 answer)"); + + Task[] public tasks; + + constructor( + address network, + uint48 slashingWindow, + address operatorRegistry, + address vaultRegistry, + address operatorNetOptin, + address reader, + address owner + ) { + initialize(network, slashingWindow, vaultRegistry, operatorRegistry, operatorNetOptin, reader, owner); + } + + function initialize( + address network, + uint48 slashingWindow, + address vaultRegistry, + address operatorRegistry, + address operatorNetOptin, + address reader, + address owner + ) internal initializer { + __BaseMiddleware_init(network, slashingWindow, vaultRegistry, operatorRegistry, operatorNetOptin, reader); + __OwnableAccessManager_init(owner); + __SelfRegisterOperators_init("SqrtTaskMiddleware"); + } + + function createTask(uint256 value, address operator) external returns (uint256 taskIndex) { + taskIndex = tasks.length; + tasks.push(Task({captureTimestamp: getCaptureTimestamp(), value: value, operator: operator, completed: false})); + + emit CreateTask(taskIndex); + } + + function completeTask( + uint256 taskIndex, + uint256 answer, + bytes calldata signature, + bytes[] calldata stakeHints, + bytes[] calldata slashHints + ) external returns (bool isValidAnswer) { + isValidAnswer = _verify(taskIndex, answer, signature); + + tasks[taskIndex].completed = true; + + if (!isValidAnswer) { + _slash(taskIndex, stakeHints, slashHints); + } + + emit CompleteTask(taskIndex, isValidAnswer); + } + + function _verify(uint256 taskIndex, uint256 answer, bytes calldata signature) private view returns (bool) { + if (tasks[taskIndex].completed) { + revert TaskCompleted(); + } + _verifySignature(taskIndex, answer, signature); + return _verifyAnswer(taskIndex, answer); + } + + function _verifySignature(uint256 taskIndex, uint256 answer, bytes calldata signature) private view { + Task storage task = tasks[taskIndex]; + + bytes32 hash_ = _hashTypedDataV4(keccak256(abi.encode(COMPLETE_TASK_TYPEHASH, taskIndex, answer))); + + if (!SignatureChecker.isValidSignatureNow(task.operator, hash_, signature)) { + revert InvalidSignature(); + } + } + + function _verifyAnswer(uint256 taskIndex, uint256 answer) private view returns (bool) { + uint256 value = tasks[taskIndex].value; + uint256 square = answer ** 2; + if (square == value) { + return true; + } + + if (square < value) { + uint256 difference = value - square; + uint256 nextSquare = (answer + 1) ** 2; + uint256 nextDifference = nextSquare > value ? nextSquare - value : value - nextSquare; + if (difference <= nextDifference) { + return true; + } + } else { + uint256 difference = square - value; + uint256 prevSquare = (answer - 1) ** 2; + uint256 prevDifference = prevSquare > value ? prevSquare - value : value - prevSquare; + if (difference <= prevDifference) { + return true; + } + } + + return false; + } + + function _slash(uint256 taskIndex, bytes[] calldata stakeHints, bytes[] calldata slashHints) private { + Task storage task = tasks[taskIndex]; + address[] memory vaults = _activeVaultsAt(task.captureTimestamp, task.operator); + uint256 vaultsLength = vaults.length; + + if (stakeHints.length != slashHints.length || stakeHints.length != vaultsLength) { + revert InvalidHints(); + } + + bytes32 subnetwork = _NETWORK().subnetwork(0); + for (uint256 i; i < vaultsLength; ++i) { + address vault = vaults[i]; + uint256 slashAmount = IBaseDelegator(IVault(vault).delegator()).stakeAt( + subnetwork, task.operator, task.captureTimestamp, stakeHints[i] + ); + + if (slashAmount == 0) { + continue; + } + + _slashVault(task.captureTimestamp, vault, subnetwork, task.operator, slashAmount, slashHints[i]); + } + } + + function executeSlash( + uint48 epochStart, + address vault, + bytes32 subnetwork, + address operator, + uint256 amount, + bytes memory hints + ) external checkAccess { + _slashVault(epochStart, vault, subnetwork, operator, amount, hints); + } +} From a75ae356b9941dcefe99c6da703ff02cb192683b Mon Sep 17 00:00:00 2001 From: alrxy Date: Thu, 12 Dec 2024 15:21:08 +0400 Subject: [PATCH 2/2] fix: eip712 name --- .../sqrt-task-network/SelfRegisterSqrtTaskMiddleware.sol | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/examples/sqrt-task-network/SelfRegisterSqrtTaskMiddleware.sol b/src/examples/sqrt-task-network/SelfRegisterSqrtTaskMiddleware.sol index 99a101b..6974430 100644 --- a/src/examples/sqrt-task-network/SelfRegisterSqrtTaskMiddleware.sol +++ b/src/examples/sqrt-task-network/SelfRegisterSqrtTaskMiddleware.sol @@ -70,7 +70,7 @@ contract SelfRegisterSqrtTaskMiddleware is ) internal initializer { __BaseMiddleware_init(network, slashingWindow, vaultRegistry, operatorRegistry, operatorNetOptin, reader); __OwnableAccessManager_init(owner); - __SelfRegisterOperators_init("SqrtTaskMiddleware"); + __SelfRegisterOperators_init("SelfRegisterSqrtTaskMiddleware"); } function createTask(uint256 value, address operator) external returns (uint256 taskIndex) {