From 2dff29c059893a29e75e8c036d92c1b0b7770cd9 Mon Sep 17 00:00:00 2001 From: Udit Yadav Date: Tue, 17 Feb 2026 11:49:26 +0530 Subject: [PATCH 1/8] refactored to use diamond proxy --- .../script/DeployAutomationRegistry.s.sol | 129 -- .../script/DeployDiamond.s.sol | 66 + .../src/AutomationController.sol | 812 ------------ .../supra_contracts/src/AutomationCore.sol | 1027 --------------- .../src/AutomationRegistry.sol | 699 ---------- solidity/supra_contracts/src/BlockMeta.sol | 4 +- solidity/supra_contracts/src/CommonUtils.sol | 132 -- solidity/supra_contracts/src/Diamond.sol | 63 + .../src/IAutomationController.sol | 34 - .../supra_contracts/src/IAutomationCore.sol | 113 -- .../src/IAutomationRegistry.sol | 45 - solidity/supra_contracts/src/LibConfig.sol | 379 ------ .../supra_contracts/src/LibController.sol | 234 ---- solidity/supra_contracts/src/LibRegistry.sol | 207 --- .../src/SupraContractsBindings.sol | 20 +- .../src/facets/ConfigFacet.sol | 210 +++ .../supra_contracts/src/facets/CoreFacet.sol | 105 ++ .../src/facets/DiamondCutFacet.sol | 30 + .../src/facets/DiamondLoupeFacet.sol | 67 + .../src/facets/OwnershipFacet.sol | 16 + .../src/facets/RegistryFacet.sol | 609 +++++++++ .../src/interfaces/IConfigFacet.sol | 26 + .../src/interfaces/ICoreFacet.sol | 21 + .../src/interfaces/IDiamondCut.sol | 32 + .../src/interfaces/IDiamondLoupe.sol | 38 + .../src/interfaces/IERC165.sol | 12 + .../src/interfaces/IERC173.sol | 19 + .../src/interfaces/IRegistryFacet.sol | 53 + .../src/libraries/LibAppStorage.sol | 120 ++ .../supra_contracts/src/libraries/LibCore.sol | 697 ++++++++++ .../src/libraries/LibDiamond.sol | 214 +++ .../src/libraries/LibDiamondUtils.sol | 298 +++++ .../src/libraries/LibRegistry.sol | 711 ++++++++++ .../src/libraries/LibUtils.sol | 162 +++ .../src/upgradeInitializers/DiamondInit.sol | 144 ++ .../test/AutomationController.t.sol | 683 ---------- .../supra_contracts/test/AutomationCore.t.sol | 945 -------------- .../test/AutomationRegistry.t.sol | 1161 ----------------- .../test/BaseDiamondTest.t.sol | 101 ++ solidity/supra_contracts/test/BlockMeta.t.sol | 10 +- .../supra_contracts/test/ConfigFacet.t.sol | 436 +++++++ solidity/supra_contracts/test/CoreFacet.t.sol | 440 +++++++ .../supra_contracts/test/DiamondInit.t.sol | 640 +++++++++ .../supra_contracts/test/RegistryFacet.t.sol | 838 ++++++++++++ 44 files changed, 6185 insertions(+), 6617 deletions(-) delete mode 100644 solidity/supra_contracts/script/DeployAutomationRegistry.s.sol create mode 100644 solidity/supra_contracts/script/DeployDiamond.s.sol delete mode 100644 solidity/supra_contracts/src/AutomationController.sol delete mode 100644 solidity/supra_contracts/src/AutomationCore.sol delete mode 100644 solidity/supra_contracts/src/AutomationRegistry.sol delete mode 100644 solidity/supra_contracts/src/CommonUtils.sol create mode 100644 solidity/supra_contracts/src/Diamond.sol delete mode 100644 solidity/supra_contracts/src/IAutomationController.sol delete mode 100644 solidity/supra_contracts/src/IAutomationCore.sol delete mode 100644 solidity/supra_contracts/src/IAutomationRegistry.sol delete mode 100644 solidity/supra_contracts/src/LibConfig.sol delete mode 100644 solidity/supra_contracts/src/LibController.sol delete mode 100644 solidity/supra_contracts/src/LibRegistry.sol create mode 100644 solidity/supra_contracts/src/facets/ConfigFacet.sol create mode 100644 solidity/supra_contracts/src/facets/CoreFacet.sol create mode 100644 solidity/supra_contracts/src/facets/DiamondCutFacet.sol create mode 100644 solidity/supra_contracts/src/facets/DiamondLoupeFacet.sol create mode 100644 solidity/supra_contracts/src/facets/OwnershipFacet.sol create mode 100644 solidity/supra_contracts/src/facets/RegistryFacet.sol create mode 100644 solidity/supra_contracts/src/interfaces/IConfigFacet.sol create mode 100644 solidity/supra_contracts/src/interfaces/ICoreFacet.sol create mode 100644 solidity/supra_contracts/src/interfaces/IDiamondCut.sol create mode 100644 solidity/supra_contracts/src/interfaces/IDiamondLoupe.sol create mode 100644 solidity/supra_contracts/src/interfaces/IERC165.sol create mode 100644 solidity/supra_contracts/src/interfaces/IERC173.sol create mode 100644 solidity/supra_contracts/src/interfaces/IRegistryFacet.sol create mode 100644 solidity/supra_contracts/src/libraries/LibAppStorage.sol create mode 100644 solidity/supra_contracts/src/libraries/LibCore.sol create mode 100644 solidity/supra_contracts/src/libraries/LibDiamond.sol create mode 100644 solidity/supra_contracts/src/libraries/LibDiamondUtils.sol create mode 100644 solidity/supra_contracts/src/libraries/LibRegistry.sol create mode 100644 solidity/supra_contracts/src/libraries/LibUtils.sol create mode 100644 solidity/supra_contracts/src/upgradeInitializers/DiamondInit.sol delete mode 100644 solidity/supra_contracts/test/AutomationController.t.sol delete mode 100644 solidity/supra_contracts/test/AutomationCore.t.sol delete mode 100644 solidity/supra_contracts/test/AutomationRegistry.t.sol create mode 100644 solidity/supra_contracts/test/BaseDiamondTest.t.sol create mode 100644 solidity/supra_contracts/test/ConfigFacet.t.sol create mode 100644 solidity/supra_contracts/test/CoreFacet.t.sol create mode 100644 solidity/supra_contracts/test/DiamondInit.t.sol create mode 100644 solidity/supra_contracts/test/RegistryFacet.t.sol diff --git a/solidity/supra_contracts/script/DeployAutomationRegistry.s.sol b/solidity/supra_contracts/script/DeployAutomationRegistry.s.sol deleted file mode 100644 index 5955c1bcc7..0000000000 --- a/solidity/supra_contracts/script/DeployAutomationRegistry.s.sol +++ /dev/null @@ -1,129 +0,0 @@ -// SPDX-License-Identifier: MIT -pragma solidity 0.8.27; - -import {Script, console} from "forge-std/Script.sol"; -import {AutomationCore} from "../src/AutomationCore.sol"; -import {AutomationController} from "../src/AutomationController.sol"; -import {AutomationRegistry} from "../src/AutomationRegistry.sol"; -import {ERC1967Proxy} from "../lib/openzeppelin-contracts/contracts/proxy/ERC1967/ERC1967Proxy.sol"; - -contract DeployAutomationRegistry is Script { - uint64 taskDurationCapSecs; - uint128 registryMaxGasCap; - uint128 automationBaseFeeWeiPerSec; - uint128 flatRegistrationFeeWei; - uint8 congestionThresholdPercentage; - uint128 congestionBaseFeeWeiPerSec; - uint8 congestionExponent; - uint16 taskCapacity; - uint64 cycleDurationSecs; - uint64 sysTaskDurationCapSecs; - uint128 sysRegistryMaxGasCap; - uint16 sysTaskCapacity; - address vmSigner; - address erc20Supra; - - // Config values loaded from .env file - function setUp() public { - taskDurationCapSecs = uint64(vm.envUint("TASK_DURATION_CAP_SEC")); - registryMaxGasCap = uint128(vm.envUint("REGISTRY_MAX_GAS_CAP")); - automationBaseFeeWeiPerSec = uint128(vm.envUint("AUTOMATION_BASE_FEE_PER_SEC")); - flatRegistrationFeeWei = uint128(vm.envUint("FLAT_REGISTRATION_FEE")); - congestionThresholdPercentage = uint8(vm.envUint("CONGESTION_THRESHOLD_PERCENTAGE")); - congestionBaseFeeWeiPerSec = uint128(vm.envUint("CONGESTION_BASE_FEE_PER_SEC")); - congestionExponent = uint8(vm.envUint("CONGESTION_EXPONENT")); - taskCapacity = uint16(vm.envUint("TASK_CAPACITY")); - cycleDurationSecs = uint64(vm.envUint("CYCLE_DURATION_SEC")); - sysTaskDurationCapSecs = uint64(vm.envUint("SYS_TASK_DURATION_CAP_SEC")); - sysRegistryMaxGasCap = uint128(vm.envUint("SYS_REGISTRY_MAX_GAS_CAP")); - sysTaskCapacity = uint16(vm.envUint("SYS_TASK_CAPACITY")); - vmSigner = vm.envAddress("VM_SIGNER"); - erc20Supra = vm.envAddress("ERC20_SUPRA"); - } - - function run() public { - vm.startBroadcast(); - - AutomationCore coreImpl; // AutomationCore implementation contract - ERC1967Proxy coreProxy; // AutomationCore proxy contract - AutomationCore automationCore; // Instance of AutomationCore at proxy address - - AutomationRegistry registryImpl; // AutomationRegistry implementation contract - ERC1967Proxy registryProxy; // AutomationRegistry proxy contract - AutomationRegistry registry; // Instance of AutomationRegistry at proxy address - - AutomationController controllerImpl; // AutomationController implementation contract - ERC1967Proxy controllerProxy; // AutomationController proxy contract - AutomationController controller; // Instance of AutomationController at proxy address - - // --------------------------------------------------------------------- - // Deploy AutomationCore - // --------------------------------------------------------------------- - coreImpl = new AutomationCore(); - console.log("AutomationCore implementation deployed at: ", address(coreImpl)); - bytes memory coreInitData = abi.encodeCall( - AutomationCore.initialize, - ( - taskDurationCapSecs, // taskDurationCapSecs - registryMaxGasCap, // registryMaxGasCap - automationBaseFeeWeiPerSec, // automationBaseFeeWeiPerSec - flatRegistrationFeeWei, // flatRegistrationFeeWei - congestionThresholdPercentage, // congestionThresholdPercentage - congestionBaseFeeWeiPerSec, // congestionBaseFeeWeiPerSec - congestionExponent, // congestionExponent - taskCapacity, // taskCapacity - cycleDurationSecs, // cycleDurationSecs - sysTaskDurationCapSecs, // sysTaskDurationCapSecs - sysRegistryMaxGasCap, // sysRegistryMaxGasCap - sysTaskCapacity, // sysTaskCapacity - vmSigner, // VM Signer address - erc20Supra // ERC20Supra address - ) - ); - coreProxy = new ERC1967Proxy(address(coreImpl), coreInitData); - console.log("AutomationCore proxy deployed at: ", address(coreProxy)); - automationCore = AutomationCore(address(coreProxy)); - - // --------------------------------------------------------------------- - // Deploy AutomationRegistry - // --------------------------------------------------------------------- - registryImpl = new AutomationRegistry(); - console.log("AutomationRegistry implementation deployed at: ", address(registryImpl)); - - bytes memory registryInitData = abi.encodeCall(AutomationRegistry.initialize, (address(automationCore))); - registryProxy = new ERC1967Proxy(address(registryImpl), registryInitData); - console.log("AutomationRegistry proxy deployed at: ", address(registryProxy)); - registry = AutomationRegistry(address(registryProxy)); - - // --------------------------------------------------------------------- - // Deploy AutomationController - // --------------------------------------------------------------------- - controllerImpl = new AutomationController(); - console.log("AutomationController implementation deployed at: ", address(controllerImpl)); - - bytes memory controllerInitData = abi.encodeCall( - AutomationController.initialize, - ( - address(automationCore), - address(registry), - true - ) - ); - controllerProxy = new ERC1967Proxy(address(controllerImpl), controllerInitData); - console.log("AutomationController proxy deployed at: ", address(controllerProxy)); - controller = AutomationController(address(controllerProxy)); - - // -------------------------------------------------------------------------- - // Set AutomationRegistry and AutomationController address in AutomationCore - // -------------------------------------------------------------------------- - automationCore.setAutomationRegistry(address(registry)); - automationCore.setAutomationController(address(controller)); - - // -------------------------------------------------------------------------- - // Set AutomationController address in AutomationRegistry - // -------------------------------------------------------------------------- - registry.setAutomationController(address(controller)); - - vm.stopBroadcast(); - } -} diff --git a/solidity/supra_contracts/script/DeployDiamond.s.sol b/solidity/supra_contracts/script/DeployDiamond.s.sol new file mode 100644 index 0000000000..c1f899a886 --- /dev/null +++ b/solidity/supra_contracts/script/DeployDiamond.s.sol @@ -0,0 +1,66 @@ +// SPDX-License-Identifier: MIT +pragma solidity 0.8.27; + +import {Script, console} from "forge-std/Script.sol"; +import {Diamond} from "../src/Diamond.sol"; +import {IDiamondCut} from "../src/interfaces/IDiamondCut.sol"; +import {DiamondCutFacet} from "../src/facets/DiamondCutFacet.sol"; +import {DiamondLoupeFacet} from "../src/facets/DiamondLoupeFacet.sol"; +import {OwnershipFacet} from "../src/facets/OwnershipFacet.sol"; +import {RegistryFacet} from "../src/facets/RegistryFacet.sol"; +import {CoreFacet} from "../src/facets/CoreFacet.sol"; +import {DiamondInit} from "../src/upgradeInitializers/DiamondInit.sol"; +import {Deployment, InitParams, LibDiamondUtils} from "../src/libraries/LibDiamondUtils.sol"; + +contract DeployDiamond is Script { + address vmSigner; + address erc20Supra; + address multiSig; + + InitParams initParams; + + // Config values loaded from .env file + function setUp() public { + initParams = InitParams({ + taskDurationCapSecs: uint64(vm.envUint("TASK_DURATION_CAP_SEC")), + registryMaxGasCap: uint128(vm.envUint("REGISTRY_MAX_GAS_CAP")), + automationBaseFeeWeiPerSec: uint128(vm.envUint("AUTOMATION_BASE_FEE_PER_SEC")), + flatRegistrationFeeWei: uint128(vm.envUint("FLAT_REGISTRATION_FEE")), + congestionThresholdPercentage: uint8(vm.envUint("CONGESTION_THRESHOLD_PERCENTAGE")), + congestionBaseFeeWeiPerSec: uint128(vm.envUint("CONGESTION_BASE_FEE_PER_SEC")), + congestionExponent: uint8(vm.envUint("CONGESTION_EXPONENT")), + taskCapacity: uint16(vm.envUint("TASK_CAPACITY")), + cycleDurationSecs: uint64(vm.envUint("CYCLE_DURATION_SEC")), + sysTaskDurationCapSecs: uint64(vm.envUint("SYS_TASK_DURATION_CAP_SEC")), + sysRegistryMaxGasCap: uint128(vm.envUint("SYS_REGISTRY_MAX_GAS_CAP")), + sysTaskCapacity: uint16(vm.envUint("SYS_TASK_CAPACITY")), + automationEnabled: vm.envBool("AUTOMATION_ENABLED"), + registrationEnabled: vm.envBool("REGISTRATION_ENABLED") + }); + + vmSigner = vm.envAddress("VM_SIGNER"); + erc20Supra = vm.envAddress("ERC20_SUPRA"); + multiSig = vm.envAddress("MULTI_SIG"); + } + + function run() external { + vm.startBroadcast(); + + // Deploy the Diamond, its facets and the DiamondInit + Deployment memory deployment = LibDiamondUtils.deploy(multiSig); + + // Execute the diamond cut to initialize the Diamond state + LibDiamondUtils.executeCut(vmSigner, erc20Supra, initParams, deployment); + + console.log("Diamond owner:", OwnershipFacet(address(deployment.diamond)).owner()); + console.log("Diamond deployed at:", address(deployment.diamond)); + console.log("DiamondCutFacet deployed at:", address(deployment.diamondCutFacet)); + console.log("DiamondLoupeFacet deployed at:", address(deployment.loupeFacet)); + console.log("OwnershipFacet deployed at:", address(deployment.ownershipFacet)); + console.log("RegistryFacet deployed at:", address(deployment.registryFacet)); + console.log("CoreFacet deployed at:", address(deployment.coreFacet)); + console.log("DiamondInit deployed at:", address(deployment.diamondInit)); + + vm.stopBroadcast(); + } +} \ No newline at end of file diff --git a/solidity/supra_contracts/src/AutomationController.sol b/solidity/supra_contracts/src/AutomationController.sol deleted file mode 100644 index 5f035786ff..0000000000 --- a/solidity/supra_contracts/src/AutomationController.sol +++ /dev/null @@ -1,812 +0,0 @@ -// SPDX-License-Identifier: MIT -pragma solidity 0.8.27; - -import {EnumerableSet} from "../lib/openzeppelin-contracts/contracts/utils/structs/EnumerableSet.sol"; -import {CommonUtils} from "./CommonUtils.sol"; -import {LibController} from "./LibController.sol"; - -import {IAutomationController} from "./IAutomationController.sol"; -import {IAutomationCore} from "./IAutomationCore.sol"; -import {IAutomationRegistry} from "./IAutomationRegistry.sol"; -import {IERC20} from "../lib/openzeppelin-contracts/contracts/token/ERC20/IERC20.sol"; -import {Ownable2StepUpgradeable} from "../lib/openzeppelin-contracts-upgradeable/contracts/access/Ownable2StepUpgradeable.sol"; -import {UUPSUpgradeable} from "../lib/openzeppelin-contracts/contracts/proxy/utils/UUPSUpgradeable.sol"; - -contract AutomationController is IAutomationController, Ownable2StepUpgradeable, UUPSUpgradeable { - using EnumerableSet for EnumerableSet.UintSet; - using CommonUtils for *; - using LibController for *; - - /// @dev State variables - LibController.AutomationCycleInfo cycleInfo; - address public registry; - address public automationCore; - - // ::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::: EVENTS ::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::: - - /// @notice Emitted when a task is removed as fee exceeds task's automation fee cap for the cycle. - event TaskCancelledCapacitySurpassed( - uint64 indexed taskIndex, - address indexed owner, - uint128 fee, - uint128 automationFeeCapForCycle, - bytes32 registrationHash - ); - - /// @notice Emitted when a task is removed due to insufficient balance. - event TaskCancelledInsufficentBalance( - uint64 indexed taskIndex, - address indexed owner, - uint128 fee, - uint256 balance, - bytes32 registrationHash - ); - - /// @notice Emitted when an automation fee is charged for an automation task for the cycle. - event TaskCycleFeeWithdraw( - uint64 indexed taskIndex, - address indexed owner, - uint128 fee - ); - - /// @notice Emitted when the cycle state transitions. - event AutomationCycleEvent( - uint64 indexed index, - CommonUtils.CycleState indexed state, - uint64 startTime, - uint64 durationSecs, - CommonUtils.CycleState indexed oldState - ); - - /// @notice Event emitted on cycle transition containing active task indexes for the new cycle. - event ActiveTasks(uint256[] indexed taskIndexes); - - /// @notice Event emitted on cycle transition containing removed task indexes. - event RemovedTasks(uint64[] indexed taskIndexes); - - /// @notice Event emitted when on a new cycle inconsistent state of the registry has been identified. - /// When automation is in suspended state, there are no tasks expected. - event ErrorInconsistentSuspendedState(); - - /// @notice Emitted when the AutomationRegistry contract address is updated. - event AutomationRegistryUpdated(address indexed oldRegistryAddress, address indexed newRegistryAddress); - - /// @notice Emitted when the AutomationCore contract address is updated. - event AutomationCoreUpdated(address indexed oldAutomationCore, address indexed newAutomationCore); - - /// @notice Emitted when automation is enabled. - event AutomationEnabled(bool indexed status); - - /// @notice Emitted when automation is disabled. - event AutomationDisabled(bool indexed status); - - // :::::::::::::::::::::::::::::::::::::::::::::::::::::::::: CONSTRUCTOR AND INITIALIZER :::::::::::::::::::::::::::::::::::::::::::::::::::::::::: - - /// @dev Disables the initialization for the implementation contract. - constructor() { - _disableInitializers(); - } - - /// @notice Initializes the configuration parameters of the contract, can only be called once. - /// @param _automationCore Address of the AutomationCore smart contract. - /// @param _registry Address of the AutomationRegistry smart contract. - /// @param _automationEnabled Bool to set automation enabled status. - function initialize(address _automationCore, address _registry, bool _automationEnabled) public initializer { - _automationCore.validateContractAddress(); - _registry.validateContractAddress(); - - automationCore = _automationCore; - registry = _registry; - - (CommonUtils.CycleState state, uint64 cycleId) = _automationEnabled ? (CommonUtils.CycleState.STARTED, 1) : (CommonUtils.CycleState.READY, 0); - - cycleInfo.initializeCycle( - cycleId, - uint64(block.timestamp), - IAutomationCore(_automationCore).cycleDurationSecs(), - state, - _automationEnabled - ); - - __Ownable2Step_init(); - __Ownable_init(msg.sender); - } - - /// @notice Called by the VM Signer on `AutomationBookkeepingAction::Process` action emitted by native layer ahead of the cycle transition. - /// @param _cycleIndex Index of the cycle. - /// @param _taskIndexes Array of task index to be processed. - function processTasks(uint64 _cycleIndex, uint64[] memory _taskIndexes) external { - // Check caller is VM Signer - if (msg.sender != IAutomationCore(automationCore).getVmSigner()) { revert CallerNotVmSigner(); } - - CommonUtils.CycleState state = cycleInfo.state(); - - if(state == CommonUtils.CycleState.FINISHED) { - onCycleTransition(_cycleIndex, _taskIndexes); - } else { - if(state != CommonUtils.CycleState.SUSPENDED) { revert InvalidRegistryState(); } - onCycleSuspend(_cycleIndex, _taskIndexes); - } - } - - /// @notice Checks the cycle end and emit an event on it. Does nothing if SUPRA_NATIVE_AUTOMATION or SUPRA_AUTOMATION_V2 is disabled. - function monitorCycleEnd() external { - if (tx.origin != IAutomationCore(automationCore).getVmSigner()) { revert CallerNotVmSigner(); } - - if(!isCycleStarted() || getCycleEndTime() > block.timestamp) { - return; - } - - onCycleEndInternal(); - } - - // ::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::: HELPER FUNCTIONS ::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::: - - /// @notice Traverses the list of the tasks and based on the task state and expiry information either charges or drops the task after refunding eligable fees. - /// Tasks are checked not to be processed more than once. - /// This function should be called only if registry is in FINISHED state, meaning a normal cycle transition is happening. - /// After processing all input tasks, intermediate transition state is updated and transition end is checked (whether all expected tasks has been processed already). - /// In case if transition end is detected a start of the new cycle is given (if during trasition period suspention is not requested) and corresponding event is emitted. - /// @param _cycleIndex Cycle index of the new cycle to which the transition is being done. - /// @param _taskIndexes Array of task indexes to be processed. - function onCycleTransition(uint64 _cycleIndex, uint64[] memory _taskIndexes) private { - if(_taskIndexes.length == 0) { return; } - - if(cycleInfo.state() != CommonUtils.CycleState.FINISHED) { revert InvalidRegistryState(); } - - // Check if transition state exists - if(!cycleInfo.ifTransitionStateExists()) { revert InvalidRegistryState(); } - if(cycleInfo.index() + 1 != _cycleIndex) { revert InvalidInputCycleIndex(); } - - LibController.IntermediateStateOfCycleChange memory intermediateState = dropOrChargeTasks(_taskIndexes); - - cycleInfo.transitionState.lockedFees += intermediateState.cycleLockedFees; - cycleInfo.setGasCommittedForNextCycle(cycleInfo.gasCommittedForNextCycle() + intermediateState.gasCommittedForNextCycle); - cycleInfo.setSysGasCommittedForNextCycle(cycleInfo.sysGasCommittedForNextCycle() + intermediateState.sysGasCommittedForNextCycle); - - updateCycleTransitionStateFromFinished(); - if(intermediateState.removedTasks.length > 0) { - emit RemovedTasks(intermediateState.removedTasks); - } - } - - /// @notice Traverses the list of the tasks and refunds automation(if not PENDING) and deposit fees for all tasks and removes from registry. - /// This function is called only if automation feature is disabled, i.e. cycle is in SUSPENDED state. - /// After processing input set of tasks the end of suspention process is checked(i.e. all expected tasks have been processed). - /// In case if end is identified, the registry state is update to READY and corresponding event is emitted. - /// @param _cycleIndex Input cycle index of the cycle being suspended. - /// @param _taskIndexes Array of task indexes to be processed. - function onCycleSuspend(uint64 _cycleIndex, uint64[] memory _taskIndexes) private { - if (_taskIndexes.length == 0) { return; } - - if(cycleInfo.state() != CommonUtils.CycleState.SUSPENDED) { revert InvalidRegistryState(); } - if(cycleInfo.index() != _cycleIndex) { revert InvalidInputCycleIndex(); } - // Check if transition state exists - if(!cycleInfo.ifTransitionStateExists()) { revert InvalidRegistryState(); } - - uint64 currentTime = uint64(block.timestamp); - - // Sort task indexes as order is important - uint64[] memory taskIndexes = _taskIndexes.sortUint64(); - uint64[] memory removedTasks = new uint64[](taskIndexes.length); - - IAutomationRegistry automationRegistry = IAutomationRegistry(registry); - uint64 removedCounter; - for (uint i = 0; i < taskIndexes.length; i++) { - if(automationRegistry.ifTaskExists(taskIndexes[i])) { - CommonUtils.TaskDetails memory task = automationRegistry.getTaskDetails(taskIndexes[i]); - - (bool removed, ) = address(automationRegistry).call(abi.encodeCall(IAutomationRegistry.removeTask, (taskIndexes[i], false))); - require(removed, RemoveTaskFailed()); - - removedTasks[removedCounter++] = taskIndexes[i]; - markTaskProcessed(taskIndexes[i]); - - // Nothing to refund for GST tasks - if (task.taskType == CommonUtils.TaskType.UST) { - (bool refunded, ) = automationCore.call( - abi.encodeCall( - IAutomationCore.refundTaskFees, - (currentTime, cycleInfo.refundDuration(), cycleInfo.automationFeePerSec(), task) - ) - ); - require(refunded, RefundFailed()); - } - } - } - - updateCycleTransitionStateFromSuspended(); - emit RemovedTasks(removedTasks); - } - - /// @notice Traverses all input task indexes and either drops or tries to charge automation fee if possible. - /// @param _taskIndexes Input task indexes. - /// @return intermediateState Returns the intermediate state. - function dropOrChargeTasks( - uint64[] memory _taskIndexes - ) private returns (LibController.IntermediateStateOfCycleChange memory intermediateState) { - uint64 currentTime = uint64(block.timestamp); - uint64 currentCycleEndTime = currentTime + cycleInfo.newCycleDuration(); - - // Sort task indexes to charge automation fees in their chronological order - uint64[] memory taskIndexes = _taskIndexes.sortUint64(); - - uint64[] memory removedBuffer = new uint64[](taskIndexes.length); - uint256 removedCount; - - // Process each active task and calculate fee for the cycle for the tasks - for (uint256 i = 0; i < taskIndexes.length; i++) { - LibController.TransitionResult memory result = dropOrChargeTask( - taskIndexes[i], - currentTime, - currentCycleEndTime - ); - - if (result.isRemoved) { - removedBuffer[removedCount] = taskIndexes[i]; - removedCount += 1; - } - - intermediateState.gasCommittedForNextCycle += result.gas; - intermediateState.sysGasCommittedForNextCycle += result.sysGas; - intermediateState.cycleLockedFees += result.fees; - } - - uint64[] memory removedTasks = new uint64[](removedCount); - for (uint256 j = 0; j < removedCount; j++) { - removedTasks[j] = removedBuffer[j]; - } - intermediateState.removedTasks = removedTasks; - } - - /// @notice Drops or charges the input task. If the task is already processed or missing from the registry then nothing is done. - /// @param _taskIndex Task index to be dropped or charged. - /// @param _currentTime Current time. - /// @param _currentCycleEndTime End time of the current cycle. - /// @return result Returns the TransitionResult. - function dropOrChargeTask( - uint64 _taskIndex, - uint64 _currentTime, - uint64 _currentCycleEndTime - ) private returns (LibController.TransitionResult memory result){ - address registryAddr = registry; - if(IAutomationRegistry(registryAddr).ifTaskExists(_taskIndex)) { - markTaskProcessed(_taskIndex); - - CommonUtils.TaskDetails memory task = IAutomationRegistry(registryAddr).getTaskDetails(_taskIndex); - bool isUst = task.taskType == CommonUtils.TaskType.UST; - - // Task is cancelled or expired - if(task.state == CommonUtils.TaskState.CANCELLED || _currentTime >= task.expiryTime) { - if(isUst) { - (bool sent, ) = registryAddr.call( - abi.encodeCall( - IAutomationRegistry.refundDepositAndDrop, - (_taskIndex, task.owner, task.depositFee, task.depositFee) - ) - ); - require(sent, RefundDepositAndDropFailed()); - } else { - // Remove the task from registry and system registry - (bool removed, ) = registryAddr.call(abi.encodeCall(IAutomationRegistry.removeTask, (_taskIndex, true))); - require(removed, RemoveTaskFailed()); - } - result.isRemoved = true; - } else if(!isUst) { - // Active GST - // Governance submitted tasks are not charged - - result.sysGas = task.maxGasAmount; - (bool updated, ) = registryAddr.call(abi.encodeCall(IAutomationRegistry.updateTaskState, (_taskIndex, CommonUtils.TaskState.ACTIVE))); - require(updated, UpdateTaskStateFailed()); - } else { - // Active UST - uint128 fee = IAutomationCore(automationCore).calculateTaskFee( - task.state, - task.expiryTime, - task.maxGasAmount, - cycleInfo.newCycleDuration(), - _currentTime, - cycleInfo.automationFeePerSec() - ); - - // If the task reached this phase that means it is a valid active task for the new cycle. - // During cleanup all expired tasks has been removed from the registry but the state of the tasks is not updated. - // As here we need to distinguish new tasks from already existing active tasks, - // as the fee calculation for them will be different based on their active duration in the cycle. - // For more details see calculateTaskFee function. - (bool updated, ) = registryAddr.call(abi.encodeCall(IAutomationRegistry.updateTaskState, (_taskIndex, CommonUtils.TaskState.ACTIVE))); - require(updated, UpdateTaskStateFailed()); - - (result.isRemoved, result.gas, result.fees) = tryWithdrawTaskAutomationFee( - _taskIndex, - task.owner, - task.maxGasAmount, - task.expiryTime, - task.depositFee, - fee, - _currentCycleEndTime, - task.automationFeeCapForCycle, - task.txHash - ); - } - } - } - - /// @notice Marks a task as processed. - /// @param _taskIndex Index of the task to be marked as processed. - function markTaskProcessed(uint64 _taskIndex) private { - uint64 nextTaskIndexPosition = cycleInfo.nextTaskIndexPosition(); - - if(nextTaskIndexPosition >= cycleInfo.transitionState.expectedTasksToBeProcessed.length()) { revert InconsistentTransitionState(); } - uint64 expectedTask = uint64(cycleInfo.transitionState.expectedTasksToBeProcessed.at(nextTaskIndexPosition)); - - if(expectedTask != _taskIndex) { revert OutOfOrderTaskProcessingRequest(); } - cycleInfo.setNextTaskIndexPosition(nextTaskIndexPosition + 1); - } - - /// @notice Helper function to withdraw automation task fees for an active task. - /// @param _taskIndex Index of the task. - /// @param _owner Owner of the task. - /// @param _maxGasAmount Max gas amount of the task. - /// @param _expiryTime Expiry time of the task. - /// @param _depositFee Deposit fees of the task. - /// @param _fee Fees to be charged for the task. - /// @param _currentCycleEndTime End time of the current cycle. - /// @param _automationFeeCapForCycle Max automation fee for a cycle to be paid. - /// @param _regHash Tx hash of the task. - /// @return Bool representing if the task was removed. - /// @return Amount to add to gasCommittedForNextCycle - /// @return Amount to add to cycleLockedFees - function tryWithdrawTaskAutomationFee( - uint64 _taskIndex, - address _owner, - uint128 _maxGasAmount, - uint64 _expiryTime, - uint128 _depositFee, - uint128 _fee, - uint64 _currentCycleEndTime, - uint128 _automationFeeCapForCycle, - bytes32 _regHash - ) private returns (bool, uint128, uint128) { - // Remove the automation task if the cycle fee cap is exceeded. - // It might happen that task has been expired by the time charging is being done. - // This may be caused by the fact that bookkeeping transactions has been withheld due to cycle transition. - - address automationCoreAddr = automationCore; - address erc20Supra = IAutomationCore(automationCoreAddr).erc20Supra(); - bool isRemoved; - uint128 gas; - uint128 fees; - address registryAddr = registry; - if(_fee > _automationFeeCapForCycle) { - (bool sent, ) = registryAddr.call( - abi.encodeCall( - IAutomationRegistry.refundDepositAndDrop, - (_taskIndex, _owner, _depositFee, _depositFee) - ) - ); - require(sent, RefundDepositAndDropFailed()); - - isRemoved = true; - - emit TaskCancelledCapacitySurpassed( - _taskIndex, - _owner, - _fee, - _automationFeeCapForCycle, - _regHash - ); - } else { - uint256 userBalance = IERC20(erc20Supra).balanceOf(_owner); - if(userBalance < _fee) { - // If the user does not have enough balance, remove the task, DON'T refund the locked deposit, but simply unlock it and emit an event. - - (bool unlocked, ) = automationCoreAddr.call( - abi.encodeCall( - IAutomationCore.safeUnlockLockedDeposit, - (_taskIndex, _depositFee) - ) - ); - require(unlocked, UnlockLockedDepositFailed()); - - (bool removed, ) = registryAddr.call(abi.encodeCall(IAutomationRegistry.removeTask, (_taskIndex, false))); - require(removed, RemoveTaskFailed()); - isRemoved = true; - - emit TaskCancelledInsufficentBalance( - _taskIndex, - _owner, - _fee, - userBalance, - _regHash - ); - } else { - if(_fee != 0) { - // Charge the fee - (bool sent, ) = automationCoreAddr.call(abi.encodeCall(IAutomationCore.chargeFees, (_owner, _fee))); - if (!sent) { revert TransferFailed(); } - - fees = _fee; - } - - emit TaskCycleFeeWithdraw( - _taskIndex, - _owner, - _fee - ); - - // Calculate gas commitment for the next cycle only for valid active tasks - if (_expiryTime > _currentCycleEndTime) { - gas = _maxGasAmount; - } - } - } - - return (isRemoved, gas, fees); - } - - /// @notice Updates the cycle state if the transition is identified to be finalized. - /// From FINISHED state we always move to the next cycle and in STARTED state. - /// But if it happened so that there was a suspension during cycle transition which was ignored, then immediately cycle state is updated to suspended. - /// Expectation will be that native layer catches this double transition and issues refund for the new cycle fees which will not be proceeded further in any case. - function updateCycleTransitionStateFromFinished() private { - // Check if transition state exists - if(!cycleInfo.ifTransitionStateExists()) { revert InvalidRegistryState(); } - - bool transitionFinalized = isTransitionFinalized(); - if (transitionFinalized) { - if (!cycleInfo.automationEnabled() && cycleInfo.state() == CommonUtils.CycleState.FINISHED) { - tryMoveToSuspendedState(); - } else { - (bool updated, ) = automationCore.call( - abi.encodeCall( - IAutomationCore.updateGasCommittedAndCycleLockedFees, - ( - cycleInfo.transitionState.lockedFees, - cycleInfo.sysGasCommittedForNextCycle(), - cycleInfo.gasCommittedForNextCycle(), - cycleInfo.gasCommittedForNewCycle() - ) - ) - ); - require(updated, UpdateGasCommittedAndCycleLockedFeesFailed()); - - IAutomationRegistry automationRegistry = IAutomationRegistry(registry); - automationRegistry.updateTaskIds(CommonUtils.CycleState.FINISHED); - - // Set current timestamp as cycle start time - // Increment the cycle and update the state to STARTED - moveToStartedState(); - if(automationRegistry.getTotalActiveTasks() > 0 ) { - uint256[] memory activeTasks = automationRegistry.getAllActiveTaskIds(); - emit ActiveTasks(activeTasks); - } - } - } - } - - /// @notice Updates the cycle state if the transition is identified to be finalized. - /// As transition happens from suspended state and while transition was in progress - /// - if the feature was enabled back, then the transition will happen direclty to STARTED state, - /// - otherwise the transition will be done to the READY state. - /// - /// In both cases config will be updated. In this case we will make sure to keep the consistency of state - /// when transition to READY state happens through paths - /// - Started -> Suspended -> Ready - /// - or Started-> {Finished, Suspended} -> Ready - /// - or Started -> Finished -> {Started, Suspended} - function updateCycleTransitionStateFromSuspended() private { - // Check if transition state exists - if(!cycleInfo.ifTransitionStateExists()) { revert InvalidRegistryState(); } - if(!isTransitionFinalized()) { - return; - } - - (bool updated, )= automationCore.call(abi.encodeCall(IAutomationCore.updateGasCommittedAndCycleLockedFees, (0, 0, 0, 0))); - require(updated, UpdateGasCommittedAndCycleLockedFeesFailed()); - - IAutomationRegistry(registry).updateTaskIds(CommonUtils.CycleState.SUSPENDED); - - // Check if automation is enabled - if (cycleInfo.automationEnabled()) { - // Update the config in case if transition flow is STARTED -> SUSPENDED-> STARTED. - // to reflect new configs for the new cycle if it has been updated during SUSPENDED state processing - updateConfigFromBuffer(); - moveToStartedState(); - } else { - moveToReadyState(); - } - } - - /// @notice Transition to suspended state is expected to be called - /// a) when cycle is active and in progress - /// - here we simply move to suspended state so native layer can start requesting tasks processing - /// which will end up in refunds and cleanup. Note that refund will be done based on total gas-committed - /// for the current cycle defined at the begining for the cycle, and using current automation fee parameters - /// b) when cycle has just finished and there was another transaction causing feature suspension - /// - as this both events happen in scope of the same block, then we will simply update the state to suspended - /// and the native layer should identify the transition and request processing of the all available tasks. - /// Note that in this case automation fee refund will not be expected and suspention and cycle end matched and - /// no fee was yet charged to be refunded. - /// So the duration for refund and automation-fee-per-second for refund will be 0 - /// c) when cycle transition was in progress and there was a feature suspension, but it could not be applied, - /// and postponed till the cycle transition concludes - /// In all the cases if there are no tasks in registry the state will be updated directly to READY state. - function tryMoveToSuspendedState() private { - IAutomationRegistry automationRegistry = IAutomationRegistry(registry); - if(automationRegistry.totalTasks() == 0) { - // Registry is empty move to ready state directly - updateCycleStateTo(CommonUtils.CycleState.READY); - } else if (!cycleInfo.ifTransitionStateExists()) { - // Indicates that cycle was in STARTED state when suspention has been identified. - // It is safe to assert that cycleEndTime will always be greater than current chain time as - // the cycle end is check in the block metadata txn execution which proceeds any other transaction in the block. - // Including the transaction which caused transition to suspended state. - // So in case if cycleEndTime < currentTime then cycle end would have been identified - // and we would have enterend else branch instead. - // This holds true even if we identified suspention when moving from FINALIZED->STARTED state. - // As in this case we will first transition to the STARTED state and only then to SUSPENDED. - // And when transition to STARTED state we update the cycle start-time to be the current-chain-time. - uint64 currentTime = uint64(block.timestamp); - uint64 cycleEndTime = getCycleEndTime(); - - if(currentTime < cycleInfo.startTime()) { revert InvalidRegistryState(); } - if(currentTime >= cycleEndTime) { revert InvalidRegistryState(); } - if(!isCycleStarted()) { revert InvalidRegistryState(); } - - uint256[] memory expectedTasksToBeProcessed = automationRegistry.getTaskIdList().sortUint256(); - - cycleInfo.setRefundDuration(cycleEndTime - currentTime); - cycleInfo.setNewCycleDuration(cycleInfo.durationSecs()); - cycleInfo.setAutomationFeePerSec(IAutomationCore(automationCore).calculateAutomationFeeMultiplierForCurrentCycleInternal()); - cycleInfo.setGasCommittedForNewCycle(0); - cycleInfo.setGasCommittedForNextCycle(0); - cycleInfo.setSysGasCommittedForNextCycle(0); - cycleInfo.transitionState.lockedFees = 0; - cycleInfo.setNextTaskIndexPosition(0); - - updateExpectedTasks(expectedTasksToBeProcessed); - cycleInfo.setTransitionStateExists(true); - - updateCycleStateTo(CommonUtils.CycleState.SUSPENDED); - } else { - if(cycleInfo.state() != CommonUtils.CycleState.FINISHED) { revert InvalidRegistryState(); } - if(isTransitionInProgress()) { revert InvalidRegistryState(); } - - // Did not manage to charge cycle fee, so automationFeePerSec will be 0 along with remaining duration - // So the tasks sent for refund, will get only deposit refunded. - cycleInfo.setRefundDuration(0); - cycleInfo.setAutomationFeePerSec(0); - cycleInfo.setGasCommittedForNewCycle(0); - - updateCycleStateTo(CommonUtils.CycleState.SUSPENDED); - } - } - - /// @notice Transitions cycle state to the READY state. - function moveToReadyState() private { - // If the cycle duration updated has been identified during transtion, then the transition state is kept - // with reset values except new cycle duration to have it properly set for the next new cycle. - // This may happen in case if cycle was ended and feature-flag has been disbaled before any task has - // been processed for the cycle transition. - // Note that we want to have consistent data in ready state which says that the cycle pointed in the ready state - // has been finished/summerized, and we are ready to start the next new cycle, and all the cycle information should - // match the finalized/summerized cycle since its start, including cycle duration. - - // Check if transition state exists - if(cycleInfo.ifTransitionStateExists()) { - if (cycleInfo.newCycleDuration() == cycleInfo.durationSecs()) { - // Delete transition state - cycleInfo.transitionState.expectedTasksToBeProcessed.clear(); - delete cycleInfo.transitionState; - cycleInfo.setTransitionStateExists(false); - } else { - // Reset all except new cycle duration - cycleInfo.setRefundDuration(0); - cycleInfo.setAutomationFeePerSec(0); - cycleInfo.setGasCommittedForNewCycle(0); - cycleInfo.setGasCommittedForNextCycle(0); - cycleInfo.setSysGasCommittedForNextCycle(0); - cycleInfo.transitionState.lockedFees = 0; - cycleInfo.setNextTaskIndexPosition(0); - cycleInfo.transitionState.expectedTasksToBeProcessed.clear(); - } - } - updateCycleStateTo(CommonUtils.CycleState.READY); - } - - /// @notice Transitions cycle state to the STARTED state. - function moveToStartedState() private { - cycleInfo.setIndex(cycleInfo.index() + 1); - - cycleInfo.setStartTime(uint64(block.timestamp)); - - // Check if the transition state exists - if(cycleInfo.ifTransitionStateExists()) { - cycleInfo.setDurationSecs(cycleInfo.newCycleDuration()); - } - - updateCycleStateTo(CommonUtils.CycleState.STARTED); - } - - /// @notice Updates the state of the cycle. - /// @param _state Input state to update cycle state with. - function updateCycleStateTo(CommonUtils.CycleState _state) private { - CommonUtils.CycleState oldState = cycleInfo.state(); - cycleInfo.setState(uint8(_state)); - - emit AutomationCycleEvent ( - cycleInfo.index(), - cycleInfo.state(), - cycleInfo.startTime(), - cycleInfo.durationSecs(), - oldState - ); - } - - /// @notice Helper function to update the expected tasks of the transition state. - function updateExpectedTasks(uint256[] memory _expectedTasks) private { - cycleInfo.transitionState.expectedTasksToBeProcessed.clear(); - - for (uint256 i = 0; i < _expectedTasks.length; i++) { - cycleInfo.transitionState.expectedTasksToBeProcessed.add(_expectedTasks[i]); - } - } - - /// @notice Helper function called when cycle end is identified. - function onCycleEndInternal() private { - if (!cycleInfo.automationEnabled()) { - tryMoveToSuspendedState(); - } else{ - IAutomationRegistry automationRegistry = IAutomationRegistry(registry); - if(automationRegistry.totalTasks() == 0) { - // Registry is empty update config buffer and move to STARTED state directly - updateConfigFromBuffer(); - moveToStartedState(); - } else { - IAutomationCore core = IAutomationCore(automationCore); - uint256[] memory expectedTasksToBeProcessed = automationRegistry.getTaskIdList().sortUint256(); - - // Updates transition state - cycleInfo.setRefundDuration(0); - cycleInfo.setNewCycleDuration(cycleInfo.durationSecs()); - cycleInfo.setGasCommittedForNewCycle(core.getGasCommittedForNextCycle()); - cycleInfo.setGasCommittedForNextCycle(0); - cycleInfo.setSysGasCommittedForNextCycle (0); - cycleInfo.transitionState.lockedFees = 0; - cycleInfo.setNextTaskIndexPosition(0); - updateExpectedTasks(expectedTasksToBeProcessed); - - cycleInfo.setTransitionStateExists(true); - - // During cycle transition we update config only after transition state is created in order to have new cycle duration as transition state parameter. - updateConfigFromBuffer(); - - // Calculate automation fee per second for the new cycle only after configuration is updated. - // As we already know the committed gas for the new cycle it is being calculated using updated fee parameters - // and will be used to charge tasks during transition process. - cycleInfo.setAutomationFeePerSec(core.calculateAutomationFeeMultiplierForCommittedOccupancy(cycleInfo.gasCommittedForNewCycle())); - updateCycleStateTo(CommonUtils.CycleState.FINISHED); - } - } - } - - /// @notice Function to update the registry config structure with values extracted from the buffer, if the buffer exists. - function updateConfigFromBuffer() private { - (bool applied, uint64 cycleDuration) = IAutomationCore(automationCore).applyPendingConfig(); - if (!applied) return; - - // Check if transition state exists - if (cycleInfo.ifTransitionStateExists()) { - cycleInfo.setNewCycleDuration(cycleDuration); - } else { - cycleInfo.setDurationSecs(cycleDuration); - } - } - - /// @notice Checks if the cycle transition is finalized. - /// @return Bool representing if the cycle transition is finalized. - function isTransitionFinalized() private view returns (bool) { - return cycleInfo.transitionState.expectedTasksToBeProcessed.length() == cycleInfo.nextTaskIndexPosition(); - } - - // :::::::::::::::::::::::::::::::::::::::::::::::::::::::::::: VIEW FUNCTIONS :::::::::::::::::::::::::::::::::::::::::::::::::::::::::::: - - /// @notice Checks if the cycle transition is in progress. - /// @return Bool representing if the cycle transition is in progress. - function isTransitionInProgress() public view returns (bool) { - return cycleInfo.nextTaskIndexPosition() != 0; - } - - /// @notice Checks whether cycle is in STARTED state. - function isCycleStarted() public view returns (bool) { - return cycleInfo.state() == CommonUtils.CycleState.STARTED; - } - - /// @notice Returns the index, start time, duration and state of the current cycle. - function getCycleInfo() external view returns (uint64, uint64, uint64, CommonUtils.CycleState) { - return (cycleInfo.index(), cycleInfo.startTime(), cycleInfo.durationSecs(), cycleInfo.state()); - } - - /// @notice Returns the duration of the current cycle. - function getCycleDuration() external view returns (uint64) { - return cycleInfo.durationSecs(); - } - - /// @notice Returns the refund duration and automation fee per sec of the transtition state. - /// @return Refund duration - /// @return Automation fee per sec - function getTransitionInfo() external view returns (uint64, uint128) { - return (cycleInfo.refundDuration(), cycleInfo.automationFeePerSec()); - } - - /// @notice Returns if automation is enabled. - function isAutomationEnabled() external view returns (bool) { - return cycleInfo.automationEnabled(); - } - - /// @notice Returns the cycle end time. - function getCycleEndTime() public view returns (uint64 cycleEndTime) { - cycleEndTime = cycleInfo.startTime() + cycleInfo.durationSecs(); - } - - // ::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::: ADMIN FUNCTIONS ::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::: - - /// @notice Function to update the AutomationRegistry contract address. - /// @param _registry Address of the AutomationRegistry contract. - function setAutomationRegistry(address _registry) external onlyOwner { - _registry.validateContractAddress(); - - address oldRegistry = registry; - registry = _registry; - - emit AutomationRegistryUpdated(oldRegistry, _registry); - } - - /// @notice Function to update the AutomationCore contract address. - /// @param _automationCore Address of the AutomationCore contract. - function setAutomationCore(address _automationCore) external onlyOwner { - _automationCore.validateContractAddress(); - - address oldAutomationCore = automationCore; - automationCore = _automationCore; - - emit AutomationCoreUpdated(oldAutomationCore, _automationCore); - } - - /// @notice Function to enable the automation. - function enableAutomation() external onlyOwner { - if (cycleInfo.automationEnabled()) { revert AlreadyEnabled(); } - - cycleInfo.setAutomationEnabled(true); - - if (cycleInfo.state() == CommonUtils.CycleState.READY) { - moveToStartedState(); - updateConfigFromBuffer(); - } - - emit AutomationEnabled(cycleInfo.automationEnabled()); - } - - /// @notice Function to disable the automation. - function disableAutomation() external onlyOwner { - if(!cycleInfo.automationEnabled()) { revert AlreadyDisabled(); } - - cycleInfo.setAutomationEnabled(false); - - if (cycleInfo.state() == CommonUtils.CycleState.FINISHED && !isTransitionInProgress()) { - tryMoveToSuspendedState(); - } - - emit AutomationDisabled(cycleInfo.automationEnabled()); - } - - // ::::::::::::::::::::::::::::::::::::::::::::::::::::::::: UPGRADEABILITY FUNCTIONS ::::::::::::::::::::::::::::::::::::::::::::::::::::::::: - - /// @notice Helper function that reverts when 'msg.sender' is not authorized to upgrade the contract. - /// @dev called by 'upgradeTo' and 'upgradeToAndCall' in UUPSUpgradeable - /// @dev must be called by 'owner' - /// @param newImplementation address of the new implementation - function _authorizeUpgrade(address newImplementation) internal virtual override onlyOwner{ } -} diff --git a/solidity/supra_contracts/src/AutomationCore.sol b/solidity/supra_contracts/src/AutomationCore.sol deleted file mode 100644 index 313e34d109..0000000000 --- a/solidity/supra_contracts/src/AutomationCore.sol +++ /dev/null @@ -1,1027 +0,0 @@ -// SPDX-License-Identifier: MIT -pragma solidity 0.8.27; - -import {CommonUtils} from "./CommonUtils.sol"; -import {LibConfig} from "./LibConfig.sol"; - -import {IAutomationCore} from "./IAutomationCore.sol"; -import {IAutomationController} from "./IAutomationController.sol"; -import {IAutomationRegistry} from "./IAutomationRegistry.sol"; -import {IERC20} from "../lib/openzeppelin-contracts/contracts/token/ERC20/IERC20.sol"; -import {Ownable2StepUpgradeable} from "../lib/openzeppelin-contracts-upgradeable/contracts/access/Ownable2StepUpgradeable.sol"; -import {UUPSUpgradeable} from "../lib/openzeppelin-contracts/contracts/proxy/utils/UUPSUpgradeable.sol"; - -contract AutomationCore is IAutomationCore, Ownable2StepUpgradeable, UUPSUpgradeable { - using CommonUtils for *; - using LibConfig for *; - - /// @dev Constant for 10^8 - uint256 constant DECIMAL = 100_000_000; - - /// @dev Constants describing REFUND TYPE - uint8 constant DEPOSIT_CYCLE_FEE = 0; - uint8 constant CYCLE_FEE = 1; - - /// @dev Refund fraction - uint8 constant REFUND_FRACTION = 2; - - /// @dev State variables - LibConfig.ConfigBuffer configBuffer; - LibConfig.RegistryConfig regConfig; - - // ::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::: EVENTS ::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::: - - /// @notice Emitted when a new config is added. - event ConfigBufferUpdated(LibConfig.ConfigDetails indexed pendingConfig); - - /// @notice Emitted when task registration is enabled. - event TaskRegistrationEnabled(bool indexed status); - - /// @notice Emitted when task registration is disabled. - event TaskRegistrationDisabled(bool indexed status); - - /// @notice Emitted when the VM Signer address is updated. - event VmSignerUpdated(address indexed oldVmSigner, address indexed newVmSigner); - - /// @notice Emitted when the ERC20Supra address is updated. - event Erc20SupraUpdated(address indexed oldErc20Supra, address indexed newErc20Supra); - - /// @notice Emitted when the automation controller smart contract address is updated. - event AutomationControllerUpdated(address indexed oldController, address indexed newController); - - /// @notice Emitted when the automation registry smart contract address is updated. - event AutomationRegistryUpdated(address indexed oldRegistry, address indexed newRegistry); - - /// @notice Emitted when the registry fees is withdrawn by the admin. - event RegistryFeeWithdrawn(address indexed recipient, uint256 indexed feesWithdrawn); - - /// @notice Emitted when deposit fee is being refunded but total locked deposits is less than the locked deposit for the task. - event ErrorUnlockTaskDepositFee( - uint64 indexed taskIndex, - uint256 indexed totalDepositedAutomationFees, - uint128 indexed lockedDeposit - ); - - /// @notice Emitted during cycle transition when refunds to be paid is not possible due to insufficient contract balance. - /// Type of the refund can be related either to the deposit paid during registration (0), or to cycle fee caused by - /// the shortening of the cycle (1) - event ErrorInsufficientBalanceToRefund( - uint64 indexed _taskIndex, - address indexed _owner, - uint8 indexed _refundType, - uint128 _amount - ); - - /// @notice Emitted when a deposit fee is refunded for an automation task. - event TaskDepositFeeRefund(uint64 indexed taskIndex, address owner, uint128 amount); - - /// @notice Emitted when an automation fee is refunded for an automation task at the end of the cycle for excessive - /// duration paid at the beginning of the cycle due to cycle duration reduction by governance. - event TaskFeeRefund( - uint64 indexed taskIndex, - address indexed owner, - uint64 indexed amount - ); - - /// @notice Emitted when a task cycle fee is being refunded but locked cycle fees is less than the requested refund. - event ErrorUnlockTaskCycleFee( - uint64 indexed taskIndex, - uint256 indexed lockedCycleFees, - uint64 indexed refund - ); - - // :::::::::::::::::::::::::::::::::::::::::::::::::::::::::: CONSTRUCTOR AND INITIALIZER :::::::::::::::::::::::::::::::::::::::::::::::::::::::::: - - /// @dev Disables the initialization for the implementation contract. - constructor() { - _disableInitializers(); - } - - /// @notice Initializes the configuration parameters of the registry, can only be called once. - /// @param _taskDurationCapSecs Maximum allowable duration (in seconds) from the registration time that a user automation task can run. - /// @param _registryMaxGasCap Maximum gas allocation for automation tasks per cycle. - /// @param _automationBaseFeeWeiPerSec Base fee per second for the full capacity of the automation registry, measured in wei/sec. - /// @param _flatRegistrationFeeWei Flat registration fee charged by default for each task. - /// @param _congestionThresholdPercentage Percentage representing the acceptable upper limit of committed gas amount relative to registry_max_gas_cap. - /// Beyond this threshold, congestion fees apply. - /// @param _congestionBaseFeeWeiPerSec Base fee per second for the full capacity of the automation registry when the congestion threshold is exceeded. - /// @param _congestionExponent The congestion fee increases exponentially based on this value, ensuring higher fees as the registry approaches full capacity. - /// @param _taskCapacity Maximum number of tasks that the registry can hold. - /// @param _cycleDurationSecs Automation cycle duration in seconds. - /// @param _sysTaskDurationCapSecs Maximum allowable duration (in seconds) from the registration time that a system automation task can run. - /// @param _sysRegistryMaxGasCap Maximum gas allocation for system automation tasks per cycle. - /// @param _sysTaskCapacity Maximum number of system tasks that the registry can hold. - /// @param _vmSigner Address for the VM Signer. - /// @param _erc20Supra Address of the ERC20Supra contract. - function initialize( - uint64 _taskDurationCapSecs, - uint128 _registryMaxGasCap, - uint128 _automationBaseFeeWeiPerSec, - uint128 _flatRegistrationFeeWei, - uint8 _congestionThresholdPercentage, - uint128 _congestionBaseFeeWeiPerSec, - uint8 _congestionExponent, - uint16 _taskCapacity, - uint64 _cycleDurationSecs, - uint64 _sysTaskDurationCapSecs, - uint128 _sysRegistryMaxGasCap, - uint16 _sysTaskCapacity, - address _vmSigner, - address _erc20Supra - ) public initializer { - validateConfigParameters( - _taskDurationCapSecs, - _registryMaxGasCap, - _congestionThresholdPercentage, - _congestionExponent, - _taskCapacity, - _cycleDurationSecs, - _sysTaskDurationCapSecs, - _sysRegistryMaxGasCap, - _sysTaskCapacity - ); - if(_vmSigner == address(0)) revert AddressCannotBeZero(); - _erc20Supra.validateContractAddress(); - - - LibConfig.Config memory config = LibConfig.createConfig( - _registryMaxGasCap, - _sysRegistryMaxGasCap, - _automationBaseFeeWeiPerSec, - _flatRegistrationFeeWei, - _congestionBaseFeeWeiPerSec, - _taskDurationCapSecs, - _sysTaskDurationCapSecs, - _cycleDurationSecs, - _taskCapacity, - _sysTaskCapacity, - _congestionThresholdPercentage, - _congestionExponent - ); - - regConfig = LibConfig.createRegistryConfig( - _registryMaxGasCap, - _sysRegistryMaxGasCap, - true, - _vmSigner, - _erc20Supra, - config - ); - - __Ownable2Step_init(); - __Ownable_init(msg.sender); - } - - // :::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::: HELPER FUNCTIONS :::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::: - - /// @notice Helper function to validate the registry configuration parameters. - function validateConfigParameters( - uint64 _taskDurationCapSecs, - uint128 _registryMaxGasCap, - uint8 _congestionThresholdPercentage, - uint8 _congestionExponent, - uint16 _taskCapacity, - uint64 _cycleDurationSecs, - uint64 _sysTaskDurationCapSecs, - uint128 _sysRegistryMaxGasCap, - uint16 _sysTaskCapacity - ) private pure { - if(_taskDurationCapSecs <= _cycleDurationSecs) { revert InvalidTaskDuration(); } - if(_registryMaxGasCap == 0) { revert InvalidRegistryMaxGasCap(); } - if(_congestionThresholdPercentage > 100) { revert InvalidCongestionThreshold(); } - if(_congestionExponent == 0) { revert InvalidCongestionExponent(); } - if(_taskCapacity == 0) { revert InvalidTaskCapacity(); } - if(_cycleDurationSecs == 0) { revert InvalidCycleDuration(); } - if(_sysTaskDurationCapSecs <= _cycleDurationSecs) { revert InvalidSysTaskDuration(); } - if(_sysRegistryMaxGasCap == 0) { revert InvalidSysRegistryMaxGasCap(); } - if(_sysTaskCapacity == 0) { revert InvalidSysTaskCapacity(); } - } - - /// @notice Helper function to validate the task duration. - function validateTaskDuration( - uint64 _regTime, - uint64 _expiryTime, - uint64 _taskDurationCap, - uint64 _cycleEndTime - ) private pure { - if(_expiryTime <= _regTime) { revert InvalidExpiryTime(); } - - uint64 taskDuration = _expiryTime - _regTime; - if(taskDuration > _taskDurationCap) { revert InvalidTaskDuration(); } - - if( _expiryTime <= _cycleEndTime) { revert TaskExpiresBeforeNextCycle(); } - } - - /// @notice Helper function to validate the inputs while registering a task. - function validateInputs(bytes memory _payloadTx, uint128 _maxGasAmount) private view { - ( , address payloadTarget, , ) = abi.decode(_payloadTx, (uint128, address, bytes, LibConfig.AccessListEntry[])); - payloadTarget.validateContractAddress(); - - if(_maxGasAmount == 0) { revert InvalidMaxGasAmount(); } - } - - /// @notice Function to ensure that AutomationController contract is the caller. - function onlyController() private view { - if(msg.sender != regConfig.automationController()) { revert CallerNotController(); } - } - - /// @notice Function to ensure that AutomationRegistry contract is the caller. - function onlyRegistry() private view { - if(msg.sender != regConfig.registry) { revert CallerNotRegistry(); } - } - - /// @notice Helper function to charge fees from the user. - function chargeFees(address _from, uint256 _amount) external { - if (msg.sender != regConfig.automationController() && msg.sender != regConfig.registry) { revert UnauthorizedCaller(); } - - bool sent = IERC20(regConfig.erc20Supra).transferFrom(_from, address(this), _amount); - if(!sent) { revert TransferFailed(); } - } - - /// @notice Function to calculate the automation congestion fee. - /// @param _totalCommittedGas Total committed gas. - /// @param _registryMaxGasCap Registry max gas cap. - /// @return Returns the automation congestion fee. - function calculateAutomationCongestionFee( - uint128 _totalCommittedGas, - uint128 _registryMaxGasCap - ) private view returns (uint128) { - if (regConfig.congestionThresholdPercentage() == 100 || regConfig.congestionBaseFeeWeiPerSec() == 0) { return 0; } - - // thresholdUsage = (totalCommittedGas / maxGasCap) * 100 - uint256 thresholdUsageScaled = (uint256(_totalCommittedGas) * DECIMAL * 100) / uint256(_registryMaxGasCap); - - uint256 thresholdPercentageScaled = uint256(regConfig.congestionThresholdPercentage()) * DECIMAL; - - // If usage is below threshold → no congestion fee - if (thresholdUsageScaled <= thresholdPercentageScaled) { - return 0; - } else { - // Calculate how much usage exceeds threshold - uint256 surplusScaled = (thresholdUsageScaled - thresholdPercentageScaled) / 100; - - - // Ensure threshold + threshold surplus does not exceed 1 (1 in scaled terms) - uint256 thresholdScaledAsFraction = thresholdPercentageScaled / 100; // DECIMAL-scaled fraction - uint256 surplusClipped = thresholdScaledAsFraction + surplusScaled > DECIMAL ? DECIMAL - thresholdScaledAsFraction : surplusScaled; - - uint256 baseScaled = DECIMAL + surplusClipped; // (1 + base) - uint256 resultScaled = DECIMAL; - for (uint8 i = 0; i < regConfig.congestionExponent(); i++) { - resultScaled = (resultScaled * baseScaled) / DECIMAL; - } - uint256 exponentResult = resultScaled - DECIMAL; // subtract 1 - - - // Multiply base fee (wei/sec) with exponentResult and downscale by DECIMAL - uint256 acf = (uint256(regConfig.congestionBaseFeeWeiPerSec()) * exponentResult) / DECIMAL; - - return uint128(acf); - } - } - - /// @notice Calculates the automation fee multiplier for cycle. - /// @param _totalCommittedGas Total committed gas. - /// @param _registryMaxGasCap Registry max gas cap. - function calculateAutomationFeeMultiplierForCycle( - uint128 _totalCommittedGas, - uint128 _registryMaxGasCap - ) private view returns (uint128) { - uint128 congesionFee = calculateAutomationCongestionFee(_totalCommittedGas, _registryMaxGasCap); - return (congesionFee + regConfig.automationBaseFeeWeiPerSec()); - } - - /// @notice Calculates automation task fees for a single task at the time of new cycle. - /// This is supposed to be called only after removing expired task and must not be called for expired task. - function calculateAutomationFeeForInterval( - uint64 _duration, - uint128 _taskOccupancy, - uint128 _automationFeePerSec, - uint128 _registryMaxGasCap - ) private pure returns (uint128) { - uint256 taskOccupancyRatioByDuration = (uint256(_duration) * uint256(_taskOccupancy) * DECIMAL) / uint256(_registryMaxGasCap); - - uint256 automationFeeForInterval = _automationFeePerSec * taskOccupancyRatioByDuration; - - return uint128(automationFeeForInterval / DECIMAL); - } - - /// @notice Calculates automation task fees for a single task at the time of new cycle. - /// This is supposed to be called only after removing expired task and must not be called for expired task. - /// @param _state State of the task. - /// @param _expiryTime Task expiry time. - /// @param _maxGasAmount Task's max gas amount - /// @param _potentialFeeTimeframe Potential time frame to calculate task fees for. - /// @param _currentTime Current time - /// @param _automationFeePerSec Automation fee per sec - /// @return Calculated task fee for the interval the task will be active. - function _calculateTaskFee( - CommonUtils.TaskState _state, - uint64 _expiryTime, - uint128 _maxGasAmount, - uint64 _potentialFeeTimeframe, - uint64 _currentTime, - uint128 _automationFeePerSec - ) private view returns (uint128) { - if (_automationFeePerSec == 0) { return 0; } - if (_expiryTime <= _currentTime) { return 0; } - - uint64 taskActiveTimeframe = _expiryTime - _currentTime; - - // If the task is a new task i.e. in Pending state, then it is charged always for - // the input _potentialFeeTimeframe(which is cycle-interval), - // For the new tasks which active-timeframe is less than cycle-interval - // it would mean it is their first and only cycle and we charge the fee for entire cycle. - // Note that although the new short tasks are charged for entire cycle, the refunding logic remains the same for - // them as for the long tasks. - // This way bad-actors will be discourged to submit small and short tasks with big occupancy by blocking other - // good-actors register tasks. - uint64 actualFeeTimeframe; - if(_state == CommonUtils.TaskState.PENDING) { - actualFeeTimeframe = _potentialFeeTimeframe; - } else { - actualFeeTimeframe = taskActiveTimeframe < _potentialFeeTimeframe ? taskActiveTimeframe : _potentialFeeTimeframe; - } - return calculateAutomationFeeForInterval( - actualFeeTimeframe, - _maxGasAmount, - _automationFeePerSec, - regConfig.registryMaxGasCap() - ); - } - - /// @notice Estimates automation fee the next cycle for specified task occupancy for the configured cycle interval - /// referencing the current automation registry fee parameters, specified total/committed occupancy and registry - /// maximum allowed occupancy for the next cycle. - /// Note it is expected that committed_occupancy does not include current task's occupancy. - function estimateAutomationFeeWithCommittedOccupancyInternal( - uint128 _taskOccupancy, - uint128 _committedOccupancy - ) private view returns (uint128) { - uint128 totalCommittedGas = _taskOccupancy + _committedOccupancy; - - uint128 automationFeePerSec = calculateAutomationFeeMultiplierForCycle(totalCommittedGas, regConfig.nextCycleRegistryMaxGasCap()); - - if(automationFeePerSec == 0) return 0; - - uint64 durationSecs = IAutomationController(regConfig.automationController()).getCycleDuration(); - return calculateAutomationFeeForInterval(durationSecs, _taskOccupancy, automationFeePerSec, regConfig.nextCycleRegistryMaxGasCap()); - } - - /// @notice Unlocks the deposit paid by the task from the total automation fees deposited. - /// @dev Error event is emitted if the total automation fees deposited is less than the requested unlock amount. - /// @param _taskIndex Index of the task. - /// @param _lockedDeposit Locked deposit amount to be unlocked. - /// @return Bool if _lockedDeposit can be unlocked safely. - function _safeUnlockLockedDeposit( - uint64 _taskIndex, - uint128 _lockedDeposit - ) private returns (bool) { - uint256 totalDeposited = regConfig.totalDepositedAutomationFees; - - if(totalDeposited >= _lockedDeposit) { - regConfig.totalDepositedAutomationFees = totalDeposited - _lockedDeposit; - return true; - } - - emit ErrorUnlockTaskDepositFee(_taskIndex, totalDeposited, _lockedDeposit); - return false; - } - - /// @notice Helper function to transfer refunds. - /// @param _to Recipeint of the refund - /// @param _amount Amount to refund - /// @return Bool representing if refund was successful. - function _refund(address _to, uint128 _amount) private returns (bool) { - bool sent = IERC20(regConfig.erc20Supra).transfer(_to, _amount); - if (!sent) { revert TransferFailed(); } - - return sent; - } - - /// @notice Refunds the specified amount to the task owner. - /// @dev Error event is emitted if the registry contract does not have sufficient balance. - /// @param _taskIndex Index of the task. - /// @param _taskOwner Owner of the task. - /// @param _refundableAmount Amount to refund. - /// @param _refundType Type of refund. - /// @return Bool representing if refund was successful. - function safeRefund( - uint64 _taskIndex, - address _taskOwner, - uint128 _refundableAmount, - uint8 _refundType - ) private returns (bool) { - uint256 balance = IERC20(regConfig.erc20Supra).balanceOf(address(this)); - if(balance < _refundableAmount) { - emit ErrorInsufficientBalanceToRefund(_taskIndex, _taskOwner, _refundType, _refundableAmount); - return false; - } else { - return _refund(_taskOwner, _refundableAmount); - } - } - - /// @notice Refunds the specified amount of deposit to the task owner and unlocks full deposit from the total automation fees deposited. - /// @param _taskIndex Index of the task. - /// @param _taskOwner Owner of the task. - /// @param _refundableDeposit Refundable amount of deposit. - /// @param _lockedDeposit Total locked deposit. - function _safeDepositRefund( - uint64 _taskIndex, - address _taskOwner, - uint128 _refundableDeposit, - uint128 _lockedDeposit - ) private returns (bool) { - // Ensures that amount to unlock is not more than the total automation fees deposited. - bool result = _safeUnlockLockedDeposit(_taskIndex, _lockedDeposit); - if (!result) { - return result; - } - - result = safeRefund(_taskIndex, _taskOwner, _refundableDeposit, DEPOSIT_CYCLE_FEE); - - if (result) { emit TaskDepositFeeRefund(_taskIndex, _taskOwner, _refundableDeposit); } - return result; - } - - /// @notice Unlocks the locked fee paid by the task for cycle. - /// Error event is emitted if the cycle locked fee amount is inconsistent with the requested unlock amount. - /// @param _cycleLockedFees Locked cycle fees - /// @param _refundableFee Refundable fees - /// @param _taskIndex Index of the task - /// @return Bool if _refundableFee can be unlocked safely. - /// @return Updated _cycleLockedFees after unlocking _refundableFee. - function safeUnlockLockedCycleFee( - uint256 _cycleLockedFees, - uint64 _refundableFee, - uint64 _taskIndex - ) private returns (bool, uint256) { - // This check makes sure that more than locked amount of the fees will be not be refunded. - // Any attempt means internal bug. - bool hasLockedFee = _cycleLockedFees >= _refundableFee; - if (hasLockedFee) { - // Unlock the refunded amount - _cycleLockedFees = _cycleLockedFees - _refundableFee; - } else { - emit ErrorUnlockTaskCycleFee(_taskIndex, _cycleLockedFees, _refundableFee); - } - return (hasLockedFee, _cycleLockedFees); - } - - /// @notice Refunds fee paid by the task for the cycle to the task owner. - /// Note that here we do not unlock the fee, as on cycle change locked cycle-fees for the ended cycle are - /// automatically unlocked. - function safeFeeRefund( - uint64 _taskIndex, - address _taskOwner, - uint256 _cycleLockedFees, - uint64 _refundableFee - ) private returns (bool, uint256) { - bool result; - uint256 remainingLockedFees; - - (result, remainingLockedFees) = safeUnlockLockedCycleFee(_cycleLockedFees, _refundableFee, _taskIndex); - if (!result) { return (result, remainingLockedFees); } - - result = safeRefund( _taskIndex, _taskOwner, _refundableFee, CYCLE_FEE); - if (result) { emit TaskFeeRefund(_taskIndex, _taskOwner, _refundableFee); } - return (result, remainingLockedFees); - } - - // ::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::: CONTROLLER FUNCTIONS ::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::: - - /// @notice Function to update the registry configuration, reverts if caller is not AutomationController. - function applyPendingConfig() external returns (bool, uint64) { - onlyController(); - - if (!configBuffer.ifExists) { - return (false, 0); - } - uint64 pendingCycleDuration = configBuffer.pendingConfig.cycleDurationSecs(); - regConfig.config = configBuffer.pendingConfig; - - delete configBuffer; - - return (true, pendingCycleDuration); - } - - /// @notice Internally calls _calculateTaskFee. - function calculateTaskFee( - CommonUtils.TaskState _state, - uint64 _expiryTime, - uint128 _maxGasAmount, - uint64 _potentialFeeTimeframe, - uint64 _currentTime, - uint128 _automationFeePerSec - ) external view returns (uint128) { - return _calculateTaskFee( - _state, - _expiryTime, - _maxGasAmount, - _potentialFeeTimeframe, - _currentTime, - _automationFeePerSec - ); - } - - /// @notice Internally calls _safeUnlockLockedDeposit, reverts if caller is not AutomationController. - function safeUnlockLockedDeposit( - uint64 _taskIndex, - uint128 _lockedDeposit - ) external returns (bool) { - onlyController(); - - return _safeUnlockLockedDeposit(_taskIndex, _lockedDeposit); - } - - /// @notice Refunds the deposit fee and any autoamtion fees of the task. - function refundTaskFees( - uint64 _currentTime, - uint64 _refundDuration, - uint128 _automationFeePerSec, - CommonUtils.TaskDetails memory _task - ) external { - onlyController(); - - // Do not attempt fee refund if remaining duration is 0 - if (_task.state != CommonUtils.TaskState.PENDING && _refundDuration != 0) { - uint128 _refundFee = _calculateTaskFee( - _task.state, - _task.expiryTime, - _task.maxGasAmount, - _refundDuration, - _currentTime, - _automationFeePerSec - ); - ( , uint256 remainingCycleLockedFees) = safeFeeRefund( - _task.taskIndex, - _task.owner, - regConfig.cycleLockedFees, - uint64(_refundFee) - ); - regConfig.cycleLockedFees = remainingCycleLockedFees; - } - - _safeDepositRefund( - _task.taskIndex, - _task.owner, - _task.depositFee, - _task.depositFee - ); - } - - function calculateAutomationFeeMultiplierForCurrentCycleInternal() external view returns (uint128) { - // Compute the automation fee multiplier for this cycle - return calculateAutomationFeeMultiplierForCycle( - regConfig.gasCommittedForThisCycle(), - regConfig.registryMaxGasCap() - ); - } - - /// @notice Calculates automation fee per second for the specified task occupancy - /// referencing the current automation registry fee parameters, specified total/committed occupancy and current registry - /// maximum allowed occupancy. - function calculateAutomationFeeMultiplierForCommittedOccupancy( - uint128 _totalCommittedMaxGas - ) external view returns (uint128) { - // Compute the automation fee multiplier for cycle - return calculateAutomationFeeMultiplierForCycle( - _totalCommittedMaxGas, - regConfig.registryMaxGasCap() - ); - } - - /// @notice Function to update the cycle locked fees and gas committed. - /// @param _lockedFees Updated cycle locked fees - /// @param _sysGasCommittedForNextCycle Updated system gas committed for next cycle - /// @param _gasCommittedForNextCycle Updated gas committed for next cycle - /// @param _gasCommittedForNewCycle Updated gas committed for new cycle - function updateGasCommittedAndCycleLockedFees( - uint256 _lockedFees, - uint128 _sysGasCommittedForNextCycle, - uint128 _gasCommittedForNextCycle, - uint128 _gasCommittedForNewCycle - ) external { - onlyController(); - - regConfig.cycleLockedFees = _lockedFees; - regConfig.setSysGasCommittedForNextCycle(_sysGasCommittedForNextCycle); - regConfig.setSysGasCommittedForThisCycle(_sysGasCommittedForNextCycle); - regConfig.setGasCommittedForNextCycle(_gasCommittedForNextCycle); - regConfig.setGasCommittedForThisCycle(_gasCommittedForNewCycle); - } - - // :::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::: REGISTRY FUNCTIONS ::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::: - - /// @notice Helper function that performs validation and updates state for a valid task. - function updateStateForValidRegistration( - uint256 _totalTasks, - uint64 _regTime, - uint64 _expiryTime, - CommonUtils.TaskType _taskType, - bytes memory _payloadTx, - uint128 _maxGasAmount, - uint128 _gasPriceCap, - uint128 _automationFeeCapForCycle - ) external { - onlyRegistry(); - - // Check if automation and registration is enabled - IAutomationController automationController = IAutomationController(regConfig.automationController()); - if (!automationController.isAutomationEnabled()) { revert AutomationNotEnabled(); } - if (!regConfig.registrationEnabled()) { revert RegistrationDisabled(); } - - if (!automationController.isCycleStarted()) { revert CycleTransitionInProgress(); } - - bool isUST = _taskType == CommonUtils.TaskType.UST; - - uint64 taskDurationCap; - uint128 gasCommittedForNextCycle; - uint128 nextCycleRegistryMaxGasCap; - if (isUST) { - if(_totalTasks >= regConfig.taskCapacity()) { revert TaskCapacityReached(); } - if(_gasPriceCap == 0) { revert InvalidGasPriceCap(); } - - gasCommittedForNextCycle = regConfig.gasCommittedForNextCycle(); - uint128 estimatedAutomationFeeForCycle = estimateAutomationFeeWithCommittedOccupancyInternal(_maxGasAmount, gasCommittedForNextCycle); - if(_automationFeeCapForCycle < estimatedAutomationFeeForCycle) { revert InsufficientFeeCapForCycle(); } - - taskDurationCap = regConfig.taskDurationCapSecs(); - nextCycleRegistryMaxGasCap = regConfig.nextCycleRegistryMaxGasCap(); - } else { - if(_totalTasks >= regConfig.sysTaskCapacity()) { revert TaskCapacityReached(); } - - gasCommittedForNextCycle = regConfig.sysGasCommittedForNextCycle(); - taskDurationCap = regConfig.sysTaskDurationCapSecs(); - nextCycleRegistryMaxGasCap = regConfig.nextCycleSysRegistryMaxGasCap(); - } - - validateTaskDuration(_regTime, _expiryTime, taskDurationCap, automationController.getCycleEndTime()); - validateInputs(_payloadTx, _maxGasAmount); - - uint128 gasCommitted = _maxGasAmount + gasCommittedForNextCycle; - if(gasCommitted > nextCycleRegistryMaxGasCap) { revert GasCommittedExceedsMaxGasCap(); } - - if (isUST) { - regConfig.setGasCommittedForNextCycle(gasCommitted); - } else { - regConfig.setSysGasCommittedForNextCycle(gasCommitted); - } - } - - function updateGasCommittedForNextCycle(CommonUtils.TaskType _taskType, uint128 _maxGasAmount) external { - onlyRegistry(); - - bool isUST = _taskType == CommonUtils.TaskType.UST; - - uint128 gasCommittedForNextCycle = isUST ? regConfig.gasCommittedForNextCycle(): regConfig.sysGasCommittedForNextCycle(); - if (gasCommittedForNextCycle < _maxGasAmount) { revert GasCommittedValueUnderflow(); } - - // Adjust the gas committed for the next cycle by subtracting the gas amount of the cancelled/stopped task - if (isUST) { - regConfig.setGasCommittedForNextCycle(gasCommittedForNextCycle - _maxGasAmount); - } else { - regConfig.setSysGasCommittedForNextCycle(gasCommittedForNextCycle - _maxGasAmount); - } - } - - /// @notice Helper function to increment the total deposited automation fees. - function incTotalDepositedAutomationFees(uint256 _amount) external { - onlyRegistry(); - regConfig.totalDepositedAutomationFees += _amount; - } - - /// @notice Internally calls _refund, reverts if caller is not AutomationRegistry. - function refund(address _to, uint128 _amount) external { - onlyRegistry(); - uint256 balance = IERC20(regConfig.erc20Supra).balanceOf(address(this)); - - if(balance < _amount) { revert InsufficientBalanceForRefund(); } - _refund(_to, _amount); - } - - /// @notice Internally calls _safeDepositRefund, reverts if caller is not AutomationRegistry. - function safeDepositRefund( - uint64 _taskIndex, - address _taskOwner, - uint128 _refundableDeposit, - uint128 _lockedDeposit - ) external returns (bool) { - onlyRegistry(); - return _safeDepositRefund(_taskIndex, _taskOwner, _refundableDeposit, _lockedDeposit); - } - - /// @notice Helper function to unlock locked deposit and cycle fees when stopTasks is called. - function unlockDepositAndCycleFee( - uint64 _taskIndex, - CommonUtils.TaskState _taskState, - uint64 _expiryTime, - uint128 _maxGasAmount, - uint64 _residualInterval, - uint64 _currentTime, - uint128 _depositFee - ) external returns (uint128, uint128) { - onlyRegistry(); - - uint128 cycleFeeRefund; - uint128 depositRefund; - - if(_taskState != CommonUtils.TaskState.PENDING) { - // Compute the automation fee multiplier for cycle - uint128 automationFeePerSec = calculateAutomationFeeMultiplierForCycle(regConfig.gasCommittedForThisCycle(), regConfig.registryMaxGasCap()); - - uint128 taskFee = _calculateTaskFee( - _taskState, - _expiryTime, - _maxGasAmount, - _residualInterval, - _currentTime, - automationFeePerSec - ); - - // Refund full deposit and the half of the remaining run-time fee when task is active or cancelled stage - cycleFeeRefund = taskFee / REFUND_FRACTION; - depositRefund = _depositFee; - } else { - cycleFeeRefund = 0; - depositRefund = _depositFee / REFUND_FRACTION; - } - - bool result = _safeUnlockLockedDeposit(_taskIndex, _depositFee); - if(!result) { revert ErrorDepositRefund(); } - - (bool hasLockedFee, uint256 remainingCycleLockedFees ) = safeUnlockLockedCycleFee(regConfig.cycleLockedFees, uint64(cycleFeeRefund), _taskIndex); - if(!hasLockedFee) { revert ErrorCycleFeeRefund(); } - - regConfig.cycleLockedFees = remainingCycleLockedFees; - - return (cycleFeeRefund, depositRefund); - } - - // :::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::: ADMIN FUNCTIONS :::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::: - - /// @notice Function to update the registry configuration buffer. - function updateConfigBuffer( - uint64 _taskDurationCapSecs, - uint128 _registryMaxGasCap, - uint128 _automationBaseFeeWeiPerSec, - uint128 _flatRegistrationFeeWei, - uint8 _congestionThresholdPercentage, - uint128 _congestionBaseFeeWeiPerSec, - uint8 _congestionExponent, - uint16 _taskCapacity, - uint64 _cycleDurationSecs, - uint64 _sysTaskDurationCapSecs, - uint128 _sysRegistryMaxGasCap, - uint16 _sysTaskCapacity - ) external onlyOwner { - validateConfigParameters( - _taskDurationCapSecs, - _registryMaxGasCap, - _congestionThresholdPercentage, - _congestionExponent, - _taskCapacity, - _cycleDurationSecs, - _sysTaskDurationCapSecs, - _sysRegistryMaxGasCap, - _sysTaskCapacity - ); - - if(regConfig.gasCommittedForNextCycle() > _registryMaxGasCap) { revert UnacceptableRegistryMaxGasCap(); } - if(regConfig.sysGasCommittedForNextCycle() > _sysRegistryMaxGasCap) { revert UnacceptableSysRegistryMaxGasCap(); } - - // Add new config to the buffer - LibConfig.Config memory pendingConfig = LibConfig.createConfig( - _registryMaxGasCap, - _sysRegistryMaxGasCap, - _automationBaseFeeWeiPerSec, - _flatRegistrationFeeWei, - _congestionBaseFeeWeiPerSec, - _taskDurationCapSecs, - _sysTaskDurationCapSecs, - _cycleDurationSecs, - _taskCapacity, - _sysTaskCapacity, - _congestionThresholdPercentage, - _congestionExponent - ); - configBuffer = LibConfig.ConfigBuffer(pendingConfig, true); - - regConfig.setNextCycleRegistryMaxGasCap(_registryMaxGasCap); - regConfig.setNextCycleSysRegistryMaxGasCap(_sysRegistryMaxGasCap); - - emit ConfigBufferUpdated(pendingConfig.getConfig()); - } - - /// @notice Function to enable the task registration. - function enableRegistration() external onlyOwner { - if(regConfig.registrationEnabled()) { revert AlreadyEnabled(); } - regConfig.setRegistrationEnabled(true); - - emit TaskRegistrationEnabled(regConfig.registrationEnabled()); - } - - /// @notice Function to disable the task registration. - function disableRegistration() external onlyOwner { - if(!regConfig.registrationEnabled()) { revert AlreadyDisabled(); } - regConfig.setRegistrationEnabled(false); - - emit TaskRegistrationDisabled(regConfig.registrationEnabled()); - } - - /// @notice Function to update the VM Signer address. - /// @param _vmSigner New address for VM Signer. - function setVmSigner(address _vmSigner) external onlyOwner { - if(_vmSigner == address(0)) { revert AddressCannotBeZero(); } - - address oldVmSigner = regConfig.vmSigner; - regConfig.vmSigner = _vmSigner; - - emit VmSignerUpdated(oldVmSigner, _vmSigner); - } - - /// @notice Function to update the ERC20Supra address. - /// @param _erc20Supra New address for ERC20Supra. - function setErc20Supra(address _erc20Supra) external onlyOwner { - _erc20Supra.validateContractAddress(); - - address oldErc20Supra = regConfig.erc20Supra; - regConfig.erc20Supra = _erc20Supra; - - emit Erc20SupraUpdated(oldErc20Supra, _erc20Supra); - } - - /// @notice Function to update the automation controller smart contract address. - /// @param _controller Address of the automation controller smart contact. - function setAutomationController(address _controller) external onlyOwner { - _controller.validateContractAddress(); - - address oldController = regConfig.automationController(); - regConfig.setAutomationController(_controller); - - emit AutomationControllerUpdated(oldController, _controller); - } - - /// @notice Function to update the automation registry smart contract address. - /// @param _registry Address of the automation registry smart contact. - function setAutomationRegistry(address _registry) external onlyOwner { - _registry.validateContractAddress(); - - address oldRegistry = regConfig.registry; - regConfig.registry = _registry; - - emit AutomationRegistryUpdated(oldRegistry, _registry); - } - - /// @notice Function to withdraw the accumulated fees. - /// @param _amount Amount to withdraw. - /// @param _recipient Address to withdraw fees to. - function withdrawFees(uint256 _amount, address _recipient) external onlyOwner { - if(_amount == 0) { revert InvalidAmount(); } - if(_recipient == address(0)) { revert AddressCannotBeZero(); } - uint256 balance = IERC20(regConfig.erc20Supra).balanceOf(address(this)); - - if(balance < _amount) { revert InsufficientBalance(); } - if(balance - _amount < regConfig.cycleLockedFees + regConfig.totalDepositedAutomationFees) { revert RequestExceedsLockedBalance(); } - - bool sent = IERC20(regConfig.erc20Supra).transfer(_recipient, _amount); - if(!sent) { revert TransferFailed(); } - - emit RegistryFeeWithdrawn(_recipient, _amount); - } - - // :::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::: VIEW FUNCTIONS :::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::: - - /// @notice Returns the VM Signer address. - function getVmSigner() external view returns (address) { - return regConfig.vmSigner; - } - - /// @notice Returns the ERC20Supra address. - function erc20Supra() external view returns (address) { - return regConfig.erc20Supra; - } - - /// @notice Returns the address of AutomationController smart contract. - function getAutomationController() external view returns (address) { - return regConfig.automationController(); - } - - /// @notice Returns the address of AutomationRegistry smart contract. - function getAutomationRegistry() external view returns (address) { - return regConfig.registry; - } - - /// @notice Returns if task registration is enabled. - function isRegistrationEnabled() external view returns (bool) { - return regConfig.registrationEnabled(); - } - - /// @notice Returns the gas committed for the next cycle. - function getGasCommittedForNextCycle() external view returns (uint128) { - return regConfig.gasCommittedForNextCycle(); - } - - /// @notice Returns the gas committed for the current cycle. - function getGasCommittedForCurrentCycle() external view returns (uint128) { - return regConfig.gasCommittedForThisCycle(); - } - - /// @notice Returns the system gas committed for the next cycle. - function getSystemGasCommittedForNextCycle() external view returns (uint128) { - return regConfig.sysGasCommittedForNextCycle(); - } - - /// @notice Returns the system gas committed for the current cycle. - function getSystemGasCommittedForCurrentCycle() external view returns (uint128) { - return regConfig.sysGasCommittedForThisCycle(); - } - - /// @notice Returns the registry max gas cap for the next cycle. - function getNextCycleRegistryMaxGasCap() external view returns (uint128) { - return regConfig.nextCycleRegistryMaxGasCap(); - } - - /// @notice Returns the system registry max gas cap for the next cycle. - function getNextCycleSysRegistryMaxGasCap() external view returns (uint128) { - return regConfig.nextCycleSysRegistryMaxGasCap(); - } - - /// @notice Returns the flat registration fee. - function flatRegistrationFeeWei() external view returns (uint128) { - return regConfig.flatRegistrationFeeWei(); - } - - /// @notice Returns the registry configuration. - function getConfig() external view returns (LibConfig.ConfigDetails memory) { - return regConfig.config.getConfig(); - } - - /// @notice Returns the pending configuration. - function getPendingConfig() external view returns (LibConfig.ConfigDetails memory) { - return configBuffer.pendingConfig.getConfig(); - } - - /// @notice Returns the registry max gas cap configured. - function getRegistryMaxGasCap() external view returns (uint128) { - return regConfig.registryMaxGasCap(); - } - - /// @notice Returns the system registry max gas cap configured. - function getSysRegistryMaxGasCap() external view returns (uint128) { - return regConfig.sysRegistryMaxGasCap(); - } - - /// @notice Returns the automationBaseFeeWeiPerSec configured. - function getAutomationBaseFeeWeiPerSec() external view returns (uint128) { - return regConfig.automationBaseFeeWeiPerSec(); - } - - /// @notice Returns the cycle duration configured. - function cycleDurationSecs() external view returns (uint64) { - return regConfig.config.cycleDurationSecs(); - } - - /// @notice Returns the locked fees for the cycle. - function getCycleLockedFees() external view returns (uint256) { - return regConfig.cycleLockedFees; - } - - /// @notice Returns the total amount of automation fees deposited. - function getTotalDepositedAutomationFees() external view returns (uint256) { - return regConfig.totalDepositedAutomationFees; - } - - /// @notice Returns the total amount locked which comprises of 'cycleLockedFees' and 'totalDepositedAutomationFees'. - function getTotalLockedBalance() external view returns (uint256) { - return regConfig.cycleLockedFees + regConfig.totalDepositedAutomationFees; - } - - /// @notice Estimates automation fee for the next cycle for specified task occupancy for the configured cycle-interval - /// referencing the current automation registry fee parameters, current total occupancy and registry maximum allowed - /// occupancy for the next cycle. - function estimateAutomationFee(uint128 _taskOccupancy) external view returns (uint128) { - return estimateAutomationFeeWithCommittedOccupancyInternal(_taskOccupancy, regConfig.gasCommittedForNextCycle()); - } - - /// @notice Estimates automation fee the next cycle for specified task occupancy for the configured cycle-interval - /// referencing the current automation registry fee parameters, specified total/committed occupancy and registry - /// maximum allowed occupancy for the next cycle. - function estimateAutomationFeeWithCommittedOccupancy( - uint128 _taskOccupancy, - uint128 _committedOccupancy - ) external view returns (uint128) { - return estimateAutomationFeeWithCommittedOccupancyInternal( - _taskOccupancy, - _committedOccupancy - ); - } - - // ::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::: UPGRADEABILITY FUNCTIONS ::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::: - - /// @notice Helper function that reverts when 'msg.sender' is not authorized to upgrade the contract. - /// @dev called by 'upgradeTo' and 'upgradeToAndCall' in UUPSUpgradeable - /// @dev must be called by 'owner' - /// @param newImplementation address of the new implementation - function _authorizeUpgrade(address newImplementation) internal virtual override onlyOwner{ } -} \ No newline at end of file diff --git a/solidity/supra_contracts/src/AutomationRegistry.sol b/solidity/supra_contracts/src/AutomationRegistry.sol deleted file mode 100644 index cbd6071532..0000000000 --- a/solidity/supra_contracts/src/AutomationRegistry.sol +++ /dev/null @@ -1,699 +0,0 @@ -// SPDX-License-Identifier: MIT -pragma solidity 0.8.27; - -import {EnumerableSet} from "../lib/openzeppelin-contracts/contracts/utils/structs/EnumerableSet.sol"; -import {CommonUtils} from "./CommonUtils.sol"; -import {LibRegistry} from "./LibRegistry.sol"; - -import {IAutomationCore} from "./IAutomationCore.sol"; -import {IAutomationController} from "./IAutomationController.sol"; -import {IAutomationRegistry} from "./IAutomationRegistry.sol"; -import {IERC20} from "../lib/openzeppelin-contracts/contracts/token/ERC20/IERC20.sol"; -import {Ownable2StepUpgradeable} from "../lib/openzeppelin-contracts-upgradeable/contracts/access/Ownable2StepUpgradeable.sol"; -import {UUPSUpgradeable} from "../lib/openzeppelin-contracts/contracts/proxy/utils/UUPSUpgradeable.sol"; - -contract AutomationRegistry is IAutomationRegistry, Ownable2StepUpgradeable, UUPSUpgradeable { - using EnumerableSet for *; - using CommonUtils for *; - using LibRegistry for *; - - /// @dev Defines divisor for refunds of deposit fees with penalty - /// Factor of `2` suggests that `1/2` of the deposit will be refunded. - uint8 constant REFUND_FACTOR = 2; - - /// @notice Address of the transaction hash precompile. - address public constant TX_HASH_PRECOMPILE = 0x0000000000000000000000000000000053555001; - - /// @dev State variables - LibRegistry.RegistryState regState; - address public automationCore; - address public automationController; - - // ::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::: EVENTS ::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::: - - /// @notice Emitted when a user task is registered. - event TaskRegistered( - uint64 indexed taskIndex, - address indexed owner, - uint128 registrationFee, - uint128 lockedDepositFee, - CommonUtils.TaskDetails taskMetadata - ); - - /// @notice Emitted when a system task is registered. - event SystemTaskRegistered( - uint64 indexed taskIndex, - address indexed owner, - uint256 timestamp, - CommonUtils.TaskDetails taskMetadata - ); - - /// @notice Emitted when an account is authorized as submitter for system tasks. - event AuthorizationGranted(address indexed account, uint256 indexed timestamp); - - /// @notice Emitted when authorization is revoked for an account to submit system tasks. - event AuthorizationRevoked(address indexed account, uint256 indexed timestamp); - - /// @notice Emitted when the AutomationCore contract address is updated. - event AutomationCoreUpdated(address indexed oldAutomationCore, address indexed newAutomationCore); - - /// @notice Emitted when the AutomationController contract address is updated. - event AutomationControllerUpdated(address indexed oldAutomationController, address indexed newAutomationController); - - /// @notice Emitted when a task is cancelled. - event TaskCancelled( - uint64 indexed taskIndex, - address indexed owner, - bytes32 indexed regHash - ); - - /// @notice Emitted when a task is stopped. - event TasksStopped( - LibRegistry.TaskStopped[] indexed stoppedTasks, - address indexed owner - ); - - // :::::::::::::::::::::::::::::::::::::::::::::::::::::::::: CONSTRUCTOR AND INITIALIZER :::::::::::::::::::::::::::::::::::::::::::::::::::::::::: - - /// @dev Disables the initialization for the implementation contract. - constructor() { - _disableInitializers(); - } - - /// @notice Initializes the owner and AutomationCore contract address, can only be called once. - /// @param _automationCore Address of the AutomationCore contract. - function initialize(address _automationCore) public initializer { - _automationCore.validateContractAddress(); - - automationCore = _automationCore; - - __Ownable2Step_init(); - __Ownable_init(msg.sender); - } - - // ::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::: TASKS RELATED FUNCTIONS ::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::: - - /// @notice Function used to register a user task for automation. - /// @param _payloadTx Includes the target smart contract address and the data to call in abi encoded form. - /// @param _expiryTime Time after which the task gets expired. - /// @param _maxGasAmount Maximum amount of gas for the automation task. - /// @param _gasPriceCap Maximum gas willing to pay for the task. - /// @param _automationFeeCapForCycle Maximum automation fee for a cycle to be paid ever. - /// @param _priority Priority for the task. 0 for default priority. - /// @param _auxData Auxiliary data to be passed. - function register( - bytes memory _payloadTx, - uint64 _expiryTime, - uint128 _maxGasAmount, - uint128 _gasPriceCap, - uint128 _automationFeeCapForCycle, - uint64 _priority, - bytes[] memory _auxData - ) external { - uint64 regTime = uint64(block.timestamp); - - IAutomationCore core = IAutomationCore(automationCore); - core.updateStateForValidRegistration( - totalTasks(), - regTime, - _expiryTime, - CommonUtils.TaskType.UST, - _payloadTx, - _maxGasAmount, - _gasPriceCap, - _automationFeeCapForCycle - ); - - uint64 taskIndex = regState.currentIndex; - - LibRegistry.TaskMetadata memory taskMetadata = LibRegistry.createTaskMetadata( - _maxGasAmount, - _gasPriceCap, - _automationFeeCapForCycle, - _automationFeeCapForCycle , - readTxHash(), - taskIndex, - regTime, - _expiryTime, - taskIndex, // priority set to taskIndex - msg.sender, - CommonUtils.TaskType.UST, - CommonUtils.TaskState.PENDING, - _payloadTx, - _auxData - ); - - regState.tasks[taskIndex] = taskMetadata; - require(regState.taskIdList.add(taskIndex), TaskIndexNotUnique()); - regState.currentIndex += 1; - - core.incTotalDepositedAutomationFees(_automationFeeCapForCycle); - uint128 flatRegistrationFeeWei = core.flatRegistrationFeeWei(); - uint128 fee = flatRegistrationFeeWei + _automationFeeCapForCycle; - core.chargeFees(msg.sender, fee); - - emit TaskRegistered(taskIndex, msg.sender, flatRegistrationFeeWei, _automationFeeCapForCycle, regState.tasks[taskIndex].getTaskDetails()); - } - - /// @notice Function to register a system task. Reverts if caller is not authorized. - /// @param _payloadTx Includes the target smart contract address and the data to call in abi encoded form. - /// @param _expiryTime Time after which the task gets expired. - /// @param _maxGasAmount Maximum amount of gas for the automation task. - /// @param _priority Priority for the task. 0 for default priority. - /// @param _auxData Auxiliary data to be passed. - function registerSystemTask( - bytes memory _payloadTx, - uint64 _expiryTime, - uint128 _maxGasAmount, - uint64 _priority, - bytes[] memory _auxData - ) external { - if(!isAuthorizedSubmitter(msg.sender)) { revert UnauthorizedAccount(); } - - uint64 regTime = uint64(block.timestamp); - IAutomationCore(automationCore).updateStateForValidRegistration( - totalSystemTasks(), - regTime, - _expiryTime, - CommonUtils.TaskType.GST, - _payloadTx, - _maxGasAmount, - 0, - 0 - ); - - uint64 taskIndex = regState.currentIndex; - uint64 taskPriority = _priority == 0 ? taskIndex : _priority; // Defaults to taskIndex as priority if 0 is passed - LibRegistry.TaskMetadata memory taskMetadata = LibRegistry.createTaskMetadata( - _maxGasAmount, - 0, - 0, - 0, - readTxHash(), - taskIndex, - regTime, - _expiryTime, - taskPriority, - msg.sender, - CommonUtils.TaskType.GST, - CommonUtils.TaskState.PENDING, - _payloadTx, - _auxData - ); - - regState.tasks[taskIndex] = taskMetadata; - require(regState.taskIdList.add(taskIndex), TaskIndexNotUnique()); - require(regState.sysTaskIds.add(taskIndex), TaskIndexNotUnique()); - regState.currentIndex += 1; - - emit SystemTaskRegistered(taskIndex, msg.sender, block.timestamp, regState.tasks[taskIndex].getTaskDetails()); - } - - /// @notice Cancels an automation task with specified task index. - /// Only existing task, which is PENDING or ACTIVE, can be cancelled and only by task owner. - /// If the task is - /// - active, its state is updated to be CANCELLED. - /// - pending, it is removed form the list. - /// - cancelled, an error is reported - /// Committed gas limit is updated by reducing it with the max gas amount of the cancelled task. - /// @param _taskIndex Index of the task. - function cancelTask( - uint64 _taskIndex - ) external { - // Check if automation is enabled - IAutomationController controller = IAutomationController(automationController); - if (!controller.isAutomationEnabled()) { revert AutomationNotEnabled(); } - - if(!controller.isCycleStarted()) { revert CycleTransitionInProgress(); } - if(!ifTaskExists(_taskIndex)) { revert TaskDoesNotExist(); } - - CommonUtils.TaskDetails memory task = regState.tasks[_taskIndex].getTaskDetails(); - - if(task.taskType == CommonUtils.TaskType.GST) { revert UnsupportedTaskOperation(); } - if(task.owner != msg.sender) { revert UnauthorizedAccount(); } - if(task.state == CommonUtils.TaskState.CANCELLED) { revert AlreadyCancelled(); } - - IAutomationCore core = IAutomationCore(automationCore); - if (task.state == CommonUtils.TaskState.PENDING) { - // When Pending tasks are cancelled, refund of the deposit fee is done with penalty - _removeTask(_taskIndex, false); - bool result = core.safeDepositRefund( - _taskIndex, - task.owner, - task.depositFee / REFUND_FACTOR, - task.depositFee - ); - if(!result) { revert ErrorDepositRefund(); } - } else { - // It is safe not to check the state as above, the cancelled tasks are already rejected. - // Active tasks will be refunded the deposited amount fully at the end of the cycle. - LibRegistry.setState(regState.tasks[_taskIndex], uint8(CommonUtils.TaskState.CANCELLED)); - } - - // This check means the task was expected to be executed in the next cycle, but it has been cancelled. - // We need to remove its gas commitment from `gasCommittedForNextCycle` for this particular task. - if (task.expiryTime > controller.getCycleEndTime()) { - core.updateGasCommittedForNextCycle(task.taskType, task.maxGasAmount); - } - - emit TaskCancelled( _taskIndex, task.owner, task.txHash); - } - - /// @notice Cancels a system automation task with specified task index. - /// Only existing task, which is PENDING or ACTIVE, can be cancelled and only by task owner. - /// If the task is - /// - active, its state is updated to be CANCELLED. - /// - pending, it is removed form the list. - /// - cancelled, an error is reported - /// Committed gas limit is updated by reducing it with the max gas amount of the cancelled task. - /// @param _taskIndex Index of the task. - function cancelSystemTask( - uint64 _taskIndex - ) external { - // Check if automation is enabled - IAutomationController controller = IAutomationController(automationController); - if (!controller.isAutomationEnabled()) { revert AutomationNotEnabled(); } - - if(!controller.isCycleStarted()) { revert CycleTransitionInProgress(); } - if(!ifTaskExists(_taskIndex)) { revert TaskDoesNotExist(); } - if(!ifSysTaskExists(_taskIndex)) { revert SystemTaskDoesNotExist(); } - - CommonUtils.TaskDetails memory task = regState.tasks[_taskIndex].getTaskDetails(); - - // Check if GST - if(task.taskType == CommonUtils.TaskType.UST) { revert UnsupportedTaskOperation(); } - - if(task.owner != msg.sender) { revert UnauthorizedAccount(); } - if(task.state == CommonUtils.TaskState.CANCELLED) { revert AlreadyCancelled(); } - - if(task.state == CommonUtils.TaskState.PENDING) { - _removeTask(_taskIndex, true); - } else { - LibRegistry.setState(regState.tasks[_taskIndex], uint8(CommonUtils.TaskState.CANCELLED)); - } - - // This check means the task was expected to be executed in the next cycle, but it has been cancelled. - // We need to remove its gas commitment from `gasCommittedForNextCycle` for this particular task. - if(task.expiryTime > controller.getCycleEndTime()) { - IAutomationCore(automationCore).updateGasCommittedForNextCycle(task.taskType, task.maxGasAmount); - } - - emit TaskCancelled(_taskIndex, msg.sender, task.txHash); - } - - /// @notice Immediately stops automation tasks for the specified `_taskIndexes`. - /// Only tasks that exist and are owned by the sender can be stopped. - /// If any of the specified tasks are not owned by the sender, the transaction will abort. - /// When a task is stopped, the committed gas for the next cycle is reduced - /// by the max gas amount of the stopped task. Half of the remaining task fee is refunded. - /// @param _taskIndexes Array of task indexes to be stopped. - function stopTasks( - uint64[] memory _taskIndexes - ) external { - // Check if automation is enabled - IAutomationController controller = IAutomationController(automationController); - if (!controller.isAutomationEnabled()) { revert AutomationNotEnabled(); } - - if(!controller.isCycleStarted()) { revert CycleTransitionInProgress(); } - if(_taskIndexes.length == 0) { revert TaskIndexesCannotBeEmpty(); } - - LibRegistry.TaskStopped[] memory stoppedTaskDetails = new LibRegistry.TaskStopped[](_taskIndexes.length); - uint256 counter = 0; - - uint128 totalRefundFee = 0; - - // Calculate refundable fee for this remaining time task in current cycle - uint64 currentTime = uint64(block.timestamp); - uint64 cycleEndTime = controller.getCycleEndTime(); - uint64 residualInterval = cycleEndTime <= currentTime ? 0 : (cycleEndTime - currentTime); - - IAutomationCore core = IAutomationCore(automationCore); - - // Loop through each task index to validate and stop the task - for (uint256 i = 0; i < _taskIndexes.length; i++) { - if(ifTaskExists(_taskIndexes[i])) { - CommonUtils.TaskDetails memory task = regState.tasks[_taskIndexes[i]].getTaskDetails(); - - // Check if authorised - if(msg.sender != task.owner) { revert UnauthorizedAccount(); } - - // Check if UST - if(task.taskType == CommonUtils.TaskType.GST) { revert UnsupportedTaskOperation(); } - - // Remove task from the registry - _removeTask(_taskIndexes[i], false); - // Remove from active tasks - require(regState.activeTaskIds.remove(_taskIndexes[i]), TaskIndexNotFound()); - - // This check means the task was expected to be executed in the next cycle, but it has been stopped. - // We need to remove its gas commitment from `gasCommittedForNextCycle` for this particular task. - // Also it checks that task should not be cancelled. - if(task.state != CommonUtils.TaskState.CANCELLED && task.expiryTime > cycleEndTime) { - // Reduce committed gas by the stopped task's max gas - core.updateGasCommittedForNextCycle(task.taskType, task.maxGasAmount); - } - - (uint128 cycleFeeRefund, uint128 depositRefund) = core.unlockDepositAndCycleFee( - _taskIndexes[i], - task.state, - task.expiryTime, - task.maxGasAmount, - residualInterval, - uint64(currentTime), - task.depositFee - ); - totalRefundFee += (cycleFeeRefund + depositRefund); - - - // Add to stopped tasks - LibRegistry.TaskStopped memory taskStopped = LibRegistry.TaskStopped( - _taskIndexes[i], - depositRefund, - cycleFeeRefund, - task.txHash - ); - stoppedTaskDetails[counter] = taskStopped; - counter += 1; - } - } - - // Refund and emit event if any tasks were stopped - if(stoppedTaskDetails.length > 0) { - core.refund(msg.sender, totalRefundFee); - - // Emit task stopped event - emit TasksStopped( - stoppedTaskDetails, - msg.sender - ); - } - } - - /// @notice Immediately stops system automation tasks for the specified `_taskIndexes`. - /// Only tasks that exist and are owned by the sender can be stopped. - /// If any of the specified tasks are not owned by the sender, the transaction will abort. - /// When a task is stopped, the committed gas for the next cycle is reduced - /// by the max gas amount of the stopped task. - /// @param _taskIndexes Array of task indexes to be stopped. - function stopSystemTasks( - uint64[] memory _taskIndexes - ) external { - // Check if automation is enabled - IAutomationController controller = IAutomationController(automationController); - if (!controller.isAutomationEnabled()) { revert AutomationNotEnabled(); } - - if(!controller.isCycleStarted()) { revert CycleTransitionInProgress(); } - - // Ensure that task indexes are provided - if(_taskIndexes.length == 0) { revert TaskIndexesCannotBeEmpty(); } - - LibRegistry.TaskStopped[] memory stoppedTaskDetails = new LibRegistry.TaskStopped[](_taskIndexes.length); - uint256 counter = 0; - - // Loop through each task index to validate and stop the task - for (uint256 i = 0; i < _taskIndexes.length; i++) { - if(ifTaskExists(_taskIndexes[i])) { - CommonUtils.TaskDetails memory task = regState.tasks[_taskIndexes[i]].getTaskDetails(); - - if(task.owner != msg.sender) { revert UnauthorizedAccount(); } - - // Check if GST - if(task.taskType == CommonUtils.TaskType.UST) { revert UnsupportedTaskOperation(); } - _removeTask(_taskIndexes[i], true); - // Remove from active tasks - require(regState.activeTaskIds.remove(_taskIndexes[i]), TaskIndexNotFound()); - - if(task.state != CommonUtils.TaskState.CANCELLED && task.expiryTime > controller.getCycleEndTime()) { - IAutomationCore(automationCore).updateGasCommittedForNextCycle(task.taskType, task.maxGasAmount); - } - - // Add to stopped tasks - LibRegistry.TaskStopped memory taskStopped = LibRegistry.TaskStopped( - _taskIndexes[i], - 0, - 0, - task.txHash - ); - stoppedTaskDetails[counter] = taskStopped; - counter += 1; - } - } - - if(stoppedTaskDetails.length > 0) { - // Emit task stopped event - emit TasksStopped( - stoppedTaskDetails, - msg.sender - ); - } - } - - // :::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::: HELPER FUNCTIONS :::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::: - - /// @notice Read tx hash via precompile. Reverts if precompile missing/fails. - function readTxHash() private view returns (bytes32) { - (bool ok, bytes memory out) = TX_HASH_PRECOMPILE.staticcall(""); - require(ok, FailedToCallTxHashPrecompile()); - require(out.length == 32, TxnHashLengthShouldBe32(uint64(out.length))); - return abi.decode(out, (bytes32)); - } - - /// @notice Function to remove a task from the registry. - /// @param _taskIndex Index of the task to remove. - /// @param _removeFromSysReg Wheather to remove from system task registry. - function _removeTask(uint64 _taskIndex, bool _removeFromSysReg) private { - if(_removeFromSysReg) { - require(regState.sysTaskIds.remove(_taskIndex), TaskIndexNotFound()); - } - - delete regState.tasks[_taskIndex]; - require(regState.taskIdList.remove(_taskIndex), TaskIndexNotFound()); - } - - /// @notice Function to ensure that AutomationController contract is the caller. - function onlyController() private view { - if(msg.sender != automationController) { revert CallerNotController(); } - } - - // ::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::: ADMIN FUNCTIONS ::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::: - - /// @notice Grants authorization to the input account to submit system automation tasks. - /// @param _account Address to grant authorization to. - function grantAuthorization(address _account) external onlyOwner { - require(regState.authorizedAccounts.add(_account), AddressAlreadyExists()); - emit AuthorizationGranted(_account, block.timestamp); - } - - /// @notice Revokes authorization from the input account to submit system automation tasks. - /// @param _account Address to revoke authorization from. - function revokeAuthorization(address _account) external onlyOwner { - require(regState.authorizedAccounts.remove(_account), AddressDoesNotExist()); - emit AuthorizationRevoked(_account, block.timestamp); - } - - /// @notice Function to update the AutomationCore contract address. - /// @param _automationCore Address of the AutomationCore contract. - function setAutomationCore(address _automationCore) external onlyOwner { - _automationCore.validateContractAddress(); - - address oldAutomationCore = automationCore; - automationCore = _automationCore; - - emit AutomationCoreUpdated(oldAutomationCore, _automationCore); - } - - /// @notice Function to update the AutomationController contract address. - /// @param _automationController Address of the AutomationController contract. - function setAutomationController(address _automationController) external onlyOwner { - _automationController.validateContractAddress(); - - address oldAutomationController = automationController; - automationController = _automationController; - - emit AutomationControllerUpdated(oldAutomationController, _automationController); - } - - // ::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::: CONTROLLER FUNCTIONS ::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::: - - /// @notice Internally calls _removeTask, reverts if caller is not AutomationController. - function removeTask(uint64 _taskIndex, bool _removeFromSysReg) external { - onlyController(); - _removeTask(_taskIndex, _removeFromSysReg); - } - - /// @notice Function to update state of the task. - /// @param _taskIndex Index of the task. - /// @param _taskState State to update task to. - function updateTaskState(uint64 _taskIndex, CommonUtils.TaskState _taskState) external { - onlyController(); - LibRegistry.setState(regState.tasks[_taskIndex], uint8(_taskState)); - } - - /// @notice Function to update tasks lists. - /// @param _state Cycle transition state executing the update. - function updateTaskIds(CommonUtils.CycleState _state) external { - onlyController(); - - regState.activeTaskIds.clear(); - - if(_state == CommonUtils.CycleState.FINISHED) { - uint256[] memory taskIds = regState.taskIdList.values(); - for (uint256 i = 0; i < taskIds.length; i++) { - regState.activeTaskIds.add(taskIds[i]); - } - } else { - regState.sysTaskIds.clear(); - } - } - - /// @notice Refunds the deposit fee of the task and removes from the registry. - /// @param _taskIndex Index of the task. - /// @param _taskOwner Owner of the task. - /// @param _refundableDeposit Refundable amount of deposit. - /// @param _lockedDeposit Total locked deposit. - function refundDepositAndDrop( - uint64 _taskIndex, - address _taskOwner, - uint128 _refundableDeposit, - uint128 _lockedDeposit - ) external { - onlyController(); - // Check if task is UST - if (regState.tasks[_taskIndex].taskType() == CommonUtils.TaskType.GST) { revert RegisteredTaskInvalidType(); } - - // Remove task from the registry state - _removeTask(_taskIndex, false); - - // Refund - IAutomationCore(automationCore).safeDepositRefund( - _taskIndex, - _taskOwner, - _refundableDeposit, - _lockedDeposit - ); - } - - // :::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::: VIEW FUNCTIONS :::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::: - - /// @notice Retrieves the details of automation tasks by their task index. Skips a task if it doesn't exist. - /// @param _taskIndexes Input task indexes to get details of. - /// @return Task details of the tasks that exist. - function getTaskDetailsBulk(uint64[] memory _taskIndexes) external view returns (CommonUtils.TaskDetails[] memory) { - uint256 count = _taskIndexes.length; - CommonUtils.TaskDetails[] memory temp = new CommonUtils.TaskDetails[](count); - uint256 exists; - - for (uint256 i = 0; i < count; i++) { - if(ifTaskExists(_taskIndexes[i])) { - temp[exists] = regState.tasks[_taskIndexes[i]].getTaskDetails(); - exists += 1; - } - } - - CommonUtils.TaskDetails[] memory taskDetails = new CommonUtils.TaskDetails[](exists); - for (uint256 i = 0; i < exists; i++) { - taskDetails[i] = temp[i]; - } - return taskDetails; - } - - /// @notice Returns all the automation tasks available in the registry. - function getTaskIdList() external view returns (uint256[] memory) { - return regState.taskIdList.values(); - } - - /// @notice Returns the number of total tasks. - function totalTasks() public view returns (uint256) { - return regState.taskIdList.length(); - } - - /// @notice Returns the number of total system tasks. - function totalSystemTasks() public view returns (uint256) { - return regState.sysTaskIds.length(); - } - - /// @notice Returns the next task index. - function getNextTaskIndex() external view returns (uint64) { - return regState.currentIndex; - } - - /// @notice Returns the details of a task. Reverts if task doesn't exist. - /// @param _taskIndex Task index to get details for. - function getTaskDetails(uint64 _taskIndex) external view returns (CommonUtils.TaskDetails memory) { - if(!ifTaskExists(_taskIndex)) { revert TaskDoesNotExist(); } - return regState.tasks[_taskIndex].getTaskDetails(); - } - - /// @notice Checks if a task exist. - /// @param _taskIndex Task index to check if a task exists against it. - function ifTaskExists(uint64 _taskIndex) public view returns (bool) { - return regState.tasks[_taskIndex].owner() != address(0) && regState.taskIdList.contains(_taskIndex); - } - - /// @notice Checks if a system task exist. - /// @param _taskIndex Task index to check if a system task exists against it. - function ifSysTaskExists(uint64 _taskIndex) public view returns (bool) { - return regState.sysTaskIds.contains(_taskIndex); - } - - /// @notice Validates the input task type against the task type. - /// @param _taskIndex Index of the task. - /// @param _type Input task type. - function checkTaskType(uint64 _taskIndex, CommonUtils.TaskType _type) external view returns (bool) { - if (!ifTaskExists(_taskIndex)) { revert TaskDoesNotExist(); } - return _type == regState.tasks[_taskIndex].taskType(); - } - - /// @notice Returns the owner of the task - /// @param _taskIndex Task index of the task to query. - function getTaskOwner(uint64 _taskIndex) external view returns (address) { - return regState.tasks[_taskIndex].owner(); - } - - /// @notice Returns the state of the task - /// @param _taskIndex Task index of the task to query. - function getTaskState(uint64 _taskIndex) external view returns (CommonUtils.TaskState) { - return LibRegistry.state(regState.tasks[_taskIndex]); - } - - /// @notice Checks if the input account is an authorized submitter to submit system automation tasks. - /// @param _account Address to check if it's authorized. - function isAuthorizedSubmitter(address _account) public view returns (bool) { - return regState.authorizedAccounts.contains(_account); - } - - /// @notice Returns the total number of active tasks. - function getTotalActiveTasks() external view returns (uint256) { - return regState.activeTaskIds.length(); - } - - /// @notice Returns all the active task indexes. - function getAllActiveTaskIds() external view returns (uint256[] memory) { - return regState.activeTaskIds.values(); - } - - /// @notice Checks whether there is an active task in registry with specified input task index. - function hasActiveUserTask(address _account, uint64 _taskIndex) external view returns (bool) { - return hasActiveTaskOfType(_account, _taskIndex, CommonUtils.TaskType.UST); - } - - /// @notice Checks whether there is an active system task in registry with specified input task index. - function hasActiveSystemTask(address _account, uint64 _taskIndex) external view returns (bool) { - return hasActiveTaskOfType(_account, _taskIndex, CommonUtils.TaskType.GST); - } - - /// @notice Checks whether there is an active task in registry with specified input task index of the input type. - /// The type can be either 0 for user submitted tasks, and 1 for governance authorized tasks. - function hasActiveTaskOfType(address _account, uint64 _taskIndex, CommonUtils.TaskType _type) public view returns (bool) { - LibRegistry.TaskMetadata storage task = regState.tasks[_taskIndex]; - return task.owner() == _account && task.state() != CommonUtils.TaskState.PENDING && task.taskType() == _type; - } - - // ::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::: UPGRADEABILITY FUNCTIONS ::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::: - - /// @notice Helper function that reverts when 'msg.sender' is not authorized to upgrade the contract. - /// @dev called by 'upgradeTo' and 'upgradeToAndCall' in UUPSUpgradeable - /// @dev must be called by 'owner' - /// @param newImplementation address of the new implementation - function _authorizeUpgrade(address newImplementation) internal virtual override onlyOwner{ } -} diff --git a/solidity/supra_contracts/src/BlockMeta.sol b/solidity/supra_contracts/src/BlockMeta.sol index 5f839debb3..b359c7c4f1 100644 --- a/solidity/supra_contracts/src/BlockMeta.sol +++ b/solidity/supra_contracts/src/BlockMeta.sol @@ -3,10 +3,10 @@ pragma solidity 0.8.27; import {OwnableUpgradeable} from "../lib/openzeppelin-contracts-upgradeable/contracts/access/OwnableUpgradeable.sol"; import {UUPSUpgradeable} from "../lib/openzeppelin-contracts/contracts/proxy/utils/UUPSUpgradeable.sol"; -import {CommonUtils} from "./CommonUtils.sol"; +import {LibUtils} from "./libraries/LibUtils.sol"; contract BlockMeta is OwnableUpgradeable, UUPSUpgradeable { - using CommonUtils for address; + using LibUtils for address; /// @dev Custom errors error CallerNotVmSigner(); diff --git a/solidity/supra_contracts/src/CommonUtils.sol b/solidity/supra_contracts/src/CommonUtils.sol deleted file mode 100644 index 14215de15a..0000000000 --- a/solidity/supra_contracts/src/CommonUtils.sol +++ /dev/null @@ -1,132 +0,0 @@ -// SPDX-License-Identifier: MIT -pragma solidity 0.8.27; - -import {LibRegistry} from "./LibRegistry.sol"; - -// Helper library used by supra contracts -library CommonUtils { - - // Custom errors - error AddressCannotBeEOA(); - error AddressCannotBeZero(); - - // Address of the VM Signer: SUP0 - address constant VM_SIGNER = address(0x53555000); - - /// @notice Enum describing state of the cycle. - enum CycleState { - READY, - STARTED, - FINISHED, - SUSPENDED - } - - /// @notice Enum describing state of a task. - enum TaskState { - PENDING, - ACTIVE, - CANCELLED - } - - /// @notice Enum describing task type. - enum TaskType { - UST, - GST - } - - /// @notice Task details for individual automation tasks. - struct TaskDetails { - uint128 maxGasAmount; - uint128 gasPriceCap; - uint128 automationFeeCapForCycle; - uint128 depositFee; - bytes32 txHash; - uint64 taskIndex; - uint64 registrationTime; - uint64 expiryTime; - uint64 priority; - TaskType taskType; - TaskState state; - address owner; - bytes payloadTx; - bytes[] auxData; - } - - function getTaskDetails(LibRegistry.TaskMetadata storage t) internal view returns (TaskDetails memory details) { - // --- Decode maxGasAmount (upper 128 bits) --- - details.maxGasAmount = uint128(t.maxGasAmount_gasPriceCap >> 128); - - // --- Decode gasPriceCap (lower 128 bits) --- - details.gasPriceCap = uint128(t.maxGasAmount_gasPriceCap); - - // --- Decode automationFeeCapForCycle (upper 128 bits) --- - details.automationFeeCapForCycle = uint128(t.automationFeeCapForCycle_depositFee >> 128); - - // --- Decode depositFee (lower 128 bits) --- - details.depositFee = uint128(t.automationFeeCapForCycle_depositFee); - - // --- Direct values --- - details.txHash = t.txHash; - details.payloadTx = t.payloadTx; - details.auxData = t.auxData; - - // --- Decode packed uint256: taskIndex | registrationTime | expiryTime | priority --- - details.taskIndex = uint64(t.taskIndex_registrationTime_expiryTime_priority >> 192); - details.registrationTime = uint64(t.taskIndex_registrationTime_expiryTime_priority >> 128); - details.expiryTime = uint64(t.taskIndex_registrationTime_expiryTime_priority >> 64); - details.priority = uint64(t.taskIndex_registrationTime_expiryTime_priority); - - // --- Decode packed uint256: owner | taskType | taskState --- - details.owner = address(uint160(t.owner_type_state >> 96)); - details.taskType = TaskType(uint8(t.owner_type_state >> 88)); - details.state = TaskState(uint8(t.owner_type_state >> 80)); - } - - - /// @notice Deposit and fee related accounting. - struct Deposit { - uint256 totalDepositedAutomationFees; - address coldWallet; - // mapping(uint64 => uint256) taskLockedFees; // TO_DO - } - - /// @notice Struct representing a stopped task. - struct TaskStopped { - uint64 taskIndex; - uint128 depositRefund; - uint128 cycleFeeRefund; - bytes32 txHash; - } - - /// @dev Returns a boolean indicating whether the given address is a contract or not. - /// @param _addr The address to be checked. - /// @return A boolean indicating whether the given address is a contract or not. - function isContract(address _addr) internal view returns (bool) { - uint256 size; - assembly { - size := extcodesize(_addr) - } - return size > 0; - } - - /// @notice Validates a contract address. - function validateContractAddress(address _contractAddr) internal view { - if (_contractAddr == address(0)) { revert AddressCannotBeZero(); } - if (!isContract(_contractAddr)) { revert AddressCannotBeEOA(); } - } - - /// @notice Checks if an address is VM Signer. - /// @param _addr Address to check. - /// @return bool If it is VM Signer. - function isVmSigner(address _addr) internal pure returns (bool) { - return _addr == VM_SIGNER; - } - - /// @notice Checks if an address is a reserved address. - /// @param _addr Address to check. - /// @return bool If it is a reserved address. - function isReservedAddress(address _addr) internal pure returns (bool) { - uint160 addr = uint160(_addr); - return addr >= uint160(VM_SIGNER) && addr <= uint160(0x535550FF); - } -} diff --git a/solidity/supra_contracts/src/Diamond.sol b/solidity/supra_contracts/src/Diamond.sol new file mode 100644 index 0000000000..cf37ea950e --- /dev/null +++ b/solidity/supra_contracts/src/Diamond.sol @@ -0,0 +1,63 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.0; + +/******************************************************************************\ +* Author: Nick Mudge (https://twitter.com/mudgen) +* EIP-2535 Diamonds: https://eips.ethereum.org/EIPS/eip-2535 +* +* Implementation of a diamond. +/******************************************************************************/ + +import { LibDiamond } from "./libraries/LibDiamond.sol"; +import { IDiamondCut } from "./interfaces/IDiamondCut.sol"; + +contract Diamond { + + constructor(address _contractOwner, address _diamondCutFacet) payable { + LibDiamond.setContractOwner(_contractOwner); + + // Add the diamondCut external function from the diamondCutFacet + IDiamondCut.FacetCut[] memory cut = new IDiamondCut.FacetCut[](1); + bytes4[] memory functionSelectors = new bytes4[](1); + functionSelectors[0] = IDiamondCut.diamondCut.selector; + cut[0] = IDiamondCut.FacetCut({ + facetAddress: _diamondCutFacet, + action: IDiamondCut.FacetCutAction.Add, + functionSelectors: functionSelectors + }); + LibDiamond.diamondCut(cut, address(0), ""); + } + + // Find facet for function that is called and execute the + // function if a facet is found and return any value. + fallback() external payable { + LibDiamond.DiamondStorage storage ds; + bytes32 position = LibDiamond.DIAMOND_STORAGE_POSITION; + // get diamond storage + assembly { + ds.slot := position + } + // get facet from function selector + address facet = ds.selectorToFacetAndPosition[msg.sig].facetAddress; + require(facet != address(0), "Diamond: Function does not exist"); + // Execute external function from facet using delegatecall and return any value. + assembly { + // copy function selector and any arguments + calldatacopy(0, 0, calldatasize()) + // execute function call using the facet + let result := delegatecall(gas(), facet, 0, calldatasize(), 0, 0) + // get any return value + returndatacopy(0, 0, returndatasize()) + // return any return value or error back to the caller + switch result + case 0 { + revert(0, returndatasize()) + } + default { + return(0, returndatasize()) + } + } + } + + receive() external payable {} +} diff --git a/solidity/supra_contracts/src/IAutomationController.sol b/solidity/supra_contracts/src/IAutomationController.sol deleted file mode 100644 index 465789d8bb..0000000000 --- a/solidity/supra_contracts/src/IAutomationController.sol +++ /dev/null @@ -1,34 +0,0 @@ -// SPDX-License-Identifier: MIT -pragma solidity 0.8.27; - -import {CommonUtils} from "./CommonUtils.sol"; - -interface IAutomationController { - // Custom errors - error AlreadyEnabled(); - error AlreadyDisabled(); - error CallerNotVmSigner(); - error InconsistentTransitionState(); - error InvalidInputCycleIndex(); - error InvalidRegistryState(); - error OutOfOrderTaskProcessingRequest(); - error RefundFailed(); - error RefundDepositAndDropFailed(); - error RemoveTaskFailed(); - error TransferFailed(); - error UnlockLockedDepositFailed(); - error UpdateGasCommittedAndCycleLockedFeesFailed(); - error UpdateTaskStateFailed(); - - // View functions - function getCycleInfo() external view returns (uint64, uint64, uint64, CommonUtils.CycleState); - function getCycleDuration() external view returns (uint64); - function getCycleEndTime() external view returns (uint64 cycleEndTime); - function getTransitionInfo() external view returns (uint64, uint128); - function isAutomationEnabled() external view returns (bool); - function isCycleStarted() external view returns (bool); - function isTransitionInProgress() external view returns (bool); - - // State updating functions - function monitorCycleEnd() external; -} diff --git a/solidity/supra_contracts/src/IAutomationCore.sol b/solidity/supra_contracts/src/IAutomationCore.sol deleted file mode 100644 index 1fc8a80b6f..0000000000 --- a/solidity/supra_contracts/src/IAutomationCore.sol +++ /dev/null @@ -1,113 +0,0 @@ -// SPDX-License-Identifier: MIT -pragma solidity 0.8.27; - -import {CommonUtils} from "./CommonUtils.sol"; - -interface IAutomationCore { - // Custom errors - error AddressCannotBeZero(); - error AutomationNotEnabled(); - error CallerNotController(); - error CallerNotRegistry(); - error CycleTransitionInProgress(); - error ErrorDepositRefund(); - error ErrorCycleFeeRefund(); - error InvalidAmount(); - error InvalidMaxGasAmount(); - error InvalidTaskType(); - error InvalidTxHash(); - error AlreadyEnabled(); - error AlreadyDisabled(); - error GasCommittedExceedsMaxGasCap(); - error GasCommittedValueUnderflow(); - error InsufficientBalance(); - error InsufficientFeeCapForCycle(); - error InsufficientBalanceForRefund(); - error InvalidCongestionExponent(); - error InvalidCongestionThreshold(); - error InvalidCycleDuration(); - error InvalidExpiryTime(); - error InvalidGasPriceCap(); - error InvalidRegistryMaxGasCap(); - error InvalidSysRegistryMaxGasCap(); - error InvalidSysTaskCapacity(); - error InvalidSysTaskDuration(); - error InvalidTaskCapacity(); - error InvalidTaskDuration(); - error RegistrationDisabled(); - error RequestExceedsLockedBalance(); - error TaskCapacityReached(); - error TaskExpiresBeforeNextCycle(); - error TransferFailed(); - error UnacceptableRegistryMaxGasCap(); - error UnacceptableSysRegistryMaxGasCap(); - error UnauthorizedCaller(); - - // View functions - function flatRegistrationFeeWei() external view returns (uint128); - function getAutomationController() external view returns (address); - function erc20Supra() external view returns (address); - function calculateTaskFee( - CommonUtils.TaskState _state, - uint64 _expiryTime, - uint128 _maxGasAmount, - uint64 _potentialFeeTimeframe, - uint64 _currentTime, - uint128 _automationFeePerSec - ) external view returns (uint128); - function calculateAutomationFeeMultiplierForCurrentCycleInternal() external view returns (uint128); - function calculateAutomationFeeMultiplierForCommittedOccupancy(uint128 _totalCommittedMaxGas) external view returns (uint128); - function cycleDurationSecs() external view returns (uint64); - function getVmSigner() external view returns (address); - function getGasCommittedForNextCycle() external view returns (uint128); - function getCycleLockedFees() external view returns (uint256); - function getTotalDepositedAutomationFees() external view returns (uint256); - function updateStateForValidRegistration( - uint256 _totalTasks, - uint64 _regTime, - uint64 _expiryTime, - CommonUtils.TaskType _taskType, - bytes memory _payloadTx, - uint128 _maxGasAmount, - uint128 _gasPriceCap, - uint128 _automationFeeCapForCycle - ) external; - - // State updating functions - function applyPendingConfig() external returns (bool, uint64); - function incTotalDepositedAutomationFees(uint256 _totalDepositedAutomationFees) external; - function chargeFees(address _from, uint256 _amount) external; - function safeUnlockLockedDeposit( - uint64 _taskIndex, - uint128 _lockedDeposit - ) external returns (bool); - function refundTaskFees( - uint64 _currentTime, - uint64 _refundDuration, - uint128 _automationFeePerSec, - CommonUtils.TaskDetails memory _task - ) external; - function safeDepositRefund( - uint64 _taskIndex, - address _taskOwner, - uint128 _refundableDeposit, - uint128 _lockedDeposit - ) external returns (bool); - function refund(address _to, uint128 _amount) external; - function unlockDepositAndCycleFee( - uint64 _taskIndex, - CommonUtils.TaskState _taskState, - uint64 _expiryTime, - uint128 _maxGasAmount, - uint64 _residualInterval, - uint64 _currentTime, - uint128 _depositFee - ) external returns (uint128, uint128); - function updateGasCommittedForNextCycle(CommonUtils.TaskType _taskType, uint128 _maxGasAmount) external; - function updateGasCommittedAndCycleLockedFees( - uint256 _lockedFees, - uint128 _sysGasCommittedForNextCycle, - uint128 _gasCommittedForNextCycle, - uint128 _gasCommittedForNewCycle - ) external; -} \ No newline at end of file diff --git a/solidity/supra_contracts/src/IAutomationRegistry.sol b/solidity/supra_contracts/src/IAutomationRegistry.sol deleted file mode 100644 index 8c0462a0cb..0000000000 --- a/solidity/supra_contracts/src/IAutomationRegistry.sol +++ /dev/null @@ -1,45 +0,0 @@ -// SPDX-License-Identifier: MIT -pragma solidity 0.8.27; - -import {CommonUtils} from "./CommonUtils.sol"; - -interface IAutomationRegistry { - // Custom errors - error AddressAlreadyExists(); - error AddressDoesNotExist(); - error AutomationNotEnabled(); - error CallerNotController(); - error UnauthorizedAccount(); - error CycleTransitionInProgress(); - error TaskDoesNotExist(); - error UnsupportedTaskOperation(); - error AlreadyCancelled(); - error ErrorDepositRefund(); - error SystemTaskDoesNotExist(); - error TaskIndexesCannotBeEmpty(); - error RegisteredTaskInvalidType(); - error TaskIndexNotFound(); - error TaskIndexNotUnique(); - error FailedToCallTxHashPrecompile(); - error TxnHashLengthShouldBe32(uint64); - - // View functions - function ifTaskExists(uint64 _taskIndex) external view returns (bool); - function checkTaskType(uint64 _taskIndex, CommonUtils.TaskType _type) external view returns (bool); - function getAllActiveTaskIds() external view returns (uint256[] memory); - function getTaskDetails(uint64 _taskIndex) external view returns (CommonUtils.TaskDetails memory); - function getTaskIdList() external view returns (uint256[] memory); - function getTotalActiveTasks() external view returns (uint256); - function totalTasks() external view returns (uint256); - - // State updating functions - function removeTask(uint64 _taskIndex, bool _removeFromSysReg) external; - function updateTaskState(uint64 _taskIndex, CommonUtils.TaskState _taskState) external; - function updateTaskIds(CommonUtils.CycleState _state) external; - function refundDepositAndDrop( - uint64 _taskIndex, - address _taskOwner, - uint128 _refundableDeposit, - uint128 _lockedDeposit - ) external; -} diff --git a/solidity/supra_contracts/src/LibConfig.sol b/solidity/supra_contracts/src/LibConfig.sol deleted file mode 100644 index ec86fa22c1..0000000000 --- a/solidity/supra_contracts/src/LibConfig.sol +++ /dev/null @@ -1,379 +0,0 @@ -// SPDX-License-Identifier: MIT -pragma solidity 0.8.27; - -// Helper library used by AutomationConfig. -library LibConfig { - uint256 private constant MAX_UINT128 = type(uint128).max; - uint256 private constant MAX_UINT160 = type(uint160).max; - uint256 private constant MAX_UINT64 = type(uint64).max; - uint256 private constant MAX_UINT16 = type(uint16).max; - uint256 private constant MAX_UINT8 = type(uint8).max; - - // ::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::: AccessListEntry ::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::: - - /// @notice Struct representing an entry in access list. - struct AccessListEntry { - address addr; - bytes32[] storageKeys; - } - - // ::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::: ConfigBuffer ::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::: - - /// @notice Struct representing configuration buffer. - struct ConfigBuffer { - Config pendingConfig; - bool ifExists; - } - - // ::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::: RegistryConfig ::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::: - - /// @notice Configuration of the automation registry. - struct RegistryConfig { - // uint128 | uint128 - uint256 gasCommittedForNextCycle_gasCommittedForThisCycle; - // uint128 | uint128 - uint256 sysGasCommittedForNextCycle_sysGasCommittedForThisCycle; - - // uint128 | uint128 - uint256 nextCycleRegistryMaxGasCap_nextCycleSysRegistryMaxGasCap; - // address | bool(1 bit) - uint256 controller_registrationEnabled; - uint256 cycleLockedFees; - uint256 totalDepositedAutomationFees; - address vmSigner; - address erc20Supra; - address registry; - Config config; - } - - function createRegistryConfig( - uint128 _nextCycleRegistryMaxGasCap, - uint128 _nextCycleSysRegistryMaxGasCap, - bool _registrationEnabled, - address _vmSigner, - address _erc20Supra, - Config memory _config - ) internal pure returns (RegistryConfig memory rcfg) { - // Pack nextCycleRegistryMaxGasCap | nextCycleSysRegistryMaxGasCap - rcfg.nextCycleRegistryMaxGasCap_nextCycleSysRegistryMaxGasCap = - (uint256(_nextCycleRegistryMaxGasCap) << 128) | - uint256(_nextCycleSysRegistryMaxGasCap); - - // Pack controller (address) | registrationEnabled (bool at bit 95) - // Sets controller as address(0) - rcfg.controller_registrationEnabled = _registrationEnabled ? uint256(1) << 95 : 0; - - rcfg.vmSigner = _vmSigner; - rcfg.erc20Supra = _erc20Supra; - - // Assign inner Config - rcfg.config = _config; - } - - // gasCommittedForNextCycle (uint128) | gasCommittedForThisCycle (uint128) - function gasCommittedForNextCycle(RegistryConfig storage r) internal view returns (uint128) { - return uint128(r.gasCommittedForNextCycle_gasCommittedForThisCycle >> 128); - } - - function gasCommittedForThisCycle(RegistryConfig storage r) internal view returns (uint128) { - return uint128(r.gasCommittedForNextCycle_gasCommittedForThisCycle); - } - - function setGasCommittedForNextCycle(RegistryConfig storage r, uint128 _value) internal { - // Clear upper 128 bits - r.gasCommittedForNextCycle_gasCommittedForThisCycle &= MAX_UINT128; - // Insert new upper 128 bits - r.gasCommittedForNextCycle_gasCommittedForThisCycle |= uint256(_value) << 128; - } - - function setGasCommittedForThisCycle(RegistryConfig storage r, uint128 _value) internal { - // Clear lower 128 bits - r.gasCommittedForNextCycle_gasCommittedForThisCycle &= MAX_UINT128 << 128; - // Insert new lower 128 bits - r.gasCommittedForNextCycle_gasCommittedForThisCycle |= uint256(_value); - } - - // sysGasCommittedForNextCycle (uint128) | sysGasCommittedForThisCycle (uint128) - function sysGasCommittedForNextCycle(RegistryConfig storage r) internal view returns (uint128){ - return uint128(r.sysGasCommittedForNextCycle_sysGasCommittedForThisCycle >> 128); - } - - function sysGasCommittedForThisCycle(RegistryConfig storage r) internal view returns (uint128){ - return uint128(r.sysGasCommittedForNextCycle_sysGasCommittedForThisCycle); - } - - function setSysGasCommittedForNextCycle(RegistryConfig storage r, uint128 _value) internal { - // Clear upper 128 bits - r.sysGasCommittedForNextCycle_sysGasCommittedForThisCycle &= MAX_UINT128; // mask = lower 128 bits all 1s - - // Insert new upper 128 bits - r.sysGasCommittedForNextCycle_sysGasCommittedForThisCycle |= uint256(_value) << 128; - } - - function setSysGasCommittedForThisCycle(RegistryConfig storage r, uint128 _value) internal { - // Clear lower 128 bits - r.sysGasCommittedForNextCycle_sysGasCommittedForThisCycle &= MAX_UINT128 << 128; // mask = upper 128 bits all 1s - - // Insert new lower 128 bits - r.sysGasCommittedForNextCycle_sysGasCommittedForThisCycle |= uint256(_value); - } - - // nextCycleRegistryMaxGasCap (uint128) | nextCycleSysRegistryMaxGasCap (uint128) - function nextCycleRegistryMaxGasCap(RegistryConfig storage r) internal view returns (uint128) { - return uint128(r.nextCycleRegistryMaxGasCap_nextCycleSysRegistryMaxGasCap >> 128); - } - - function nextCycleSysRegistryMaxGasCap(RegistryConfig storage r) internal view returns (uint128) { - return uint128(r.nextCycleRegistryMaxGasCap_nextCycleSysRegistryMaxGasCap); - } - - function setNextCycleRegistryMaxGasCap(RegistryConfig storage r, uint128 value) internal { - // clear upper 128 bits then set - r.nextCycleRegistryMaxGasCap_nextCycleSysRegistryMaxGasCap &= MAX_UINT128; - r.nextCycleRegistryMaxGasCap_nextCycleSysRegistryMaxGasCap |= uint256(value) << 128; - } - - function setNextCycleSysRegistryMaxGasCap(RegistryConfig storage r, uint128 value) internal { - // clear lower 128 bits then set - r.nextCycleRegistryMaxGasCap_nextCycleSysRegistryMaxGasCap &= (MAX_UINT128 << 128); - r.nextCycleRegistryMaxGasCap_nextCycleSysRegistryMaxGasCap |= uint256(value); - } - - // controller (address) | registrationEnabled (bool)[bit 95] - function automationController(RegistryConfig storage r) internal view returns (address) { - return address(uint160(r.controller_registrationEnabled >> 96)); - } - - function registrationEnabled(RegistryConfig storage r) internal view returns (bool) { - return (r.controller_registrationEnabled >> 95) & 1 != 0; - } - - function setAutomationController(RegistryConfig storage r, address _controller) internal { - // clear top 160 bits - r.controller_registrationEnabled &= ~(MAX_UINT160 << 96); - - // insert 160-bit address - r.controller_registrationEnabled |= uint256(uint160(_controller)) << 96; - } - - function setRegistrationEnabled(RegistryConfig storage r, bool enabled) internal { - // clear bit 95 - r.controller_registrationEnabled &= ~(uint256(1) << 95); - - // set bit 95 if enabled - r.controller_registrationEnabled |= enabled ? (uint256(1) << 95) : 0; - } - - // ::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::: Config ::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::: - - /// @notice Struct representing configuration parameters. - struct Config { - // uint128 | uint128 - uint256 registryMaxGasCap_sysRegistryMaxGasCap; - // uint128 | uint128 // TO_DO: need to decide on the currency - uint256 automationBaseFeeWeiPerSec_flatRegistrationFeeWei; - // uint128 | uint64 | uint64 // TO_DO: need to decide on the currency - uint256 congestionBaseFeeWeiPerSec_taskDurationCapSecs_sysTaskDurationCapSecs; - // uint64 | uint16 | uint16 | uint8 | uint8 - uint256 cycleDurationSecs_taskCapacity_sysTaskCapacity_congestionThresholdPercentage_congestionExponent; - } - - function createConfig( - uint128 _registryMaxGasCap, - uint128 _sysRegistryMaxGasCap, - uint128 _automationBaseFeeWeiPerSec, - uint128 _flatRegistrationFeeWei, - uint128 _congestionBaseFeeWeiPerSec, - uint64 _taskDurationCapSecs, - uint64 _sysTaskDurationCapSecs, - uint64 _cycleDurationSecs, - uint16 _taskCapacity, - uint16 _sysTaskCapacity, - uint8 _congestionThresholdPercentage, - uint8 _congestionExponent - ) internal pure returns (Config memory cfg) { - // Pack registryMaxGasCap | sysRegistryMaxGasCap - cfg.registryMaxGasCap_sysRegistryMaxGasCap = (uint256(_registryMaxGasCap) << 128) | uint256(_sysRegistryMaxGasCap); - - // Pack automationBaseFeeWeiPerSec | flatRegistrationFeeWei - cfg.automationBaseFeeWeiPerSec_flatRegistrationFeeWei = (uint256(_automationBaseFeeWeiPerSec) << 128) | uint256(_flatRegistrationFeeWei); - - // Pack congestionBaseFeeWeiPerSec | taskDurationCapSecs | sysTaskDurationCapSecs - cfg.congestionBaseFeeWeiPerSec_taskDurationCapSecs_sysTaskDurationCapSecs = - (uint256(_congestionBaseFeeWeiPerSec) << 128) | - (uint256(_taskDurationCapSecs) << 64) | - uint256(_sysTaskDurationCapSecs); - - // Pack cycleDurationSecs | taskCapacity | sysTaskCapacity | congestionThresholdPercentage | congestionExponent - cfg.cycleDurationSecs_taskCapacity_sysTaskCapacity_congestionThresholdPercentage_congestionExponent = - (uint256(_cycleDurationSecs) << 192) | - (uint256(_taskCapacity) << 176) | - (uint256(_sysTaskCapacity) << 160) | - (uint256(_congestionThresholdPercentage) << 152) | - (uint256(_congestionExponent) << 144); - } - - // uint256 registryMaxGasCap (uint128) | sysRegistryMaxGasCap (uint128) - function registryMaxGasCap(RegistryConfig storage r) internal view returns (uint128) { - return uint128(r.config.registryMaxGasCap_sysRegistryMaxGasCap >> 128); - } - - function sysRegistryMaxGasCap(RegistryConfig storage r) internal view returns (uint128) { - return uint128(r.config.registryMaxGasCap_sysRegistryMaxGasCap); - } - - function setRegistryMaxGasCap(RegistryConfig storage r, uint128 value) internal { - r.config.registryMaxGasCap_sysRegistryMaxGasCap &= MAX_UINT128; - r.config.registryMaxGasCap_sysRegistryMaxGasCap |= uint256(value) << 128; - } - - function setSysRegistryMaxGasCap(RegistryConfig storage r, uint128 value) internal { - r.config.registryMaxGasCap_sysRegistryMaxGasCap &= (MAX_UINT128 << 128); - r.config.registryMaxGasCap_sysRegistryMaxGasCap |= uint256(value); - } - - // automationBaseFeeWeiPerSec (uint128) | flatRegistrationFeeWei (uint128) - function automationBaseFeeWeiPerSec(RegistryConfig storage r) internal view returns (uint128) { - return uint128(r.config.automationBaseFeeWeiPerSec_flatRegistrationFeeWei >> 128); - } - - function flatRegistrationFeeWei(RegistryConfig storage r) internal view returns (uint128) { - return uint128(r.config.automationBaseFeeWeiPerSec_flatRegistrationFeeWei); - } - - function setAutomationBaseFeeWeiPerSec(RegistryConfig storage r, uint128 value) internal { - r.config.automationBaseFeeWeiPerSec_flatRegistrationFeeWei &= MAX_UINT128; - r.config.automationBaseFeeWeiPerSec_flatRegistrationFeeWei |= uint256(value) << 128; - } - - function setFlatRegistrationFeeWei(RegistryConfig storage r, uint128 value) internal { - r.config.automationBaseFeeWeiPerSec_flatRegistrationFeeWei &= (MAX_UINT128 << 128); - r.config.automationBaseFeeWeiPerSec_flatRegistrationFeeWei |= uint256(value); - } - - // congestionBaseFeeWeiPerSec (uint128) | taskDurationCapSecs (uint64) | sysTaskDurationCapSecs (uint64) - function congestionBaseFeeWeiPerSec(RegistryConfig storage r) internal view returns (uint128) { - return uint128(r.config.congestionBaseFeeWeiPerSec_taskDurationCapSecs_sysTaskDurationCapSecs >> 128); - } - - function taskDurationCapSecs(RegistryConfig storage r) internal view returns (uint64) { - return uint64(r.config.congestionBaseFeeWeiPerSec_taskDurationCapSecs_sysTaskDurationCapSecs >> 64); - } - - function sysTaskDurationCapSecs(RegistryConfig storage r) internal view returns (uint64) { - return uint64(r.config.congestionBaseFeeWeiPerSec_taskDurationCapSecs_sysTaskDurationCapSecs); - } - - function setCongestionBaseFeeWeiPerSec(RegistryConfig storage r, uint128 _value) internal { - r.config.congestionBaseFeeWeiPerSec_taskDurationCapSecs_sysTaskDurationCapSecs &= MAX_UINT128; - r.config.congestionBaseFeeWeiPerSec_taskDurationCapSecs_sysTaskDurationCapSecs |= uint256(_value) << 128; - } - - function setTaskDurationCapSecs(RegistryConfig storage r, uint64 value) internal { - r.config.congestionBaseFeeWeiPerSec_taskDurationCapSecs_sysTaskDurationCapSecs &= ~(MAX_UINT64 << 64); - r.config.congestionBaseFeeWeiPerSec_taskDurationCapSecs_sysTaskDurationCapSecs |= uint256(value) << 64; - } - - function setSysTaskDurationCapSecs(RegistryConfig storage r, uint64 value) internal { - r.config.congestionBaseFeeWeiPerSec_taskDurationCapSecs_sysTaskDurationCapSecs &= ~MAX_UINT64; - r.config.congestionBaseFeeWeiPerSec_taskDurationCapSecs_sysTaskDurationCapSecs |= uint256(value); - } - - // cycleDurationSecs (uint64) | taskCapacity (uint16) | sysTaskCapacity (uint16) | congestionThresholdPercentage (uint8) | congestionExponent (uint8) - function cycleDurationSecs(Config storage c) internal view returns (uint64) { - return uint64(c.cycleDurationSecs_taskCapacity_sysTaskCapacity_congestionThresholdPercentage_congestionExponent >> 192); - } - - function taskCapacity(RegistryConfig storage r) internal view returns (uint16) { - return uint16(r.config.cycleDurationSecs_taskCapacity_sysTaskCapacity_congestionThresholdPercentage_congestionExponent >> 176); - } - - function sysTaskCapacity(RegistryConfig storage r) internal view returns (uint16) { - return uint16(r.config.cycleDurationSecs_taskCapacity_sysTaskCapacity_congestionThresholdPercentage_congestionExponent >> 160); - } - - function congestionThresholdPercentage(RegistryConfig storage r) internal view returns (uint8) { - return uint8(r.config.cycleDurationSecs_taskCapacity_sysTaskCapacity_congestionThresholdPercentage_congestionExponent >> 152); - } - - function congestionExponent(RegistryConfig storage r) internal view returns (uint8) { - return uint8(r.config.cycleDurationSecs_taskCapacity_sysTaskCapacity_congestionThresholdPercentage_congestionExponent >> 144); - } - - function setCycleDurationSecs(RegistryConfig storage r, uint64 _value) internal { - r.config.cycleDurationSecs_taskCapacity_sysTaskCapacity_congestionThresholdPercentage_congestionExponent &= ~(MAX_UINT64 << 192); - r.config.cycleDurationSecs_taskCapacity_sysTaskCapacity_congestionThresholdPercentage_congestionExponent |= uint256(_value) << 192; - } - - function setTaskCapacity(RegistryConfig storage r, uint16 _value) internal { - r.config.cycleDurationSecs_taskCapacity_sysTaskCapacity_congestionThresholdPercentage_congestionExponent &= ~(MAX_UINT16 << 176); - r.config.cycleDurationSecs_taskCapacity_sysTaskCapacity_congestionThresholdPercentage_congestionExponent |= uint256(_value) << 176; - } - - function setSysTaskCapacity(RegistryConfig storage r, uint16 _value) internal { - r.config.cycleDurationSecs_taskCapacity_sysTaskCapacity_congestionThresholdPercentage_congestionExponent &= ~(MAX_UINT16 << 160); - r.config.cycleDurationSecs_taskCapacity_sysTaskCapacity_congestionThresholdPercentage_congestionExponent |= uint256(_value) << 160; - } - - function setCongestionThresholdPercentage(RegistryConfig storage r, uint8 _value) internal { - r.config.cycleDurationSecs_taskCapacity_sysTaskCapacity_congestionThresholdPercentage_congestionExponent &= ~(MAX_UINT8 << 152); - r.config.cycleDurationSecs_taskCapacity_sysTaskCapacity_congestionThresholdPercentage_congestionExponent |= uint256(_value) << 152; - } - - function setCongestionExponent(RegistryConfig storage r, uint8 _value) internal { - r.config.cycleDurationSecs_taskCapacity_sysTaskCapacity_congestionThresholdPercentage_congestionExponent &= ~(MAX_UINT8 << 144); - r.config.cycleDurationSecs_taskCapacity_sysTaskCapacity_congestionThresholdPercentage_congestionExponent |= uint256(_value) << 144; - } - - /// @notice Struct representing configuration details. - struct ConfigDetails { - uint128 registryMaxGasCap; - uint128 sysRegistryMaxGasCap; - uint128 automationBaseFeeWeiPerSec; // TO_DO: need to decide on the currency - uint128 flatRegistrationFeeWei; // TO_DO: need to decide on the currency - uint128 congestionBaseFeeWeiPerSec; // TO_DO: need to decide on the currency - uint64 taskDurationCapSecs; - uint64 sysTaskDurationCapSecs; - uint64 cycleDurationSecs; - uint16 taskCapacity; - uint16 sysTaskCapacity; - uint8 congestionThresholdPercentage; - uint8 congestionExponent; - } - - function getConfig(Config memory cfg) internal pure returns (ConfigDetails memory config) { - // ------------------------------------------------------------- - // 1. registryMaxGasCap (high 128) | sysRegistryMaxGasCap (low 128) - // ------------------------------------------------------------- - config.registryMaxGasCap = uint128(cfg.registryMaxGasCap_sysRegistryMaxGasCap >> 128); - config.sysRegistryMaxGasCap = uint128(cfg.registryMaxGasCap_sysRegistryMaxGasCap); - - // ------------------------------------------------------------- - // 2. automationBaseFeeWeiPerSec (high 128) | flatRegistrationFeeWei (low 128) - // ------------------------------------------------------------- - config.automationBaseFeeWeiPerSec = uint128(cfg.automationBaseFeeWeiPerSec_flatRegistrationFeeWei >> 128); - config.flatRegistrationFeeWei = uint128(cfg.automationBaseFeeWeiPerSec_flatRegistrationFeeWei); - - // ------------------------------------------------------------- - // 3. congestionBaseFeeWeiPerSec (high 128) - // taskDurationCapSecs (next 64) - // sysTaskDurationCapSecs (low 64) - // ------------------------------------------------------------- - config.congestionBaseFeeWeiPerSec = uint128(cfg.congestionBaseFeeWeiPerSec_taskDurationCapSecs_sysTaskDurationCapSecs >> 128); - config.taskDurationCapSecs = uint64(cfg.congestionBaseFeeWeiPerSec_taskDurationCapSecs_sysTaskDurationCapSecs >> 64); - config.sysTaskDurationCapSecs = uint64(cfg.congestionBaseFeeWeiPerSec_taskDurationCapSecs_sysTaskDurationCapSecs); - - // ------------------------------------------------------------- - // 4. cycleDurationSecs (high 64) - // taskCapacity (next 16) - // sysTaskCapacity (next 16) - // congestionThresholdPercentage (next 8) - // congestionExponent (low 8) - // ------------------------------------------------------------- - config.cycleDurationSecs = uint64(cfg.cycleDurationSecs_taskCapacity_sysTaskCapacity_congestionThresholdPercentage_congestionExponent >> 192); - config.taskCapacity = uint16(cfg.cycleDurationSecs_taskCapacity_sysTaskCapacity_congestionThresholdPercentage_congestionExponent >> 176); - config.sysTaskCapacity = uint16(cfg.cycleDurationSecs_taskCapacity_sysTaskCapacity_congestionThresholdPercentage_congestionExponent >> 160); - config.congestionThresholdPercentage = uint8(cfg.cycleDurationSecs_taskCapacity_sysTaskCapacity_congestionThresholdPercentage_congestionExponent >> 152); - config.congestionExponent = uint8(cfg.cycleDurationSecs_taskCapacity_sysTaskCapacity_congestionThresholdPercentage_congestionExponent >> 144); - } -} \ No newline at end of file diff --git a/solidity/supra_contracts/src/LibController.sol b/solidity/supra_contracts/src/LibController.sol deleted file mode 100644 index f6f07b7a7e..0000000000 --- a/solidity/supra_contracts/src/LibController.sol +++ /dev/null @@ -1,234 +0,0 @@ - -// SPDX-License-Identifier: MIT -pragma solidity 0.8.27; - -import {EnumerableSet} from "../lib/openzeppelin-contracts/contracts/utils/structs/EnumerableSet.sol"; -import {CommonUtils} from "./CommonUtils.sol"; - -// Helper library used by AutomationController. -library LibController { - - uint256 private constant MAX_UINT128 = type(uint128).max; - uint256 private constant MAX_UINT64 = type(uint64).max; - uint256 private constant MAX_UINT8 = type(uint8).max; - - /// @notice Struct representing the state of current cycle. - struct AutomationCycleInfo{ - // uint64 | uint64 | uint64 | CycleState(uint8) | bool(1 bit) | bool(1 bit) - uint256 index_startTime_durationSecs_state_ifTransitionStateExists_automationEnabled; - TransitionState transitionState; - } - - /// @notice Struct representing state transition information. - struct TransitionState { - uint256 lockedFees; - // uint128 | uint128; - uint256 automationFeePerSec_gasCommittedForNewCycle; - // uint128 | uint128 - uint256 gasCommittedForNextCycle_sysGasCommittedForNextCycle; - // uint64 | uint64 | uint64 - uint256 refundDuration_newCycleDuration_nextTaskIndexPosition; - EnumerableSet.UintSet expectedTasksToBeProcessed; - } - - // :::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::: AutomationCycleInfo :::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::: - - function initializeCycle( - AutomationCycleInfo storage _cycleInfo, - uint64 _index, - uint64 _startTime, - uint64 _durationSecs, - CommonUtils.CycleState _cycleState, - bool _automationEnabled - ) internal { - _cycleInfo.index_startTime_durationSecs_state_ifTransitionStateExists_automationEnabled = - (uint256(_index) << 192) | - (uint256(_startTime) << 128) | - (uint256(_durationSecs) << 64) | - (uint256(_cycleState) << 56) | - (_automationEnabled ? (uint256(1) << 54) : 0); - } - - // index(uint64) | startTime(uint64) | durationSecs(uint64) | state(CycleState/uint8) | ifTransitionStateExists(bool)[bit 55] | automationEnabled(bool)[bit 54] - function index(AutomationCycleInfo storage cycle) internal view returns (uint64) { - return uint64(cycle.index_startTime_durationSecs_state_ifTransitionStateExists_automationEnabled >> 192); - } - - function startTime(AutomationCycleInfo storage cycle) internal view returns (uint64) { - return uint64(cycle.index_startTime_durationSecs_state_ifTransitionStateExists_automationEnabled >> 128); - } - - function durationSecs(AutomationCycleInfo storage cycle) internal view returns (uint64) { - return uint64(cycle.index_startTime_durationSecs_state_ifTransitionStateExists_automationEnabled >> 64); - } - - function state(AutomationCycleInfo storage cycle) internal view returns (CommonUtils.CycleState) { - return CommonUtils.CycleState(uint8(cycle.index_startTime_durationSecs_state_ifTransitionStateExists_automationEnabled >> 56)); - } - - function ifTransitionStateExists(AutomationCycleInfo storage cycle) internal view returns (bool) { - return ((cycle.index_startTime_durationSecs_state_ifTransitionStateExists_automationEnabled >> 55) & 1) != 0; - } - - function automationEnabled(AutomationCycleInfo storage cycle) internal view returns (bool) { - return ((cycle.index_startTime_durationSecs_state_ifTransitionStateExists_automationEnabled >> 54) & 1) != 0; - } - - function setIndex(AutomationCycleInfo storage cycle, uint64 _index) internal { - cycle.index_startTime_durationSecs_state_ifTransitionStateExists_automationEnabled &= ~(MAX_UINT64 << 192); // Clear old bits - cycle.index_startTime_durationSecs_state_ifTransitionStateExists_automationEnabled |= uint256(_index) << 192; // Set new value - } - - function setStartTime(AutomationCycleInfo storage cycle, uint64 _startTime) internal { - cycle.index_startTime_durationSecs_state_ifTransitionStateExists_automationEnabled &= ~(MAX_UINT64 << 128); - cycle.index_startTime_durationSecs_state_ifTransitionStateExists_automationEnabled |= uint256(_startTime) << 128; - } - - function setDurationSecs(AutomationCycleInfo storage cycle, uint64 _durationSecs) internal { - cycle.index_startTime_durationSecs_state_ifTransitionStateExists_automationEnabled &= ~(MAX_UINT64 << 64); - cycle.index_startTime_durationSecs_state_ifTransitionStateExists_automationEnabled |= uint256(_durationSecs) << 64; - } - - function setState(AutomationCycleInfo storage cycle, uint8 _state) internal { - cycle.index_startTime_durationSecs_state_ifTransitionStateExists_automationEnabled &= ~(MAX_UINT8 << 56); - cycle.index_startTime_durationSecs_state_ifTransitionStateExists_automationEnabled |= uint256(_state) << 56; - } - - function setTransitionStateExists(AutomationCycleInfo storage cycle, bool exists) internal { - cycle.index_startTime_durationSecs_state_ifTransitionStateExists_automationEnabled &= ~(uint256(1) << 55); - cycle.index_startTime_durationSecs_state_ifTransitionStateExists_automationEnabled |= exists ? (uint256(1) << 55) : 0; - } - - function setAutomationEnabled(AutomationCycleInfo storage cycle, bool enabled) internal { - cycle.index_startTime_durationSecs_state_ifTransitionStateExists_automationEnabled &= ~(uint256(1) << 54); - cycle.index_startTime_durationSecs_state_ifTransitionStateExists_automationEnabled |= enabled ? (uint256(1) << 54) : 0; - } - - // :::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::: TransitionState :::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::: - - // automationFeePerSec (uint128) | gasCommittedForNewCycle (uint128) - function automationFeePerSec(AutomationCycleInfo storage cycle) internal view returns (uint128) { - return uint128(cycle.transitionState.automationFeePerSec_gasCommittedForNewCycle >> 128); - } - - function gasCommittedForNewCycle(AutomationCycleInfo storage cycle) internal view returns (uint128) { - return uint128(cycle.transitionState.automationFeePerSec_gasCommittedForNewCycle); - } - - function setAutomationFeePerSec(AutomationCycleInfo storage cycle, uint128 fee) internal { - cycle.transitionState.automationFeePerSec_gasCommittedForNewCycle &= MAX_UINT128; - cycle.transitionState.automationFeePerSec_gasCommittedForNewCycle |= uint256(fee) << 128; - } - - function setGasCommittedForNewCycle(AutomationCycleInfo storage cycle, uint128 gas) internal { - cycle.transitionState.automationFeePerSec_gasCommittedForNewCycle &= MAX_UINT128 << 128; - cycle.transitionState.automationFeePerSec_gasCommittedForNewCycle |= uint256(gas); - } - - - // gasCommittedForNextCycle (uint128) | sysGasCommittedForNextCycle (uint128) - function gasCommittedForNextCycle(AutomationCycleInfo storage cycle) internal view returns (uint128) { - return uint128(cycle.transitionState.gasCommittedForNextCycle_sysGasCommittedForNextCycle >> 128); - } - - function sysGasCommittedForNextCycle(AutomationCycleInfo storage cycle) internal view returns (uint128) { - return uint128(cycle.transitionState.gasCommittedForNextCycle_sysGasCommittedForNextCycle); - } - - function setGasCommittedForNextCycle(AutomationCycleInfo storage cycle, uint128 gas) internal { - cycle.transitionState.gasCommittedForNextCycle_sysGasCommittedForNextCycle &= MAX_UINT128; - cycle.transitionState.gasCommittedForNextCycle_sysGasCommittedForNextCycle |= uint256(gas) << 128; - } - - function setSysGasCommittedForNextCycle(AutomationCycleInfo storage cycle, uint128 sysGas) internal { - cycle.transitionState.gasCommittedForNextCycle_sysGasCommittedForNextCycle &= MAX_UINT128 << 128; - cycle.transitionState.gasCommittedForNextCycle_sysGasCommittedForNextCycle |= uint256(sysGas); - } - - // refundDuration (uint64) | newCycleDuration (uint64) | nextTaskIndexPosition (uint64) - function refundDuration(AutomationCycleInfo storage cycle) internal view returns (uint64) { - return uint64(cycle.transitionState.refundDuration_newCycleDuration_nextTaskIndexPosition >> 192); - } - - function newCycleDuration(AutomationCycleInfo storage cycle) internal view returns (uint64) { - return uint64(cycle.transitionState.refundDuration_newCycleDuration_nextTaskIndexPosition >> 128); - } - - function nextTaskIndexPosition(AutomationCycleInfo storage cycle) internal view returns (uint64) { - return uint64(cycle.transitionState.refundDuration_newCycleDuration_nextTaskIndexPosition >> 64); - } - - function setRefundDuration(AutomationCycleInfo storage cycle, uint64 refund) internal { - TransitionState storage ts = cycle.transitionState; - - // clear bits 192–255 (upper 64 bits) - ts.refundDuration_newCycleDuration_nextTaskIndexPosition &= ~(MAX_UINT64 << 192); - ts.refundDuration_newCycleDuration_nextTaskIndexPosition |= uint256(refund) << 192; - } - - function setNewCycleDuration(AutomationCycleInfo storage cycle, uint64 duration) internal { - TransitionState storage ts = cycle.transitionState; - - // clear bits 128-191 - ts.refundDuration_newCycleDuration_nextTaskIndexPosition &= ~(MAX_UINT64 << 128); - ts.refundDuration_newCycleDuration_nextTaskIndexPosition |= uint256(duration) << 128; - } - - function setNextTaskIndexPosition(AutomationCycleInfo storage cycle, uint64 pos) internal { - TransitionState storage ts = cycle.transitionState; - - // clear bits 64-127 - ts.refundDuration_newCycleDuration_nextTaskIndexPosition &= ~(MAX_UINT64 << 64); - ts.refundDuration_newCycleDuration_nextTaskIndexPosition |= uint256(pos) << 64; - } - - /// @notice Represents intermediate state of the registry on cycle change. - struct IntermediateStateOfCycleChange { - uint256 cycleLockedFees; - uint128 gasCommittedForNextCycle; - uint128 sysGasCommittedForNextCycle; - uint64[] removedTasks; - } - - /// @notice Struct representing transition result. - struct TransitionResult { - uint128 fees; - uint128 gas; - uint128 sysGas; - bool isRemoved; - } - - /// @notice Helper function to sort an array. - /// @param arr Input array to sort. - /// @return Returns the sorted array. - function sortUint64(uint64[] memory arr) internal pure returns (uint64[] memory) { - uint256 length = arr.length; - for (uint256 i = 0; i < length; i++) { - for (uint256 j = 0; j < length - 1; j++) { - if (arr[j] > arr[j + 1]) { - uint64 temp = arr[j]; - arr[j] = arr[j + 1]; - arr[j + 1] = temp; - } - } - } - return arr; - } - - /// @notice Helper function to sort an array. - /// @param arr Input array to sort. - /// @return Returns the sorted array. - function sortUint256(uint256[] memory arr) internal pure returns (uint256[] memory) { - uint256 length = arr.length; - for (uint256 i = 0; i < length; i++) { - for (uint256 j = 0; j < length - 1; j++) { - if (arr[j] > arr[j + 1]) { - uint256 temp = arr[j]; - arr[j] = arr[j + 1]; - arr[j + 1] = temp; - } - } - } - return arr; - } -} diff --git a/solidity/supra_contracts/src/LibRegistry.sol b/solidity/supra_contracts/src/LibRegistry.sol deleted file mode 100644 index c0efccdb28..0000000000 --- a/solidity/supra_contracts/src/LibRegistry.sol +++ /dev/null @@ -1,207 +0,0 @@ - -// SPDX-License-Identifier: MIT -pragma solidity 0.8.27; - -import {EnumerableSet} from "../lib/openzeppelin-contracts/contracts/utils/structs/EnumerableSet.sol"; -import {CommonUtils} from "./CommonUtils.sol"; - -// Helper library used by AutomationRegistry. -library LibRegistry { - - uint256 private constant MAX_UINT128 = type(uint128).max; - uint256 private constant MAX_UINT160 = type(uint160).max; - uint256 private constant MAX_UINT64 = type(uint64).max; - uint256 private constant MAX_UINT8 = type(uint8).max; - - // :::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::: TaskMetadata :::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::: - - /// @notice Task metadata for individual automation tasks. - struct TaskMetadata { - // uint128 | uint128 - uint256 maxGasAmount_gasPriceCap; - - // uint128 | uint128 - uint256 automationFeeCapForCycle_depositFee; - - bytes32 txHash; - - // uint64 | uint64 | uint64 | uint64 - uint256 taskIndex_registrationTime_expiryTime_priority; - - // address | TaskType (uint8) | TaskState (uint8) - uint256 owner_type_state; - - bytes payloadTx; - bytes[] auxData; - } - - function createTaskMetadata( - uint128 _maxGasAmount, - uint128 _gasPriceCap, - uint128 _automationFeeCapForCycle, - uint128 _depositFee, - bytes32 _txHash, - uint64 _taskIndex, - uint64 _registrationTime, - uint64 _expiryTime, - uint64 _priority, - address _owner, - CommonUtils.TaskType _type, - CommonUtils.TaskState _state, - bytes memory _payloadTx, - bytes[] memory _auxData - ) internal pure returns (TaskMetadata memory t) { - // Pack (uint128 | uint128) - t.maxGasAmount_gasPriceCap = (uint256(_maxGasAmount) << 128) | uint256(_gasPriceCap); - - // Pack (uint128 | uint128) - t.automationFeeCapForCycle_depositFee = (uint256(_automationFeeCapForCycle) << 128) | uint256(_depositFee); - - // Direct fields - t.txHash = _txHash; - t.payloadTx = _payloadTx; - t.auxData = _auxData; - - // Pack (uint64 | uint64 | uint64 | uint64) - // Layout: [taskIndex | registrationTime | expiryTime | priority] - t.taskIndex_registrationTime_expiryTime_priority = - (uint256(_taskIndex) << 192) | - (uint256(_registrationTime) << 128) | - (uint256(_expiryTime) << 64) | - uint256(_priority); - - // Pack (address | uint8 | uint8) - // Layout: [owner | taskType | taskState] - t.owner_type_state = - (uint256(uint160(_owner)) << 96) | - (uint256(uint8(_type)) << 88) | - (uint256(uint8(_state))<< 80); - } - - // maxGasAmount (uint128) | gasPriceCap (uint128) - function maxGasAmount(TaskMetadata storage t) internal view returns (uint128) { - return uint128(t.maxGasAmount_gasPriceCap >> 128); - } - - function gasPriceCap(TaskMetadata storage t) internal view returns (uint128) { - return uint128(t.maxGasAmount_gasPriceCap); - } - - function setMaxGasAmount(TaskMetadata storage t, uint128 _value) internal { - t.maxGasAmount_gasPriceCap &= MAX_UINT128; // clear upper 128 - t.maxGasAmount_gasPriceCap |= uint256(_value) << 128; // insert upper 128 - } - - function setGasPriceCap(TaskMetadata storage t, uint128 _value) internal { - t.maxGasAmount_gasPriceCap &= (MAX_UINT128 << 128); // clear lower 128 - t.maxGasAmount_gasPriceCap |= uint256(_value); // insert lower 128 - } - - // automationFeeCapForCycle (uint128) | depositFee (uint128) - function automationFeeCapForCycle(TaskMetadata storage t) internal view returns (uint128) { - return uint128(t.automationFeeCapForCycle_depositFee >> 128); - } - - function depositFee(TaskMetadata storage t) internal view returns (uint128) { - return uint128(t.automationFeeCapForCycle_depositFee); - } - - function setAutomationFeeCapForCycle(TaskMetadata storage t, uint128 _value) internal { - t.automationFeeCapForCycle_depositFee &= MAX_UINT128; - t.automationFeeCapForCycle_depositFee |= uint256(_value) << 128; - } - - function setDepositFee(TaskMetadata storage t, uint128 _value) internal { - t.automationFeeCapForCycle_depositFee &= (MAX_UINT128 << 128); - t.automationFeeCapForCycle_depositFee |= uint256(_value); - } - - // taskIndex (uint64) | registrationTime (uint64) | expiryTime (uint64) | priority (uint64) - function taskIndex(TaskMetadata storage t) internal view returns (uint64) { - return uint64(t.taskIndex_registrationTime_expiryTime_priority >> 192); - } - - function registrationTime(TaskMetadata storage t) internal view returns (uint64) { - return uint64(t.taskIndex_registrationTime_expiryTime_priority >> 128); - } - - function expiryTime(TaskMetadata storage t) internal view returns (uint64) { - return uint64(t.taskIndex_registrationTime_expiryTime_priority >> 64); - } - - function priority(TaskMetadata storage t) internal view returns (uint64) { - return uint64(t.taskIndex_registrationTime_expiryTime_priority); - } - - function setTaskIndex(TaskMetadata storage t, uint64 _value) internal { - t.taskIndex_registrationTime_expiryTime_priority &= ~(MAX_UINT64 << 192); - t.taskIndex_registrationTime_expiryTime_priority |= uint256(_value) << 192; - } - - function setRegistrationTime(TaskMetadata storage t, uint64 _value) internal { - t.taskIndex_registrationTime_expiryTime_priority &= ~(MAX_UINT64 << 128); - t.taskIndex_registrationTime_expiryTime_priority |= uint256(_value) << 128; - } - - function setExpiryTime(TaskMetadata storage t, uint64 _value) internal { - t.taskIndex_registrationTime_expiryTime_priority &= ~(MAX_UINT64 << 64); - t.taskIndex_registrationTime_expiryTime_priority |= uint256(_value) << 64; - } - - function setPriority(TaskMetadata storage t, uint64 _value) internal { - t.taskIndex_registrationTime_expiryTime_priority &= ~MAX_UINT64; - t.taskIndex_registrationTime_expiryTime_priority |= uint256(_value); - } - - // owner (address/uint160) | type (TaskType/uint8) | state (TaskState/uint8) - function owner(TaskMetadata storage t) internal view returns (address) { - return address(uint160(t.owner_type_state >> 96)); - } - - function taskType(TaskMetadata storage t) internal view returns (CommonUtils.TaskType) { - return CommonUtils.TaskType(uint8(t.owner_type_state >> 88)); - } - - function state(TaskMetadata storage t) internal view returns (CommonUtils.TaskState) { - return CommonUtils.TaskState(uint8(t.owner_type_state >> 80)); - } - - function setOwner(TaskMetadata storage t, address _value) internal { - t.owner_type_state &= ~(MAX_UINT160 << 96); - t.owner_type_state |= uint256(uint160(_value)) << 96; - } - - function setType(TaskMetadata storage t, uint8 _value) internal { - t.owner_type_state &= ~(MAX_UINT8 << 88); - t.owner_type_state |= uint256(_value) << 88; - } - - function setState(TaskMetadata storage t, uint8 _value) internal { - t.owner_type_state &= ~(MAX_UINT8 << 80); - t.owner_type_state |= uint256(_value) << 80; - } - - // :::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::: RegistryState :::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::: - - /// @notice Tracks per-cycle automation state and task indexes. - struct RegistryState { - uint64 currentIndex; - - EnumerableSet.UintSet activeTaskIds; - EnumerableSet.UintSet taskIdList; - mapping(uint64 => TaskMetadata) tasks; - // mapping(address => uint64[]) userTasks TO_DO: user to their tasks, need to decide on this - - EnumerableSet.UintSet sysTaskIds; - EnumerableSet.AddressSet authorizedAccounts; - } - - /// @notice Struct representing a stopped task. - struct TaskStopped { - uint64 taskIndex; - uint128 depositRefund; - uint128 cycleFeeRefund; - bytes32 txHash; - } -} - diff --git a/solidity/supra_contracts/src/SupraContractsBindings.sol b/solidity/supra_contracts/src/SupraContractsBindings.sol index 23039776a1..d865178526 100644 --- a/solidity/supra_contracts/src/SupraContractsBindings.sol +++ b/solidity/supra_contracts/src/SupraContractsBindings.sol @@ -1,21 +1,21 @@ // SPDX-License-Identifier: MIT pragma solidity 0.8.27; -import {CommonUtils} from "./CommonUtils.sol"; +import {LibUtils} from "./libraries/LibUtils.sol"; +import {TaskMetadata} from "./libraries/LibAppStorage.sol"; interface SupraContractsBindings { - // View functions of AutomationRegistry + // View functions of RegistryFacet function ifTaskExists(uint64 _taskIndex) external view returns (bool); - function getAllActiveTaskIds() external view returns (uint256[] memory); - function getTaskIdList() external view returns (uint256[] memory); - function isAutomationEnabled() external view returns (bool); - - function getTaskDetails(uint64 _taskIndex) external view returns (CommonUtils.TaskDetails memory); - function getTaskDetailsBulk(uint64[] memory _taskIndexes) external view returns (CommonUtils.TaskDetails[] memory); + function getActiveTaskIds() external view returns (uint256[] memory); + function getTaskIds() external view returns (uint256[] memory); + function getTaskDetails(uint64 _taskIndex) external view returns (TaskMetadata memory); + function getTaskDetailsBulk(uint64[] memory _taskIndexes) external view returns (TaskMetadata[] memory); - // View functions of AutomationController - function getCycleInfo() external view returns(uint64, uint64, uint64, CommonUtils.CycleState); + // View functions of CoreFacet + function isAutomationEnabled() external view returns (bool); + function getCycleInfo() external view returns (uint64, uint64, uint64, LibUtils.CycleState); function getTransitionInfo() external view returns (uint64, uint128); // Entry function to be called by node runtime for bookkeeping diff --git a/solidity/supra_contracts/src/facets/ConfigFacet.sol b/solidity/supra_contracts/src/facets/ConfigFacet.sol new file mode 100644 index 0000000000..b33bbc5003 --- /dev/null +++ b/solidity/supra_contracts/src/facets/ConfigFacet.sol @@ -0,0 +1,210 @@ +// SPDX-License-Identifier: MIT +pragma solidity 0.8.27; + +import {EnumerableSet} from "../../lib/openzeppelin-contracts/contracts/utils/structs/EnumerableSet.sol"; +import {AppStorage, Config} from "../libraries/LibAppStorage.sol"; +import {LibUtils} from "../libraries/LibUtils.sol"; +import {IConfigFacet} from "../interfaces/IConfigFacet.sol"; +import {LibDiamond} from "../libraries/LibDiamond.sol"; +import {IERC20} from "../../lib/openzeppelin-contracts/contracts/token/ERC20/IERC20.sol"; + +contract ConfigFacet is IConfigFacet { + using EnumerableSet for *; + + /// @dev State variables + AppStorage internal s; + + // ::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::: EVENTS ::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::: + + /// @notice Emitted when an account is authorized as submitter for system tasks. + event AuthorizationGranted(address indexed account, uint256 indexed timestamp); + + /// @notice Emitted when authorization is revoked for an account to submit system tasks. + event AuthorizationRevoked(address indexed account, uint256 indexed timestamp); + + /// @notice Emitted when task registration is enabled. + event TaskRegistrationEnabled(bool indexed status); + + /// @notice Emitted when task registration is disabled. + event TaskRegistrationDisabled(bool indexed status); + + /// @notice Emitted when the VM Signer address is updated. + event VmSignerUpdated(address indexed oldVmSigner, address indexed newVmSigner); + + /// @notice Emitted when the ERC20Supra address is updated. + event Erc20SupraUpdated(address indexed oldErc20Supra, address indexed newErc20Supra); + + /// @notice Emitted when the registry fees is withdrawn by the admin. + event RegistryFeeWithdrawn(address indexed recipient, uint256 indexed feesWithdrawn); + + /// @notice Emitted when a new config is added. + event ConfigBufferUpdated(Config indexed pendingConfig); + + // ::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::: ADMIN FUNCTIONS ::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::: + + /// @notice Grants authorization to the input account to submit system automation tasks. + /// @param _account Address to grant authorization to. + function grantAuthorization(address _account) external { + LibDiamond.enforceIsContractOwner(); + + require(s.authorizedAccounts.add(_account), AddressAlreadyExists()); + emit AuthorizationGranted(_account, block.timestamp); + } + + /// @notice Revokes authorization from the input account to submit system automation tasks. + /// @param _account Address to revoke authorization from. + function revokeAuthorization(address _account) external { + LibDiamond.enforceIsContractOwner(); + + require(s.authorizedAccounts.remove(_account), AddressDoesNotExist()); + emit AuthorizationRevoked(_account, block.timestamp); + } + + /// @notice Function to enable the task registration. + function enableRegistration() external { + LibDiamond.enforceIsContractOwner(); + + if(s.registrationEnabled) { revert AlreadyEnabled(); } + s.registrationEnabled = true; + + emit TaskRegistrationEnabled(s.registrationEnabled); + } + + /// @notice Function to disable the task registration. + function disableRegistration() external { + LibDiamond.enforceIsContractOwner(); + + if(!s.registrationEnabled) { revert AlreadyDisabled(); } + s.registrationEnabled = false; + + emit TaskRegistrationDisabled(s.registrationEnabled); + } + + /// @notice Function to update the VM Signer address. + /// @param _vmSigner New address for VM Signer. + function setVmSigner(address _vmSigner) external { + LibDiamond.enforceIsContractOwner(); + + if(_vmSigner == address(0)) { revert AddressCannotBeZero(); } + + address oldVmSigner = s.vmSigner; + s.vmSigner = _vmSigner; + + emit VmSignerUpdated(oldVmSigner, _vmSigner); + } + + /// @notice Function to update the ERC20Supra address. + /// @param _erc20Supra New address for ERC20Supra. + function setErc20Supra(address _erc20Supra) external { + LibDiamond.enforceIsContractOwner(); + + LibUtils.validateContractAddress(_erc20Supra); + + address oldErc20Supra = s.erc20Supra; + s.erc20Supra = _erc20Supra; + + emit Erc20SupraUpdated(oldErc20Supra, _erc20Supra); + } + + /// @notice Function to withdraw the accumulated fees. + /// @param _amount Amount to withdraw. + /// @param _recipient Address to withdraw fees to. + function withdrawFees(uint256 _amount, address _recipient) external { + LibDiamond.enforceIsContractOwner(); + + if(_amount == 0) { revert InvalidAmount(); } + if(_recipient == address(0)) { revert AddressCannotBeZero(); } + uint256 balance = IERC20(s.erc20Supra).balanceOf(address(this)); + + if(balance < _amount) { revert InsufficientBalance(); } + if(balance - _amount < s.registryState.cycleLockedFees + s.registryState.totalDepositedAutomationFees) { revert RequestExceedsLockedBalance(); } + + bool sent = IERC20(s.erc20Supra).transfer(_recipient, _amount); + if(!sent) { revert TransferFailed(); } + + emit RegistryFeeWithdrawn(_recipient, _amount); + } + + /// @notice Function to update the registry configuration buffer. + function updateConfigBuffer( + uint64 _taskDurationCapSecs, + uint128 _registryMaxGasCap, + uint128 _automationBaseFeeWeiPerSec, + uint128 _flatRegistrationFeeWei, + uint8 _congestionThresholdPercentage, + uint128 _congestionBaseFeeWeiPerSec, + uint8 _congestionExponent, + uint16 _taskCapacity, + uint64 _cycleDurationSecs, + uint64 _sysTaskDurationCapSecs, + uint128 _sysRegistryMaxGasCap, + uint16 _sysTaskCapacity + ) external { + LibDiamond.enforceIsContractOwner(); + + LibUtils.validateConfigParameters( + _taskDurationCapSecs, + _registryMaxGasCap, + _congestionThresholdPercentage, + _congestionExponent, + _taskCapacity, + _cycleDurationSecs, + _sysTaskDurationCapSecs, + _sysRegistryMaxGasCap, + _sysTaskCapacity + ); + + if(s.registryState.gasCommittedForNextCycle > _registryMaxGasCap) { revert UnacceptableRegistryMaxGasCap(); } + if(s.registryState.sysGasCommittedForNextCycle > _sysRegistryMaxGasCap) { revert UnacceptableSysRegistryMaxGasCap(); } + + // Add new config to the buffer + Config memory configBuffer = Config({ + registryMaxGasCap: _registryMaxGasCap, + sysRegistryMaxGasCap: _sysRegistryMaxGasCap, + automationBaseFeeWeiPerSec: _automationBaseFeeWeiPerSec, + flatRegistrationFeeWei: _flatRegistrationFeeWei, + congestionBaseFeeWeiPerSec: _congestionBaseFeeWeiPerSec, + taskDurationCapSecs: _taskDurationCapSecs, + sysTaskDurationCapSecs: _sysTaskDurationCapSecs, + cycleDurationSecs: _cycleDurationSecs, + taskCapacity: _taskCapacity, + sysTaskCapacity: _sysTaskCapacity, + congestionThresholdPercentage: _congestionThresholdPercentage, + congestionExponent: _congestionExponent + }); + s.configBuffer = configBuffer; + s.ifBufferExists = true; + + s.registryState.nextCycleRegistryMaxGasCap = _registryMaxGasCap; + s.registryState.nextCycleSysRegistryMaxGasCap = _sysRegistryMaxGasCap; + + emit ConfigBufferUpdated(configBuffer); + } + + // :::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::: VIEW FUNCTIONS :::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::: + + /// @notice Returns the VM Signer address. + function getVmSigner() external view returns (address) { + return s.vmSigner; + } + + /// @notice Returns the ERC20Supra address. + function erc20Supra() external view returns (address) { + return s.erc20Supra; + } + + /// @notice Returns if task registration is enabled. + function isRegistrationEnabled() external view returns (bool) { + return s.registrationEnabled; + } + + /// @notice Returns the registry configuration. + function getConfig() external view returns (Config memory) { + return s.activeConfig; + } + + /// @notice Returns the pending configuration. + function getConfigBuffer() external view returns (Config memory) { + return s.configBuffer; + } +} \ No newline at end of file diff --git a/solidity/supra_contracts/src/facets/CoreFacet.sol b/solidity/supra_contracts/src/facets/CoreFacet.sol new file mode 100644 index 0000000000..46a737b081 --- /dev/null +++ b/solidity/supra_contracts/src/facets/CoreFacet.sol @@ -0,0 +1,105 @@ +// SPDX-License-Identifier: MIT +pragma solidity 0.8.27; + +import {AppStorage} from "../libraries/LibAppStorage.sol"; +import {LibUtils} from "../libraries/LibUtils.sol"; +import {LibCore} from "../libraries/LibCore.sol"; +import {ICoreFacet} from "../interfaces/ICoreFacet.sol"; +import {LibDiamond} from "../libraries/LibDiamond.sol"; + +contract CoreFacet is ICoreFacet { + /// @dev State variables + AppStorage internal s; + + // ::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::: EVENTS ::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::: + + /// @notice Emitted when automation is enabled. + event AutomationEnabled(bool indexed status); + + /// @notice Emitted when automation is disabled. + event AutomationDisabled(bool indexed status); + + // :::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::: VM FUNCTIONS :::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::: + + /// @notice Called by the VM Signer on `AutomationBookkeepingAction::Process` action emitted by native layer ahead of the cycle transition. + /// @param _cycleIndex Index of the cycle. + /// @param _taskIndexes Array of task index to be processed. + function processTasks(uint64 _cycleIndex, uint64[] memory _taskIndexes) external { + // Check caller is VM Signer + if (msg.sender != s.vmSigner) { revert CallerNotVmSigner(); } + + LibUtils.CycleState state = s.cycleState; + if (state == LibUtils.CycleState.FINISHED) { + LibCore.onCycleTransition(_cycleIndex, _taskIndexes); + } else { + if (state != LibUtils.CycleState.SUSPENDED) { revert InvalidRegistryState(); } + LibCore.onCycleSuspend(_cycleIndex, _taskIndexes); + } + } + + /// @notice Checks the cycle end and emit an event on it. Does nothing if SUPRA_NATIVE_AUTOMATION or SUPRA_AUTOMATION_V2 is disabled. + function monitorCycleEnd() external { + if (tx.origin != s.vmSigner) { revert CallerNotVmSigner(); } + + if (!LibCore.isCycleStarted() || LibCore.getCycleEndTime() > block.timestamp) { + return; + } + + LibCore.onCycleEndInternal(); + } + + // ::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::: ADMIN FUNCTIONS ::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::: + + /// @notice Function to enable the automation. + function enableAutomation() external { + LibDiamond.enforceIsContractOwner(); + + if (s.automationEnabled) { revert AlreadyEnabled(); } + + s.automationEnabled = true; + if (s.cycleState == LibUtils.CycleState.READY) { + LibCore.moveToStartedState(); + LibCore.updateConfigFromBuffer(); + } + + emit AutomationEnabled(s.automationEnabled); + } + + /// @notice Function to disable the automation. + function disableAutomation() external { + LibDiamond.enforceIsContractOwner(); + + if (!s.automationEnabled) { revert AlreadyDisabled(); } + + s.automationEnabled = false; + if (s.cycleState == LibUtils.CycleState.FINISHED && !LibCore.isTransitionInProgress()) { + LibCore.tryMoveToSuspendedState(); + } + + emit AutomationDisabled(s.automationEnabled); + } + + // :::::::::::::::::::::::::::::::::::::::::::::::::::::::::::: VIEW FUNCTIONS :::::::::::::::::::::::::::::::::::::::::::::::::::::::::::: + + /// @notice Returns the index, start time, duration and state of the current cycle. + function getCycleInfo() external view returns (uint64, uint64, uint64, LibUtils.CycleState) { + return (s.index, s.startTime, s.durationSecs, s.cycleState); + } + + /// @notice Returns the duration of the current cycle. + function getCycleDuration() external view returns (uint64) { + return s.durationSecs; + } + + /// @notice Returns the refund duration and automation fee per sec of the transtition state. + /// @return Refund duration + /// @return Automation fee per sec + function getTransitionInfo() external view returns (uint64, uint128) { + return (s.transitionState.refundDuration, s.transitionState.automationFeePerSec); + } + + /// @notice Returns if automation is enabled. + function isAutomationEnabled() external view returns (bool) { + return s.automationEnabled; + } +} diff --git a/solidity/supra_contracts/src/facets/DiamondCutFacet.sol b/solidity/supra_contracts/src/facets/DiamondCutFacet.sol new file mode 100644 index 0000000000..89c360067d --- /dev/null +++ b/solidity/supra_contracts/src/facets/DiamondCutFacet.sol @@ -0,0 +1,30 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.0; + +/******************************************************************************\ +* Author: Nick Mudge (https://twitter.com/mudgen) +* EIP-2535 Diamonds: https://eips.ethereum.org/EIPS/eip-2535 +/******************************************************************************/ + +import { IDiamondCut } from "../interfaces/IDiamondCut.sol"; +import { LibDiamond } from "../libraries/LibDiamond.sol"; + +// Remember to add the loupe functions from DiamondLoupeFacet to the diamond. +// The loupe functions are required by the EIP2535 Diamonds standard + +contract DiamondCutFacet is IDiamondCut { + /// @notice Add/replace/remove any number of functions and optionally execute + /// a function with delegatecall + /// @param _diamondCut Contains the facet addresses and function selectors + /// @param _init The address of the contract or facet to execute _calldata + /// @param _calldata A function call, including function selector and arguments + /// _calldata is executed with delegatecall on _init + function diamondCut( + FacetCut[] calldata _diamondCut, + address _init, + bytes calldata _calldata + ) external override { + LibDiamond.enforceIsContractOwner(); + LibDiamond.diamondCut(_diamondCut, _init, _calldata); + } +} diff --git a/solidity/supra_contracts/src/facets/DiamondLoupeFacet.sol b/solidity/supra_contracts/src/facets/DiamondLoupeFacet.sol new file mode 100644 index 0000000000..538516f91a --- /dev/null +++ b/solidity/supra_contracts/src/facets/DiamondLoupeFacet.sol @@ -0,0 +1,67 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.0; +/******************************************************************************\ +* Author: Nick Mudge (https://twitter.com/mudgen) +* EIP-2535 Diamonds: https://eips.ethereum.org/EIPS/eip-2535 +/******************************************************************************/ + +import { LibDiamond } from "../libraries/LibDiamond.sol"; +import { IDiamondLoupe } from "../interfaces/IDiamondLoupe.sol"; +import { IERC165 } from "../interfaces/IERC165.sol"; + +// The functions in DiamondLoupeFacet MUST be added to a diamond. +// The EIP-2535 Diamond standard requires these functions. + +contract DiamondLoupeFacet is IDiamondLoupe, IERC165 { + // Diamond Loupe Functions + //////////////////////////////////////////////////////////////////// + /// These functions are expected to be called frequently by tools. + // + // struct Facet { + // address facetAddress; + // bytes4[] functionSelectors; + // } + + /// @notice Gets all facets and their selectors. + /// @return facets_ Facet + function facets() external override view returns (Facet[] memory facets_) { + LibDiamond.DiamondStorage storage ds = LibDiamond.diamondStorage(); + uint256 numFacets = ds.facetAddresses.length; + facets_ = new Facet[](numFacets); + for (uint256 i; i < numFacets; i++) { + address facetAddress_ = ds.facetAddresses[i]; + facets_[i].facetAddress = facetAddress_; + facets_[i].functionSelectors = ds.facetFunctionSelectors[facetAddress_].functionSelectors; + } + } + + /// @notice Gets all the function selectors provided by a facet. + /// @param _facet The facet address. + /// @return facetFunctionSelectors_ + function facetFunctionSelectors(address _facet) external override view returns (bytes4[] memory facetFunctionSelectors_) { + LibDiamond.DiamondStorage storage ds = LibDiamond.diamondStorage(); + facetFunctionSelectors_ = ds.facetFunctionSelectors[_facet].functionSelectors; + } + + /// @notice Get all the facet addresses used by a diamond. + /// @return facetAddresses_ + function facetAddresses() external override view returns (address[] memory facetAddresses_) { + LibDiamond.DiamondStorage storage ds = LibDiamond.diamondStorage(); + facetAddresses_ = ds.facetAddresses; + } + + /// @notice Gets the facet that supports the given selector. + /// @dev If facet is not found return address(0). + /// @param _functionSelector The function selector. + /// @return facetAddress_ The facet address. + function facetAddress(bytes4 _functionSelector) external override view returns (address facetAddress_) { + LibDiamond.DiamondStorage storage ds = LibDiamond.diamondStorage(); + facetAddress_ = ds.selectorToFacetAndPosition[_functionSelector].facetAddress; + } + + // This implements ERC-165. + function supportsInterface(bytes4 _interfaceId) external override view returns (bool) { + LibDiamond.DiamondStorage storage ds = LibDiamond.diamondStorage(); + return ds.supportedInterfaces[_interfaceId]; + } +} diff --git a/solidity/supra_contracts/src/facets/OwnershipFacet.sol b/solidity/supra_contracts/src/facets/OwnershipFacet.sol new file mode 100644 index 0000000000..45cba09baa --- /dev/null +++ b/solidity/supra_contracts/src/facets/OwnershipFacet.sol @@ -0,0 +1,16 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.0; + +import { LibDiamond } from "../libraries/LibDiamond.sol"; +import { IERC173 } from "../interfaces/IERC173.sol"; + +contract OwnershipFacet is IERC173 { + function transferOwnership(address _newOwner) external override { + LibDiamond.enforceIsContractOwner(); + LibDiamond.setContractOwner(_newOwner); + } + + function owner() external override view returns (address owner_) { + owner_ = LibDiamond.contractOwner(); + } +} diff --git a/solidity/supra_contracts/src/facets/RegistryFacet.sol b/solidity/supra_contracts/src/facets/RegistryFacet.sol new file mode 100644 index 0000000000..f540b953fe --- /dev/null +++ b/solidity/supra_contracts/src/facets/RegistryFacet.sol @@ -0,0 +1,609 @@ +// SPDX-License-Identifier: MIT +pragma solidity 0.8.27; + +import {EnumerableSet} from "../../lib/openzeppelin-contracts/contracts/utils/structs/EnumerableSet.sol"; +import {AppStorage, TaskMetadata} from "../libraries/LibAppStorage.sol"; +import {LibUtils} from "../libraries/LibUtils.sol"; +import {LibRegistry} from "../libraries/LibRegistry.sol"; +import {LibCore} from "../libraries/LibCore.sol"; +import {IRegistryFacet} from "../interfaces/IRegistryFacet.sol"; + +contract RegistryFacet is IRegistryFacet { + using EnumerableSet for *; + using LibRegistry for *; + + /// @dev Defines divisor for refunds of deposit fees with penalty + /// Factor of `2` suggests that `1/2` of the deposit will be refunded. + uint8 constant REFUND_FACTOR = 2; + + /// @notice Address of the transaction hash precompile. + address public constant TX_HASH_PRECOMPILE = 0x0000000000000000000000000000000053555001; + + /// @dev State variables + AppStorage internal s; + + // ::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::: EVENTS ::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::: + + /// @notice Emitted when a user task is registered. + event TaskRegistered( + uint64 indexed taskIndex, + address indexed owner, + uint128 registrationFee, + uint128 lockedDepositFee, + TaskMetadata taskMetadata + ); + + /// @notice Emitted when a system task is registered. + event SystemTaskRegistered( + uint64 indexed taskIndex, + address indexed owner, + uint256 timestamp, + TaskMetadata taskMetadata + ); + + /// @notice Emitted when a task is cancelled. + event TaskCancelled( + uint64 indexed taskIndex, + address indexed owner, + bytes32 indexed regHash + ); + + /// @notice Emitted when a task is stopped. + event TasksStopped( + LibUtils.TaskStopped[] indexed stoppedTasks, + address indexed owner + ); + + // ::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::: TASKS RELATED FUNCTIONS ::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::: + + /// @notice Function used to register a user task for automation. + /// @param _payloadTx Includes the target smart contract address and the data to call in abi encoded form. + /// @param _expiryTime Time after which the task gets expired. + /// @param _maxGasAmount Maximum amount of gas for the automation task. + /// @param _gasPriceCap Maximum gas willing to pay for the task. + /// @param _automationFeeCapForCycle Maximum automation fee for a cycle to be paid ever. + /// @param _priority Priority for the task. 0 for default priority. + /// @param _auxData Auxiliary data to be passed. + function register( + bytes memory _payloadTx, + uint64 _expiryTime, + uint128 _maxGasAmount, + uint128 _gasPriceCap, + uint128 _automationFeeCapForCycle, + uint64 _priority, + bytes[] memory _auxData + ) external { + uint64 regTime = uint64(block.timestamp); + + LibRegistry.updateStateForValidRegistration( + totalTasks(), + regTime, + _expiryTime, + LibUtils.TaskType.UST, + _payloadTx, + _maxGasAmount, + _gasPriceCap, + _automationFeeCapForCycle + ); + + uint64 taskIndex = s.registryState.currentIndex; + + TaskMetadata memory taskMetadata = TaskMetadata({ + maxGasAmount: _maxGasAmount, + gasPriceCap: _gasPriceCap, + automationFeeCapForCycle: _automationFeeCapForCycle, + depositFee: _automationFeeCapForCycle, + txHash: readTxHash(), + taskIndex: taskIndex, + registrationTime: regTime, + expiryTime: _expiryTime, + priority: taskIndex, // priority set to taskIndex by default + owner: msg.sender, + taskType: LibUtils.TaskType.UST, + taskState: LibUtils.TaskState.PENDING, + payloadTx: _payloadTx, + auxData: _auxData + }); + + s.registryState.tasks[taskIndex] = taskMetadata; + require(s.registryState.taskIdList.add(taskIndex), TaskIndexNotUnique()); + s.registryState.currentIndex += 1; + s.registryState.totalDepositedAutomationFees += _automationFeeCapForCycle; + + uint128 flatRegistrationFee = s.activeConfig.flatRegistrationFeeWei; + uint128 fee = flatRegistrationFee + _automationFeeCapForCycle; + LibRegistry.chargeFees(msg.sender, fee); + + emit TaskRegistered(taskIndex, msg.sender, flatRegistrationFee, _automationFeeCapForCycle, s.registryState.tasks[taskIndex]); + } + + /// @notice Function to register a system task. Reverts if caller is not authorized. + /// @param _payloadTx Includes the target smart contract address and the data to call in abi encoded form. + /// @param _expiryTime Time after which the task gets expired. + /// @param _maxGasAmount Maximum amount of gas for the automation task. + /// @param _priority Priority for the task. 0 for default priority. + /// @param _auxData Auxiliary data to be passed. + function registerSystemTask( + bytes memory _payloadTx, + uint64 _expiryTime, + uint128 _maxGasAmount, + uint64 _priority, + bytes[] memory _auxData + ) external { + if(!isAuthorizedSubmitter(msg.sender)) { revert UnauthorizedAccount(); } + + uint64 regTime = uint64(block.timestamp); + LibRegistry.updateStateForValidRegistration( + totalSystemTasks(), + regTime, + _expiryTime, + LibUtils.TaskType.GST, + _payloadTx, + _maxGasAmount, + 0, + 0 + ); + + uint64 taskIndex = s.registryState.currentIndex; + uint64 taskPriority = _priority == 0 ? taskIndex : _priority; // Defaults to taskIndex as priority if 0 is passed + TaskMetadata memory taskMetadata = TaskMetadata({ + maxGasAmount: _maxGasAmount, + gasPriceCap: 0, + automationFeeCapForCycle: 0, + depositFee: 0, + txHash: readTxHash(), + taskIndex: taskIndex, + registrationTime: regTime, + expiryTime: _expiryTime, + priority: taskPriority, + owner: msg.sender, + taskType: LibUtils.TaskType.GST, + taskState: LibUtils.TaskState.PENDING, + payloadTx: _payloadTx, + auxData: _auxData + }); + + s.registryState.tasks[taskIndex] = taskMetadata; + require(s.registryState.taskIdList.add(taskIndex), TaskIndexNotUnique()); + require(s.registryState.sysTaskIds.add(taskIndex), TaskIndexNotUnique()); + s.registryState.currentIndex += 1; + + emit SystemTaskRegistered(taskIndex, msg.sender, block.timestamp, s.registryState.tasks[taskIndex]); + } + + /// @notice Cancels an automation task with specified task index. + /// Only existing task, which is PENDING or ACTIVE, can be cancelled and only by task owner. + /// If the task is + /// - active, its state is updated to be CANCELLED. + /// - pending, it is removed form the list. + /// - cancelled, an error is reported + /// Committed gas limit is updated by reducing it with the max gas amount of the cancelled task. + /// @param _taskIndex Index of the task. + function cancelTask( + uint64 _taskIndex + ) external { + // Check if automation is enabled + if (!s.automationEnabled) { revert AutomationNotEnabled(); } + + if(!LibRegistry.isCycleStarted()) { revert CycleTransitionInProgress(); } + if(!LibRegistry.ifTaskExists(_taskIndex)) { revert TaskDoesNotExist(); } + + TaskMetadata memory task = s.registryState.tasks[_taskIndex]; + + if(task.taskType == LibUtils.TaskType.GST) { revert UnsupportedTaskOperation(); } + if(task.owner != msg.sender) { revert UnauthorizedAccount(); } + if(task.taskState == LibUtils.TaskState.CANCELLED) { revert AlreadyCancelled(); } + + if (task.taskState == LibUtils.TaskState.PENDING) { + // When Pending tasks are cancelled, refund of the deposit fee is done with penalty + _removeTask(_taskIndex, false); + bool result = LibRegistry.safeDepositRefund( + _taskIndex, + task.owner, + task.depositFee / REFUND_FACTOR, + task.depositFee + ); + if(!result) { revert ErrorDepositRefund(); } + } else { + // It is safe not to check the state as above, the cancelled tasks are already rejected. + // Active tasks will be refunded the deposited amount fully at the end of the cycle. + s.registryState.tasks[_taskIndex].taskState = LibUtils.TaskState.CANCELLED; + } + + // This check means the task was expected to be executed in the next cycle, but it has been cancelled. + // We need to remove its gas commitment from `gasCommittedForNextCycle` for this particular task. + if (task.expiryTime > LibCore.getCycleEndTime()) { + LibRegistry.updateGasCommittedForNextCycle(task.taskType, task.maxGasAmount); + } + + emit TaskCancelled( _taskIndex, task.owner, task.txHash); + } + + /// @notice Cancels a system automation task with specified task index. + /// Only existing task, which is PENDING or ACTIVE, can be cancelled and only by task owner. + /// If the task is + /// - active, its state is updated to be CANCELLED. + /// - pending, it is removed form the list. + /// - cancelled, an error is reported + /// Committed gas limit is updated by reducing it with the max gas amount of the cancelled task. + /// @param _taskIndex Index of the task. + function cancelSystemTask( + uint64 _taskIndex + ) external { + // Check if automation is enabled + if (!s.automationEnabled) { revert AutomationNotEnabled(); } + + if(!LibRegistry.isCycleStarted()) { revert CycleTransitionInProgress(); } + if(!LibRegistry.ifTaskExists(_taskIndex)) { revert TaskDoesNotExist(); } + if(!LibRegistry.ifSysTaskExists(_taskIndex)) { revert SystemTaskDoesNotExist(); } + + TaskMetadata memory task = s.registryState.tasks[_taskIndex]; + + // Check if GST + if(task.taskType == LibUtils.TaskType.UST) { revert UnsupportedTaskOperation(); } + + if(task.owner != msg.sender) { revert UnauthorizedAccount(); } + if(task.taskState == LibUtils.TaskState.CANCELLED) { revert AlreadyCancelled(); } + + if(task.taskState == LibUtils.TaskState.PENDING) { + _removeTask(_taskIndex, true); + } else { + s.registryState.tasks[_taskIndex].taskState = LibUtils.TaskState.CANCELLED; + } + + // This check means the task was expected to be executed in the next cycle, but it has been cancelled. + // We need to remove its gas commitment from `gasCommittedForNextCycle` for this particular task. + if(task.expiryTime > LibCore.getCycleEndTime()) { + LibRegistry.updateGasCommittedForNextCycle(task.taskType, task.maxGasAmount); + } + + emit TaskCancelled(_taskIndex, msg.sender, task.txHash); + } + + /// @notice Immediately stops automation tasks for the specified `_taskIndexes`. + /// Only tasks that exist and are owned by the sender can be stopped. + /// If any of the specified tasks are not owned by the sender, the transaction will abort. + /// When a task is stopped, the committed gas for the next cycle is reduced + /// by the max gas amount of the stopped task. Half of the remaining task fee is refunded. + /// @param _taskIndexes Array of task indexes to be stopped. + function stopTasks( + uint64[] memory _taskIndexes + ) external { + // Check if automation is enabled + if (!s.automationEnabled) { revert AutomationNotEnabled(); } + + if(!LibRegistry.isCycleStarted()) { revert CycleTransitionInProgress(); } + if(_taskIndexes.length == 0) { revert TaskIndexesCannotBeEmpty(); } + + LibUtils.TaskStopped[] memory stoppedTaskDetails = new LibUtils.TaskStopped[](_taskIndexes.length); + uint256 counter = 0; + + uint128 totalRefundFee = 0; + + // Calculate refundable fee for this remaining time task in current cycle + uint64 currentTime = uint64(block.timestamp); + uint64 cycleEndTime = LibCore.getCycleEndTime(); + uint64 residualInterval = cycleEndTime <= currentTime ? 0 : (cycleEndTime - currentTime); + + // Loop through each task index to validate and stop the task + for (uint256 i = 0; i < _taskIndexes.length; i++) { + if(LibRegistry.ifTaskExists(_taskIndexes[i])) { + TaskMetadata memory task = s.registryState.tasks[_taskIndexes[i]]; + + // Check if authorised + if(msg.sender != task.owner) { revert UnauthorizedAccount(); } + + // Check if UST + if(task.taskType == LibUtils.TaskType.GST) { revert UnsupportedTaskOperation(); } + + // Remove task from the registry + _removeTask(_taskIndexes[i], false); + // Remove from active tasks + require(s.registryState.activeTaskIds.remove(_taskIndexes[i]), TaskIndexNotFound()); + + // This check means the task was expected to be executed in the next cycle, but it has been stopped. + // We need to remove its gas commitment from `gasCommittedForNextCycle` for this particular task. + // Also it checks that task should not be cancelled. + if(task.taskState != LibUtils.TaskState.CANCELLED && task.expiryTime > cycleEndTime) { + // Reduce committed gas by the stopped task's max gas + LibRegistry.updateGasCommittedForNextCycle(task.taskType, task.maxGasAmount); + } + + (uint128 cycleFeeRefund, uint128 depositRefund) = LibRegistry.unlockDepositAndCycleFee( + _taskIndexes[i], + task.taskState, + task.expiryTime, + task.maxGasAmount, + residualInterval, + uint64(currentTime), + task.depositFee + ); + totalRefundFee += (cycleFeeRefund + depositRefund); + + + // Add to stopped tasks + LibUtils.TaskStopped memory taskStopped = LibUtils.TaskStopped( + _taskIndexes[i], + depositRefund, + cycleFeeRefund, + task.txHash + ); + stoppedTaskDetails[counter] = taskStopped; + counter += 1; + } + } + + // Refund and emit event if any tasks were stopped + if(stoppedTaskDetails.length > 0) { + LibRegistry.refund(msg.sender, totalRefundFee); + + // Emit task stopped event + emit TasksStopped( + stoppedTaskDetails, + msg.sender + ); + } + } + + /// @notice Immediately stops system automation tasks for the specified `_taskIndexes`. + /// Only tasks that exist and are owned by the sender can be stopped. + /// If any of the specified tasks are not owned by the sender, the transaction will abort. + /// When a task is stopped, the committed gas for the next cycle is reduced + /// by the max gas amount of the stopped task. + /// @param _taskIndexes Array of task indexes to be stopped. + function stopSystemTasks( + uint64[] memory _taskIndexes + ) external { + // Check if automation is enabled + if (!s.automationEnabled) { revert AutomationNotEnabled(); } + + if(!LibRegistry.isCycleStarted()) { revert CycleTransitionInProgress(); } + + // Ensure that task indexes are provided + if(_taskIndexes.length == 0) { revert TaskIndexesCannotBeEmpty(); } + + LibUtils.TaskStopped[] memory stoppedTaskDetails = new LibUtils.TaskStopped[](_taskIndexes.length); + uint256 counter = 0; + + // Loop through each task index to validate and stop the task + for (uint256 i = 0; i < _taskIndexes.length; i++) { + if(LibRegistry.ifTaskExists(_taskIndexes[i])) { + TaskMetadata memory task = s.registryState.tasks[_taskIndexes[i]]; + + if(task.owner != msg.sender) { revert UnauthorizedAccount(); } + + // Check if GST + if(task.taskType == LibUtils.TaskType.UST) { revert UnsupportedTaskOperation(); } + _removeTask(_taskIndexes[i], true); + // Remove from active tasks + require(s.registryState.activeTaskIds.remove(_taskIndexes[i]), TaskIndexNotFound()); + + if(task.taskState != LibUtils.TaskState.CANCELLED && task.expiryTime > LibCore.getCycleEndTime()) { + LibRegistry.updateGasCommittedForNextCycle(task.taskType, task.maxGasAmount); + } + + // Add to stopped tasks + LibUtils.TaskStopped memory taskStopped = LibUtils.TaskStopped( + _taskIndexes[i], + 0, + 0, + task.txHash + ); + stoppedTaskDetails[counter] = taskStopped; + counter += 1; + } + } + + if(stoppedTaskDetails.length > 0) { + // Emit task stopped event + emit TasksStopped( + stoppedTaskDetails, + msg.sender + ); + } + } + + // :::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::: HELPER FUNCTIONS :::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::: + + /// @notice Read tx hash via precompile. Reverts if precompile missing/fails. + function readTxHash() private view returns (bytes32) { + (bool ok, bytes memory out) = TX_HASH_PRECOMPILE.staticcall(""); + require(ok, FailedToCallTxHashPrecompile()); + require(out.length == 32, TxnHashLengthShouldBe32(uint64(out.length))); + return abi.decode(out, (bytes32)); + } + + /// @notice Function to remove a task from the registry. + /// @param _taskIndex Index of the task to remove. + /// @param _removeFromSysReg Wheather to remove from system task registry. + function _removeTask(uint64 _taskIndex, bool _removeFromSysReg) private { + if(_removeFromSysReg) { + require(s.registryState.sysTaskIds.remove(_taskIndex), TaskIndexNotFound()); + } + + delete s.registryState.tasks[_taskIndex]; + require(s.registryState.taskIdList.remove(_taskIndex), TaskIndexNotFound()); + } + + // :::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::: VIEW FUNCTIONS :::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::: + + /// @notice Returns all the automation tasks available in the registry. + function getTaskIds() external view returns (uint256[] memory) { + return s.registryState.taskIdList.values(); + } + + /// @notice Returns all the system tasks available in the registry. + function getSystemTaskIds() external view returns (uint256[] memory) { + return s.registryState.sysTaskIds.values(); + } + + /// @notice Returns the owner of the task + /// @param _taskIndex Task index of the task to query. + function getTaskOwner(uint64 _taskIndex) external view returns (address) { + return s.registryState.tasks[_taskIndex].owner; + } + + /// @notice Returns the next task index. + function getNextTaskIndex() external view returns (uint64) { + return s.registryState.currentIndex; + } + + /// @notice Returns the number of total tasks. + function totalTasks() public view returns (uint256) { + return s.registryState.taskIdList.length(); + } + + /// @notice Returns the number of total system tasks. + function totalSystemTasks() public view returns (uint256) { + return s.registryState.sysTaskIds.length(); + } + + /// @notice Returns if a task exists in the registry. + /// @param _taskIndex Task index to check existence for. + function ifTaskExists(uint64 _taskIndex) external view returns (bool) { + return LibRegistry.ifTaskExists(_taskIndex); + } + + /// @notice Returns if a system task exists in the registry. + /// @param _taskIndex Task index of the system task to check existence for. + function ifSysTaskExists(uint64 _taskIndex) external view returns (bool) { + return LibRegistry.ifSysTaskExists(_taskIndex); + } + + /// @notice Returns the details of a task. Reverts if task doesn't exist. + function getTaskDetails(uint64 _taskIndex) external view returns (TaskMetadata memory) { + return LibRegistry.getTask(_taskIndex); + } + + /// @notice Retrieves the details of automation tasks by their task index. Skips a task if it doesn't exist. + /// @param _taskIndexes Input task indexes to get details of. + /// @return Task details of the tasks that exist. + function getTaskDetailsBulk(uint64[] memory _taskIndexes) external view returns (TaskMetadata[] memory) { + uint256 count = _taskIndexes.length; + TaskMetadata[] memory temp = new TaskMetadata[](count); + uint256 exists; + + for (uint256 i = 0; i < count; i++) { + if(LibRegistry.ifTaskExists(_taskIndexes[i])) { + temp[exists] = s.registryState.tasks[_taskIndexes[i]]; + exists += 1; + } + } + + TaskMetadata[] memory taskDetails = new TaskMetadata[](exists); + for (uint256 i = 0; i < exists; i++) { + taskDetails[i] = temp[i]; + } + return taskDetails; + } + + /// @notice Checks if the input account is an authorized submitter to submit system automation tasks. + /// @param _account Address to check if it's authorized. + function isAuthorizedSubmitter(address _account) public view returns (bool) { + return s.authorizedAccounts.contains(_account); + } + + /// @notice Returns the total number of active tasks. + function getTotalActiveTasks() external view returns (uint256) { + return s.registryState.activeTaskIds.length(); + } + + /// @notice Returns all the active task indexes. + function getActiveTaskIds() external view returns (uint256[] memory) { + return s.registryState.activeTaskIds.values(); + } + + /// @notice Checks whether there is an active task in registry with specified input task index. + function hasActiveUserTask(address _account, uint64 _taskIndex) external view returns (bool) { + return hasActiveTaskOfType(_account, _taskIndex, LibUtils.TaskType.UST); + } + + /// @notice Checks whether there is an active system task in registry with specified input task index. + function hasActiveSystemTask(address _account, uint64 _taskIndex) external view returns (bool) { + return hasActiveTaskOfType(_account, _taskIndex, LibUtils.TaskType.GST); + } + + /// @notice Checks whether there is an active task in registry with specified input task index of the input type. + /// The type can be either 0 for user submitted tasks, and 1 for governance authorized tasks. + function hasActiveTaskOfType(address _account, uint64 _taskIndex, LibUtils.TaskType _type) public view returns (bool) { + TaskMetadata storage task = s.registryState.tasks[_taskIndex]; + return task.owner == _account && task.taskState != LibUtils.TaskState.PENDING && task.taskType == _type; + } + + /// @notice Returns the gas committed for the next cycle. + function getGasCommittedForNextCycle() external view returns (uint128) { + return s.registryState.gasCommittedForNextCycle; + } + + /// @notice Returns the gas committed for the current cycle. + function getGasCommittedForCurrentCycle() external view returns (uint128) { + return s.registryState.gasCommittedForThisCycle; + } + + /// @notice Returns the system gas committed for the next cycle. + function getSystemGasCommittedForNextCycle() external view returns (uint128) { + return s.registryState.sysGasCommittedForNextCycle; + } + + /// @notice Returns the system gas committed for the current cycle. + function getSystemGasCommittedForCurrentCycle() external view returns (uint128) { + return s.registryState.sysGasCommittedForThisCycle; + } + + /// @notice Returns the registry max gas cap for the next cycle. + function getNextCycleRegistryMaxGasCap() external view returns (uint128) { + return s.registryState.nextCycleRegistryMaxGasCap; + } + + /// @notice Returns the system registry max gas cap for the next cycle. + function getNextCycleSysRegistryMaxGasCap() external view returns (uint128) { + return s.registryState.nextCycleSysRegistryMaxGasCap; + } + + /// @notice Returns the locked fees for the cycle. + function getCycleLockedFees() external view returns (uint256) { + return s.registryState.cycleLockedFees; + } + + /// @notice Returns the total amount of automation fees deposited. + function getTotalDepositedAutomationFees() external view returns (uint256) { + return s.registryState.totalDepositedAutomationFees; + } + + /// @notice Returns the total amount locked which comprises of 'cycleLockedFees' and 'totalDepositedAutomationFees'. + function getTotalLockedBalance() external view returns (uint256) { + return s.registryState.cycleLockedFees + s.registryState.totalDepositedAutomationFees; + } + + /// @notice Calculates automation fee per second for the specified task occupancy + /// referencing the current automation registry fee parameters, specified total/committed occupancy and current registry + /// maximum allowed occupancy. + function calculateAutomationFeeMultiplierForCommittedOccupancy(uint128 _totalCommittedMaxGas) external view returns (uint128) { + return LibRegistry.calculateAutomationFeeMultiplierForCommittedOccupancy(_totalCommittedMaxGas); + } + + /// @notice Calculates the automation fee multiplier for current cycle. + function calculateAutomationFeeMultiplierForCurrentCycle() external view returns (uint128) { + return LibRegistry.calculateAutomationFeeMultiplierForCurrentCycle(); + } + + /// @notice Estimates automation fee for the next cycle for specified task occupancy for the configured cycle-interval + /// referencing the current automation registry fee parameters, current total occupancy and registry maximum allowed + /// occupancy for the next cycle. + function estimateAutomationFee(uint128 _taskOccupancy) external view returns (uint128) { + return LibRegistry.estimateAutomationFeeWithCommittedOccupancyInternal(_taskOccupancy, s.registryState.gasCommittedForNextCycle); + } + + /// @notice Estimates automation fee the next cycle for specified task occupancy for the configured cycle-interval + /// referencing the current automation registry fee parameters, specified total/committed occupancy and registry + /// maximum allowed occupancy for the next cycle. + function estimateAutomationFeeWithCommittedOccupancy( + uint128 _taskOccupancy, + uint128 _committedOccupancy + ) external view returns (uint128) { + return LibRegistry.estimateAutomationFeeWithCommittedOccupancyInternal( + _taskOccupancy, + _committedOccupancy + ); + } +} diff --git a/solidity/supra_contracts/src/interfaces/IConfigFacet.sol b/solidity/supra_contracts/src/interfaces/IConfigFacet.sol new file mode 100644 index 0000000000..db1f70dfc4 --- /dev/null +++ b/solidity/supra_contracts/src/interfaces/IConfigFacet.sol @@ -0,0 +1,26 @@ +// SPDX-License-Identifier: MIT +pragma solidity 0.8.27; + +import {Config} from "../libraries/LibAppStorage.sol"; + +interface IConfigFacet { + // Custom errors + error AddressAlreadyExists(); + error AddressCannotBeZero(); + error AddressDoesNotExist(); + error AlreadyDisabled(); + error AlreadyEnabled(); + error InvalidAmount(); + error InsufficientBalance(); + error RequestExceedsLockedBalance(); + error TransferFailed(); + error UnacceptableRegistryMaxGasCap(); + error UnacceptableSysRegistryMaxGasCap(); + + // View functions + function erc20Supra() external view returns (address); + function getConfig() external view returns (Config memory); + function getConfigBuffer() external view returns (Config memory); + function getVmSigner() external view returns (address); + function isRegistrationEnabled() external view returns (bool); +} diff --git a/solidity/supra_contracts/src/interfaces/ICoreFacet.sol b/solidity/supra_contracts/src/interfaces/ICoreFacet.sol new file mode 100644 index 0000000000..68d2d4f2ef --- /dev/null +++ b/solidity/supra_contracts/src/interfaces/ICoreFacet.sol @@ -0,0 +1,21 @@ +// SPDX-License-Identifier: MIT +pragma solidity 0.8.27; + +import {LibUtils} from "../libraries/LibUtils.sol"; + +interface ICoreFacet { + // Custom errors + error AlreadyDisabled(); + error AlreadyEnabled(); + error CallerNotVmSigner(); + error InvalidRegistryState(); + + // View functions + function getCycleInfo() external view returns (uint64, uint64, uint64, LibUtils.CycleState); + function getCycleDuration() external view returns (uint64); + function getTransitionInfo() external view returns (uint64, uint128); + function isAutomationEnabled() external view returns (bool); + + // State update functions + function monitorCycleEnd() external; +} diff --git a/solidity/supra_contracts/src/interfaces/IDiamondCut.sol b/solidity/supra_contracts/src/interfaces/IDiamondCut.sol new file mode 100644 index 0000000000..2972f6954a --- /dev/null +++ b/solidity/supra_contracts/src/interfaces/IDiamondCut.sol @@ -0,0 +1,32 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.0; + +/******************************************************************************\ +* Author: Nick Mudge (https://twitter.com/mudgen) +* EIP-2535 Diamonds: https://eips.ethereum.org/EIPS/eip-2535 +/******************************************************************************/ + +interface IDiamondCut { + enum FacetCutAction {Add, Replace, Remove} + // Add=0, Replace=1, Remove=2 + + struct FacetCut { + address facetAddress; + FacetCutAction action; + bytes4[] functionSelectors; + } + + /// @notice Add/replace/remove any number of functions and optionally execute + /// a function with delegatecall + /// @param _diamondCut Contains the facet addresses and function selectors + /// @param _init The address of the contract or facet to execute _calldata + /// @param _calldata A function call, including function selector and arguments + /// _calldata is executed with delegatecall on _init + function diamondCut( + FacetCut[] calldata _diamondCut, + address _init, + bytes calldata _calldata + ) external; + + event DiamondCut(FacetCut[] _diamondCut, address _init, bytes _calldata); +} diff --git a/solidity/supra_contracts/src/interfaces/IDiamondLoupe.sol b/solidity/supra_contracts/src/interfaces/IDiamondLoupe.sol new file mode 100644 index 0000000000..c3b2570fe1 --- /dev/null +++ b/solidity/supra_contracts/src/interfaces/IDiamondLoupe.sol @@ -0,0 +1,38 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.0; + +/******************************************************************************\ +* Author: Nick Mudge (https://twitter.com/mudgen) +* EIP-2535 Diamonds: https://eips.ethereum.org/EIPS/eip-2535 +/******************************************************************************/ + +// A loupe is a small magnifying glass used to look at diamonds. +// These functions look at diamonds +interface IDiamondLoupe { + /// These functions are expected to be called frequently + /// by tools. + + struct Facet { + address facetAddress; + bytes4[] functionSelectors; + } + + /// @notice Gets all facet addresses and their four byte function selectors. + /// @return facets_ Facet + function facets() external view returns (Facet[] memory facets_); + + /// @notice Gets all the function selectors supported by a specific facet. + /// @param _facet The facet address. + /// @return facetFunctionSelectors_ + function facetFunctionSelectors(address _facet) external view returns (bytes4[] memory facetFunctionSelectors_); + + /// @notice Get all the facet addresses used by a diamond. + /// @return facetAddresses_ + function facetAddresses() external view returns (address[] memory facetAddresses_); + + /// @notice Gets the facet that supports the given selector. + /// @dev If facet is not found return address(0). + /// @param _functionSelector The function selector. + /// @return facetAddress_ The facet address. + function facetAddress(bytes4 _functionSelector) external view returns (address facetAddress_); +} diff --git a/solidity/supra_contracts/src/interfaces/IERC165.sol b/solidity/supra_contracts/src/interfaces/IERC165.sol new file mode 100644 index 0000000000..04b7bcc9ab --- /dev/null +++ b/solidity/supra_contracts/src/interfaces/IERC165.sol @@ -0,0 +1,12 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.0; + +interface IERC165 { + /// @notice Query if a contract implements an interface + /// @param interfaceId The interface identifier, as specified in ERC-165 + /// @dev Interface identification is specified in ERC-165. This function + /// uses less than 30,000 gas. + /// @return `true` if the contract implements `interfaceID` and + /// `interfaceID` is not 0xffffffff, `false` otherwise + function supportsInterface(bytes4 interfaceId) external view returns (bool); +} diff --git a/solidity/supra_contracts/src/interfaces/IERC173.sol b/solidity/supra_contracts/src/interfaces/IERC173.sol new file mode 100644 index 0000000000..a708048457 --- /dev/null +++ b/solidity/supra_contracts/src/interfaces/IERC173.sol @@ -0,0 +1,19 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.0; + +/// @title ERC-173 Contract Ownership Standard +/// Note: the ERC-165 identifier for this interface is 0x7f5828d0 +/* is ERC165 */ +interface IERC173 { + /// @dev This emits when ownership of a contract changes. + event OwnershipTransferred(address indexed previousOwner, address indexed newOwner); + + /// @notice Get the address of the owner + /// @return owner_ The address of the owner. + function owner() external view returns (address owner_); + + /// @notice Set the address of the new owner of the contract + /// @dev Set _newOwner to address(0) to renounce any ownership. + /// @param _newOwner The address of the new owner of the contract + function transferOwnership(address _newOwner) external; +} diff --git a/solidity/supra_contracts/src/interfaces/IRegistryFacet.sol b/solidity/supra_contracts/src/interfaces/IRegistryFacet.sol new file mode 100644 index 0000000000..c37dd8c008 --- /dev/null +++ b/solidity/supra_contracts/src/interfaces/IRegistryFacet.sol @@ -0,0 +1,53 @@ +// SPDX-License-Identifier: MIT +pragma solidity 0.8.27; + +import {LibUtils} from "../libraries/LibUtils.sol"; +import {Config, TaskMetadata} from "../libraries/LibAppStorage.sol"; + +interface IRegistryFacet { + // Custom errors + error AlreadyCancelled(); + error AutomationNotEnabled(); + error CycleTransitionInProgress(); + error ErrorDepositRefund(); + error FailedToCallTxHashPrecompile(); + error SystemTaskDoesNotExist(); + error TaskDoesNotExist(); + error TaskIndexesCannotBeEmpty(); + error TaskIndexNotFound(); + error TaskIndexNotUnique(); + error TxnHashLengthShouldBe32(uint64); + error UnauthorizedAccount(); + error UnsupportedTaskOperation(); + + // View functions + function calculateAutomationFeeMultiplierForCommittedOccupancy(uint128 _totalCommittedMaxGas) external view returns (uint128); + function calculateAutomationFeeMultiplierForCurrentCycle() external view returns (uint128); + function estimateAutomationFee(uint128 _taskOccupancy) external view returns (uint128); + function estimateAutomationFeeWithCommittedOccupancy(uint128 _taskOccupancy, uint128 _committedOccupancy) external view returns (uint128); + function isAuthorizedSubmitter(address _account) external view returns (bool); + function ifTaskExists(uint64 _taskIndex) external view returns (bool); + function ifSysTaskExists(uint64 _taskIndex) external view returns (bool); + function getActiveTaskIds() external view returns (uint256[] memory); + function getCycleLockedFees() external view returns (uint256); + function getGasCommittedForCurrentCycle() external view returns (uint128); + function getGasCommittedForNextCycle() external view returns (uint128); + function getNextCycleRegistryMaxGasCap() external view returns (uint128); + function getNextCycleSysRegistryMaxGasCap() external view returns (uint128); + function getNextTaskIndex() external view returns (uint64); + function getSystemGasCommittedForCurrentCycle() external view returns (uint128); + function getSystemGasCommittedForNextCycle() external view returns (uint128); + function getSystemTaskIds() external view returns (uint256[] memory); + function getTaskDetails(uint64 _taskIndex) external view returns (TaskMetadata memory); + function getTaskDetailsBulk(uint64[] memory _taskIndexes) external view returns (TaskMetadata[] memory); + function getTaskIds() external view returns (uint256[] memory); + function getTaskOwner(uint64 _taskIndex) external view returns (address); + function getTotalActiveTasks() external view returns (uint256); + function getTotalDepositedAutomationFees() external view returns (uint256); + function getTotalLockedBalance() external view returns (uint256); + function hasActiveSystemTask(address _account, uint64 _taskIndex) external view returns (bool); + function hasActiveTaskOfType(address _account, uint64 _taskIndex, LibUtils.TaskType _type) external view returns (bool); + function hasActiveUserTask(address _account, uint64 _taskIndex) external view returns (bool); + function totalSystemTasks() external view returns (uint256); + function totalTasks() external view returns (uint256); +} diff --git a/solidity/supra_contracts/src/libraries/LibAppStorage.sol b/solidity/supra_contracts/src/libraries/LibAppStorage.sol new file mode 100644 index 0000000000..ad26928925 --- /dev/null +++ b/solidity/supra_contracts/src/libraries/LibAppStorage.sol @@ -0,0 +1,120 @@ +// SPDX-License-Identifier: MIT +pragma solidity 0.8.27; + +import {LibUtils} from "../libraries/LibUtils.sol"; +import {EnumerableSet} from "../../lib/openzeppelin-contracts/contracts/utils/structs/EnumerableSet.sol"; + +/// @notice Struct representing Automation Registry configuration. +struct Config { + uint128 registryMaxGasCap; + uint128 sysRegistryMaxGasCap; + uint128 automationBaseFeeWeiPerSec; // TO_DO: need to decide on the currency + uint128 flatRegistrationFeeWei; // TO_DO: need to decide on the currency + uint128 congestionBaseFeeWeiPerSec; // TO_DO: need to decide on the currency + uint64 taskDurationCapSecs; + uint64 sysTaskDurationCapSecs; + uint64 cycleDurationSecs; + uint16 taskCapacity; + uint16 sysTaskCapacity; + uint8 congestionThresholdPercentage; + uint8 congestionExponent; +} + +/// @notice Struct representing cycle state transition information. +struct TransitionState { + uint256 lockedFees; + uint128 automationFeePerSec; + uint128 gasCommittedForNewCycle; + uint128 gasCommittedForNextCycle; + uint128 sysGasCommittedForNextCycle; + uint64 refundDuration; + uint64 newCycleDuration; + uint64 nextTaskIndexPosition; + EnumerableSet.UintSet expectedTasksToBeProcessed; +} + +/// @notice Task metadata for individual automation tasks. +struct TaskMetadata { + uint128 maxGasAmount; + uint128 gasPriceCap; + uint128 automationFeeCapForCycle; + uint128 depositFee; + bytes32 txHash; + uint64 taskIndex; + uint64 registrationTime; + uint64 expiryTime; + uint64 priority; + address owner; + LibUtils.TaskType taskType; + LibUtils.TaskState taskState; + bytes payloadTx; + bytes[] auxData; +} + +/// @notice Tracks per-cycle Automation Registry state and tasks related information. +struct RegistryState { + uint256 cycleLockedFees; + uint256 totalDepositedAutomationFees; + uint128 gasCommittedForNextCycle; + uint128 gasCommittedForThisCycle; + uint128 sysGasCommittedForNextCycle; + uint128 sysGasCommittedForThisCycle; + uint128 nextCycleRegistryMaxGasCap; + uint128 nextCycleSysRegistryMaxGasCap; + + uint64 currentIndex; + EnumerableSet.UintSet activeTaskIds; + EnumerableSet.UintSet taskIdList; + mapping(uint64 => TaskMetadata) tasks; + EnumerableSet.UintSet sysTaskIds; + // mapping(address => uint64[]) userTasks TO_DO: user to their tasks, need to decide on this +} + +/// @notice Central AppStorage layout for Diamond proxy +struct AppStorage { + + // ============================================================= + // CONFIGURATION + // ============================================================= + + bool automationEnabled; + bool registrationEnabled; + address vmSigner; + address erc20Supra; + EnumerableSet.AddressSet authorizedAccounts; + + /// @notice Active registry configuration + Config activeConfig; + + /// @notice Configuration buffer (for updates) + bool ifBufferExists; + Config configBuffer; + + // ============================================================= + // CYCLE MANAGEMENT + // ============================================================= + + /// @notice Current automation cycle and transition data + uint64 index; + uint64 startTime; + uint64 durationSecs; + LibUtils.CycleState cycleState; + bool ifTransitionStateExists; + TransitionState transitionState; + + // ============================================================= + // REGISTRY STATE + // ============================================================= + + /// @notice Registry and tasks state + RegistryState registryState; +} + +/// @notice AppStorage accessor for Diamond facets +library LibAppStorage { + function appStorage() internal pure returns (AppStorage storage s) { + assembly { + s.slot := 0 + } + } +} diff --git a/solidity/supra_contracts/src/libraries/LibCore.sol b/solidity/supra_contracts/src/libraries/LibCore.sol new file mode 100644 index 0000000000..a9105d2ca2 --- /dev/null +++ b/solidity/supra_contracts/src/libraries/LibCore.sol @@ -0,0 +1,697 @@ +// SPDX-License-Identifier: MIT +pragma solidity 0.8.27; + +import {LibUtils} from "./LibUtils.sol"; +import {AppStorage, LibAppStorage, TaskMetadata} from "./LibAppStorage.sol"; +import {LibRegistry} from "./LibRegistry.sol"; +import {IERC20} from "../../lib/openzeppelin-contracts/contracts/token/ERC20/IERC20.sol"; +import {EnumerableSet} from "../../lib/openzeppelin-contracts/contracts/utils/structs/EnumerableSet.sol"; + +library LibCore { + using LibUtils for *; + using EnumerableSet for EnumerableSet.UintSet; + + // ::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::: CUSTOM ERRORS ::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::: + + error InconsistentTransitionState(); + error InvalidInputCycleIndex(); + error InvalidRegistryState(); + error OutOfOrderTaskProcessingRequest(); + error TaskIndexNotFound(); + + // ::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::: EVENTS ::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::: + + /// @notice Event emitted on cycle transition containing active task indexes for the new cycle. + event ActiveTasks(uint256[] indexed taskIndexes); + + /// @notice Event emitted on cycle transition containing removed task indexes. + event RemovedTasks(uint64[] indexed taskIndexes); + + /// @notice Emitted when the cycle state transitions. + event AutomationCycleEvent( + uint64 indexed index, + LibUtils.CycleState indexed state, + uint64 startTime, + uint64 durationSecs, + LibUtils.CycleState indexed oldState + ); + + /// @notice Emitted when an automation fee is charged for an automation task for the cycle. + event TaskCycleFeeWithdraw( + uint64 indexed taskIndex, + address indexed owner, + uint128 fee + ); + + /// @notice Emitted when a task is removed as fee exceeds task's automation fee cap for the cycle. + event TaskCancelledCapacitySurpassed( + uint64 indexed taskIndex, + address indexed owner, + uint128 fee, + uint128 automationFeeCapForCycle, + bytes32 registrationHash + ); + + /// @notice Emitted when a task is removed due to insufficient balance. + event TaskCancelledInsufficentBalance( + uint64 indexed taskIndex, + address indexed owner, + uint128 fee, + uint256 balance, + bytes32 registrationHash + ); + + // ::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::: HELPER FUNCTIONS ::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::: + + /// @notice Function to remove a task from the registry. + /// @param _taskIndex Index of the task to remove. + /// @param _removeFromSysReg Wheather to remove from system task registry. + function removeTask(uint64 _taskIndex, bool _removeFromSysReg) private { + AppStorage storage s = LibAppStorage.appStorage(); + + if(_removeFromSysReg) { + require(s.registryState.sysTaskIds.remove(_taskIndex), TaskIndexNotFound()); + } + + delete s.registryState.tasks[_taskIndex]; + require(s.registryState.taskIdList.remove(_taskIndex), TaskIndexNotFound()); + } + + /// @notice Function to update the cycle locked fees, gas committed and tasks lists. + /// @param _lockedFees Updated cycle locked fees + /// @param _sysGasCommittedForNextCycle Updated system gas committed for next cycle + /// @param _gasCommittedForNextCycle Updated gas committed for next cycle + /// @param _gasCommittedForNewCycle Updated gas committed for new cycle + /// @param _state Cycle transition state executing the update. + function updateRegistryState( + uint256 _lockedFees, + uint128 _sysGasCommittedForNextCycle, + uint128 _gasCommittedForNextCycle, + uint128 _gasCommittedForNewCycle, + LibUtils.CycleState _state + ) private { + AppStorage storage s = LibAppStorage.appStorage(); + + s.registryState.cycleLockedFees = _lockedFees; + s.registryState.sysGasCommittedForNextCycle = _sysGasCommittedForNextCycle; + s.registryState.sysGasCommittedForThisCycle = _sysGasCommittedForNextCycle; + s.registryState.gasCommittedForNextCycle = _gasCommittedForNextCycle; + s.registryState.gasCommittedForThisCycle = _gasCommittedForNewCycle; + + s.registryState.activeTaskIds.clear(); + if (_state == LibUtils.CycleState.FINISHED) { + uint256[] memory taskIds = s.registryState.taskIdList.values(); + for (uint256 i = 0; i < taskIds.length; i++) { + s.registryState.activeTaskIds.add(taskIds[i]); + } + } else { + s.registryState.sysTaskIds.clear(); + } + } + + /// @notice Function to update the registry configuration, reverts if caller is not AutomationController. + function applyPendingConfig() private returns (bool, uint64) { + AppStorage storage s = LibAppStorage.appStorage(); + + if (!s.ifBufferExists) { + return (false, 0); + } + uint64 pendingCycleDuration = s.configBuffer.cycleDurationSecs; + s.activeConfig = s.configBuffer; + + delete s.configBuffer; + + return (true, pendingCycleDuration); + } + + /// @notice Updates the state of the cycle. + /// @param _state Input state to update cycle state with. + function updateCycleStateTo(LibUtils.CycleState _state) private { + AppStorage storage s = LibAppStorage.appStorage(); + + LibUtils.CycleState oldState = s.cycleState; + s.cycleState = _state; + + emit AutomationCycleEvent ( + s.index, + s.cycleState, + s.startTime, + s.durationSecs, + oldState + ); + } + + /// @notice Helper function to update the expected tasks of the transition state. + function updateExpectedTasks(uint256[] memory _expectedTasks) private { + AppStorage storage s = LibAppStorage.appStorage(); + + s.transitionState.expectedTasksToBeProcessed.clear(); + + for (uint256 i = 0; i < _expectedTasks.length; i++) { + s.transitionState.expectedTasksToBeProcessed.add(_expectedTasks[i]); + } + } + + /// @notice Transitions cycle state to the READY state. + function moveToReadyState() private { + // If the cycle duration updated has been identified during transtion, then the transition state is kept + // with reset values except new cycle duration to have it properly set for the next new cycle. + // This may happen in case if cycle was ended and feature-flag has been disbaled before any task has + // been processed for the cycle transition. + // Note that we want to have consistent data in ready state which says that the cycle pointed in the ready state + // has been finished/summerized, and we are ready to start the next new cycle, and all the cycle information should + // match the finalized/summerized cycle since its start, including cycle duration. + + AppStorage storage s = LibAppStorage.appStorage(); + + // Check if transition state exists + if (s.ifTransitionStateExists) { + if (s.transitionState.newCycleDuration == s.durationSecs) { + // Delete transition state + s.transitionState.expectedTasksToBeProcessed.clear(); + delete s.transitionState; + s.ifTransitionStateExists = false; + } else { + // Reset all except new cycle duration + s.transitionState.refundDuration = 0; + s.transitionState.automationFeePerSec = 0; + s.transitionState.gasCommittedForNewCycle = 0; + s.transitionState.gasCommittedForNextCycle = 0; + s.transitionState.sysGasCommittedForNextCycle = 0; + s.transitionState.lockedFees = 0; + s.transitionState.nextTaskIndexPosition = 0; + s.transitionState.expectedTasksToBeProcessed.clear(); + } + } + updateCycleStateTo(LibUtils.CycleState.READY); + } + + /// @notice Updates the cycle state if the transition is identified to be finalized. + /// As transition happens from suspended state and while transition was in progress + /// - if the feature was enabled back, then the transition will happen direclty to STARTED state, + /// - otherwise the transition will be done to the READY state. + /// + /// In both cases config will be updated. In this case we will make sure to keep the consistency of state + /// when transition to READY state happens through paths + /// - Started -> Suspended -> Ready + /// - or Started-> {Finished, Suspended} -> Ready + /// - or Started -> Finished -> {Started, Suspended} + function updateCycleTransitionStateFromSuspended() private { + AppStorage storage s = LibAppStorage.appStorage(); + + // Check if transition state exists + if (!s.ifTransitionStateExists) { revert InvalidRegistryState(); } + if (!isTransitionFinalized()) { + return; + } + + updateRegistryState(0, 0, 0, 0, LibUtils.CycleState.SUSPENDED); + + // Check if automation is enabled + if (s.automationEnabled) { + // Update the config in case if transition flow is STARTED -> SUSPENDED-> STARTED. + // to reflect new configs for the new cycle if it has been updated during SUSPENDED state processing + updateConfigFromBuffer(); + moveToStartedState(); + } else { + moveToReadyState(); + } + } + + /// @notice Marks a task as processed. + /// @param _taskIndex Index of the task to be marked as processed. + function markTaskProcessed(uint64 _taskIndex) private { + AppStorage storage s = LibAppStorage.appStorage(); + + uint64 nextTaskIndexPosition = s.transitionState.nextTaskIndexPosition; + + if (nextTaskIndexPosition >= s.transitionState.expectedTasksToBeProcessed.length()) { revert InconsistentTransitionState(); } + uint64 expectedTask = uint64(s.transitionState.expectedTasksToBeProcessed.at(nextTaskIndexPosition)); + + if (expectedTask != _taskIndex) { revert OutOfOrderTaskProcessingRequest(); } + s.transitionState.nextTaskIndexPosition = nextTaskIndexPosition + 1; + } + + /// @notice Updates the cycle state if the transition is identified to be finalized. + /// From FINISHED state we always move to the next cycle and in STARTED state. + /// But if it happened so that there was a suspension during cycle transition which was ignored, then immediately cycle state is updated to suspended. + /// Expectation will be that native layer catches this double transition and issues refund for the new cycle fees which will not be proceeded further in any case. + function updateCycleTransitionStateFromFinished() private { + AppStorage storage s = LibAppStorage.appStorage(); + + // Check if transition state exists + if (!s.ifTransitionStateExists) { revert InvalidRegistryState(); } + + if (isTransitionFinalized()) { + if (!s.automationEnabled && s.cycleState == LibUtils.CycleState.FINISHED) { + tryMoveToSuspendedState(); + } else { + updateRegistryState( + s.transitionState.lockedFees, + s.transitionState.sysGasCommittedForNextCycle, + s.transitionState.gasCommittedForNextCycle, + s.transitionState.gasCommittedForNewCycle, + LibUtils.CycleState.FINISHED + ); + + // Set current timestamp as cycle start time + // Increment the cycle and update the state to STARTED + moveToStartedState(); + if (LibRegistry.getTotalActiveTasks() > 0 ) { + uint256[] memory activeTasks = LibRegistry.getAllActiveTaskIds(); + emit ActiveTasks(activeTasks); + } + } + } + } + + /// @notice Traverses all input task indexes and either drops or tries to charge automation fee if possible. + /// @param _taskIndexes Input task indexes. + /// @return intermediateState Returns the intermediate state. + function dropOrChargeTasks( + uint64[] memory _taskIndexes + ) private returns (LibUtils.IntermediateStateOfCycleChange memory intermediateState) { + AppStorage storage s = LibAppStorage.appStorage(); + + uint64 currentTime = uint64(block.timestamp); + uint64 currentCycleEndTime = currentTime + s.transitionState.newCycleDuration; + + // Sort task indexes to charge automation fees in their chronological order + uint64[] memory taskIndexes = _taskIndexes.sortUint64(); + + uint64[] memory removedBuffer = new uint64[](taskIndexes.length); + uint256 removedCount; + + // Process each active task and calculate fee for the cycle for the tasks + for (uint256 i = 0; i < taskIndexes.length; i++) { + LibUtils.TransitionResult memory result = dropOrChargeTask( + taskIndexes[i], + currentTime, + currentCycleEndTime + ); + + if (result.isRemoved) { + removedBuffer[removedCount] = taskIndexes[i]; + removedCount += 1; + } + + intermediateState.gasCommittedForNextCycle += result.gas; + intermediateState.sysGasCommittedForNextCycle += result.sysGas; + intermediateState.cycleLockedFees += result.fees; + } + + uint64[] memory removedTasks = new uint64[](removedCount); + for (uint256 j = 0; j < removedCount; j++) { + removedTasks[j] = removedBuffer[j]; + } + intermediateState.removedTasks = removedTasks; + } + + /// @notice Drops or charges the input task. If the task is already processed or missing from the registry then nothing is done. + /// @param _taskIndex Task index to be dropped or charged. + /// @param _currentTime Current time. + /// @param _currentCycleEndTime End time of the current cycle. + /// @return result Returns the TransitionResult. + function dropOrChargeTask( + uint64 _taskIndex, + uint64 _currentTime, + uint64 _currentCycleEndTime + ) private returns (LibUtils.TransitionResult memory result) { + AppStorage storage s = LibAppStorage.appStorage(); + + if (LibRegistry.ifTaskExists(_taskIndex)) { + markTaskProcessed(_taskIndex); + + TaskMetadata memory task = LibRegistry.getTask(_taskIndex); + bool isUst = task.taskType == LibUtils.TaskType.UST; + + // Task is cancelled or expired + if (task.taskState == LibUtils.TaskState.CANCELLED || _currentTime >= task.expiryTime) { + if (isUst) { + LibRegistry.refundDepositAndDrop(_taskIndex, task.owner, task.depositFee, task.depositFee); + } else { + // Remove the task from registry and system registry + removeTask(_taskIndex, true); + } + result.isRemoved = true; + } else if (!isUst) { + // Active GST + // Governance submitted tasks are not charged + + result.sysGas = task.maxGasAmount; + s.registryState.tasks[_taskIndex].taskState = LibUtils.TaskState.ACTIVE; + } else { + // Active UST + uint128 fee = LibRegistry.calculateTaskFee( + task.taskState, + task.expiryTime, + task.maxGasAmount, + s.transitionState.newCycleDuration, + _currentTime, + s.transitionState.automationFeePerSec + ); + + // If the task reached this phase that means it is a valid active task for the new cycle. + // During cleanup all expired tasks has been removed from the registry but the state of the tasks is not updated. + // As here we need to distinguish new tasks from already existing active tasks, + // as the fee calculation for them will be different based on their active duration in the cycle. + // For more details see calculateTaskFee function. + + s.registryState.tasks[_taskIndex].taskState = LibUtils.TaskState.ACTIVE; + (result.isRemoved, result.gas, result.fees) = tryWithdrawTaskAutomationFee( + _taskIndex, + task.owner, + task.maxGasAmount, + task.expiryTime, + task.depositFee, + fee, + _currentCycleEndTime, + task.automationFeeCapForCycle, + task.txHash + ); + } + } + } + + /// @notice Helper function to withdraw automation task fees for an active task. + /// @param _taskIndex Index of the task. + /// @param _owner Owner of the task. + /// @param _maxGasAmount Max gas amount of the task. + /// @param _expiryTime Expiry time of the task. + /// @param _lockedFeeForNextCycle Locked fees of the task. + /// @param _fee Fees to be charged for the task. + /// @param _currentCycleEndTime End time of the current cycle. + /// @param _automationFeeCapForCycle Max automation fee for a cycle to be paid. + /// @param _regHash Tx hash of the task. + /// @return Bool representing if the task was removed. + /// @return Amount to add to gasCommittedForNextCycle + /// @return Amount to add to cycleLockedFees + function tryWithdrawTaskAutomationFee( + uint64 _taskIndex, + address _owner, + uint128 _maxGasAmount, + uint64 _expiryTime, + uint128 _lockedFeeForNextCycle, + uint128 _fee, + uint64 _currentCycleEndTime, + uint128 _automationFeeCapForCycle, + bytes32 _regHash + ) private returns (bool, uint128, uint128) { + AppStorage storage s = LibAppStorage.appStorage(); + + // Remove the automation task if the cycle fee cap is exceeded. + // It might happen that task has been expired by the time charging is being done. + // This may be caused by the fact that bookkeeping transactions has been withheld due to cycle transition. + + bool isRemoved; + uint128 gas; + uint128 fees; + if (_fee > _automationFeeCapForCycle) { + LibRegistry.refundDepositAndDrop(_taskIndex, _owner, _lockedFeeForNextCycle, _lockedFeeForNextCycle); + + isRemoved = true; + + emit TaskCancelledCapacitySurpassed( + _taskIndex, + _owner, + _fee, + _automationFeeCapForCycle, + _regHash + ); + } else { + uint256 userBalance = IERC20(s.erc20Supra).balanceOf(_owner); + if (userBalance < _fee) { + // If the user does not have enough balance, remove the task, DON'T refund the locked deposit, but simply unlock it and emit an event. + + // require(unlocked, UnlockLockedDepositFailed()); + LibRegistry.safeUnlockLockedDeposit(_taskIndex, _lockedFeeForNextCycle); + removeTask(_taskIndex, false); + + isRemoved = true; + + emit TaskCancelledInsufficentBalance( + _taskIndex, + _owner, + _fee, + userBalance, + _regHash + ); + } else { + if (_fee != 0) { + // Charge the fee + LibRegistry.chargeFees(_owner, _fee); + fees = _fee; + } + + emit TaskCycleFeeWithdraw( + _taskIndex, + _owner, + _fee + ); + + // Calculate gas commitment for the next cycle only for valid active tasks + if (_expiryTime > _currentCycleEndTime) { + gas = _maxGasAmount; + } + } + } + + return (isRemoved, gas, fees); + } + + // :::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::: INTERNAL FUNCTIONS :::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::: + + /// @notice Returns the cycle end time. + function getCycleEndTime() internal view returns (uint64 cycleEndTime) { + AppStorage storage s = LibAppStorage.appStorage(); + cycleEndTime = s.startTime + s.durationSecs; + } + + /// @notice Checks whether cycle is in STARTED state. + function isCycleStarted() internal view returns (bool) { + AppStorage storage s = LibAppStorage.appStorage(); + return s.cycleState == LibUtils.CycleState.STARTED; + } + + /// @notice Checks if the cycle transition is finalized. + /// @return Bool representing if the cycle transition is finalized. + function isTransitionFinalized() internal view returns (bool) { + AppStorage storage s = LibAppStorage.appStorage(); + return s.transitionState.expectedTasksToBeProcessed.length() == s.transitionState.nextTaskIndexPosition; + } + + /// @notice Checks if the cycle transition is in progress. + /// @return Bool representing if the cycle transition is in progress. + function isTransitionInProgress() internal view returns (bool) { + AppStorage storage s = LibAppStorage.appStorage(); + return s.transitionState.nextTaskIndexPosition != 0; + } + + /// @notice Traverses the list of the tasks and based on the task state and expiry information either charges or drops the task after refunding eligable fees. + /// Tasks are checked not to be processed more than once. + /// This function should be called only if registry is in FINISHED state, meaning a normal cycle transition is happening. + /// After processing all input tasks, intermediate transition state is updated and transition end is checked (whether all expected tasks has been processed already). + /// In case if transition end is detected a start of the new cycle is given (if during trasition period suspention is not requested) and corresponding event is emitted. + /// @param _cycleIndex Cycle index of the new cycle to which the transition is being done. + /// @param _taskIndexes Array of task indexes to be processed. + function onCycleTransition(uint64 _cycleIndex, uint64[] memory _taskIndexes) internal { + AppStorage storage s = LibAppStorage.appStorage(); + + if (_taskIndexes.length == 0) { return; } + + if (s.cycleState != LibUtils.CycleState.FINISHED) { revert InvalidRegistryState(); } + + // Check if transition state exists + if (!s.ifTransitionStateExists) { revert InvalidRegistryState(); } + if (s.index + 1 != _cycleIndex) { revert InvalidInputCycleIndex(); } + + LibUtils.IntermediateStateOfCycleChange memory intermediateState = dropOrChargeTasks(_taskIndexes); + + s.transitionState.lockedFees += intermediateState.cycleLockedFees; + s.transitionState.gasCommittedForNextCycle += intermediateState.gasCommittedForNextCycle; + s.transitionState.sysGasCommittedForNextCycle += intermediateState.sysGasCommittedForNextCycle; + + updateCycleTransitionStateFromFinished(); + if (intermediateState.removedTasks.length > 0) { + emit RemovedTasks(intermediateState.removedTasks); + } + } + + /// @notice Traverses the list of the tasks and refunds automation(if not PENDING) and deposit fees for all tasks and removes from registry. + /// This function is called only if automation feature is disabled, i.e. cycle is in SUSPENDED state. + /// After processing input set of tasks the end of suspention process is checked(i.e. all expected tasks have been processed). + /// In case if end is identified, the registry state is update to READY and corresponding event is emitted. + /// @param _cycleIndex Input cycle index of the cycle being suspended. + /// @param _taskIndexes Array of task indexes to be processed. + function onCycleSuspend(uint64 _cycleIndex, uint64[] memory _taskIndexes) internal { + AppStorage storage s = LibAppStorage.appStorage(); + + if (_taskIndexes.length == 0) { return; } + + if (s.cycleState != LibUtils.CycleState.SUSPENDED) { revert InvalidRegistryState(); } + if (s.index != _cycleIndex) { revert InvalidInputCycleIndex(); } + // Check if transition state exists + if (!s.ifTransitionStateExists) { revert InvalidRegistryState(); } + + uint64 currentTime = uint64(block.timestamp); + + // Sort task indexes as order is important + uint64[] memory taskIndexes = _taskIndexes.sortUint64(); + uint64[] memory removedTasks = new uint64[](taskIndexes.length); + + uint64 removedCounter; + for (uint i = 0; i < taskIndexes.length; i++) { + if (LibRegistry.ifTaskExists(taskIndexes[i])) { + TaskMetadata memory task = LibRegistry.getTask(taskIndexes[i]); + + removeTask(taskIndexes[i], false); + + removedTasks[removedCounter++] = taskIndexes[i]; + markTaskProcessed(taskIndexes[i]); + + // Nothing to refund for GST tasks + if (task.taskType == LibUtils.TaskType.UST) { + LibRegistry.refundTaskFees(currentTime, s.transitionState.refundDuration, s.transitionState.automationFeePerSec, task); + } + } + } + + updateCycleTransitionStateFromSuspended(); + emit RemovedTasks(removedTasks); + } + + /// @notice Helper function called when cycle end is identified. + function onCycleEndInternal() internal { + AppStorage storage s = LibAppStorage.appStorage(); + + if (!s.automationEnabled) { + tryMoveToSuspendedState(); + } else { + if (LibRegistry.totalTasks() == 0) { + // Registry is empty update config buffer and move to STARTED state directly + updateConfigFromBuffer(); + moveToStartedState(); + } else { + uint256[] memory expectedTasksToBeProcessed = LibRegistry.getTaskIdList().sortUint256(); + + // Updates transition state + s.transitionState.refundDuration = 0; + s.transitionState.newCycleDuration = s.durationSecs; + s.transitionState.gasCommittedForNewCycle = s.registryState.gasCommittedForNextCycle; + s.transitionState.gasCommittedForNextCycle = 0; + s.transitionState.sysGasCommittedForNextCycle = 0; + s.transitionState.lockedFees = 0; + s.transitionState.nextTaskIndexPosition = 0; + updateExpectedTasks(expectedTasksToBeProcessed); + + s.ifTransitionStateExists = true; + + // During cycle transition we update config only after transition state is created in order to have new cycle duration as transition state parameter. + updateConfigFromBuffer(); + + // Calculate automation fee per second for the new cycle only after configuration is updated. + // As we already know the committed gas for the new cycle it is being calculated using updated fee parameters + // and will be used to charge tasks during transition process. + s.transitionState.automationFeePerSec = LibRegistry.calculateAutomationFeeMultiplierForCommittedOccupancy(s.transitionState.gasCommittedForNewCycle); + updateCycleStateTo(LibUtils.CycleState.FINISHED); + } + } + } + + /// @notice Transition to suspended state is expected to be called + /// a) when cycle is active and in progress + /// - here we simply move to suspended state so native layer can start requesting tasks processing + /// which will end up in refunds and cleanup. Note that refund will be done based on total gas-committed + /// for the current cycle defined at the begining for the cycle, and using current automation fee parameters + /// b) when cycle has just finished and there was another transaction causing feature suspension + /// - as this both events happen in scope of the same block, then we will simply update the state to suspended + /// and the native layer should identify the transition and request processing of the all available tasks. + /// Note that in this case automation fee refund will not be expected and suspention and cycle end matched and + /// no fee was yet charged to be refunded. + /// So the duration for refund and automation-fee-per-second for refund will be 0 + /// c) when cycle transition was in progress and there was a feature suspension, but it could not be applied, + /// and postponed till the cycle transition concludes + /// In all the cases if there are no tasks in registry the state will be updated directly to READY state. + function tryMoveToSuspendedState() internal { + AppStorage storage s = LibAppStorage.appStorage(); + + if (LibRegistry.totalTasks() == 0) { + // Registry is empty move to ready state directly + updateCycleStateTo(LibUtils.CycleState.READY); + } else if (!s.ifTransitionStateExists) { + // Indicates that cycle was in STARTED state when suspention has been identified. + // It is safe to assert that cycleEndTime will always be greater than current chain time as + // the cycle end is check in the block metadata txn execution which proceeds any other transaction in the block. + // Including the transaction which caused transition to suspended state. + // So in case if cycleEndTime < currentTime then cycle end would have been identified + // and we would have enterend else branch instead. + // This holds true even if we identified suspention when moving from FINALIZED->STARTED state. + // As in this case we will first transition to the STARTED state and only then to SUSPENDED. + // And when transition to STARTED state we update the cycle start-time to be the current-chain-time. + uint64 currentTime = uint64(block.timestamp); + uint64 cycleEndTime = getCycleEndTime(); + + if (currentTime < s.startTime) { revert InvalidRegistryState(); } + if (currentTime >= cycleEndTime) { revert InvalidRegistryState(); } + if (!isCycleStarted()) { revert InvalidRegistryState(); } + + uint256[] memory tasksIdList = LibRegistry.getTaskIdList(); + uint256[] memory expectedTasksToBeProcessed = tasksIdList.sortUint256(); + + s.transitionState.refundDuration = cycleEndTime - currentTime; + s.transitionState.newCycleDuration = s.durationSecs; + s.transitionState.automationFeePerSec = LibRegistry.calculateAutomationFeeMultiplierForCurrentCycle(); + s.transitionState.gasCommittedForNewCycle = 0; + s.transitionState.gasCommittedForNextCycle = 0; + s.transitionState.sysGasCommittedForNextCycle = 0; + s.transitionState.lockedFees = 0; + s.transitionState.nextTaskIndexPosition = 0; + + updateExpectedTasks(expectedTasksToBeProcessed); + s.ifTransitionStateExists = true; + + updateCycleStateTo(LibUtils.CycleState.SUSPENDED); + } else { + if (s.cycleState != LibUtils.CycleState.FINISHED) { revert InvalidRegistryState(); } + if (isTransitionInProgress()) { revert InvalidRegistryState(); } + + // Did not manage to charge cycle fee, so automationFeePerSec will be 0 along with remaining duration + // So the tasks sent for refund, will get only deposit refunded. + s.transitionState.refundDuration = 0; + s.transitionState.automationFeePerSec = 0; + s.transitionState.gasCommittedForNewCycle = 0; + + updateCycleStateTo(LibUtils.CycleState.SUSPENDED); + } + } + + /// @notice Transitions cycle state to the STARTED state. + function moveToStartedState() internal { + AppStorage storage s = LibAppStorage.appStorage(); + + s.index += 1; + s.startTime = uint64(block.timestamp); + + // Check if the transition state exists + if (s.ifTransitionStateExists) { + s.durationSecs = s.transitionState.newCycleDuration; + } + + updateCycleStateTo(LibUtils.CycleState.STARTED); + } + + /// @notice Function to update the registry config structure with values extracted from the buffer, if the buffer exists. + function updateConfigFromBuffer() internal { + AppStorage storage s = LibAppStorage.appStorage(); + + (bool applied, uint64 cycleDuration) = applyPendingConfig(); + if (!applied) return; + + // Check if transition state exists + if (s.ifTransitionStateExists) { + s.transitionState.newCycleDuration = cycleDuration; + } else { + s.durationSecs = cycleDuration; + } + } +} \ No newline at end of file diff --git a/solidity/supra_contracts/src/libraries/LibDiamond.sol b/solidity/supra_contracts/src/libraries/LibDiamond.sol new file mode 100644 index 0000000000..bb3ca39235 --- /dev/null +++ b/solidity/supra_contracts/src/libraries/LibDiamond.sol @@ -0,0 +1,214 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.0; + +/******************************************************************************\ +* Author: Nick Mudge (https://twitter.com/mudgen) +* EIP-2535 Diamonds: https://eips.ethereum.org/EIPS/eip-2535 +/******************************************************************************/ +import { IDiamondCut } from "../interfaces/IDiamondCut.sol"; + +// Remember to add the loupe functions from DiamondLoupeFacet to the diamond. +// The loupe functions are required by the EIP2535 Diamonds standard + +error InitializationFunctionReverted(address _initializationContractAddress, bytes _calldata); + +library LibDiamond { + // 32 bytes keccak hash of a string to use as a diamond storage location. + bytes32 constant DIAMOND_STORAGE_POSITION = keccak256("diamond.standard.diamond.storage"); + + struct FacetAddressAndPosition { + address facetAddress; + uint96 functionSelectorPosition; // position in facetFunctionSelectors.functionSelectors array + } + + struct FacetFunctionSelectors { + bytes4[] functionSelectors; + uint256 facetAddressPosition; // position of facetAddress in facetAddresses array + } + + struct DiamondStorage { + // maps function selector to the facet address and + // the position of the selector in the facetFunctionSelectors.selectors array + mapping(bytes4 => FacetAddressAndPosition) selectorToFacetAndPosition; + // maps facet addresses to function selectors + mapping(address => FacetFunctionSelectors) facetFunctionSelectors; + // facet addresses + address[] facetAddresses; + // Used to query if a contract implements an interface. + // Used to implement ERC-165. + mapping(bytes4 => bool) supportedInterfaces; + // owner of the contract + address contractOwner; + } + + function diamondStorage() internal pure returns (DiamondStorage storage ds) { + bytes32 position = DIAMOND_STORAGE_POSITION; + // assigns struct storage slot to the storage position + assembly { + ds.slot := position + } + } + + event OwnershipTransferred(address indexed previousOwner, address indexed newOwner); + + function setContractOwner(address _newOwner) internal { + DiamondStorage storage ds = diamondStorage(); + address previousOwner = ds.contractOwner; + ds.contractOwner = _newOwner; + emit OwnershipTransferred(previousOwner, _newOwner); + } + + function contractOwner() internal view returns (address contractOwner_) { + contractOwner_ = diamondStorage().contractOwner; + } + + function enforceIsContractOwner() internal view { + require(msg.sender == diamondStorage().contractOwner, "LibDiamond: Must be contract owner"); + } + + event DiamondCut(IDiamondCut.FacetCut[] _diamondCut, address _init, bytes _calldata); + + // Internal function version of diamondCut + function diamondCut( + IDiamondCut.FacetCut[] memory _diamondCut, + address _init, + bytes memory _calldata + ) internal { + for (uint256 facetIndex; facetIndex < _diamondCut.length; facetIndex++) { + IDiamondCut.FacetCutAction action = _diamondCut[facetIndex].action; + if (action == IDiamondCut.FacetCutAction.Add) { + addFunctions(_diamondCut[facetIndex].facetAddress, _diamondCut[facetIndex].functionSelectors); + } else if (action == IDiamondCut.FacetCutAction.Replace) { + replaceFunctions(_diamondCut[facetIndex].facetAddress, _diamondCut[facetIndex].functionSelectors); + } else if (action == IDiamondCut.FacetCutAction.Remove) { + removeFunctions(_diamondCut[facetIndex].facetAddress, _diamondCut[facetIndex].functionSelectors); + } else { + revert("LibDiamondCut: Incorrect FacetCutAction"); + } + } + emit DiamondCut(_diamondCut, _init, _calldata); + initializeDiamondCut(_init, _calldata); + } + + function addFunctions(address _facetAddress, bytes4[] memory _functionSelectors) internal { + require(_functionSelectors.length > 0, "LibDiamondCut: No selectors in facet to cut"); + DiamondStorage storage ds = diamondStorage(); + require(_facetAddress != address(0), "LibDiamondCut: Add facet can't be address(0)"); + uint96 selectorPosition = uint96(ds.facetFunctionSelectors[_facetAddress].functionSelectors.length); + // add new facet address if it does not exist + if (selectorPosition == 0) { + addFacet(ds, _facetAddress); + } + for (uint256 selectorIndex; selectorIndex < _functionSelectors.length; selectorIndex++) { + bytes4 selector = _functionSelectors[selectorIndex]; + address oldFacetAddress = ds.selectorToFacetAndPosition[selector].facetAddress; + require(oldFacetAddress == address(0), "LibDiamondCut: Can't add function that already exists"); + addFunction(ds, selector, selectorPosition, _facetAddress); + selectorPosition++; + } + } + + function replaceFunctions(address _facetAddress, bytes4[] memory _functionSelectors) internal { + require(_functionSelectors.length > 0, "LibDiamondCut: No selectors in facet to cut"); + DiamondStorage storage ds = diamondStorage(); + require(_facetAddress != address(0), "LibDiamondCut: Add facet can't be address(0)"); + uint96 selectorPosition = uint96(ds.facetFunctionSelectors[_facetAddress].functionSelectors.length); + // add new facet address if it does not exist + if (selectorPosition == 0) { + addFacet(ds, _facetAddress); + } + for (uint256 selectorIndex; selectorIndex < _functionSelectors.length; selectorIndex++) { + bytes4 selector = _functionSelectors[selectorIndex]; + address oldFacetAddress = ds.selectorToFacetAndPosition[selector].facetAddress; + require(oldFacetAddress != _facetAddress, "LibDiamondCut: Can't replace function with same function"); + removeFunction(ds, oldFacetAddress, selector); + addFunction(ds, selector, selectorPosition, _facetAddress); + selectorPosition++; + } + } + + function removeFunctions(address _facetAddress, bytes4[] memory _functionSelectors) internal { + require(_functionSelectors.length > 0, "LibDiamondCut: No selectors in facet to cut"); + DiamondStorage storage ds = diamondStorage(); + // if function does not exist then do nothing and return + require(_facetAddress == address(0), "LibDiamondCut: Remove facet address must be address(0)"); + for (uint256 selectorIndex; selectorIndex < _functionSelectors.length; selectorIndex++) { + bytes4 selector = _functionSelectors[selectorIndex]; + address oldFacetAddress = ds.selectorToFacetAndPosition[selector].facetAddress; + removeFunction(ds, oldFacetAddress, selector); + } + } + + function addFacet(DiamondStorage storage ds, address _facetAddress) internal { + enforceHasContractCode(_facetAddress, "LibDiamondCut: New facet has no code"); + ds.facetFunctionSelectors[_facetAddress].facetAddressPosition = ds.facetAddresses.length; + ds.facetAddresses.push(_facetAddress); + } + + + function addFunction(DiamondStorage storage ds, bytes4 _selector, uint96 _selectorPosition, address _facetAddress) internal { + ds.selectorToFacetAndPosition[_selector].functionSelectorPosition = _selectorPosition; + ds.facetFunctionSelectors[_facetAddress].functionSelectors.push(_selector); + ds.selectorToFacetAndPosition[_selector].facetAddress = _facetAddress; + } + + function removeFunction(DiamondStorage storage ds, address _facetAddress, bytes4 _selector) internal { + require(_facetAddress != address(0), "LibDiamondCut: Can't remove function that doesn't exist"); + // an immutable function is a function defined directly in a diamond + require(_facetAddress != address(this), "LibDiamondCut: Can't remove immutable function"); + // replace selector with last selector, then delete last selector + uint256 selectorPosition = ds.selectorToFacetAndPosition[_selector].functionSelectorPosition; + uint256 lastSelectorPosition = ds.facetFunctionSelectors[_facetAddress].functionSelectors.length - 1; + // if not the same then replace _selector with lastSelector + if (selectorPosition != lastSelectorPosition) { + bytes4 lastSelector = ds.facetFunctionSelectors[_facetAddress].functionSelectors[lastSelectorPosition]; + ds.facetFunctionSelectors[_facetAddress].functionSelectors[selectorPosition] = lastSelector; + ds.selectorToFacetAndPosition[lastSelector].functionSelectorPosition = uint96(selectorPosition); + } + // delete the last selector + ds.facetFunctionSelectors[_facetAddress].functionSelectors.pop(); + delete ds.selectorToFacetAndPosition[_selector]; + + // if no more selectors for facet address then delete the facet address + if (lastSelectorPosition == 0) { + // replace facet address with last facet address and delete last facet address + uint256 lastFacetAddressPosition = ds.facetAddresses.length - 1; + uint256 facetAddressPosition = ds.facetFunctionSelectors[_facetAddress].facetAddressPosition; + if (facetAddressPosition != lastFacetAddressPosition) { + address lastFacetAddress = ds.facetAddresses[lastFacetAddressPosition]; + ds.facetAddresses[facetAddressPosition] = lastFacetAddress; + ds.facetFunctionSelectors[lastFacetAddress].facetAddressPosition = facetAddressPosition; + } + ds.facetAddresses.pop(); + delete ds.facetFunctionSelectors[_facetAddress].facetAddressPosition; + } + } + + function initializeDiamondCut(address _init, bytes memory _calldata) internal { + if (_init == address(0)) { + return; + } + enforceHasContractCode(_init, "LibDiamondCut: _init address has no code"); + (bool success, bytes memory error) = _init.delegatecall(_calldata); + if (!success) { + if (error.length > 0) { + // bubble up error + /// @solidity memory-safe-assembly + assembly { + let returndata_size := mload(error) + revert(add(32, error), returndata_size) + } + } else { + revert InitializationFunctionReverted(_init, _calldata); + } + } + } + + function enforceHasContractCode(address _contract, string memory _errorMessage) internal view { + uint256 contractSize; + assembly { + contractSize := extcodesize(_contract) + } + require(contractSize > 0, _errorMessage); + } +} diff --git a/solidity/supra_contracts/src/libraries/LibDiamondUtils.sol b/solidity/supra_contracts/src/libraries/LibDiamondUtils.sol new file mode 100644 index 0000000000..dc3d083ac6 --- /dev/null +++ b/solidity/supra_contracts/src/libraries/LibDiamondUtils.sol @@ -0,0 +1,298 @@ +// SPDX-License-Identifier: MIT +pragma solidity 0.8.27; + +import {Diamond} from "../Diamond.sol"; +import {DiamondCutFacet} from "../facets/DiamondCutFacet.sol"; +import {DiamondLoupeFacet} from "../facets/DiamondLoupeFacet.sol"; +import {OwnershipFacet} from "../facets/OwnershipFacet.sol"; +import {ConfigFacet} from "../facets/ConfigFacet.sol"; +import {RegistryFacet} from "../facets/RegistryFacet.sol"; +import {CoreFacet} from "../facets/CoreFacet.sol"; +import {DiamondInit} from "../upgradeInitializers/DiamondInit.sol"; +import {IDiamondCut} from "../interfaces/IDiamondCut.sol"; + +// ============================================================= +// STRUCTS +// ============================================================= + +struct Deployment { + address diamondCutFacet; + address diamond; + address loupeFacet; + address ownershipFacet; + address configFacet; + address registryFacet; + address coreFacet; + address diamondInit; +} + +struct InitParams { + uint64 taskDurationCapSecs; + uint128 registryMaxGasCap; + uint128 automationBaseFeeWeiPerSec; + uint128 flatRegistrationFeeWei; + uint8 congestionThresholdPercentage; + uint128 congestionBaseFeeWeiPerSec; + uint8 congestionExponent; + uint16 taskCapacity; + uint64 cycleDurationSecs; + uint64 sysTaskDurationCapSecs; + uint128 sysRegistryMaxGasCap; + uint16 sysTaskCapacity; + bool automationEnabled; + bool registrationEnabled; +} + +library LibDiamondUtils { + + // ============================================================= + // DEFAULT INIT CONFIG + // ============================================================= + + function defaultInitParams() internal pure returns (InitParams memory p) { + p = InitParams({ + taskDurationCapSecs: 3600, + registryMaxGasCap: 10_000_000, + automationBaseFeeWeiPerSec: 0.001 ether, + flatRegistrationFeeWei: 0.002 ether, + congestionThresholdPercentage: 50, + congestionBaseFeeWeiPerSec: 0.002 ether, + congestionExponent: 2, + taskCapacity: 500, + cycleDurationSecs: 2000, + sysTaskDurationCapSecs: 3600, + sysRegistryMaxGasCap: 5_000_000, + sysTaskCapacity: 500, + registrationEnabled: true, + automationEnabled: true + }); + } + + // ============================================================= + // DEPLOY FUNCTION + // ============================================================= + + function deploy(address _owner) internal returns (Deployment memory d) { + + // 1) Deploy DiamondCutFacet + DiamondCutFacet cutFacet = new DiamondCutFacet(); + d.diamondCutFacet = address(cutFacet); + + // 2) Deploy Diamond + Diamond diamond = new Diamond(_owner, address(cutFacet)); + d.diamond = address(diamond); + + // 3. Deploy other facets + DiamondLoupeFacet loupeFacet = new DiamondLoupeFacet(); + OwnershipFacet ownershipFacet = new OwnershipFacet(); + ConfigFacet configFacet = new ConfigFacet(); + RegistryFacet registryFacet = new RegistryFacet(); + CoreFacet coreFacet = new CoreFacet(); + + d.loupeFacet = address(loupeFacet); + d.ownershipFacet = address(ownershipFacet); + d.configFacet = address(configFacet); + d.registryFacet = address(registryFacet); + d.coreFacet = address(coreFacet); + + // 4) Deploy DiamondInit + DiamondInit diamondInit = new DiamondInit(); + d.diamondInit = address(diamondInit); + } + + // ============================================================= + // EXECUTE DIAMOND CUT + // ============================================================= + + function executeCut( + address _vmSigner, + address _erc20Supra, + InitParams memory _params, + Deployment memory _deployment + ) internal { + + // 1) Build the facet cuts + IDiamondCut.FacetCut[] memory cut = buildFacetCuts( + _deployment.loupeFacet, + _deployment.ownershipFacet, + _deployment.configFacet, + _deployment.registryFacet, + _deployment.coreFacet + ); + + // 2) Prepare init calldata for DiamondInit + bytes memory initCalldata = abi.encodeCall( + DiamondInit.init, + ( + _params.taskDurationCapSecs, + _params.registryMaxGasCap, + _params.automationBaseFeeWeiPerSec, + _params.flatRegistrationFeeWei, + _params.congestionThresholdPercentage, + _params.congestionBaseFeeWeiPerSec, + _params.congestionExponent, + _params.taskCapacity, + _params.cycleDurationSecs, + _params.sysTaskDurationCapSecs, + _params.sysRegistryMaxGasCap, + _params.sysTaskCapacity, + _vmSigner, + _erc20Supra, + _params.registrationEnabled, + _params.automationEnabled + ) + ); + + // 3) Execute diamondCut to add all the facets and initialize the state + IDiamondCut(_deployment.diamond).diamondCut( + cut, + _deployment.diamondInit, + initCalldata + ); + } + + + // ============================================================= + // FACET CUT BUILDER + // ============================================================= + + function buildFacetCuts( + address loupeFacet, + address ownershipFacet, + address configFacet, + address registryFacet, + address coreFacet + ) internal pure returns (IDiamondCut.FacetCut[] memory cut) { + cut = new IDiamondCut.FacetCut[](5); + + // ------------------------------------------------------------ + // DiamondLoupeFacet + // ------------------------------------------------------------ + { + bytes4[] memory selectors = new bytes4[](5); + selectors[0] = DiamondLoupeFacet.facets.selector; + selectors[1] = DiamondLoupeFacet.facetFunctionSelectors.selector; + selectors[2] = DiamondLoupeFacet.facetAddresses.selector; + selectors[3] = DiamondLoupeFacet.facetAddress.selector; + selectors[4] = DiamondLoupeFacet.supportsInterface.selector; + + cut[0] = IDiamondCut.FacetCut({ + facetAddress: loupeFacet, + action: IDiamondCut.FacetCutAction.Add, + functionSelectors: selectors + }); + } + + // ------------------------------------------------------------ + // OwnershipFacet + // ------------------------------------------------------------ + { + bytes4[] memory selectors = new bytes4[](2); + selectors[0] = OwnershipFacet.owner.selector; + selectors[1] = OwnershipFacet.transferOwnership.selector; + + cut[1] = IDiamondCut.FacetCut({ + facetAddress: ownershipFacet, + action: IDiamondCut.FacetCutAction.Add, + functionSelectors: selectors + }); + } + + // ------------------------------------------------------------ + // ConfigFacet + // ------------------------------------------------------------ + { + bytes4[] memory selectors = new bytes4[](13); + selectors[0] = ConfigFacet.grantAuthorization.selector; + selectors[1] = ConfigFacet.revokeAuthorization.selector; + selectors[2] = ConfigFacet.enableRegistration.selector; + selectors[3] = ConfigFacet.disableRegistration.selector; + selectors[4] = ConfigFacet.setVmSigner.selector; + selectors[5] = ConfigFacet.setErc20Supra.selector; + selectors[6] = ConfigFacet.withdrawFees.selector; + selectors[7] = ConfigFacet.updateConfigBuffer.selector; + + selectors[8] = ConfigFacet.getVmSigner.selector; + selectors[9] = ConfigFacet.erc20Supra.selector; + selectors[10] = ConfigFacet.isRegistrationEnabled.selector; + selectors[11] = ConfigFacet.getConfig.selector; + selectors[12] = ConfigFacet.getConfigBuffer.selector; + + cut[2] = IDiamondCut.FacetCut({ + facetAddress: configFacet, + action: IDiamondCut.FacetCutAction.Add, + functionSelectors: selectors + }); + } + + // ------------------------------------------------------------ + // RegistryFacet + // ------------------------------------------------------------ + { + bytes4[] memory selectors = new bytes4[](35); + selectors[0] = RegistryFacet.register.selector; + selectors[1] = RegistryFacet.registerSystemTask.selector; + selectors[2] = RegistryFacet.cancelTask.selector; + selectors[3] = RegistryFacet.cancelSystemTask.selector; + selectors[4] = RegistryFacet.stopTasks.selector; + selectors[5] = RegistryFacet.stopSystemTasks.selector; + + selectors[6] = RegistryFacet.getTaskIds.selector; + selectors[7] = RegistryFacet.getSystemTaskIds.selector; + selectors[8] = RegistryFacet.getTaskOwner.selector; + selectors[9] = RegistryFacet.getNextTaskIndex.selector; + selectors[10] = RegistryFacet.totalTasks.selector; + selectors[11] = RegistryFacet.totalSystemTasks.selector; + selectors[12] = RegistryFacet.getTaskDetails.selector; + selectors[13] = RegistryFacet.getTaskDetailsBulk.selector; + selectors[14] = RegistryFacet.isAuthorizedSubmitter.selector; + selectors[15] = RegistryFacet.getTotalActiveTasks.selector; + selectors[16] = RegistryFacet.getActiveTaskIds.selector; + selectors[17] = RegistryFacet.hasActiveUserTask.selector; + selectors[18] = RegistryFacet.hasActiveSystemTask.selector; + selectors[19] = RegistryFacet.hasActiveTaskOfType.selector; + selectors[20] = RegistryFacet.getGasCommittedForNextCycle.selector; + selectors[21] = RegistryFacet.getGasCommittedForCurrentCycle.selector; + selectors[22] = RegistryFacet.getSystemGasCommittedForNextCycle.selector; + selectors[23] = RegistryFacet.getSystemGasCommittedForCurrentCycle.selector; + selectors[24] = RegistryFacet.getNextCycleRegistryMaxGasCap.selector; + selectors[25] = RegistryFacet.getNextCycleSysRegistryMaxGasCap.selector; + selectors[26] = RegistryFacet.getCycleLockedFees.selector; + selectors[27] = RegistryFacet.getTotalDepositedAutomationFees.selector; + selectors[28] = RegistryFacet.getTotalLockedBalance.selector; + selectors[29] = RegistryFacet.calculateAutomationFeeMultiplierForCommittedOccupancy.selector; + selectors[30] = RegistryFacet.calculateAutomationFeeMultiplierForCurrentCycle.selector; + selectors[31] = RegistryFacet.estimateAutomationFee.selector; + selectors[32] = RegistryFacet.estimateAutomationFeeWithCommittedOccupancy.selector; + selectors[33] = RegistryFacet.ifTaskExists.selector; + selectors[34] = RegistryFacet.ifSysTaskExists.selector; + + cut[3] = IDiamondCut.FacetCut({ + facetAddress: registryFacet, + action: IDiamondCut.FacetCutAction.Add, + functionSelectors: selectors + }); + } + + // ------------------------------------------------------------ + // CoreFacet + // ------------------------------------------------------------ + { + bytes4[] memory selectors = new bytes4[](8); + selectors[0] = CoreFacet.processTasks.selector; + selectors[1] = CoreFacet.monitorCycleEnd.selector; + selectors[2] = CoreFacet.enableAutomation.selector; + selectors[3] = CoreFacet.disableAutomation.selector; + selectors[4] = CoreFacet.getCycleInfo.selector; + selectors[5] = CoreFacet.getCycleDuration.selector; + selectors[6] = CoreFacet.getTransitionInfo.selector; + selectors[7] = CoreFacet.isAutomationEnabled.selector; + + cut[4] = IDiamondCut.FacetCut({ + facetAddress: coreFacet, + action: IDiamondCut.FacetCutAction.Add, + functionSelectors: selectors + }); + } + } +} diff --git a/solidity/supra_contracts/src/libraries/LibRegistry.sol b/solidity/supra_contracts/src/libraries/LibRegistry.sol new file mode 100644 index 0000000000..add1e4f616 --- /dev/null +++ b/solidity/supra_contracts/src/libraries/LibRegistry.sol @@ -0,0 +1,711 @@ +// SPDX-License-Identifier: MIT +pragma solidity 0.8.27; + +import {LibUtils} from "./LibUtils.sol"; +import {AppStorage, LibAppStorage, TaskMetadata} from "./LibAppStorage.sol"; +import {EnumerableSet} from "../../lib/openzeppelin-contracts/contracts/utils/structs/EnumerableSet.sol"; +import {IERC20} from "../../lib/openzeppelin-contracts/contracts/token/ERC20/IERC20.sol"; + +library LibRegistry { + using LibUtils for *; + using EnumerableSet for EnumerableSet.UintSet; + + /// @dev Constant for 10^8 + uint256 constant DECIMAL = 100_000_000; + + /// @dev Constants describing REFUND TYPE + uint8 constant DEPOSIT_CYCLE_FEE = 0; + uint8 constant CYCLE_FEE = 1; + + /// @dev Refund fraction + uint8 constant REFUND_FRACTION = 2; + + // :::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::: ERRORS :::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::: + + error TransferFailed(); + error ErrorCycleFeeRefund(); + error RegistrationDisabled(); + error AddressAlreadyExists(); + error AddressDoesNotExist(); + error AutomationNotEnabled(); + error CallerNotController(); + error UnauthorizedAccount(); + error CycleTransitionInProgress(); + error TaskDoesNotExist(); + error UnsupportedTaskOperation(); + error AlreadyCancelled(); + error ErrorDepositRefund(); + error SystemTaskDoesNotExist(); + error TaskIndexesCannotBeEmpty(); + error RegisteredTaskInvalidType(); + error TaskIndexNotFound(); + error TaskIndexNotUnique(); + error FailedToCallTxHashPrecompile(); + error TxnHashLengthShouldBe32(uint64); + error InvalidMaxGasAmount(); + error GasCommittedExceedsMaxGasCap(); + error GasCommittedValueUnderflow(); + error InsufficientBalance(); + error InsufficientFeeCapForCycle(); + error InsufficientBalanceForRefund(); + error InvalidCongestionExponent(); + error InvalidCongestionThreshold(); + error InvalidCycleDuration(); + error InvalidExpiryTime(); + error InvalidGasPriceCap(); + error InvalidRegistryMaxGasCap(); + error InvalidSysRegistryMaxGasCap(); + error InvalidSysTaskCapacity(); + error InvalidSysTaskDuration(); + error InvalidTaskCapacity(); + error InvalidTaskDuration(); + error RequestExceedsLockedBalance(); + error TaskCapacityReached(); + error TaskExpiresBeforeNextCycle(); + + // :::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::: EVENTS :::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::: + + /// @notice Emitted when an automation fee is refunded for an automation task at the end of the cycle for excessive + /// duration paid at the beginning of the cycle due to cycle duration reduction by governance. + event TaskFeeRefund( + uint64 indexed taskIndex, + address indexed owner, + uint64 indexed amount + ); + + /// @notice Emitted when a deposit fee is refunded for an automation task. + event TaskDepositFeeRefund(uint64 indexed taskIndex, address owner, uint128 amount); + + /// @notice Emitted when a task cycle fee is being refunded but locked cycle fees is less than the requested refund. + event ErrorUnlockTaskCycleFee( + uint64 indexed taskIndex, + uint256 indexed lockedCycleFees, + uint64 indexed refund + ); + + /// @notice Emitted during cycle transition when refunds to be paid is not possible due to insufficient contract balance. + /// Type of the refund can be related either to the deposit paid during registration (0), or to cycle fee caused by + /// the shortening of the cycle (1) + event ErrorInsufficientBalanceToRefund( + uint64 indexed _taskIndex, + address indexed _owner, + uint8 indexed _refundType, + uint128 _amount + ); + + /// @notice Emitted when deposit fee is being refunded but total locked deposits is less than the locked deposit for the task. + event ErrorUnlockTaskDepositFee( + uint64 indexed taskIndex, + uint256 indexed totalDepositedAutomationFees, + uint128 indexed lockedDeposit + ); + + // :::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::: VIEW FUNCTIONS :::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::: + + /// @notice Returns the total number of active tasks. + function getTotalActiveTasks() internal view returns (uint256) { + AppStorage storage s = LibAppStorage.appStorage(); + return s.registryState.activeTaskIds.length(); + } + + /// @notice Returns all the active task indexes. + function getAllActiveTaskIds() internal view returns (uint256[] memory) { + AppStorage storage s = LibAppStorage.appStorage(); + return s.registryState.activeTaskIds.values(); + } + + /// @notice Returns the number of total tasks. + function totalTasks() internal view returns (uint256) { + AppStorage storage s = LibAppStorage.appStorage(); + return s.registryState.taskIdList.length(); + } + + /// @notice Returns all the automation tasks available in the registry. + function getTaskIdList() internal view returns (uint256[] memory) { + AppStorage storage s = LibAppStorage.appStorage(); + return s.registryState.taskIdList.values(); + } + + /// @notice Checks whether cycle is in STARTED state. + function isCycleStarted() internal view returns (bool) { + AppStorage storage s = LibAppStorage.appStorage(); + return s.cycleState == LibUtils.CycleState.STARTED; + } + + /// @notice Checks if a task exist. + /// @param _taskIndex Task index to check if a task exists against it. + function ifTaskExists(uint64 _taskIndex) internal view returns (bool) { + AppStorage storage s = LibAppStorage.appStorage(); + return s.registryState.tasks[_taskIndex].owner != address(0) && s.registryState.taskIdList.contains(_taskIndex); + } + + /// @notice Checks if a system task exist. + /// @param _taskIndex Task index to check if a system task exists against it. + function ifSysTaskExists(uint64 _taskIndex) internal view returns (bool) { + AppStorage storage s = LibAppStorage.appStorage(); + return s.registryState.sysTaskIds.contains(_taskIndex); + } + + /// @notice Returns the details of a task. Reverts if task doesn't exist. + /// @param _taskIndex Task index to get details for. + function getTask(uint64 _taskIndex) internal view returns (TaskMetadata storage task) { + AppStorage storage s = LibAppStorage.appStorage(); + + if (!ifTaskExists(_taskIndex)) { revert TaskDoesNotExist(); } + task = s.registryState.tasks[_taskIndex]; + } + + /// @notice Function to remove a task from the registry. + /// @param _taskIndex Index of the task to remove. + /// @param _removeFromSysReg Wheather to remove from system task registry. + function removeTask(uint64 _taskIndex, bool _removeFromSysReg) internal { + AppStorage storage s = LibAppStorage.appStorage(); + + if (_removeFromSysReg) { + require(s.registryState.sysTaskIds.remove(_taskIndex), TaskIndexNotFound()); + } + + delete s.registryState.tasks[_taskIndex]; + require(s.registryState.taskIdList.remove(_taskIndex), TaskIndexNotFound()); + } + + /// @notice Refunds the specified amount of deposit to the task owner and unlocks full deposit from the total automation fees deposited. + /// @param _taskIndex Index of the task. + /// @param _taskOwner Owner of the task. + /// @param _refundableDeposit Refundable amount of deposit. + /// @param _lockedDeposit Total locked deposit. + function safeDepositRefund( + uint64 _taskIndex, + address _taskOwner, + uint128 _refundableDeposit, + uint128 _lockedDeposit + ) internal returns (bool) { + // Ensures that amount to unlock is not more than the total automation fees deposited. + bool result = safeUnlockLockedDeposit(_taskIndex, _lockedDeposit); + if (!result) { + return result; + } + + result = safeRefund(_taskIndex, _taskOwner, _refundableDeposit, DEPOSIT_CYCLE_FEE); + + if (result) { emit TaskDepositFeeRefund(_taskIndex, _taskOwner, _refundableDeposit); } + return result; + } + + /// @notice Refunds the deposit fee and any autoamtion fees of the task. + function refundTaskFees( + uint64 _currentTime, + uint64 _refundDuration, + uint128 _automationFeePerSec, + TaskMetadata memory _task + ) internal { + AppStorage storage s = LibAppStorage.appStorage(); + + // Do not attempt fee refund if remaining duration is 0 + if (_task.taskState != LibUtils.TaskState.PENDING && _refundDuration != 0) { + uint128 _refundFee = calculateTaskFee( + _task.taskState, + _task.expiryTime, + _task.maxGasAmount, + _refundDuration, + _currentTime, + _automationFeePerSec + ); + ( , uint256 remainingCycleLockedFees) = safeFeeRefund( + _task.taskIndex, + _task.owner, + s.registryState.cycleLockedFees, + uint64(_refundFee) + ); + s.registryState.cycleLockedFees = remainingCycleLockedFees; + } + + safeDepositRefund( + _task.taskIndex, + _task.owner, + _task.depositFee, + _task.depositFee + ); + } + + /// @notice Calculates automation task fees for a single task at the time of new cycle. + /// This is supposed to be called only after removing expired task and must not be called for expired task. + function calculateAutomationFeeForInterval( + uint64 _duration, + uint128 _taskOccupancy, + uint128 _automationFeePerSec, + uint128 _registryMaxGasCap + ) private pure returns (uint128) { + uint256 taskOccupancyRatioByDuration = (uint256(_duration) * uint256(_taskOccupancy) * DECIMAL) / uint256(_registryMaxGasCap); + + uint256 automationFeeForInterval = _automationFeePerSec * taskOccupancyRatioByDuration; + + return uint128(automationFeeForInterval / DECIMAL); + } + + /// @notice Calculates automation task fees for a single task at the time of new cycle. + /// This is supposed to be called only after removing expired task and must not be called for expired task. + /// @param _state State of the task. + /// @param _expiryTime Task expiry time. + /// @param _maxGasAmount Task's max gas amount + /// @param _potentialFeeTimeframe Potential time frame to calculate task fees for. + /// @param _currentTime Current time + /// @param _automationFeePerSec Automation fee per sec + /// @return Calculated task fee for the interval the task will be active. + function calculateTaskFee( + LibUtils.TaskState _state, + uint64 _expiryTime, + uint128 _maxGasAmount, + uint64 _potentialFeeTimeframe, + uint64 _currentTime, + uint128 _automationFeePerSec + ) internal view returns (uint128) { + if (_automationFeePerSec == 0) { return 0; } + if (_expiryTime <= _currentTime) { return 0; } + + uint64 taskActiveTimeframe = _expiryTime - _currentTime; + + // If the task is a new task i.e. in Pending state, then it is charged always for + // the input _potentialFeeTimeframe(which is cycle-interval), + // For the new tasks which active-timeframe is less than cycle-interval + // it would mean it is their first and only cycle and we charge the fee for entire cycle. + // Note that although the new short tasks are charged for entire cycle, the refunding logic remains the same for + // them as for the long tasks. + // This way bad-actors will be discourged to submit small and short tasks with big occupancy by blocking other + // good-actors register tasks. + uint64 actualFeeTimeframe; + if (_state == LibUtils.TaskState.PENDING) { + actualFeeTimeframe = _potentialFeeTimeframe; + } else { + actualFeeTimeframe = taskActiveTimeframe < _potentialFeeTimeframe ? taskActiveTimeframe : _potentialFeeTimeframe; + } + + AppStorage storage s = LibAppStorage.appStorage(); + return calculateAutomationFeeForInterval( + actualFeeTimeframe, + _maxGasAmount, + _automationFeePerSec, + s.activeConfig.registryMaxGasCap + ); + } + + /// @notice Unlocks the locked fee paid by the task for cycle. + /// Error event is emitted if the cycle locked fee amount is inconsistent with the requested unlock amount. + /// @param _cycleLockedFees Locked cycle fees + /// @param _refundableFee Refundable fees + /// @param _taskIndex Index of the task + /// @return Bool if _refundableFee can be unlocked safely. + /// @return Updated _cycleLockedFees after unlocking _refundableFee. + function safeUnlockLockedCycleFee( + uint256 _cycleLockedFees, + uint64 _refundableFee, + uint64 _taskIndex + ) private returns (bool, uint256) { + // This check makes sure that more than locked amount of the fees will be not be refunded. + // Any attempt means internal bug. + bool hasLockedFee = _cycleLockedFees >= _refundableFee; + if (hasLockedFee) { + // Unlock the refunded amount + _cycleLockedFees = _cycleLockedFees - _refundableFee; + } else { + emit ErrorUnlockTaskCycleFee(_taskIndex, _cycleLockedFees, _refundableFee); + } + return (hasLockedFee, _cycleLockedFees); + } + + /// @notice Helper function to transfer refunds. + /// @param _erc20Supra Address of the ERC20Supra token. + /// @param _to Recipeint of the refund + /// @param _amount Amount to refund + /// @return Bool representing if refund was successful. + function _refund(address _erc20Supra, address _to, uint128 _amount) private returns (bool) { + bool sent = IERC20(_erc20Supra).transfer(_to, _amount); + if (!sent) { revert TransferFailed(); } + + return sent; + } + + /// @notice Refunds the specified amount to the task owner. + /// @dev Error event is emitted if the registry contract does not have sufficient balance. + /// @param _taskIndex Index of the task. + /// @param _taskOwner Owner of the task. + /// @param _refundableAmount Amount to refund. + /// @param _refundType Type of refund. + /// @return Bool representing if refund was successful. + function safeRefund( + uint64 _taskIndex, + address _taskOwner, + uint128 _refundableAmount, + uint8 _refundType + ) private returns (bool) { + AppStorage storage s = LibAppStorage.appStorage(); + + address erc20Supra = s.erc20Supra; + uint256 balance = IERC20(erc20Supra).balanceOf(address(this)); + if (balance < _refundableAmount) { + emit ErrorInsufficientBalanceToRefund(_taskIndex, _taskOwner, _refundType, _refundableAmount); + return false; + } else { + return _refund(erc20Supra, _taskOwner, _refundableAmount); + } + } + + /// @notice Refunds fee paid by the task for the cycle to the task owner. + /// Note that here we do not unlock the fee, as on cycle change locked cycle-fees for the ended cycle are + /// automatically unlocked. + function safeFeeRefund( + uint64 _taskIndex, + address _taskOwner, + uint256 _cycleLockedFees, + uint64 _refundableFee + ) private returns (bool, uint256) { + bool result; + uint256 remainingLockedFees; + + (result, remainingLockedFees) = safeUnlockLockedCycleFee(_cycleLockedFees, _refundableFee, _taskIndex); + if (!result) { return (result, remainingLockedFees); } + + result = safeRefund( _taskIndex, _taskOwner, _refundableFee, CYCLE_FEE); + if (result) { emit TaskFeeRefund(_taskIndex, _taskOwner, _refundableFee); } + return (result, remainingLockedFees); + } + + /// @notice Internally calls _refund, reverts if caller is not AutomationRegistry. + function refund(address _to, uint128 _amount) internal { + AppStorage storage s = LibAppStorage.appStorage(); + + address erc20Supra = s.erc20Supra; + uint256 balance = IERC20(erc20Supra).balanceOf(address(this)); + if (balance < _amount) { revert InsufficientBalanceForRefund(); } + _refund(erc20Supra, _to, _amount); + } + + /// @notice Unlocks the deposit paid by the task from the total automation fees deposited. + /// @dev Error event is emitted if the total automation fees deposited is less than the requested unlock amount. + /// @param _taskIndex Index of the task. + /// @param _lockedDeposit Locked deposit amount to be unlocked. + /// @return Bool if _lockedDeposit can be unlocked safely. + function safeUnlockLockedDeposit( + uint64 _taskIndex, + uint128 _lockedDeposit + ) internal returns (bool) { + AppStorage storage s = LibAppStorage.appStorage(); + + uint256 totalDeposited = s.registryState.totalDepositedAutomationFees; + + if (totalDeposited >= _lockedDeposit) { + s.registryState.totalDepositedAutomationFees = totalDeposited - _lockedDeposit; + return true; + } + + emit ErrorUnlockTaskDepositFee(_taskIndex, totalDeposited, _lockedDeposit); + return false; + } + + /// @notice Calculates the automation fee multiplier for current cycle. + function calculateAutomationFeeMultiplierForCurrentCycle() internal view returns (uint128) { + AppStorage storage s = LibAppStorage.appStorage(); + + // Compute the automation fee multiplier for this cycle + return calculateAutomationFeeMultiplierForCycle( + s.registryState.gasCommittedForThisCycle, + s.activeConfig.registryMaxGasCap, + s.activeConfig.automationBaseFeeWeiPerSec + ); + } + + /// @notice Calculates the automation fee multiplier for cycle. + /// @param _totalCommittedGas Total committed gas. + /// @param _registryMaxGasCap Registry max gas cap. + /// @param _automationBaseFeeWeiPerSec Automation base fee in wei per sec. + function calculateAutomationFeeMultiplierForCycle( + uint128 _totalCommittedGas, + uint128 _registryMaxGasCap, + uint128 _automationBaseFeeWeiPerSec + ) private view returns (uint128) { + uint128 congesionFee = calculateAutomationCongestionFee(_totalCommittedGas, _registryMaxGasCap); + return (congesionFee + _automationBaseFeeWeiPerSec); + } + + /// @notice Function to calculate the automation congestion fee. + /// @param _totalCommittedGas Total committed gas. + /// @param _registryMaxGasCap Registry max gas cap. + /// @return Returns the automation congestion fee. + function calculateAutomationCongestionFee( + uint128 _totalCommittedGas, + uint128 _registryMaxGasCap + ) internal view returns (uint128) { + AppStorage storage s = LibAppStorage.appStorage(); + if (s.activeConfig.congestionThresholdPercentage == 100 || s.activeConfig.congestionBaseFeeWeiPerSec == 0) { return 0; } + + // thresholdUsage = (totalCommittedGas / maxGasCap) * 100 + uint256 thresholdUsageScaled = (uint256(_totalCommittedGas) * DECIMAL * 100) / uint256(_registryMaxGasCap); + + uint256 thresholdPercentageScaled = uint256(s.activeConfig.congestionThresholdPercentage) * DECIMAL; + + // If usage is below threshold → no congestion fee + if (thresholdUsageScaled <= thresholdPercentageScaled) { + return 0; + } else { + // Calculate how much usage exceeds threshold + uint256 surplusScaled = (thresholdUsageScaled - thresholdPercentageScaled) / 100; + + + // Ensure threshold + threshold surplus does not exceed 1 (1 in scaled terms) + uint256 thresholdScaledAsFraction = thresholdPercentageScaled / 100; // DECIMAL-scaled fraction + uint256 surplusClipped = thresholdScaledAsFraction + surplusScaled > DECIMAL ? DECIMAL - thresholdScaledAsFraction : surplusScaled; + + uint256 baseScaled = DECIMAL + surplusClipped; // (1 + base) + uint256 resultScaled = DECIMAL; + for (uint8 i = 0; i < s.activeConfig.congestionExponent; i++) { + resultScaled = (resultScaled * baseScaled) / DECIMAL; + } + uint256 exponentResult = resultScaled - DECIMAL; // subtract 1 + + + // Multiply base fee (wei/sec) with exponentResult and downscale by DECIMAL + uint256 acf = (uint256(s.activeConfig.congestionBaseFeeWeiPerSec) * exponentResult) / DECIMAL; + + return uint128(acf); + } + } + + /// @notice Function to update the cycle locked fees and gas committed. + /// @param _lockedFees Updated cycle locked fees + /// @param _sysGasCommittedForNextCycle Updated system gas committed for next cycle + /// @param _gasCommittedForNextCycle Updated gas committed for next cycle + /// @param _gasCommittedForNewCycle Updated gas committed for new cycle + function updateGasCommittedAndCycleLockedFees( + uint256 _lockedFees, + uint128 _sysGasCommittedForNextCycle, + uint128 _gasCommittedForNextCycle, + uint128 _gasCommittedForNewCycle + ) internal { + AppStorage storage s = LibAppStorage.appStorage(); + + s.registryState.cycleLockedFees = _lockedFees; + s.registryState.sysGasCommittedForNextCycle = _sysGasCommittedForNextCycle; + s.registryState.sysGasCommittedForThisCycle = _sysGasCommittedForNextCycle; + s.registryState.gasCommittedForNextCycle = _gasCommittedForNextCycle; + s.registryState.gasCommittedForThisCycle = _gasCommittedForNewCycle; + } + + /// @notice Calculates automation fee per second for the specified task occupancy + /// referencing the current automation registry fee parameters, specified total/committed occupancy and current registry + /// maximum allowed occupancy. + function calculateAutomationFeeMultiplierForCommittedOccupancy( + uint128 _totalCommittedMaxGas + ) internal view returns (uint128) { + AppStorage storage s = LibAppStorage.appStorage(); + + // Compute the automation fee multiplier for cycle + return calculateAutomationFeeMultiplierForCycle( + _totalCommittedMaxGas, + s.activeConfig.registryMaxGasCap, + s.activeConfig.automationBaseFeeWeiPerSec + ); + } + + /// @notice Helper function to charge fees from the user. + function chargeFees(address _from, uint256 _amount) internal { + AppStorage storage s = LibAppStorage.appStorage(); + + bool sent = IERC20(s.erc20Supra).transferFrom(_from, address(this), _amount); + if (!sent) { revert TransferFailed(); } + } + + /// @notice Refunds the deposit fee of the task and removes from the registry. + /// @param _taskIndex Index of the task. + /// @param _taskOwner Owner of the task. + /// @param _refundableDeposit Refundable amount of deposit. + /// @param _lockedDeposit Total locked deposit. + function refundDepositAndDrop( + uint64 _taskIndex, + address _taskOwner, + uint128 _refundableDeposit, + uint128 _lockedDeposit + ) internal { + AppStorage storage s = LibAppStorage.appStorage(); + + // Check if task is UST + if (s.registryState.tasks[_taskIndex].taskType == LibUtils.TaskType.GST) { revert RegisteredTaskInvalidType(); } + + // Remove task from the registry state + removeTask(_taskIndex, false); + + // Refund + safeDepositRefund( + _taskIndex, + _taskOwner, + _refundableDeposit, + _lockedDeposit + ); + } + + /// @notice Helper function that performs validation and updates state for a valid task. + function updateStateForValidRegistration( + uint256 _totalTasks, + uint64 _regTime, + uint64 _expiryTime, + LibUtils.TaskType _taskType, + bytes memory _payloadTx, + uint128 _maxGasAmount, + uint128 _gasPriceCap, + uint128 _automationFeeCapForCycle + ) internal { + AppStorage storage s = LibAppStorage.appStorage(); + + // Check if automation and registration is enabled + if (!s.automationEnabled) { revert AutomationNotEnabled(); } + if (!s.registrationEnabled) { revert RegistrationDisabled(); } + + if (!isCycleStarted()) { revert CycleTransitionInProgress(); } + + bool isUST = _taskType == LibUtils.TaskType.UST; + + uint64 taskDurationCap; + uint128 gasCommittedForNextCycle; + uint128 nextCycleRegistryMaxGasCap; + if (isUST) { + if (_totalTasks >= s.activeConfig.taskCapacity) { revert TaskCapacityReached(); } + if (_gasPriceCap == 0) { revert InvalidGasPriceCap(); } + + gasCommittedForNextCycle = s.registryState.gasCommittedForNextCycle; + uint128 estimatedAutomationFeeForCycle = estimateAutomationFeeWithCommittedOccupancyInternal(_maxGasAmount, gasCommittedForNextCycle); + if (_automationFeeCapForCycle < estimatedAutomationFeeForCycle) { revert InsufficientFeeCapForCycle(); } + + taskDurationCap = s.activeConfig.taskDurationCapSecs; + nextCycleRegistryMaxGasCap = s.registryState.nextCycleRegistryMaxGasCap; + } else { + if (_totalTasks >= s.activeConfig.sysTaskCapacity) { revert TaskCapacityReached(); } + + gasCommittedForNextCycle = s.registryState.sysGasCommittedForNextCycle; + taskDurationCap = s.activeConfig.sysTaskDurationCapSecs; + nextCycleRegistryMaxGasCap = s.registryState.nextCycleSysRegistryMaxGasCap; + } + + validateTaskDuration(_regTime, _expiryTime, taskDurationCap, s.startTime + s.durationSecs); + validateInputs(_payloadTx, _maxGasAmount); + + uint128 gasCommitted = _maxGasAmount + gasCommittedForNextCycle; + if (gasCommitted > nextCycleRegistryMaxGasCap) { revert GasCommittedExceedsMaxGasCap(); } + + if (isUST) { + s.registryState.gasCommittedForNextCycle = gasCommitted; + } else { + s.registryState.sysGasCommittedForNextCycle = gasCommitted; + } + } + + /// @notice Helper function to validate the task duration. + function validateTaskDuration( + uint64 _regTime, + uint64 _expiryTime, + uint64 _taskDurationCap, + uint64 _cycleEndTime + ) private pure { + if (_expiryTime <= _regTime) { revert InvalidExpiryTime(); } + + uint64 taskDuration = _expiryTime - _regTime; + if (taskDuration > _taskDurationCap) { revert InvalidTaskDuration(); } + + if ( _expiryTime <= _cycleEndTime) { revert TaskExpiresBeforeNextCycle(); } + } + + /// @notice Helper function to validate the inputs while registering a task. + function validateInputs(bytes memory _payloadTx, uint128 _maxGasAmount) private view { + ( , address payloadTarget, , ) = abi.decode(_payloadTx, (uint128, address, bytes, LibUtils.AccessListEntry[])); + payloadTarget.validateContractAddress(); + + if (_maxGasAmount == 0) { revert InvalidMaxGasAmount(); } + } + + /// @notice Estimates automation fee the next cycle for specified task occupancy for the configured cycle interval + /// referencing the current automation registry fee parameters, specified total/committed occupancy and registry + /// maximum allowed occupancy for the next cycle. + /// Note it is expected that committed_occupancy does not include current task's occupancy. + function estimateAutomationFeeWithCommittedOccupancyInternal( + uint128 _taskOccupancy, + uint128 _committedOccupancy + ) internal view returns (uint128) { + AppStorage storage s = LibAppStorage.appStorage(); + + uint128 totalCommittedGas = _taskOccupancy + _committedOccupancy; + + uint128 automationFeePerSec = calculateAutomationFeeMultiplierForCycle( + totalCommittedGas, + s.registryState.nextCycleRegistryMaxGasCap, + s.activeConfig.automationBaseFeeWeiPerSec + ); + + if (automationFeePerSec == 0) return 0; + return calculateAutomationFeeForInterval(s.durationSecs, _taskOccupancy, automationFeePerSec, s.registryState.nextCycleRegistryMaxGasCap); + } + + function updateGasCommittedForNextCycle(LibUtils.TaskType _taskType, uint128 _maxGasAmount) external { + AppStorage storage s = LibAppStorage.appStorage(); + + bool isUST = _taskType == LibUtils.TaskType.UST; + + uint128 gasCommittedForNextCycle = isUST ? s.registryState.gasCommittedForNextCycle : s.registryState.sysGasCommittedForNextCycle; + if (gasCommittedForNextCycle < _maxGasAmount) { revert GasCommittedValueUnderflow(); } + + // Adjust the gas committed for the next cycle by subtracting the gas amount of the cancelled/stopped task + if (isUST) { + s.registryState.gasCommittedForNextCycle = gasCommittedForNextCycle - _maxGasAmount; + } else { + s.registryState.sysGasCommittedForNextCycle = gasCommittedForNextCycle - _maxGasAmount; + } + } + + /// @notice Helper function to unlock locked deposit and cycle fees when stopTasks is called. + function unlockDepositAndCycleFee( + uint64 _taskIndex, + LibUtils.TaskState _taskState, + uint64 _expiryTime, + uint128 _maxGasAmount, + uint64 _residualInterval, + uint64 _currentTime, + uint128 _depositFee + ) internal returns (uint128, uint128) { + AppStorage storage s = LibAppStorage.appStorage(); + + uint128 cycleFeeRefund; + uint128 depositRefund; + + if (_taskState != LibUtils.TaskState.PENDING) { + // Compute the automation fee multiplier for cycle + uint128 automationFeePerSec = calculateAutomationFeeMultiplierForCycle( + s.registryState.gasCommittedForThisCycle, + s.activeConfig.registryMaxGasCap, + s.activeConfig.automationBaseFeeWeiPerSec + ); + + uint128 taskFee = calculateTaskFee( + _taskState, + _expiryTime, + _maxGasAmount, + _residualInterval, + _currentTime, + automationFeePerSec + ); + + // Refund full deposit and the half of the remaining run-time fee when task is active or cancelled stage + cycleFeeRefund = taskFee / REFUND_FRACTION; + depositRefund = _depositFee; + } else { + cycleFeeRefund = 0; + depositRefund = _depositFee / REFUND_FRACTION; + } + + bool result = safeUnlockLockedDeposit(_taskIndex, _depositFee); + if (!result) { revert ErrorDepositRefund(); } + + (bool hasLockedFee, uint256 remainingCycleLockedFees ) = safeUnlockLockedCycleFee(s.registryState.cycleLockedFees, uint64(cycleFeeRefund), _taskIndex); + if (!hasLockedFee) { revert ErrorCycleFeeRefund(); } + + s.registryState.cycleLockedFees = remainingCycleLockedFees; + + return (cycleFeeRefund, depositRefund); + } +} diff --git a/solidity/supra_contracts/src/libraries/LibUtils.sol b/solidity/supra_contracts/src/libraries/LibUtils.sol new file mode 100644 index 0000000000..2ce4b8e709 --- /dev/null +++ b/solidity/supra_contracts/src/libraries/LibUtils.sol @@ -0,0 +1,162 @@ +// SPDX-License-Identifier: MIT +pragma solidity 0.8.27; + +// Helper library used by supra contracts +library LibUtils { + + // Custom errors + error AddressCannotBeEOA(); + error AddressCannotBeZero(); + error InvalidTaskDuration(); + error InvalidRegistryMaxGasCap(); + error InvalidCongestionThreshold(); + error InvalidCongestionExponent(); + error InvalidTaskCapacity(); + error InvalidCycleDuration(); + error InvalidSysTaskDuration(); + error InvalidSysRegistryMaxGasCap(); + error InvalidSysTaskCapacity(); + + // Address of the VM Signer: SUP0 + address constant VM_SIGNER = address(0x53555000); + + /// @notice Enum describing state of the cycle. + enum CycleState { + READY, + STARTED, + FINISHED, + SUSPENDED + } + + /// @notice Enum describing state of a task. + enum TaskState { + PENDING, + ACTIVE, + CANCELLED + } + + /// @notice Enum describing task type. + enum TaskType { + UST, + GST + } + + /// @notice Represents intermediate state of the registry on cycle change. + struct IntermediateStateOfCycleChange { + uint256 cycleLockedFees; + uint128 gasCommittedForNextCycle; + uint128 sysGasCommittedForNextCycle; + uint64[] removedTasks; + } + + /// @notice Struct representing transition result. + struct TransitionResult { + uint128 fees; + uint128 gas; + uint128 sysGas; + bool isRemoved; + } + + /// @notice Struct representing a stopped task. + struct TaskStopped { + uint64 taskIndex; + uint128 depositRefund; + uint128 cycleFeeRefund; + bytes32 txHash; + } + + /// @notice Struct representing an entry in access list. + struct AccessListEntry { + address addr; + bytes32[] storageKeys; + } + + /// @dev Returns a boolean indicating whether the given address is a contract or not. + /// @param _addr The address to be checked. + /// @return A boolean indicating whether the given address is a contract or not. + function isContract(address _addr) internal view returns (bool) { + uint256 size; + assembly { + size := extcodesize(_addr) + } + return size > 0; + } + + /// @notice Validates a contract address. + function validateContractAddress(address _contractAddr) internal view { + if (_contractAddr == address(0)) { revert AddressCannotBeZero(); } + if (!isContract(_contractAddr)) { revert AddressCannotBeEOA(); } + } + + /// @notice Checks if an address is VM Signer. + /// @param _addr Address to check. + /// @return bool If it is VM Signer. + function isVmSigner(address _addr) internal pure returns (bool) { + return _addr == VM_SIGNER; + } + + /// @notice Checks if an address is a reserved address. + /// @param _addr Address to check. + /// @return bool If it is a reserved address. + function isReservedAddress(address _addr) internal pure returns (bool) { + uint160 addr = uint160(_addr); + return addr >= uint160(VM_SIGNER) && addr <= uint160(0x535550FF); + } + + /// @notice Helper function to validate the registry configuration parameters. + function validateConfigParameters( + uint64 _taskDurationCapSecs, + uint128 _registryMaxGasCap, + uint8 _congestionThresholdPercentage, + uint8 _congestionExponent, + uint16 _taskCapacity, + uint64 _cycleDurationSecs, + uint64 _sysTaskDurationCapSecs, + uint128 _sysRegistryMaxGasCap, + uint16 _sysTaskCapacity + ) internal pure { + if(_taskDurationCapSecs <= _cycleDurationSecs) { revert InvalidTaskDuration(); } + if(_registryMaxGasCap == 0) { revert InvalidRegistryMaxGasCap(); } + if(_congestionThresholdPercentage > 100) { revert InvalidCongestionThreshold(); } + if(_congestionExponent == 0) { revert InvalidCongestionExponent(); } + if(_taskCapacity == 0) { revert InvalidTaskCapacity(); } + if(_cycleDurationSecs == 0) { revert InvalidCycleDuration(); } + if(_sysTaskDurationCapSecs <= _cycleDurationSecs) { revert InvalidSysTaskDuration(); } + if(_sysRegistryMaxGasCap == 0) { revert InvalidSysRegistryMaxGasCap(); } + if(_sysTaskCapacity == 0) { revert InvalidSysTaskCapacity(); } + } + + /// @notice Helper function to sort an array. + /// @param arr Input array to sort. + /// @return Returns the sorted array. + function sortUint64(uint64[] memory arr) internal pure returns (uint64[] memory) { + uint256 length = arr.length; + for (uint256 i = 0; i < length; i++) { + for (uint256 j = 0; j < length - 1; j++) { + if (arr[j] > arr[j + 1]) { + uint64 temp = arr[j]; + arr[j] = arr[j + 1]; + arr[j + 1] = temp; + } + } + } + return arr; + } + + /// @notice Helper function to sort an array. + /// @param arr Input array to sort. + /// @return Returns the sorted array. + function sortUint256(uint256[] memory arr) internal pure returns (uint256[] memory) { + uint256 length = arr.length; + for (uint256 i = 0; i < length; i++) { + for (uint256 j = 0; j < length - 1; j++) { + if (arr[j] > arr[j + 1]) { + uint256 temp = arr[j]; + arr[j] = arr[j + 1]; + arr[j + 1] = temp; + } + } + } + return arr; + } +} diff --git a/solidity/supra_contracts/src/upgradeInitializers/DiamondInit.sol b/solidity/supra_contracts/src/upgradeInitializers/DiamondInit.sol new file mode 100644 index 0000000000..2773317e83 --- /dev/null +++ b/solidity/supra_contracts/src/upgradeInitializers/DiamondInit.sol @@ -0,0 +1,144 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.0; + +/******************************************************************************\ +* Author: Nick Mudge (https://twitter.com/mudgen) +* EIP-2535 Diamonds: https://eips.ethereum.org/EIPS/eip-2535 +* +* Implementation of a diamond. +/******************************************************************************/ + +import { LibDiamond } from "../libraries/LibDiamond.sol"; +import { IDiamondLoupe } from "../interfaces/IDiamondLoupe.sol"; +import { IDiamondCut } from "../interfaces/IDiamondCut.sol"; +import { IERC173 } from "../interfaces/IERC173.sol"; +import { IERC165 } from "../interfaces/IERC165.sol"; + +import { AppStorage, Config } from "../libraries/LibAppStorage.sol"; +import { LibUtils } from "../libraries/LibUtils.sol"; + +/// @title DiamondInit +/// @notice Initialization contract for the Automation Registry +/// @dev +/// EIP-2535 specifies that the `diamondCut` function takes two optional +/// arguments: address _init and bytes calldata _calldata +/// These arguments are used to execute an arbitrary function using delegatecall +/// in order to set state variables in the diamond during deployment or an upgrade +/// More info here: https://eips.ethereum.org/EIPS/eip-2535#diamond-interface +/// +/// - This contract is NOT a facet and MUST NOT be added to the Diamond. +/// - The `init` function selector is never registered and is therefore +/// not callable through the Diamond after deployment. +/// +/// This initializer performs the following actions: +/// - Registers supported interfaces for ERC-165, IDiamondCut, IDiamondLoupe, and ERC-173. +/// - Sets the active registry configuration, protocol feature flags and trusted addresses. +/// - Establishes initial automation cycle state, index, and timestamp. +contract DiamondInit { + AppStorage internal s; + + /// @notice Initializes Automation Registry state in Diamond storage + /// @param _taskDurationCapSecs Maximum allowable duration (in seconds) from the registration time that a user automation task can run. + /// @param _registryMaxGasCap Maximum gas allocation for automation tasks per cycle. + /// @param _automationBaseFeeWeiPerSec Base fee per second for the full capacity of the automation registry, measured in wei/sec. + /// @param _flatRegistrationFeeWei Flat registration fee charged by default for each task. + /// @param _congestionThresholdPercentage Percentage representing the acceptable upper limit of committed gas amount relative to registry_max_gas_cap. + /// Beyond this threshold, congestion fees apply. + /// @param _congestionBaseFeeWeiPerSec Base fee per second for the full capacity of the automation registry when the congestion threshold is exceeded. + /// @param _congestionExponent The congestion fee increases exponentially based on this value, ensuring higher fees as the registry approaches full capacity. + /// @param _taskCapacity Maximum number of tasks that the registry can hold. + /// @param _cycleDurationSecs Automation cycle duration in seconds. + /// @param _sysTaskDurationCapSecs Maximum allowable duration (in seconds) from the registration time that a system automation task can run. + /// @param _sysRegistryMaxGasCap Maximum gas allocation for system automation tasks per cycle. + /// @param _sysTaskCapacity Maximum number of system tasks that the registry can hold. + /// @param _vmSigner Address for the VM Signer. + /// @param _erc20Supra Address of the ERC20Supra contract. + /// @param _automationEnabled Whether automation should start immediately + /// @param _registrationEnabled Whether registration should start immediately + function init( + uint64 _taskDurationCapSecs, + uint128 _registryMaxGasCap, + uint128 _automationBaseFeeWeiPerSec, + uint128 _flatRegistrationFeeWei, + uint8 _congestionThresholdPercentage, + uint128 _congestionBaseFeeWeiPerSec, + uint8 _congestionExponent, + uint16 _taskCapacity, + uint64 _cycleDurationSecs, + uint64 _sysTaskDurationCapSecs, + uint128 _sysRegistryMaxGasCap, + uint16 _sysTaskCapacity, + address _vmSigner, + address _erc20Supra, + bool _automationEnabled, + bool _registrationEnabled + ) external { + // Adding ERC165 data + LibDiamond.DiamondStorage storage ds = LibDiamond.diamondStorage(); + ds.supportedInterfaces[type(IERC165).interfaceId] = true; + ds.supportedInterfaces[type(IDiamondCut).interfaceId] = true; + ds.supportedInterfaces[type(IDiamondLoupe).interfaceId] = true; + ds.supportedInterfaces[type(IERC173).interfaceId] = true; + + + LibUtils.validateConfigParameters( + _taskDurationCapSecs, + _registryMaxGasCap, + _congestionThresholdPercentage, + _congestionExponent, + _taskCapacity, + _cycleDurationSecs, + _sysTaskDurationCapSecs, + _sysRegistryMaxGasCap, + _sysTaskCapacity + ); + require(_vmSigner != address(0), LibUtils.AddressCannotBeZero()); + LibUtils.validateContractAddress(_erc20Supra); + + // --------------------------------------------------------------------- + // Config initialization + // --------------------------------------------------------------------- + Config memory activeConfig = Config({ + registryMaxGasCap: _registryMaxGasCap, + sysRegistryMaxGasCap: _sysRegistryMaxGasCap, + automationBaseFeeWeiPerSec: _automationBaseFeeWeiPerSec, + flatRegistrationFeeWei: _flatRegistrationFeeWei, + congestionBaseFeeWeiPerSec: _congestionBaseFeeWeiPerSec, + taskDurationCapSecs: _taskDurationCapSecs, + sysTaskDurationCapSecs: _sysTaskDurationCapSecs, + cycleDurationSecs: _cycleDurationSecs, + taskCapacity: _taskCapacity, + sysTaskCapacity: _sysTaskCapacity, + congestionThresholdPercentage: _congestionThresholdPercentage, + congestionExponent: _congestionExponent + }); + + s.activeConfig = activeConfig; + + s.automationEnabled = _automationEnabled; + s.registrationEnabled = _registrationEnabled; + s.vmSigner = _vmSigner; + s.erc20Supra = _erc20Supra; + + // --------------------------------------------------------------------- + // Cycle initialization + // --------------------------------------------------------------------- + ( + LibUtils.CycleState cycleState, + uint64 cycleIndex + ) = _automationEnabled + ? (LibUtils.CycleState.STARTED, 1) + : (LibUtils.CycleState.READY, 0); + + s.index = cycleIndex; + s.startTime = uint64(block.timestamp); + s.durationSecs = _cycleDurationSecs; + s.cycleState = cycleState; + + // --------------------------------------------------------------------- + // Registry state initialization + // --------------------------------------------------------------------- + s.registryState.nextCycleRegistryMaxGasCap = _registryMaxGasCap; + s.registryState.nextCycleSysRegistryMaxGasCap = _sysRegistryMaxGasCap; + } +} diff --git a/solidity/supra_contracts/test/AutomationController.t.sol b/solidity/supra_contracts/test/AutomationController.t.sol deleted file mode 100644 index 21c4db7ddb..0000000000 --- a/solidity/supra_contracts/test/AutomationController.t.sol +++ /dev/null @@ -1,683 +0,0 @@ -// SPDX-License-Identifier: MIT -pragma solidity 0.8.27; - -import {Test} from "forge-std/Test.sol"; -import {ERC1967Proxy} from "../lib/openzeppelin-contracts/contracts/proxy/ERC1967/ERC1967Proxy.sol"; -import {Initializable} from "@openzeppelin/contracts/proxy/utils/Initializable.sol"; -import {OwnableUpgradeable} from"../lib/openzeppelin-contracts-upgradeable/contracts/access/Ownable2StepUpgradeable.sol"; -import {AutomationRegistry} from "../src/AutomationRegistry.sol"; -import {AutomationCore} from "../src/AutomationCore.sol"; -import {AutomationController} from "../src/AutomationController.sol"; -import {IAutomationController} from "../src/IAutomationController.sol"; -import {ERC20Supra} from "../src/ERC20Supra.sol"; -import {CommonUtils} from "../src/CommonUtils.sol"; -import {LibConfig} from "../src/LibConfig.sol"; - -contract AutomationControllerTest is Test { - ERC20Supra erc20Supra; // ERC20Supra contract - AutomationCore automationCore; // AutomationCore instance on proxy address - AutomationRegistry registry; // AutomationRegistry instance on proxy address - AutomationController controller; // AutomationController instance on proxy address - - /// @dev Address of the transaction hash precompile. - address constant TX_HASH_PRECOMPILE = 0x0000000000000000000000000000000053555001; - - address admin = address(0xA11CE); - address vmSigner = address(0x53555000); - address alice = address(0x123); - address bob = address(0x456); - - /// @dev Sets up initial state for testing. - /// @dev Sets balance of 'alice' to 100 ether. - /// @dev Deploys and initializes all contracts with required parameters. - function setUp() public { - vm.deal(alice, 100 ether); - - vm.startPrank(admin); - erc20Supra = new ERC20Supra(msg.sender); - - AutomationCore automationCoreImpl = new AutomationCore(); - bytes memory automationCoreInitData = abi.encodeCall( - AutomationCore.initialize, - ( - 3600, // taskDurationCapSecs - 10_000_000, // registryMaxGasCap - 0.001 ether, // automationBaseFeeWeiPerSec - 0.002 ether, // flatRegistrationFeeWei - 50, // congestionThresholdPercentage - 0.002 ether, // congestionBaseFeeWeiPerSec - 2, // congestionExponent - 500, // taskCapacity - 2000, // cycleDurationSecs - 3600, // sysTaskDurationCapSecs - 5_000_000, // sysRegistryMaxGasCap - 500, // sysTaskCapacity - vmSigner, // VM Signer address - address(erc20Supra) // ERC20Supra address - ) - ); - ERC1967Proxy automationCoreProxy = new ERC1967Proxy(address(automationCoreImpl), automationCoreInitData); - automationCore = AutomationCore(address(automationCoreProxy)); - - AutomationRegistry registryImpl = new AutomationRegistry(); - bytes memory registryInitData = abi.encodeCall(AutomationRegistry.initialize, (address(automationCore))); - ERC1967Proxy registryProxy = new ERC1967Proxy(address(registryImpl), registryInitData); - registry = AutomationRegistry(address(registryProxy)); - - AutomationController controllerImpl = new AutomationController(); - bytes memory controllerInitData = abi.encodeCall(AutomationController.initialize,(address(automationCore), address(registry), true)); - ERC1967Proxy controllerProxy = new ERC1967Proxy(address(controllerImpl), controllerInitData); - controller = AutomationController(address(controllerProxy)); - - automationCore.setAutomationRegistry(address(registry)); - automationCore.setAutomationController(address(controller)); - registry.setAutomationController(address(controller)); - - vm.stopPrank(); - - vm.mockCall( - TX_HASH_PRECOMPILE, - bytes(""), - abi.encode(keccak256("txHash")) - ); - } - - /// @dev Test to ensure all state variables are initialized correctly. - function testInitialize() public view { - assertEq(controller.owner(), admin); - assertEq(address(controller.automationCore()), address(automationCore)); - assertEq(address(controller.registry()), address(registry)); - assertTrue(controller.isAutomationEnabled()); - } - - /// @dev Test to ensure initialize reverts if reinitialized. - function testInitializeRevertsIfReinitialized() public { - vm.expectRevert(Initializable.InvalidInitialization.selector); - - vm.prank(admin); - controller.initialize(address(automationCore), address(registry), true); - } - - /// @dev Test to ensure initialize reverts if AutomationCore address is zero. - function testInitializeRevertsIfAutomationCoreAddressZero() public { - AutomationController impl = new AutomationController(); - bytes memory initData = abi.encodeCall(AutomationController.initialize, (address(0), address(registry), true)); - - vm.expectRevert(CommonUtils.AddressCannotBeZero.selector); - new ERC1967Proxy(address(impl), initData); - } - - /// @dev Test to ensure initialize reverts if AutomationCore address is EOA. - function testInitializeRevertsIfAutomationCoreEoa() public { - AutomationController impl = new AutomationController(); - bytes memory initData = abi.encodeCall(AutomationController.initialize, (alice, address(registry), true)); - - vm.expectRevert(CommonUtils.AddressCannotBeEOA.selector); - new ERC1967Proxy(address(impl), initData); - } - - /// @dev Test to ensure initialize reverts if AutomationRegistry address is zero. - function testInitializeRevertsIfRegistryZero() public { - AutomationController impl = new AutomationController(); - bytes memory initData = abi.encodeCall(AutomationController.initialize, (address(automationCore), address(0), true)); - - vm.expectRevert(CommonUtils.AddressCannotBeZero.selector); - new ERC1967Proxy(address(impl), initData); - } - - /// @dev Test to ensure initialize reverts if AutomationRegistry address is EOA. - function testInitializeRevertsIfRegistryEoa() public { - AutomationController impl = new AutomationController(); - bytes memory initData = abi.encodeCall(AutomationController.initialize, (address(automationCore), alice, true)); - - vm.expectRevert(CommonUtils.AddressCannotBeEOA.selector); - new ERC1967Proxy(address(impl), initData); - } - - /// @dev Test to ensure 'setAutomationRegistry' reverts if caller is not owner. - function testSetAutomationRegistryRevertsIfNotOwner() public { - AutomationRegistry registryImplementation = new AutomationRegistry(); - - vm.expectRevert(abi.encodeWithSelector(OwnableUpgradeable.OwnableUnauthorizedAccount.selector, alice)); - - vm.prank(alice); - controller.setAutomationRegistry(address(registryImplementation)); - } - - /// @dev Test to ensure 'setAutomationRegistry' reverts if address is zero. - function testSetAutomationRegistryRevertsIfAddressZero() public { - vm.expectRevert(CommonUtils.AddressCannotBeZero.selector); - - vm.prank(admin); - controller.setAutomationRegistry(address(0)); - } - - /// @dev Test to ensure 'setAutomationRegistry' reverts if address is EOA. - function testSetAutomationRegistryRevertsIfAddressEoa() public { - vm.expectRevert(CommonUtils.AddressCannotBeEOA.selector); - - vm.prank(admin); - controller.setAutomationRegistry(alice); - } - - /// @dev Test to ensure 'setAutomationRegistry' updates the registry address. - function testSetAutomationRegistry() public { - AutomationRegistry registryImplementation = new AutomationRegistry(); - - vm.prank(admin); - controller.setAutomationRegistry(address(registryImplementation)); - - assertEq(address(controller.registry()), address(registryImplementation)); - } - - /// @dev Test to ensure 'setAutomationRegistry' emits event 'AutomationRegistryUpdated'. - function testSetAutomationRegistryEmitsEvent() public { - AutomationRegistry registryImplementation = new AutomationRegistry(); - - vm.expectEmit(true, true, false, false); - emit AutomationController.AutomationRegistryUpdated(address(controller.registry()), address(registryImplementation)); - - vm.prank(admin); - controller.setAutomationRegistry(address(registryImplementation)); - } - - /// @dev Test to ensure 'setAutomationCore' reverts if caller is not owner. - function testSetAutomationCoreRevertsIfNotOwner() public { - AutomationCore automationCoreImpl = new AutomationCore(); - - vm.expectRevert(abi.encodeWithSelector(OwnableUpgradeable.OwnableUnauthorizedAccount.selector, alice)); - - vm.prank(alice); - controller.setAutomationCore(address(automationCoreImpl)); - } - - /// @dev Test to ensure 'setAutomationCore' reverts if address is zero. - function testSetAutomationCoreRevertsIfAddressZero() public { - vm.expectRevert(CommonUtils.AddressCannotBeZero.selector); - - vm.prank(admin); - controller.setAutomationCore(address(0)); - } - - /// @dev Test to ensure 'setAutomationCore' reverts if address is EOA. - function testSetAutomationCoreRevertsIfAddressEoa() public { - vm.expectRevert(CommonUtils.AddressCannotBeEOA.selector); - - vm.prank(admin); - controller.setAutomationCore(alice); - } - - /// @dev Test to ensure 'setAutomationCore' updates the AutomationCore address. - function testSetAutomationCore() public { - AutomationCore automationCoreImpl = new AutomationCore(); - - vm.prank(admin); - controller.setAutomationCore(address(automationCoreImpl)); - - assertEq(address(controller.automationCore()), address(automationCoreImpl)); - } - - /// @dev Test to ensure 'setAutomationCore' emits event 'AutomationCoreUpdated'. - function testSetAutomationCoreEmitsEvent() public { - AutomationCore automationCoreImpl = new AutomationCore(); - - vm.expectEmit(true, true, false, false); - emit AutomationController.AutomationCoreUpdated(address(controller.automationCore()), address(automationCoreImpl)); - - vm.prank(admin); - controller.setAutomationCore(address(automationCoreImpl)); - } - - /// @dev Test to ensure 'monitorCycleEnd' reverts if tx.origin is not VM Signer. - function testMonitorCycleEndRevertsIfTxOriginNotVm() public { - vm.expectRevert(IAutomationController.CallerNotVmSigner.selector); - - vm.prank(vmSigner); - controller.monitorCycleEnd(); - } - - /// @dev Test to ensure 'monitorCycleEnd' does nothing before cycle expiry. - function testMonitorCycleEndDoesNothingBeforeCycleExpiry() public { - (uint64 indexBefore, uint64 startBefore, uint64 durationBefore, CommonUtils.CycleState stateBefore) = controller.getCycleInfo(); - - vm.prank(vmSigner, vmSigner); - controller.monitorCycleEnd(); - - (uint64 indexAfter, uint64 startAfter, uint64 durationAfter, CommonUtils.CycleState stateAfter) = controller.getCycleInfo(); - - assertEq(indexAfter, indexBefore); - assertEq(startAfter, startBefore); - assertEq(durationAfter, durationBefore); - assertEq(uint8(stateAfter), uint8(stateBefore)); - } - - // /// @dev Test to ensure 'monitorCycleEnd' does nothing if state is not STARTED. - // function testMonitorCycleEndDoesNothingIfNotStarted() public { - // // Move state to READY state - // vm.prank(address(automationCore)); - // controller.tryMoveToSuspendedState(); - - // (uint64 indexBefore, uint64 startBefore, uint64 durationBefore, CommonUtils.CycleState stateBefore) = controller.getCycleInfo(); - // assertEq(uint8(stateBefore), uint8(CommonUtils.CycleState.READY)); - - // vm.warp(startBefore + durationBefore); - - // vm.prank(vmSigner, vmSigner); - // controller.monitorCycleEnd(); - - // (uint64 indexAfter, uint64 startAfter, uint64 durationAfter, CommonUtils.CycleState stateAfter) = controller.getCycleInfo(); - - // assertEq(indexAfter, indexBefore); - // assertEq(startAfter, startBefore); - // assertEq(durationAfter, durationBefore); - // assertEq(uint8(stateAfter), uint8(stateBefore)); - // } - - /// @dev Test to ensure 'monitorCycleEnd' moves cycle state to READY if automation is disabled and no tasks exist. - function testMonitorCycleEndWhenAutomationDisabledNoTasks() public { - // Disable automation - vm.prank(admin); - controller.disableAutomation(); - - assertFalse(controller.isAutomationEnabled()); - - (uint64 indexBefore, uint64 startBefore, uint64 durationBefore, CommonUtils.CycleState stateBefore) = controller.getCycleInfo(); - vm.warp(startBefore + durationBefore); - - vm.expectEmit(true, true, false, true); - emit AutomationController.AutomationCycleEvent( - indexBefore, - CommonUtils.CycleState.READY, - startBefore, - durationBefore, - stateBefore - ); - - vm.prank(vmSigner, vmSigner); - controller.monitorCycleEnd(); - - (uint64 indexAfter, uint64 startAfter, uint64 durationAfter, CommonUtils.CycleState stateAfter) = controller.getCycleInfo(); - - assertEq(indexAfter, indexBefore); - assertEq(startAfter, startBefore); - assertEq(durationAfter, durationBefore); - assertEq(uint8(stateAfter), uint8(CommonUtils.CycleState.READY)); - } - - /// @dev Test to ensure 'monitorCycleEnd' moves cycle state to STARTED if automation is enabled and no tasks exist. - function testMonitorCycleEndWhenAutomationEnabledNoTasks() public { - (uint64 indexBefore, uint64 startBefore, uint64 durationBefore, CommonUtils.CycleState stateBefore) = controller.getCycleInfo(); - - vm.warp(startBefore + durationBefore); - - vm.expectEmit(true, true, false, true); - emit AutomationController.AutomationCycleEvent( - indexBefore + 1, - CommonUtils.CycleState.STARTED, - uint64(block.timestamp), - durationBefore, - stateBefore - ); - - vm.prank(vmSigner, vmSigner); - controller.monitorCycleEnd(); - - (uint64 indexAfter, uint64 startAfter, uint64 durationAfter, CommonUtils.CycleState stateAfter) = controller.getCycleInfo(); - - assertEq(indexAfter, indexBefore + 1); - assertEq(startAfter, block.timestamp); - assertEq(durationAfter, durationBefore); - assertEq(uint8(stateAfter), uint8(CommonUtils.CycleState.STARTED)); - } - - /// @dev Test to ensure 'monitorCycleEnd' moves cycle state to FINISHED if automation is enabled and tasks exist. - function testMonitorCycleEndWhenAutomationEnabledAndTasksExist() public { - registerTask(); - - (uint64 indexBefore, uint64 startBefore, uint64 durationBefore, CommonUtils.CycleState stateBefore) = controller.getCycleInfo(); - vm.warp(startBefore + durationBefore); - - vm.expectEmit(true, true, false, true); - emit AutomationController.AutomationCycleEvent( - indexBefore, - CommonUtils.CycleState.FINISHED, - startBefore, - durationBefore, - stateBefore - ); - - vm.prank(vmSigner, vmSigner); - controller.monitorCycleEnd(); - - (uint64 indexAfter, uint64 startAfter, uint64 durationAfter, CommonUtils.CycleState stateAfter) = controller.getCycleInfo(); - - assertEq(indexAfter, indexBefore); - assertEq(startAfter, startBefore); - assertEq(durationAfter, durationBefore); - assertEq(uint8(stateAfter), uint8(CommonUtils.CycleState.FINISHED)); - - (uint64 refundDuration, uint128 automationFeePerSec) = controller.getTransitionInfo(); - assertEq(refundDuration, 0); - assertEq(automationFeePerSec, 1000000000000000); - } - - /// @dev Test to ensure 'processTasks' reverts if caller is not VM Signer. - function testProcessTasksRevertsIfNotVm() public { - uint64[] memory tasks = new uint64[](1); - tasks[0] = 0; - - vm.expectRevert(IAutomationController.CallerNotVmSigner.selector); - - vm.prank(admin); - controller.processTasks(1, tasks); - } - - /// @dev Test to ensure 'processTasks' reverts if state is not FINISHED or SUSPENDED. - function testProcessTasksRevertsIfInvalidState() public { - uint64[] memory tasks = new uint64[](1); - tasks[0] = 0; - - vm.expectRevert(IAutomationController.InvalidRegistryState.selector); - - vm.prank(vmSigner, vmSigner); - controller.processTasks(1, tasks); - } - - /// @dev Test to ensure 'processTasks' works correctly when cycle state is FINISHED. - function testProcessTasksWhenCycleStateFinished() public { - registerTask(); - - ( , uint64 startTime, uint64 duration, ) = controller.getCycleInfo(); - vm.warp(startTime + duration); - - vm.prank(vmSigner, vmSigner); - controller.monitorCycleEnd(); - - (uint64 index, , , CommonUtils.CycleState state) = controller.getCycleInfo(); - assertEq(uint8(state), uint8(CommonUtils.CycleState.FINISHED)); - - uint64[] memory tasks = new uint64[](1); - tasks[0] = 0; - - uint256[] memory activeTasks = new uint256[](1); - tasks[0] = 0; - - vm.expectEmit(true, false, false, false); - emit AutomationController.ActiveTasks(activeTasks); - - vm.prank(vmSigner, vmSigner); - controller.processTasks(index + 1, tasks); - - (uint64 newIndex, uint64 newStart, uint64 newDuration, CommonUtils.CycleState newState) = controller.getCycleInfo(); - assertEq(newIndex, index + 1); - assertEq(newStart, uint64(block.timestamp)); - assertEq(newDuration, 2000); - assertEq(uint8(newState), uint8(CommonUtils.CycleState.STARTED)); - - assertEq(registry.getAllActiveTaskIds(), activeTasks); - assertEq(automationCore.getSystemGasCommittedForNextCycle(), 0); - assertEq(automationCore.getSystemGasCommittedForCurrentCycle(), 0); - assertEq(automationCore.getGasCommittedForNextCycle(), 0); - assertEq(automationCore.getGasCommittedForCurrentCycle(), 1000000); - assertEq(automationCore.getCycleLockedFees(), 200000000000000000); - } - - /// @dev Test to ensure 'processTasks' reverts if invalid cycle index is passed when cycle state is FINISHED. - function testProcessTasksRevertsIfInvalidCycleIndexWhenCycleStateFinished() public { - registerTask(); - - ( , uint64 startTime, uint64 duration, ) = controller.getCycleInfo(); - vm.warp(startTime + duration); - - vm.prank(vmSigner, vmSigner); - controller.monitorCycleEnd(); - - (uint64 index, , , CommonUtils.CycleState state) = controller.getCycleInfo(); - assertEq(uint8(state), uint8(CommonUtils.CycleState.FINISHED)); - - uint64[] memory tasks = new uint64[](1); - tasks[0] = 0; - - vm.expectRevert(IAutomationController.InvalidInputCycleIndex.selector); - - vm.prank(vmSigner, vmSigner); - controller.processTasks(index, tasks); - } - - /// @dev Test to ensure 'processTasks' works correctly when cycle state is SUSPENDED and automation is disabled. - function testProcessTasksWhenCycleStateSuspendedAutomationDisabled() public { - registerTask(); - - ( , uint64 start, uint64 duration, ) = controller.getCycleInfo(); - vm.warp(start + duration); - - // Moves state to FINISHED - vm.prank(vmSigner, vmSigner); - controller.monitorCycleEnd(); - - ( , , , CommonUtils.CycleState stateBefore) = controller.getCycleInfo(); - assertEq(uint8(stateBefore), uint8(CommonUtils.CycleState.FINISHED)); - - // Disable automation → moves state to SUSPENDED - vm.prank(admin); - controller.disableAutomation(); - - (uint64 indexAfter, , , CommonUtils.CycleState stateAfter) = controller.getCycleInfo(); - assertEq(uint8(stateAfter), uint8(CommonUtils.CycleState.SUSPENDED)); - - uint64[] memory tasks = new uint64[](1); - tasks[0] = 0; - - vm.expectEmit(true, false, false, false); - emit AutomationController.RemovedTasks(tasks); - - vm.prank(vmSigner, vmSigner); - controller.processTasks(indexAfter, tasks); - - ( , , , CommonUtils.CycleState newState) = controller.getCycleInfo(); - assertEq(uint8(newState), uint8(CommonUtils.CycleState.READY)); - assertFalse(registry.ifTaskExists(tasks[0])); - } - - /// @dev Test to ensure 'processTasks' works correctly when cycle state is SUSPENDED and automation is enabled. - function testProcessTasksWhenCycleStateSuspendedAutomationEnabled() public { - registerTask(); - - ( , uint64 start, uint64 duration, ) = controller.getCycleInfo(); - vm.warp(start + duration); - - // Moves state to FINISHED - vm.prank(vmSigner, vmSigner); - controller.monitorCycleEnd(); - - ( , , , CommonUtils.CycleState stateBefore) = controller.getCycleInfo(); - assertEq(uint8(stateBefore), uint8(CommonUtils.CycleState.FINISHED)); - - // Disable automation → moves state to SUSPENDED - vm.prank(admin); - controller.disableAutomation(); - - (uint64 indexAfter, , , CommonUtils.CycleState stateAfter) = controller.getCycleInfo(); - assertEq(uint8(stateAfter), uint8(CommonUtils.CycleState.SUSPENDED)); - - // Enable automation - vm.prank(admin); - controller.enableAutomation(); - - uint64[] memory tasks = new uint64[](1); - tasks[0] = 0; - - vm.expectEmit(true, false, false, false); - emit AutomationController.RemovedTasks(tasks); - - vm.prank(vmSigner, vmSigner); - controller.processTasks(indexAfter, tasks); - - (uint64 newIndex, uint64 newStart, uint64 newDuration, CommonUtils.CycleState newState) = controller.getCycleInfo(); - assertEq(newIndex, indexAfter + 1); - assertEq(newStart, uint64(block.timestamp)); - assertEq(newDuration, 2000); - assertEq(uint8(newState), uint8(CommonUtils.CycleState.STARTED)); - assertFalse(registry.ifTaskExists(tasks[0])); - } - - /// @dev Test to ensure 'processTasks' reverts if invalid cycle index is passed when cycle state is SUSPENDED. - function testProcessTasksRevertsIfInvalidCycleIndexWhenCycleStateSuspended() public { - registerTask(); - - ( , uint64 start, uint64 duration, ) = controller.getCycleInfo(); - vm.warp(start + duration); - - // Moves state to FINISHED - vm.prank(vmSigner, vmSigner); - controller.monitorCycleEnd(); - - ( , , , CommonUtils.CycleState stateBefore) = controller.getCycleInfo(); - assertEq(uint8(stateBefore), uint8(CommonUtils.CycleState.FINISHED)); - - // Disable automation → moves state to SUSPENDED - vm.prank(admin); - controller.disableAutomation(); - - (uint64 indexAfter, , , CommonUtils.CycleState stateAfter) = controller.getCycleInfo(); - assertEq(uint8(stateAfter), uint8(CommonUtils.CycleState.SUSPENDED)); - - uint64[] memory tasks = new uint64[](1); - tasks[0] = 0; - - vm.expectRevert(IAutomationController.InvalidInputCycleIndex.selector); - - vm.prank(vmSigner, vmSigner); - controller.processTasks(indexAfter + 1, tasks); - } - - // :::::::::::::::::::::::::::::::::::::::::::::::::::::: Tests related to 'disableAutomation' :::::::::::::::::::::::::::::::::::::::::::::::::::::: - - /// @dev Test to ensure 'disableAutomation' disables the automation. - function testDisableAutomation() public { - // Already enabled in initialize() - vm.prank(admin); - controller.disableAutomation(); - - assertFalse(controller.isAutomationEnabled()); - } - - /// @dev Test to ensure 'disableAutomation' emits event 'AutomationDisabled'. - function testDisableAutomationEmitsEvent() public { - vm.expectEmit(true, false, false, false); - emit AutomationController.AutomationDisabled(false); - - vm.prank(admin); - controller.disableAutomation(); - } - - /// @dev Test to ensure 'disableAutomation' reverts if automation is already disabled. - function testDisableAutomationRevertsIfAlreadyDisabled() public { - // Disable automation - testDisableAutomation(); - - // Disable again → revert - vm.expectRevert(IAutomationController.AlreadyDisabled.selector); - - vm.prank(admin); - controller.disableAutomation(); - } - - /// @dev Test to ensure 'disableAutomation' reverts if caller is not owner. - function testDisableAutomationRevertsIfNotOwner() public { - vm.expectRevert(abi.encodeWithSelector(OwnableUpgradeable.OwnableUnauthorizedAccount.selector,alice)); - - vm.prank(alice); - controller.disableAutomation(); - } - - // :::::::::::::::::::::::::::::::::::::::::::::::::::::: Tests related to 'enableAutomation' :::::::::::::::::::::::::::::::::::::::::::::::::::::: - - /// @dev Test to ensure 'enableAutomation' enables the automation. - function testEnableAutomation() public { - // Disable automation - testDisableAutomation(); - - // Enable automation - vm.prank(admin); - controller.enableAutomation(); - - assertTrue(controller.isAutomationEnabled()); - } - - /// @dev Test to ensure 'enableAutomation' emits event 'AutomationEnabled'. - function testEnableAutomationEmitsEvent() public { - // Disable automation - testDisableAutomation(); - - vm.expectEmit(true, false, false, false); - emit AutomationController.AutomationEnabled(true); - - vm.prank(admin); - controller.enableAutomation(); - } - - /// @dev Test to ensure 'enableAutomation' reverts if automation is already enabled. - function testEnableAutomationRevertsIfAlreadyEnabled() public { - // Already enabled in initialize() - vm.expectRevert(IAutomationController.AlreadyEnabled.selector); - - vm.prank(admin); - controller.enableAutomation(); - } - - /// @dev Test to ensure 'enableAutomation' reverts if caller is not owner. - function testEnableAutomationRevertsIfNotOwner() public { - vm.expectRevert(abi.encodeWithSelector(OwnableUpgradeable.OwnableUnauthorizedAccount.selector,alice)); - - vm.prank(alice); - controller.enableAutomation(); - } - - /// @dev Helper function to register a UST. - function registerTask() private { - bytes[] memory auxData; - bytes memory payload = createPayload(0, address(erc20Supra)); - - vm.startPrank(alice); - erc20Supra.nativeToErc20Supra{value: 5 ether}(); - erc20Supra.approve(address(automationCore), type(uint256).max); - - registry.register( - payload, - uint64(block.timestamp + 2250), - uint128(1_000_000), - uint128(10 gwei), - uint128(0.5 ether), - 2, - auxData - ); - vm.stopPrank(); - } - - /// @dev Helper function to return payload. - /// @param _value Value to be sent along with transaction. - /// @param _target Address of destination smart contract. - function createPayload(uint128 _value, address _target) private pure returns (bytes memory) { - LibConfig.AccessListEntry[] memory accessList = new LibConfig.AccessListEntry[](2); - - bytes32[] memory keys = new bytes32[](2); - keys[0] = bytes32(uint256(0)); - keys[1] = bytes32(uint256(1)); - - accessList[0] = LibConfig.AccessListEntry({ - addr: address(0x1111), - storageKeys: keys - }); - - accessList[1] = LibConfig.AccessListEntry({ - addr: address(0x2222), - storageKeys: keys - }); - - bytes memory callData = abi.encodeCall(ERC20Supra.erc20SupraToNative, 100); - bytes memory payload = abi.encode(_value, _target, callData, accessList); - - return payload; - } -} \ No newline at end of file diff --git a/solidity/supra_contracts/test/AutomationCore.t.sol b/solidity/supra_contracts/test/AutomationCore.t.sol deleted file mode 100644 index 2350735502..0000000000 --- a/solidity/supra_contracts/test/AutomationCore.t.sol +++ /dev/null @@ -1,945 +0,0 @@ -// SPDX-License-Identifier: MIT -pragma solidity 0.8.27; - -import {Test} from "forge-std/Test.sol"; -import {ERC1967Proxy} from "../lib/openzeppelin-contracts/contracts/proxy/ERC1967/ERC1967Proxy.sol"; -import {Initializable} from "@openzeppelin/contracts/proxy/utils/Initializable.sol"; -import {OwnableUpgradeable} from "../lib/openzeppelin-contracts-upgradeable/contracts/access/Ownable2StepUpgradeable.sol"; -import {AutomationCore} from "../src/AutomationCore.sol"; -import {AutomationController} from "../src/AutomationController.sol"; -import {AutomationRegistry} from "../src/AutomationRegistry.sol"; -import {IAutomationCore} from "../src/IAutomationCore.sol"; -import {ERC20Supra} from "../src/ERC20Supra.sol"; -import {CommonUtils} from "../src/CommonUtils.sol"; -import {LibConfig} from "../src/LibConfig.sol"; - -contract AutomationCoreTest is Test { - ERC20Supra erc20Supra; // ERC20Supra contract - AutomationCore automationCore; // AutomationCore instance on proxy address - AutomationRegistry registry; // AutomationRegistry instance on proxy address - AutomationController automationController; // AutomationController instance on proxy address - - /// @dev Address of the transaction hash precompile. - address constant TX_HASH_PRECOMPILE = 0x0000000000000000000000000000000053555001; - - address admin = address(0xA11CE); - address vmSigner = address(0x53555000); - address alice = address(0x123); - address bob = address(0x456); - - /// @dev Sets up initial state for testing. - /// @dev Sets balance of 'alice' to 100 ether. - /// @dev Deploys and initializes all contracts with required parameters. - function setUp() public { - vm.deal(alice, 100 ether); - - vm.startPrank(admin); - erc20Supra = new ERC20Supra(msg.sender); - - AutomationCore automationCoreImpl = new AutomationCore(); - bytes memory automationCoreInitData = abi.encodeCall( - AutomationCore.initialize, - ( - 3600, // taskDurationCapSecs - 10_000_000, // registryMaxGasCap - 0.001 ether, // automationBaseFeeWeiPerSec - 0.002 ether, // flatRegistrationFeeWei - 50, // congestionThresholdPercentage - 0.002 ether, // congestionBaseFeeWeiPerSec - 2, // congestionExponent - 500, // taskCapacity - 2000, // cycleDurationSecs - 3600, // sysTaskDurationCapSecs - 5_000_000, // sysRegistryMaxGasCap - 500, // sysTaskCapacity - vmSigner, // VM Signer address - address(erc20Supra) // ERC20Supra address - ) - ); - ERC1967Proxy automationCoreProxy = new ERC1967Proxy(address(automationCoreImpl), automationCoreInitData); - automationCore = AutomationCore(address(automationCoreProxy)); - - AutomationRegistry registryImpl = new AutomationRegistry(); - bytes memory registryInitData = abi.encodeCall(AutomationRegistry.initialize, (address(automationCore))); - ERC1967Proxy registryProxy = new ERC1967Proxy(address(registryImpl), registryInitData); - registry = AutomationRegistry(address(registryProxy)); - - AutomationController controllerImpl = new AutomationController(); - bytes memory controllerInitData = abi.encodeCall(AutomationController.initialize,(address(automationCore), address(registry), true)); - ERC1967Proxy controllerProxy = new ERC1967Proxy(address(controllerImpl), controllerInitData); - automationController = AutomationController(address(controllerProxy)); - - automationCore.setAutomationRegistry(address(registry)); - automationCore.setAutomationController(address(automationController)); - registry.setAutomationController(address(automationController)); - - vm.stopPrank(); - - vm.mockCall( - TX_HASH_PRECOMPILE, - bytes(""), - abi.encode(keccak256("txHash")) - ); - } - - /// @dev Test to ensure all state variables are initialized correctly. - function testInitialize() public view { - assertEq(automationCore.owner(), admin); - - (uint64 index, uint64 startTime, uint64 durationSecs, CommonUtils.CycleState state) = automationController.getCycleInfo(); - assertEq(index, 1); - assertEq(startTime, block.timestamp); - assertEq(durationSecs, 2000); - assertEq(uint8(state), uint8(CommonUtils.CycleState.STARTED)); - - assertEq(automationCore.getNextCycleRegistryMaxGasCap(), 10_000_000); - assertEq(automationCore.getNextCycleSysRegistryMaxGasCap(), 5_000_000); - assertEq(automationCore.getAutomationController(), address(automationController)); - assertTrue(automationCore.isRegistrationEnabled()); - assertTrue(automationController.isAutomationEnabled()); - assertEq(automationCore.getVmSigner(), vmSigner); - assertEq(automationCore.erc20Supra(), address(erc20Supra)); - - LibConfig.ConfigDetails memory config = automationCore.getConfig(); - - assertEq(config.registryMaxGasCap, 10_000_000); - assertEq(config.sysRegistryMaxGasCap, 5_000_000); - assertEq(config.automationBaseFeeWeiPerSec, 0.001 ether); - assertEq(config.flatRegistrationFeeWei, 0.002 ether); - assertEq(config.congestionBaseFeeWeiPerSec, 0.002 ether); - assertEq(config.taskDurationCapSecs, 3600); - assertEq(config.sysTaskDurationCapSecs, 3600); - assertEq(config.cycleDurationSecs, 2000); - assertEq(config.taskCapacity, 500); - assertEq(config.sysTaskCapacity, 500); - assertEq(config.congestionThresholdPercentage, 50); - assertEq(config.congestionExponent, 2); - } - - /// @dev Test to ensure reinitialization fails. - function testInitializeRevertsIfReinitialized() public { - vm.expectRevert(Initializable.InvalidInitialization.selector); - - vm.prank(admin); - automationCore.initialize( - 3600, 10_000_000, 0.001 ether, 0.002 ether, 50, 0.002 ether, 2, - 500, 2000, 3600, 5_000_000, 500, vmSigner, address(erc20Supra) - ); - } - - /// @dev Test to ensure initialization fails if zero address is passed as VM Signer. - function testInitializeRevertsIfVmSignerZero() public { - AutomationCore implementation = new AutomationCore(); - - bytes memory initData = abi.encodeCall( - AutomationCore.initialize, - ( - 3600, 10_000_000, 0.001 ether, 0.002 ether, 50, 0.002 ether, - 2, 500, 2000, 3600, 5_000_000, 500, - address(0), // VM Signer as zero - address(erc20Supra) - ) - ); - - vm.expectRevert(IAutomationCore.AddressCannotBeZero.selector); - new ERC1967Proxy(address(implementation), initData); - } - - /// @dev Test to ensure initialization fails if ERC20Supra address is zero. - function testInitializeRevertsIfErc20SupraIsZero() public { - AutomationCore implementation = new AutomationCore(); - - bytes memory initData = abi.encodeCall( - AutomationCore.initialize, - ( - 3600, 10_000_000, 0.001 ether, 0.002 ether, 50, 0.002 ether, - 2, 500, 2000, 3600, 5_000_000, 500, vmSigner, - address(0) // address(0) as ERC20Supra - ) - ); - - vm.expectRevert(IAutomationCore.AddressCannotBeZero.selector); - new ERC1967Proxy(address(implementation), initData); - } - - /// @dev Test to ensure initialization fails if EOA is passed as ERC20Supra address. - function testInitializeRevertsIfErc20SupraIsEoa() public { - AutomationCore implementation = new AutomationCore(); - - bytes memory initData = abi.encodeCall( - AutomationCore.initialize, - ( - 3600, 10_000_000, 0.001 ether, 0.002 ether, 50, 0.002 ether, - 2, 500, 2000, 3600, 5_000_000, 500, vmSigner, - admin // EOA address as ERC20Supra - ) - ); - - vm.expectRevert(CommonUtils.AddressCannotBeEOA.selector); - new ERC1967Proxy(address(implementation), initData); - } - - /// @dev Test to ensure initialization fails if task duration is <= cycle duration. - function testInitializeRevertsIfInvalidTaskDuration() public { - AutomationCore implementation = new AutomationCore(); - - bytes memory initData = abi.encodeCall( - AutomationCore.initialize, - ( - 2000, // task duration - 10_000_000, 0.001 ether, 0.002 ether, 50, 0.002 ether, 2, 500, - 2000, // cycle duration - 3600, 5_000_000, 500, vmSigner, address(erc20Supra) - ) - ); - - vm.expectRevert(IAutomationCore.InvalidTaskDuration.selector); - new ERC1967Proxy(address(implementation), initData); - } - - /// @dev Test to ensure initialization fails if registry max gas cap is zero. - function testInitializeRevertsIfRegistryMaxGasCapZero() public { - AutomationCore implementation = new AutomationCore(); - - bytes memory initData = abi.encodeCall( - AutomationCore.initialize, - ( - 3600, - 0, // registry max gas cap - 0.001 ether, 0.002 ether, 50, 0.002 ether, 2, 500, - 2000, 3600, 5_000_000, 500, vmSigner, address(erc20Supra) - ) - ); - - vm.expectRevert(IAutomationCore.InvalidRegistryMaxGasCap.selector); - new ERC1967Proxy(address(implementation), initData); - } - - /// @dev Test to ensure initialization fails if congestion threshold percentage is > 100. - function testInitializeRevertsIfInvalidCongestionThreshold() public { - AutomationCore implementation = new AutomationCore(); - - bytes memory initData = abi.encodeCall( - AutomationCore.initialize, - ( - 3600, 10_000_000, 0.001 ether, 0.002 ether, - 101, // congestion threshold percentage > 100 - 0.002 ether, 2, 500, 2000, 3600, 5_000_000, 500, vmSigner, address(erc20Supra) - ) - ); - - vm.expectRevert(IAutomationCore.InvalidCongestionThreshold.selector); - new ERC1967Proxy(address(implementation), initData); - } - - /// @dev Test to ensure initialization fails if congestion exponent is 0. - function testInitializeRevertsIfCongestionExponentZero() public { - AutomationCore implementation = new AutomationCore(); - - bytes memory initData = abi.encodeCall( - AutomationCore.initialize, - ( - 3600, 10_000_000, 0.001 ether, 0.002 ether, 50, 0.002 ether, - 0, // congestion exponent - 500, 2000, 3600, 5_000_000, 500, vmSigner, address(erc20Supra) - ) - ); - - vm.expectRevert(IAutomationCore.InvalidCongestionExponent.selector); - new ERC1967Proxy(address(implementation), initData); - } - - /// @dev Test to ensure initialization fails if task capacity is 0. - function testInitializeRevertsIfTaskCapacityZero() public { - AutomationCore implementation = new AutomationCore(); - - bytes memory initData = abi.encodeCall( - AutomationCore.initialize, - ( - 3600, 10_000_000, 0.001 ether, 0.002 ether, 50, 0.002 ether, 2, - 0, // 0 as task capacity - 2000, 3600, 5_000_000, 500, vmSigner, address(erc20Supra) - ) - ); - - vm.expectRevert(IAutomationCore.InvalidTaskCapacity.selector); - new ERC1967Proxy(address(implementation), initData); - } - - /// @dev Test to ensure initialization fails if cycle duration is 0. - function testInitializeRevertsIfCycleDurationZero() public { - AutomationCore implementation = new AutomationCore(); - - bytes memory initData = abi.encodeCall( - AutomationCore.initialize, - ( - 3600, 10_000_000, 0.001 ether, 0.002 ether, 50, 0.002 ether, 2, 500, - 0, // cycle duration - 3600, 5_000_000, 500, vmSigner, address(erc20Supra) - ) - ); - - vm.expectRevert(IAutomationCore.InvalidCycleDuration.selector); - new ERC1967Proxy(address(implementation), initData); - } - - /// @dev Test to ensure initialization fails if system task duration is <= cycle duration. - function testInitializeRevertsIfInvalidSysTaskDuration() public { - AutomationCore implementation = new AutomationCore(); - - bytes memory initData = abi.encodeCall( - AutomationCore.initialize, - ( - 3600, 10_000_000, 0.001 ether, 0.002 ether, 50, 0.002 ether, 2, 500, - 2000, // cycle duration - 2000, // system task duration - 5_000_000, 500, vmSigner, address(erc20Supra) - ) - ); - - vm.expectRevert(IAutomationCore.InvalidSysTaskDuration.selector); - new ERC1967Proxy(address(implementation), initData); - } - - /// @dev Test to ensure initialization fails if system registry max gas cap is 0. - function testInitializeRevertsIfSysRegistryMaxGasCapZero() public { - AutomationCore implementation = new AutomationCore(); - - bytes memory initData = abi.encodeCall( - AutomationCore.initialize, - ( - 3600, 10_000_000, 0.001 ether, 0.002 ether, 50, 0.002 ether, 2, 500, 2000, 3600, - 0, // system registry max gas cap - 500, vmSigner, address(erc20Supra) - ) - ); - - vm.expectRevert(IAutomationCore.InvalidSysRegistryMaxGasCap.selector); - new ERC1967Proxy(address(implementation), initData); - } - - /// @dev Test to ensure initialization fails if system task capacity is 0. - function testInitializeRevertsIfSysTaskCapacityZero() public { - AutomationCore implementation = new AutomationCore(); - - bytes memory initData = abi.encodeCall( - AutomationCore.initialize, - ( - 3600, 10_000_000, 0.001 ether, 0.002 ether, 50, 0.002 ether, - 2, 500, 2000, 3600, 5_000_000, - 0, // system task capacity - vmSigner, address(erc20Supra) - ) - ); - - vm.expectRevert(IAutomationCore.InvalidSysTaskCapacity.selector); - new ERC1967Proxy(address(implementation), initData); - } - - // :::::::::::::::::::::::::::::::::::::::::::::::::::::: Tests related to 'disableRegistration' :::::::::::::::::::::::::::::::::::::::::::::::::::::: - - /// @dev Test to ensure 'disableRegistration' disables the registration. - function testDisableRegistration() public { - vm.prank(admin); - automationCore.disableRegistration(); - - assertFalse(automationCore.isRegistrationEnabled()); - } - - /// @dev Test to ensure 'disableRegistration' emits event 'TaskRegistrationDisabled'. - function testDisableRegistrationEmitsEvent() public { - vm.expectEmit(true, false, false, false); - emit AutomationCore.TaskRegistrationDisabled(false); - - testDisableRegistration(); - } - - /// @dev Test to ensure 'disableRegistration' reverts if registration is already disabled. - function testDisableRegistrationRevertsIfAlreadyDisabled() public { - // Disable registration - testDisableRegistration(); - - // Disable again → revert - vm.expectRevert(IAutomationCore.AlreadyDisabled.selector); - - vm.prank(admin); - automationCore.disableRegistration(); - } - - /// @dev Test to ensure 'disableRegistration' reverts if caller is not owner. - function testDisableRegistrationRevertsIfNotOwner() public { - vm.expectRevert(abi.encodeWithSelector(OwnableUpgradeable.OwnableUnauthorizedAccount.selector, alice)); - - vm.prank(alice); - automationCore.disableRegistration(); - } - - // :::::::::::::::::::::::::::::::::::::::::::::::::::::: Tests related to 'enableRegistration' :::::::::::::::::::::::::::::::::::::::::::::::::::::: - - /// @dev Test to ensure 'enableRegistration' enables the registration. - function testEnableRegistration() public { - // Disable registration - testDisableRegistration(); - - // Enable registration - vm.prank(admin); - automationCore.enableRegistration(); - - assertTrue(automationCore.isRegistrationEnabled()); - } - - /// @dev Test to ensure 'enableRegistration' emits event 'TaskRegistrationEnabled'. - function testEnableRegistrationEmitsEvent() public { - // Disable registration - testDisableRegistration(); - - vm.expectEmit(true, false, false, false); - emit AutomationCore.TaskRegistrationEnabled(true); - - // Enable registration - vm.prank(admin); - automationCore.enableRegistration(); - } - - /// @dev Test to ensure 'enableRegistration' reverts if registration is already enabled. - function testEnableRegistrationRevertsIfAlreadyEnabled() public { - // Already enabled in initialize() - vm.expectRevert(IAutomationCore.AlreadyEnabled.selector); - - vm.prank(admin); - automationCore.enableRegistration(); - } - - /// @dev Test to ensure 'enableRegistration' reverts if caller is not owner. - function testEnableRegistrationRevertsIfNotOwner() public { - vm.expectRevert(abi.encodeWithSelector(OwnableUpgradeable.OwnableUnauthorizedAccount.selector, alice)); - - vm.prank(alice); - automationCore.enableRegistration(); - } - - // :::::::::::::::::::::::::::::::::::::::::::::::::::::: Tests related to 'setAutomationRegistry' :::::::::::::::::::::::::::::::::::::::::::::::::::::: - - /// @dev Helper function that deploys AutomationRegistry and returns its address. - function deployAutomationRegistry() internal returns (address) { - // Deploy AutomationRegistry proxy - AutomationRegistry registryImpl = new AutomationRegistry(); - bytes memory registryInitData = abi.encodeCall(AutomationRegistry.initialize,(address(automationCore))); - ERC1967Proxy registryProxy = new ERC1967Proxy(address(registryImpl), registryInitData); - - return address(registryProxy); - } - - /// @dev Test to ensure 'setAutomationRegistry' updates the automation registry address. - function testSetAutomationRegistry() public { - address registryAddr = deployAutomationRegistry(); - - vm.prank(admin); - automationCore.setAutomationRegistry(registryAddr); - - assertEq(automationCore.getAutomationRegistry(), registryAddr); - } - - /// @dev Test to ensure 'setAutomationRegistry' emits event 'AutomationRegistryUpdated'. - function testSetAutomationRegistryEmitsEvent() public { - address oldRegistry = automationCore.getAutomationRegistry(); - address registryAddr = deployAutomationRegistry(); - - vm.expectEmit(true, true, false, false); - emit AutomationCore.AutomationRegistryUpdated(oldRegistry, registryAddr); - - vm.prank(admin); - automationCore.setAutomationRegistry(registryAddr); - } - - /// @dev Test to ensure 'setAutomationRegistry' reverts if caller is not owner. - function testSetAutomationRegistryRevertsIfNotOwner() public { - address registryAddr = deployAutomationRegistry(); - - vm.expectRevert(abi.encodeWithSelector(OwnableUpgradeable.OwnableUnauthorizedAccount.selector,alice)); - - vm.prank(alice); - automationCore.setAutomationRegistry(registryAddr); - } - - /// @dev Test to ensure 'setAutomationRegistry' reverts if zero address is passed. - function testSetAutomationRegistryRevertsIfZeroAddress() public { - vm.expectRevert(IAutomationCore.AddressCannotBeZero.selector); - - vm.prank(admin); - automationCore.setAutomationRegistry(address(0)); - } - - /// @dev Test to ensure 'setAutomationRegistry' reverts if EOA is passed. - function testSetAutomationRegistryRevertsIfEoa() public { - vm.expectRevert(CommonUtils.AddressCannotBeEOA.selector); - - vm.prank(admin); - automationCore.setAutomationRegistry(alice); - } - - // :::::::::::::::::::::::::::::::::::::::::::::::::::::: Tests related to 'setAutomationController' :::::::::::::::::::::::::::::::::::::::::::::::::::::: - - /// @dev Helper function that deploys AutomationController and returns its address. - function deployAutomationController() internal returns (address) { - // Deploy AutomationController proxy - AutomationController controllerImpl = new AutomationController(); - bytes memory controllerInitData = abi.encodeCall(AutomationController.initialize,(address(automationCore), address(registry), true)); - ERC1967Proxy controllerProxy = new ERC1967Proxy(address(controllerImpl), controllerInitData); - - return address(controllerProxy); - } - - /// @dev Test to ensure 'setAutomationController' updates the automation controller address. - function testSetAutomationController() public { - address controller = deployAutomationController(); - - vm.prank(admin); - automationCore.setAutomationController(controller); - - assertEq(automationCore.getAutomationController(), controller); - } - - /// @dev Test to ensure 'setAutomationController' emits event 'AutomationControllerUpdated'. - function testSetAutomationControllerEmitsEvent() public { - address oldController = automationCore.getAutomationController(); - address controller = deployAutomationController(); - - vm.expectEmit(true, true, false, false); - emit AutomationCore.AutomationControllerUpdated(oldController, controller); - - vm.prank(admin); - automationCore.setAutomationController(controller); - } - - /// @dev Test to ensure 'setAutomationController' reverts if caller is not owner. - function testSetAutomationControllerRevertsIfNotOwner() public { - address controller = deployAutomationController(); - - vm.expectRevert(abi.encodeWithSelector(OwnableUpgradeable.OwnableUnauthorizedAccount.selector,alice)); - - vm.prank(alice); - automationCore.setAutomationController(controller); - } - - /// @dev Test to ensure 'setAutomationController' reverts if zero address is passed. - function testSetAutomationControllerRevertsIfZeroAddress() public { - vm.expectRevert(IAutomationCore.AddressCannotBeZero.selector); - - vm.prank(admin); - automationCore.setAutomationController(address(0)); - } - - /// @dev Test to ensure 'setAutomationController' reverts if EOA is passed. - function testSetAutomationControllerRevertsIfEoa() public { - vm.expectRevert(CommonUtils.AddressCannotBeEOA.selector); - - vm.prank(admin); - automationCore.setAutomationController(alice); - } - - // :::::::::::::::::::::::::::::::::::::::::::::::::::::: Tests related to 'setVmSigner' :::::::::::::::::::::::::::::::::::::::::::::::::::::: - - /// @dev Test to ensure 'setVmSigner' updates the VM Signer address. - function testSetVmSigner() public { - address newVmSigner = address(0x100); - - vm.prank(admin); - automationCore.setVmSigner(newVmSigner); - - assertEq(automationCore.getVmSigner(), newVmSigner); - } - - /// @dev Test to ensure 'setVmSigner' emits event 'VmSignerUpdated'. - function testSetVmSignerEmitsEvent() public { - address oldVmSigner = automationCore.getVmSigner(); - address newVmSigner = address(0x100); - - vm.expectEmit(true, true, false, false); - emit AutomationCore.VmSignerUpdated(oldVmSigner, newVmSigner); - - vm.prank(admin); - automationCore.setVmSigner(newVmSigner); - } - - /// @dev Test to ensure 'setVmSigner' reverts if zero address is passed. - function testSetVmSignerRevertsIfZeroAddress() public { - vm.expectRevert(IAutomationCore.AddressCannotBeZero.selector); - - vm.prank(admin); - automationCore.setVmSigner(address(0)); - } - - /// @dev Test to ensure 'setVmSigner' reverts if caller is not owner. - function testSetVmSignerRevertsIfNotOwner() public { - vm.expectRevert(abi.encodeWithSelector(OwnableUpgradeable.OwnableUnauthorizedAccount.selector, alice)); - - vm.prank(alice); - automationCore.setVmSigner(address(0x100)); - } - - // :::::::::::::::::::::::::::::::::::::::::::::::::::::: Tests related to 'setErc20Supra' :::::::::::::::::::::::::::::::::::::::::::::::::::::: - - /// @dev Test to ensure 'setErc20Supra' updates the ERC20Supra address. - function testSetErc20Supra() public { - ERC20Supra supraErc20 = new ERC20Supra(msg.sender); - - vm.prank(admin); - automationCore.setErc20Supra(address(supraErc20)); - - assertEq(automationCore.erc20Supra(), address(supraErc20)); - } - - /// @dev Test to ensure 'setErc20Supra' emits event 'Erc20SupraUpdated'. - function testSetErc20SupraEmitsEvent() public { - address oldAddr = automationCore.erc20Supra(); - ERC20Supra supraErc20 = new ERC20Supra(msg.sender); - - vm.expectEmit(true, true, false, false); - emit AutomationCore.Erc20SupraUpdated(oldAddr, address(supraErc20)); - - vm.prank(admin); - automationCore.setErc20Supra(address(supraErc20)); - } - - /// @dev Test to ensure 'setErc20Supra' reverts if zero address is passed. - function testSetErc20SupraRevertsIfZeroAddress() public { - vm.expectRevert(IAutomationCore.AddressCannotBeZero.selector); - - vm.prank(admin); - automationCore.setErc20Supra(address(0)); - } - - /// @dev Test to ensure 'setErc20Supra' reverts if EOA is passed. - function testSetErc20SupraRevertsIfEoa() public { - vm.expectRevert(CommonUtils.AddressCannotBeEOA.selector); - - vm.prank(admin); - automationCore.setErc20Supra(alice); - } - - /// @dev Test to ensure 'setErc20Supra' reverts if caller is not owner. - function testSetErc20SupraRevertsIfNotOwner() public { - ERC20Supra supraErc20 = new ERC20Supra(msg.sender); - - vm.expectRevert(abi.encodeWithSelector(OwnableUpgradeable.OwnableUnauthorizedAccount.selector, alice)); - - vm.prank(alice); - automationCore.setErc20Supra(address(supraErc20)); - } - - // :::::::::::::::::::::::::::::::::::::::::::::::::::::: Tests related to 'updateConfigBuffer' :::::::::::::::::::::::::::::::::::::::::::::::::::::: - - /// @dev Helper function that returns a valid config. - function validConfig() internal pure returns (LibConfig.ConfigDetails memory cfg) { - cfg = LibConfig.ConfigDetails( - 10_000_000, // registryMaxGasCap - 5_000_000, // sysRegistryMaxGasCap - 0.001 ether, // automationBaseFeeWeiPerSec - 0.002 ether, // flatRegistrationFeeWei - 0.002 ether, // congestionBaseFeeWeiPerSec - 3600, // taskDurationCapSecs - 3600, // sysTaskDurationCapSecs - 2000, // cycleDurationSecs - 500, // taskCapacity - 500, // sysTaskCapacity - 55, // congestionThresholdPercentage - 3 // congestionExponent - ); - } - - /// @dev Test to ensure 'updateConfigBuffer' updates the config buffer. - function testUpdateConfigBuffer() public { - LibConfig.ConfigDetails memory cfg = validConfig(); - - vm.prank(admin); - automationCore.updateConfigBuffer( - cfg.taskDurationCapSecs, - cfg.registryMaxGasCap, - cfg.automationBaseFeeWeiPerSec, - cfg.flatRegistrationFeeWei, - cfg.congestionThresholdPercentage, - cfg.congestionBaseFeeWeiPerSec, - cfg.congestionExponent, - cfg.taskCapacity, - cfg.cycleDurationSecs, - cfg.sysTaskDurationCapSecs, - cfg.sysRegistryMaxGasCap, - cfg.sysTaskCapacity - ); - - // Pending config should be updated - LibConfig.ConfigDetails memory pendingCfg = automationCore.getPendingConfig(); - assertEq(pendingCfg.taskDurationCapSecs, cfg.taskDurationCapSecs); - assertEq(pendingCfg.registryMaxGasCap, cfg.registryMaxGasCap); - assertEq(pendingCfg.automationBaseFeeWeiPerSec, cfg.automationBaseFeeWeiPerSec); - assertEq(pendingCfg.flatRegistrationFeeWei, cfg.flatRegistrationFeeWei); - assertEq(pendingCfg.congestionThresholdPercentage, cfg.congestionThresholdPercentage); - assertEq(pendingCfg.congestionBaseFeeWeiPerSec, cfg.congestionBaseFeeWeiPerSec); - assertEq(pendingCfg.congestionExponent, cfg.congestionExponent); - assertEq(pendingCfg.taskCapacity, cfg.taskCapacity); - assertEq(pendingCfg.cycleDurationSecs, cfg.cycleDurationSecs); - assertEq(pendingCfg.sysTaskDurationCapSecs, cfg.sysTaskDurationCapSecs); - assertEq(pendingCfg.sysRegistryMaxGasCap, cfg.sysRegistryMaxGasCap); - assertEq(pendingCfg.sysTaskCapacity, cfg.sysTaskCapacity); - } - - /// @dev Test to ensure 'updateConfigBuffer' emits event 'ConfigBufferUpdated'. - function testUpdateConfigBufferEmitsEvent() public { - LibConfig.ConfigDetails memory cfg = validConfig(); - - vm.expectEmit(true, false, false, false); - emit AutomationCore.ConfigBufferUpdated(cfg); - - vm.prank(admin); - automationCore.updateConfigBuffer( - cfg.taskDurationCapSecs, - cfg.registryMaxGasCap, - cfg.automationBaseFeeWeiPerSec, - cfg.flatRegistrationFeeWei, - cfg.congestionThresholdPercentage, - cfg.congestionBaseFeeWeiPerSec, - cfg.congestionExponent, - cfg.taskCapacity, - cfg.cycleDurationSecs, - cfg.sysTaskDurationCapSecs, - cfg.sysRegistryMaxGasCap, - cfg.sysTaskCapacity - ); - } - - /// @dev Test to ensure 'updateConfigBuffer' reverts if caller is not owner. - function testUpdateConfigBufferRevertsIfNotOwner() public { - LibConfig.ConfigDetails memory cfg = validConfig(); - - vm.expectRevert(abi.encodeWithSelector(OwnableUpgradeable.OwnableUnauthorizedAccount.selector,alice)); - - vm.prank(alice); - automationCore.updateConfigBuffer( - cfg.taskDurationCapSecs, - cfg.registryMaxGasCap, - cfg.automationBaseFeeWeiPerSec, - cfg.flatRegistrationFeeWei, - cfg.congestionThresholdPercentage, - cfg.congestionBaseFeeWeiPerSec, - cfg.congestionExponent, - cfg.taskCapacity, - cfg.cycleDurationSecs, - cfg.sysTaskDurationCapSecs, - cfg.sysRegistryMaxGasCap, - cfg.sysTaskCapacity - ); - } - - // :::::::::::::::::::::::::::::::::::::::::::::::::::::: Tests related to 'withdrawFees' :::::::::::::::::::::::::::::::::::::::::::::::::::::: - - /// @dev Test to ensure 'withdrawFees' reverts if amount is zero. - function testWithdrawFeesRevertsIfAmountZero() public { - vm.prank(admin); - - vm.expectRevert(IAutomationCore.InvalidAmount.selector); - automationCore.withdrawFees(0, admin); - } - - /// @dev Test to ensure 'withdrawFees' reverts if recipient address is zero. - function testWithdrawFeesRevertsIfRecipientAddressZero() public { - vm.prank(admin); - - vm.expectRevert(IAutomationCore.AddressCannotBeZero.selector); - automationCore.withdrawFees(1 ether, address(0)); - } - - /// @dev Test to ensure 'withdrawFees' reverts if contract has insufficient balance. - function testWithdrawFeesRevertsIfInsufficientBalance() public { - vm.expectRevert(IAutomationCore.InsufficientBalance.selector); - - vm.prank(admin); - automationCore.withdrawFees(1 ether, admin); - } - - /// @dev Test to ensure 'withdrawFees' reverts if request amount exceeds the locked balance. - function testWithdrawFeesRevertsIfRequestExceedsLockedBalance() public { - registerUST(); - - vm.expectRevert(IAutomationCore.RequestExceedsLockedBalance.selector); - - vm.prank(admin); - automationCore.withdrawFees(0.04 ether, admin); - } - - /// @dev Test to ensure 'withdrawFees' reverts if caller is not owner. - function testWithdrawFeesRevertsIfNotOwner() public { - vm.expectRevert(abi.encodeWithSelector(OwnableUpgradeable.OwnableUnauthorizedAccount.selector, alice)); - - vm.prank(alice); - automationCore.withdrawFees(1 ether, admin); - } - - /// @dev Test to ensure 'withdrawFees' withdraws the requested amount and updates the balance. - function testWithdrawFees() public { - registerUST(); - - assertEq(erc20Supra.balanceOf(admin), 0); - assertEq(erc20Supra.balanceOf(address(automationCore)), 0.502 ether); - - vm.prank(admin); - automationCore.withdrawFees(0.002 ether, admin); - - assertEq(erc20Supra.balanceOf(admin), 0.002 ether); - assertEq(erc20Supra.balanceOf(address(automationCore)), 0.5 ether); - } - - /// @dev Test to ensure 'withdrawFees' emits event 'RegistryFeeWithdrawn'. - function testWithdrawFeesEmitsEvent() public { - registerUST(); - - vm.expectEmit(true, true, false, false); - emit AutomationCore.RegistryFeeWithdrawn(admin, 0.002 ether); - - vm.prank(admin); - automationCore.withdrawFees(0.002 ether, admin); - } - - /// @dev Test to ensure 'applyPendingConfig' reverts if caller is not AutomationController. - function testApplyPendingConfigRevertsIfCallerNotAutomationController() public { - vm.expectRevert(IAutomationCore.CallerNotController.selector); - - vm.prank(address(registry)); - automationCore.applyPendingConfig(); - } - - /// @dev Test to ensure 'safeUnlockLockedDeposit' reverts if caller is not AutomationController. - function testSafeUnlockLockedDepositRevertsIfCallerNotAutomationController() public { - vm.expectRevert(IAutomationCore.CallerNotController.selector); - - vm.prank(address(registry)); - automationCore.safeUnlockLockedDeposit(0, 0.01 ether); - } - - /// @dev Test to ensure 'refundTaskFees' reverts if caller is not AutomationController. - function testRefundTaskFeesRevertsIfCallerNotAutomationController() public { - registerUST(); - CommonUtils.TaskDetails memory task = registry.getTaskDetails(0); - - vm.expectRevert(IAutomationCore.CallerNotController.selector); - - vm.prank(address(registry)); - automationCore.refundTaskFees( - uint64(block.timestamp), - uint64(block.timestamp) + 100000, - 0.0001 ether, - task - ); - } - - /// @dev Test to ensure 'updateStateForValidRegistration' reverts if caller is not AutomationRegistry. - function test_UpdateStateForValidRegistration_RevertsIfCallerNotAutomationRegistry() public { - bytes memory payload = createPayload(0, address(erc20Supra)); - - vm.expectRevert(IAutomationCore.CallerNotRegistry.selector); - - vm.prank(address(automationController)); - automationCore.updateStateForValidRegistration( - 10, - uint64(block.timestamp), - uint64(block.timestamp) + 2250, - CommonUtils.TaskType.UST, - payload, - 1000000, - 0.001 ether, - 0.01 ether - ); - } - - /// @dev Test to ensure 'incTotalDepositedAutomationFees' reverts if caller is not AutomationRegistry. - function testIncTotalDepositedAutomationFeesRevertsIfCallerNotAutomationRegistry() public { - vm.expectRevert(IAutomationCore.CallerNotRegistry.selector); - - vm.prank(address(automationController)); - automationCore.incTotalDepositedAutomationFees(0.01 ether); - } - - /// @dev Test to ensure 'refund' reverts if caller is not AutomationRegistry. - function testRefundRevertsIfCallerNotAutomationRegistry() public { - vm.expectRevert(IAutomationCore.CallerNotRegistry.selector); - - vm.prank(address(automationController)); - automationCore.refund(alice, 0.01 ether); - } - - /// @dev Test to ensure 'safeDepositRefund' reverts if caller is not AutomationRegistry. - function testSafeDepositRefundRevertsIfCallerNotAutomationRegistry() public { - vm.expectRevert(IAutomationCore.CallerNotRegistry.selector); - - vm.prank(address(automationController)); - automationCore.safeDepositRefund( - 0, - alice, - 0.01 ether, - 0.05 ether - ); - } - - /// @dev Test to ensure 'unlockDepositAndCycleFee' reverts if caller is not AutomationRegistry. - function testUnlockDepositAndCycleFeeRevertsIfCallerNotAutomationRegistry() public { - vm.expectRevert(IAutomationCore.CallerNotRegistry.selector); - - vm.prank(address(automationController)); - automationCore.unlockDepositAndCycleFee( - 0, - CommonUtils.TaskState.ACTIVE, - uint64(block.timestamp) + 2250, - 1000000, - 2000, - uint64(block.timestamp), - 0.01 ether - ); - } - - /// @dev Helper function to return payload. - /// @param _value Value to be sent along with the transaction. - /// @param _target Address of the destination smart contract. - function createPayload(uint128 _value, address _target) private pure returns (bytes memory) { - LibConfig.AccessListEntry[] memory accessList = new LibConfig.AccessListEntry[](2); - - bytes32[] memory keys = new bytes32[](2); - keys[0] = bytes32(uint256(0)); - keys[1] = bytes32(uint256(1)); - - accessList[0] = LibConfig.AccessListEntry({ - addr: address(0x1111), - storageKeys: keys - }); - - accessList[1] = LibConfig.AccessListEntry({ - addr: address(0x2222), - storageKeys: keys - }); - - bytes memory callData = abi.encodeCall(ERC20Supra.erc20SupraToNative, 100); - bytes memory payload = abi.encode(_value, _target, callData, accessList); - - return payload; - } - - /// @dev Helper function to register a UST. - function registerUST() private { - bytes[] memory auxData; - bytes memory payload = createPayload(0, address(erc20Supra)); - - vm.startPrank(alice); - erc20Supra.nativeToErc20Supra{value: 5 ether}(); - erc20Supra.approve(address(automationCore), type(uint256).max); - - registry.register( - payload, - uint64(block.timestamp + 2250), - uint128(1_000_000), - uint128(10 gwei), - uint128(0.5 ether), - 4, - auxData - ); - vm.stopPrank(); - } -} \ No newline at end of file diff --git a/solidity/supra_contracts/test/AutomationRegistry.t.sol b/solidity/supra_contracts/test/AutomationRegistry.t.sol deleted file mode 100644 index 8eb4d6e2d6..0000000000 --- a/solidity/supra_contracts/test/AutomationRegistry.t.sol +++ /dev/null @@ -1,1161 +0,0 @@ -// SPDX-License-Identifier: MIT -pragma solidity 0.8.27; - -import {Test} from "forge-std/Test.sol"; -import {ERC1967Proxy} from "../lib/openzeppelin-contracts/contracts/proxy/ERC1967/ERC1967Proxy.sol"; -import {Initializable} from "@openzeppelin/contracts/proxy/utils/Initializable.sol"; -import {OwnableUpgradeable} from "../lib/openzeppelin-contracts-upgradeable/contracts/access/Ownable2StepUpgradeable.sol"; -import {AutomationRegistry} from "../src/AutomationRegistry.sol"; -import {AutomationCore} from "../src/AutomationCore.sol"; -import {AutomationController} from "../src/AutomationController.sol"; -import {IAutomationCore} from "../src/IAutomationCore.sol"; -import {IAutomationRegistry} from "../src/IAutomationRegistry.sol"; -import {ERC20Supra} from "../src/ERC20Supra.sol"; -import {LibConfig} from "../src/LibConfig.sol"; -import {LibRegistry} from "../src/LibRegistry.sol"; -import {CommonUtils} from "../src/CommonUtils.sol"; - -contract AutomationRegistryTest is Test { - ERC20Supra erc20Supra; // ERC20Supra contract - AutomationCore automationCore; // AutomationCore instance on proxy address - AutomationRegistry registry; // AutomationRegistry instance on proxy address - AutomationController controller; // AutomationController instance on proxy address - - /// @dev Address of the transaction hash precompile. - address constant TX_HASH_PRECOMPILE = 0x0000000000000000000000000000000053555001; - - address admin = address(0xA11CE); - address vmSigner = address(0x53555000); - address alice = address(0x123); - address bob = address(0x456); - - /// @dev Sets up initial state for testing. - /// @dev Sets balance of 'alice' to 100 ether. - /// @dev Deploys and initializes all contracts with required parameters. - function setUp() public { - vm.deal(alice, 100 ether); - - vm.startPrank(admin); - erc20Supra = new ERC20Supra(msg.sender); - - AutomationCore automationCoreImpl = new AutomationCore(); - bytes memory automationCoreInitData = abi.encodeCall( - AutomationCore.initialize, - ( - 3600, // taskDurationCapSecs - 10_000_000, // registryMaxGasCap - 0.001 ether, // automationBaseFeeWeiPerSec - 0.002 ether, // flatRegistrationFeeWei - 50, // congestionThresholdPercentage - 0.002 ether, // congestionBaseFeeWeiPerSec - 2, // congestionExponent - 500, // taskCapacity - 2000, // cycleDurationSecs - 3600, // sysTaskDurationCapSecs - 5_000_000, // sysRegistryMaxGasCap - 500, // sysTaskCapacity - vmSigner, // VM Signer address - address(erc20Supra) // ERC20Supra address - ) - ); - ERC1967Proxy automationCoreProxy = new ERC1967Proxy(address(automationCoreImpl), automationCoreInitData); - automationCore = AutomationCore(address(automationCoreProxy)); - - AutomationRegistry registryImpl = new AutomationRegistry(); - bytes memory registryInitData = abi.encodeCall(AutomationRegistry.initialize, (address(automationCore))); - ERC1967Proxy registryProxy = new ERC1967Proxy(address(registryImpl), registryInitData); - registry = AutomationRegistry(address(registryProxy)); - - AutomationController controllerImpl = new AutomationController(); - bytes memory controllerInitData = abi.encodeCall(AutomationController.initialize,(address(automationCore), address(registry), true)); - ERC1967Proxy controllerProxy = new ERC1967Proxy(address(controllerImpl), controllerInitData); - controller = AutomationController(address(controllerProxy)); - - automationCore.setAutomationRegistry(address(registry)); - automationCore.setAutomationController(address(controller)); - registry.setAutomationController(address(controller)); - - vm.stopPrank(); - - vm.mockCall( - TX_HASH_PRECOMPILE, - bytes(""), - abi.encode(keccak256("txHash")) - ); - } - - /// @dev Test to ensure all state variables are initialized correctly. - function testInitialize() public view { - assertEq(registry.owner(), admin); - assertEq(registry.automationCore(), address(automationCore)); - assertEq(registry.automationController(), address(controller)); - } - - /// @dev Test to ensure reinitialization fails. - function testInitializeRevertsIfReinitialized() public { - AutomationCore automationCoreImplementation = new AutomationCore(); - - vm.expectRevert(Initializable.InvalidInitialization.selector); - - vm.prank(admin); - registry.initialize(address(automationCoreImplementation)); - } - - /// @dev Test to ensure initialization fails if AutomationCore address is zero. - function testInitializeRevertsIfAutomationCoreAddressIsZero() public { - AutomationRegistry implementation = new AutomationRegistry(); - bytes memory initData = abi.encodeCall(AutomationRegistry.initialize, (address(0))); - - vm.expectRevert(CommonUtils.AddressCannotBeZero.selector); - new ERC1967Proxy(address(implementation), initData); - } - - /// @dev Test to ensure initialization fails if EOA is passed as AutomationCore address. - function testInitializeRevertsIfAutomationCoreAddressIsEoa() public { - AutomationRegistry implementation = new AutomationRegistry(); - bytes memory initData = abi.encodeCall(AutomationRegistry.initialize, (admin)); - - vm.expectRevert(CommonUtils.AddressCannotBeEOA.selector); - new ERC1967Proxy(address(implementation), initData); - } - - // :::::::::::::::::::::::::::::::::::::::::::::::::::::: Tests related to 'setAutomationController' :::::::::::::::::::::::::::::::::::::::::::::::::::::: - - /// @dev Helper function that deploys AutomationController and returns its address. - function deployAutomationController() internal returns (address) { - // Deploy AutomationController proxy - AutomationController controllerImpl = new AutomationController(); - bytes memory controllerInitData = abi.encodeCall(AutomationController.initialize,(address(automationCore), address(registry), true)); - ERC1967Proxy controllerProxy = new ERC1967Proxy(address(controllerImpl), controllerInitData); - - return address(controllerProxy); - } - - /// @dev Test to ensure 'setAutomationController' updates the automation controller address. - function testSetAutomationController() public { - address controllerAddr = deployAutomationController(); - - vm.prank(admin); - registry.setAutomationController(controllerAddr); - - assertEq(registry.automationController(), controllerAddr); - } - - /// @dev Test to ensure 'setAutomationController' emits event 'AutomationControllerUpdated'. - function testSetAutomationControllerEmitsEvent() public { - address oldController = registry.automationController(); - address controllerAddr = deployAutomationController(); - - vm.expectEmit(true, true, false, false); - emit AutomationRegistry.AutomationControllerUpdated(oldController, controllerAddr); - - vm.prank(admin); - registry.setAutomationController(controllerAddr); - } - - /// @dev Test to ensure 'setAutomationController' reverts if caller is not owner. - function testSetAutomationControllerRevertsIfNotOwner() public { - address controllerAddr = deployAutomationController(); - - vm.expectRevert(abi.encodeWithSelector(OwnableUpgradeable.OwnableUnauthorizedAccount.selector,alice)); - - vm.prank(alice); - registry.setAutomationController(controllerAddr); - } - - /// @dev Test to ensure 'setAutomationController' reverts if zero address is passed. - function testSetAutomationControllerRevertsIfZeroAddress() public { - vm.expectRevert(CommonUtils.AddressCannotBeZero.selector); - - vm.prank(admin); - registry.setAutomationController(address(0)); - } - - /// @dev Test to ensure 'setAutomationController' reverts if EOA is passed. - function testSetAutomationControllerRevertsIfEoa() public { - vm.expectRevert(CommonUtils.AddressCannotBeEOA.selector); - - vm.prank(admin); - registry.setAutomationController(alice); - } - - // :::::::::::::::::::::::::::::::::::::::::::::::::::::: Tests related to 'grantAuthorization' :::::::::::::::::::::::::::::::::::::::::::::::::::::: - - /// @dev Test to ensure 'grantAuthorization' grants authorization to an address. - function testGrantAuthorization() public { - vm.prank(admin); - registry.grantAuthorization(bob); - - assertTrue(registry.isAuthorizedSubmitter(bob)); - } - - /// @dev Test to ensure 'grantAuthorization' emits event 'AuthorizationGranted'. - function testGrantAuthorizationEmitsEvent() public { - vm.expectEmit(true, true, false, false); - emit AutomationRegistry.AuthorizationGranted(bob, block.timestamp); - - vm.prank(admin); - registry.grantAuthorization(bob); - } - - /// @dev Test to ensure 'grantAuthorization' reverts if address is already authorized. - function testGrantAuthorizationRevertsIfAlreadyAuthorised() public { - // Grant authorization to bob - testGrantAuthorization(); - - vm.expectRevert(IAutomationRegistry.AddressAlreadyExists.selector); - - vm.prank(admin); - registry.grantAuthorization(bob); - } - - /// @dev Test to ensure 'grantAuthorization' reverts if caller is not owner. - function testGrantAuthorizationRevertsIfNotOwner() public { - vm.expectRevert(abi.encodeWithSelector(OwnableUpgradeable.OwnableUnauthorizedAccount.selector,alice)); - - vm.prank(alice); - registry.grantAuthorization(bob); - } - - // :::::::::::::::::::::::::::::::::::::::::::::::::::::: Tests related to 'revokeAuthorization' :::::::::::::::::::::::::::::::::::::::::::::::::::::: - - /// @dev Test to ensure 'revokeAuthorization' revokes authorization from an address. - function testRevokeAuthorization() public { - // Grant authorization to bob - testGrantAuthorization(); - - // Revoke authorization - vm.prank(admin); - registry.revokeAuthorization(bob); - - assertFalse(registry.isAuthorizedSubmitter(bob)); - } - - /// @dev Test to ensure 'revokeAuthorization' emits event 'AuthorizationRevoked'. - function testRevokeAuthorizationEmitsEvent() public { - // Grant authorization to bob - testGrantAuthorization(); - - vm.expectEmit(true, true, false, false); - emit AutomationRegistry.AuthorizationRevoked(bob, block.timestamp); - - vm.prank(admin); - registry.revokeAuthorization(bob); - } - - /// @dev Test to ensure 'revokeAuthorization' reverts if address is not authorised. - function testRevokeAuthorizationRevertsIfNotAuthorised() public { - vm.expectRevert(IAutomationRegistry.AddressDoesNotExist.selector); - - vm.prank(admin); - registry.revokeAuthorization(bob); - } - - /// @dev Test to ensure 'revokeAuthorization' reverts if caller is not owner. - function testRevokeAuthorizationRevertsIfNotOwner() public { - vm.expectRevert(abi.encodeWithSelector(OwnableUpgradeable.OwnableUnauthorizedAccount.selector,alice)); - - vm.prank(alice); - registry.revokeAuthorization(bob); - } - - // :::::::::::::::::::::::::::::::::::::::::::::::::::::: Tests related to 'register' :::::::::::::::::::::::::::::::::::::::::::::::::::::: - - /// @dev Helper function to return payload. - /// @param _value Value to be sent along with transaction. - /// @param _target Address of destination smart contract. - function createPayload(uint128 _value, address _target) private pure returns (bytes memory) { - LibConfig.AccessListEntry[] memory accessList = new LibConfig.AccessListEntry[](2); - - bytes32[] memory keys = new bytes32[](2); - keys[0] = bytes32(uint256(0)); - keys[1] = bytes32(uint256(1)); - - accessList[0] = LibConfig.AccessListEntry({ - addr: address(0x1111), - storageKeys: keys - }); - - accessList[1] = LibConfig.AccessListEntry({ - addr: address(0x2222), - storageKeys: keys - }); - - bytes memory callData = abi.encodeCall(ERC20Supra.erc20SupraToNative, 100); - bytes memory payload = abi.encode(_value, _target, callData, accessList); - - return payload; - } - - /// @dev Test to ensure 'register' reverts if automation is not enabled. - function testRegisterRevertsIfAutomationNotEnabled() public { - // Disable automation - vm.prank(admin); - controller.disableAutomation(); - - bytes[] memory auxData; - bytes memory payload = createPayload(0, address(erc20Supra)); - - vm.expectRevert(IAutomationRegistry.AutomationNotEnabled.selector); - - vm.prank(alice); - registry.register( - payload, // payload - uint64(block.timestamp + 2250), // expiryTime - uint128(1_000_000), // maxGasAmount - uint128(10 gwei), // gasPriceCap - uint128(0.5 ether), // automationFeeCapForCycle - 0, // priority - auxData // aux data - ); - } - - /// @dev Test to ensure 'register' reverts if registration is disabled. - function testRegisterRevertsIfRegistrationDisabled() public { - // Disable registration - vm.prank(admin); - automationCore.disableRegistration(); - - bytes[] memory auxData; - bytes memory payload = createPayload(0, address(erc20Supra)); - - vm.expectRevert(IAutomationCore.RegistrationDisabled.selector); - - vm.prank(alice); - registry.register( - payload, // payload - uint64(block.timestamp + 2250), // expiryTime - uint128(1_000_000), // maxGasAmount - uint128(10 gwei), // gasPriceCap - uint128(0.5 ether), // automationFeeCapForCycle - 0, // priority - auxData // aux data - ); - } - - /// @dev Test to ensure 'register' reverts if expiry time is equal to or less than registration time. - function testRegisterRevertsIfInvalidExpiryTime() public { - bytes[] memory auxData; - bytes memory payload = createPayload(0, address(erc20Supra)); - - vm.expectRevert(IAutomationCore.InvalidExpiryTime.selector); - - vm.prank(alice); - registry.register( - payload, - uint64(block.timestamp), // Invalid expiryTime - uint128(1_000_000), - uint128(10 gwei), - uint128(0.5 ether), - 0, - auxData - ); - } - - /// @dev Test to ensure 'register' reverts if task duration is greater than the task duration cap. - function testRegisterRevertsIfInvalidTaskDuration() public { - bytes[] memory auxData; - bytes memory payload = createPayload(0, address(erc20Supra)); - - vm.expectRevert(IAutomationCore.InvalidTaskDuration.selector); - - vm.prank(alice); - registry.register( - payload, - uint64(block.timestamp + 3601), // Invalid task duration - uint128(1_000_000), - uint128(10 gwei), - uint128(0.5 ether), - 0, - auxData - ); - } - - /// @dev Test to ensure 'register' reverts if task expires before the next cycle. - function testRegisterRevertsIfTaskExpiresBeforeNextCycle() public { - bytes[] memory auxData; - bytes memory payload = createPayload(0, address(erc20Supra)); - - vm.expectRevert(IAutomationCore.TaskExpiresBeforeNextCycle.selector); - - vm.prank(alice); - registry.register( - payload, - uint64(block.timestamp + 2000), // Task expires before next cycle - uint128(1_000_000), - uint128(10 gwei), - uint128(0.5 ether), - 0, - auxData - ); - } - - /// @dev Test to ensure 'register' reverts if payload target address is zero. - function testRegisterRevertsIfPayloadTargetZero() public { - bytes[] memory auxData; - bytes memory payload = createPayload(0, address(0)); // Invalid address: address(0) - - vm.expectRevert(CommonUtils.AddressCannotBeZero.selector); - - vm.prank(alice); - registry.register( - payload, - uint64(block.timestamp + 2250), - uint128(1_000_000), - uint128(10 gwei), - uint128(0.5 ether), - 0, - auxData - ); - } - - /// @dev Test to ensure 'register' reverts if payload target address is EOA. - function testRegisterRevertsIfPayloadTargetEoa() public { - bytes[] memory auxData; - bytes memory payload = createPayload(0, alice); // Invalid address: EOA address being passed - - vm.expectRevert(CommonUtils.AddressCannotBeEOA.selector); - - vm.prank(alice); - registry.register( - payload, - uint64(block.timestamp + 2250), - uint128(1_000_000), - uint128(10 gwei), - uint128(0.5 ether), - 0, - auxData - ); - } - - /// @dev Test to ensure 'register' reverts if 0 is passed as max gas amount. - function testRegisterRevertsIfMaxGasAmountZero() public { - bytes[] memory auxData; - bytes memory payload = createPayload(0, address(erc20Supra)); - - vm.expectRevert(IAutomationCore.InvalidMaxGasAmount.selector); - - vm.prank(alice); - registry.register( - payload, - uint64(block.timestamp + 2250), - uint128(0), // maxGasAmount - uint128(10 gwei), - uint128(0.5 ether), - 0, - auxData - ); - } - - /// @dev Test to ensure 'register' reverts if 0 is passed as gas price cap. - function testRegisterRevertsIfGasPriceCapZero() public { - bytes[] memory auxData; - bytes memory payload = createPayload(0, address(erc20Supra)); - - vm.expectRevert(IAutomationCore.InvalidGasPriceCap.selector); - - vm.prank(alice); - registry.register( - payload, - uint64(block.timestamp + 2250), - uint128(1_000_000), - uint128(0), // gasPriceCap - uint128(0.5 ether), - 0, - auxData - ); - } - - /// @dev Test to ensure 'register' reverts if automation fee cap is less than the estimated automation fee. - function testRegisterRevertsIfAutomationFeeCapLessThanEstimated() public { - bytes[] memory auxData; - bytes memory payload = createPayload(0, address(erc20Supra)); - - vm.expectRevert(IAutomationCore.InsufficientFeeCapForCycle.selector); - - vm.prank(alice); - registry.register( - payload, - uint64(block.timestamp + 2250), - uint128(1_000_000), - uint128(10 gwei), - uint128(0), // automationFeeCapForCycle - 0, - auxData - ); - } - - /// @dev Test to ensure 'register' reverts if gas committed exceeds the registry max gas cap. - function testRegisterRevertsIfGasCommittedExceedsMaxGasCap() public { - bytes[] memory auxData; - bytes memory payload = createPayload(0, address(erc20Supra)); - - vm.expectRevert(IAutomationCore.GasCommittedExceedsMaxGasCap.selector); - - vm.prank(alice); - registry.register( - payload, - uint64(block.timestamp + 2250), - uint128(10_000_001), // Gas exceeds max gas cap - uint128(10 gwei), - uint128(7.01 ether), - 0, - auxData - ); - } - - /// @dev Test to ensure 'register' registers a UST. - function testRegister() public { - bytes[] memory auxData; - bytes memory payload = createPayload(0, address(erc20Supra)); - - vm.startPrank(alice); - erc20Supra.nativeToErc20Supra{value: 5 ether}(); - erc20Supra.approve(address(automationCore), type(uint256).max); - - registry.register( - payload, - uint64(block.timestamp + 2250), - uint128(1_000_000), - uint128(10 gwei), - uint128(0.5 ether), - 4, - auxData - ); - vm.stopPrank(); - - CommonUtils.TaskDetails memory taskMetadata = registry.getTaskDetails(0); - assertTrue(registry.ifTaskExists(0)); - assertEq(registry.totalTasks(), 1); - assertEq(registry.getNextTaskIndex(), 1); - assertEq(automationCore.getGasCommittedForNextCycle(), 1_000_000); - assertEq(automationCore.getTotalDepositedAutomationFees(), 0.5 ether); - assertEq(erc20Supra.balanceOf(address(automationCore)), 0.502 ether); - assertEq(erc20Supra.balanceOf(alice), 4.498 ether); - - assertEq(taskMetadata.maxGasAmount, 1_000_000); - assertEq(taskMetadata.gasPriceCap, 10 gwei); - assertEq(taskMetadata.automationFeeCapForCycle, 0.5 ether); - assertEq(taskMetadata.depositFee, 0.5 ether); - assertEq(taskMetadata.txHash, keccak256("txHash")); - assertEq(taskMetadata.taskIndex, 0); - assertEq(taskMetadata.registrationTime, uint64(block.timestamp)); - assertEq(taskMetadata.expiryTime, uint64(block.timestamp + 2250)); - assertEq(taskMetadata.priority, 0); - assertEq(uint8(taskMetadata.taskType), 0); - assertEq(uint8(taskMetadata.state), 0); - assertEq(taskMetadata.owner, alice); - assertEq(taskMetadata.payloadTx, payload); - assertEq(taskMetadata.auxData, auxData); - } - - /// @dev Test to ensure 'register' emits event 'TaskRegistered'. - function testRegisterEmitsEvent() public { - bytes[] memory auxData; - bytes memory payload = createPayload(0, address(erc20Supra)); - - vm.startPrank(alice); - erc20Supra.nativeToErc20Supra{value: 5 ether}(); - erc20Supra.approve(address(automationCore), type(uint256).max); - - CommonUtils.TaskDetails memory taskMetadata = CommonUtils.TaskDetails( - 1_000_000, - 10 gwei, - 0.5 ether, - 0.5 ether, - keccak256("txHash"), - 0, - uint64(block.timestamp), - uint64(block.timestamp + 2250), - 0, - CommonUtils.TaskType.UST, - CommonUtils.TaskState.PENDING, - alice, - payload, - auxData - ); - - vm.expectEmit(true, true, false, true); - emit AutomationRegistry.TaskRegistered(0, alice, 0.002 ether, 0.5 ether, taskMetadata); - - registry.register( - payload, - uint64(block.timestamp + 2250), - uint128(1_000_000), - uint128(10 gwei), - uint128(0.5 ether), - 0, - auxData - ); - vm.stopPrank(); - } - - // ::::::::::::::::::::::::::::::::::::::::::::::::::::: Tests related to 'registerSystemTask' ::::::::::::::::::::::::::::::::::::::::::::::::::::: - - /// @dev Test to ensure 'registerSystemTask' reverts if caller is not authorized. - function testRegisterSystemTaskRevertsIfUnauthorizedCaller() public { - bytes[] memory auxData; - bytes memory payload = createPayload(0, address(erc20Supra)); - - vm.expectRevert(IAutomationRegistry.UnauthorizedAccount.selector); - - vm.prank(alice); - registry.registerSystemTask( - payload, // payload - uint64(block.timestamp + 2250), // expiryTime - uint128(1_000_000), // maxGasAmount - 2, // priority - auxData // aux data - ); - } - - /// @dev Test to ensure 'registerSystemTask' reverts if automation is not enabled. - function testRegisterSystemTaskRevertsIfAutomationNotEnabled() public { - testGrantAuthorization(); - - vm.prank(admin); - controller.disableAutomation(); - - bytes[] memory auxData; - bytes memory payload = createPayload(0, address(erc20Supra)); - - vm.expectRevert(IAutomationRegistry.AutomationNotEnabled.selector); - - vm.prank(bob); - registry.registerSystemTask( - payload, // payload - uint64(block.timestamp + 2250), // expiryTime - uint128(1_000_000), // maxGasAmount - 2, // priority - auxData // aux data - ); - } - - /// @dev Test to ensure 'registerSystemTask' reverts if registration is disabled. - function testRegisterSystemTaskRevertsIfRegistrationDisabled() public { - testGrantAuthorization(); - - vm.prank(admin); - automationCore.disableRegistration(); - - bytes[] memory auxData; - bytes memory payload = createPayload(0, address(erc20Supra)); - - vm.expectRevert(IAutomationCore.RegistrationDisabled.selector); - - vm.prank(bob); - registry.registerSystemTask( - payload, // payload - uint64(block.timestamp + 2250), // expiryTime - uint128(1_000_000), // maxGasAmount - 2, // priority - auxData // aux data - ); - } - - /// @dev Test to ensure 'registerSystemTask' reverts if task duration is greater than system task duration cap. - function testRegisterSystemTaskRevertsIfInvalidTaskDuration() public { - testGrantAuthorization(); - bytes[] memory auxData; - bytes memory payload = createPayload(0, address(erc20Supra)); - - vm.expectRevert(IAutomationCore.InvalidTaskDuration.selector); - - vm.prank(bob); - registry.registerSystemTask( - payload, - uint64(block.timestamp + 3601), // Invalid task duration - uint128(1_000_000), - 2, - auxData - ); - } - - /// @dev Test to ensure 'registerSystemTask' reverts if gas committed exceeds the system registry max gas cap. - function testRegisterSystemTaskRevertsIfGasCommittedExceedsMaxGasCap() public { - testGrantAuthorization(); - bytes[] memory auxData; - bytes memory payload = createPayload(0, address(erc20Supra)); - - vm.expectRevert(IAutomationCore.GasCommittedExceedsMaxGasCap.selector); - - vm.prank(bob); - registry.registerSystemTask( - payload, - uint64(block.timestamp + 2250), - uint128(5_000_001), // Gas exceeds max gas cap - 2, - auxData - ); - } - - /// @dev Test to ensure 'registerSystemTask' registers a GST. - function testRegisterSystemTask() public { - testGrantAuthorization(); - bytes[] memory auxData; - bytes memory payload = createPayload(0, address(erc20Supra)); - - vm.prank(bob); - registry.registerSystemTask( - payload, // payload - uint64(block.timestamp + 2250), // expiryTime - uint128(1_000_000), // maxGasAmount - 2, // priority - auxData // aux data - ); - - CommonUtils.TaskDetails memory taskMetadata = registry.getTaskDetails(0); - assertTrue(registry.ifTaskExists(0)); - assertTrue(registry.ifSysTaskExists(0)); - assertEq(registry.totalTasks(), 1); - assertEq(registry.totalSystemTasks(), 1); - assertEq(registry.getNextTaskIndex(), 1); - assertEq(automationCore.getSystemGasCommittedForNextCycle(), 1_000_000); - - assertEq(taskMetadata.maxGasAmount, 1_000_000); - assertEq(taskMetadata.gasPriceCap, 0); - assertEq(taskMetadata.automationFeeCapForCycle, 0); - assertEq(taskMetadata.depositFee, 0); - assertEq(taskMetadata.txHash, keccak256("txHash")); - assertEq(taskMetadata.taskIndex, 0); - assertEq(taskMetadata.registrationTime, uint64(block.timestamp)); - assertEq(taskMetadata.expiryTime, uint64(block.timestamp + 2250)); - assertEq(taskMetadata.priority, 2); - assertEq(uint8(taskMetadata.taskType), 1); - assertEq(uint8(taskMetadata.state), 0); - assertEq(taskMetadata.owner, bob); - assertEq(taskMetadata.payloadTx, payload); - assertEq(taskMetadata.auxData, auxData); - } - - /// @dev Test to ensure 'registerSystemTask' emits event 'SystemTaskRegistered'. - function testRegisterSystemTaskEmitsEvent() public { - testGrantAuthorization(); - - bytes[] memory auxData; - bytes memory payload = createPayload(0, address(erc20Supra)); - - CommonUtils.TaskDetails memory taskMetadata = CommonUtils.TaskDetails( - 1_000_000, - 0, - 0, - 0, - keccak256("txHash"), - 0, - uint64(block.timestamp), - uint64(block.timestamp + 2250), - 2, - CommonUtils.TaskType.GST, - CommonUtils.TaskState.PENDING, - bob, - payload, - auxData - ); - - vm.expectEmit(true, true, false, true); - emit AutomationRegistry.SystemTaskRegistered(0, bob, block.timestamp, taskMetadata); - - vm.prank(bob); - registry.registerSystemTask( - payload, // payload - uint64(block.timestamp + 2250), // expiryTime - uint128(1_000_000), // maxGasAmount - 2, // priority - auxData // aux data - ); - } - - // :::::::::::::::::::::::::::::::::::::::::::::::::::::: Tests related to 'cancelTask' :::::::::::::::::::::::::::::::::::::::::::::::::::::: - - /// @dev Test to ensure 'cancelTask' reverts if automation is not enabled. - function testCancelTaskRevertsIfAutomationNotEnabled() public { - vm.prank(admin); - controller.disableAutomation(); - - vm.expectRevert(IAutomationRegistry.AutomationNotEnabled.selector); - - vm.prank(alice); - registry.cancelTask(0); - } - - /// @dev Test to ensure 'cancelTask' reverts if task does not exist. - function testCancelTaskRevertsIfTaskDoesNotExist() public { - vm.expectRevert(IAutomationRegistry.TaskDoesNotExist.selector); - - vm.prank(alice); - registry.cancelTask(0); - } - - /// @dev Test to ensure 'cancelTask' reverts if task type is not UST. - function testCancelTaskRevertsIfTaskTypeNotUST() public { - testRegisterSystemTask(); - vm.expectRevert(IAutomationRegistry.UnsupportedTaskOperation.selector); - - vm.prank(bob); - registry.cancelTask(0); - } - - /// @dev Test to ensure 'cancelTask' reverts if caller is not the task owner. - function testCancelTaskRevertsIfUnauthorizedCaller() public { - testRegister(); - vm.expectRevert(IAutomationRegistry.UnauthorizedAccount.selector); - - vm.prank(bob); - registry.cancelTask(0); - } - - /// @dev Test to ensure 'cancelTask' cancels a UST. - function testCancelTask() public { - testRegister(); - - vm.prank(alice); - registry.cancelTask(0); - - assertFalse(registry.ifTaskExists(0)); - assertEq(registry.totalTasks(), 0); - assertEq(automationCore.getGasCommittedForNextCycle(), 0); - assertEq(automationCore.getTotalDepositedAutomationFees(), 0); - assertEq(erc20Supra.balanceOf(address(automationCore)), 0.252 ether); - assertEq(erc20Supra.balanceOf(alice), 4.748 ether); - } - - /// @dev Test to ensure 'cancelTask' emits event 'TaskCancelled'. - function testCancelTaskEmitsEvent() public { - testRegister(); - - vm.expectEmit(true, true, true, false); - emit AutomationRegistry.TaskCancelled(0, alice, keccak256("txHash")); - - vm.prank(alice); - registry.cancelTask(0); - } - - // :::::::::::::::::::::::::::::::::::::::::::::::::::::: Tests related to 'cancelSystemTask' :::::::::::::::::::::::::::::::::::::::::::::::::::::: - - /// @dev Test to ensure 'cancelSystemTask' reverts if automation is not enabled. - function testCancelSystemTaskRevertsIfAutomationNotEnabled() public { - vm.prank(admin); - controller.disableAutomation(); - - vm.expectRevert(IAutomationRegistry.AutomationNotEnabled.selector); - - vm.prank(alice); - registry.cancelSystemTask(0); - } - - /// @dev Test to ensure 'cancelSystemTask' reverts if task does not exist. - function testCancelSystemTaskRevertsIfTaskDoesNotExist() public { - vm.expectRevert(IAutomationRegistry.TaskDoesNotExist.selector); - - vm.prank(alice); - registry.cancelSystemTask(0); - } - - /// @dev Test to ensure 'cancelSystemTask' reverts if task does not exist in system tasks. - function testCancelSystemTaskRevertsIfSystemTaskDoesNotExist() public { - testRegister(); - vm.expectRevert(IAutomationRegistry.SystemTaskDoesNotExist.selector); - - vm.prank(alice); - registry.cancelSystemTask(0); - } - - /// @dev Test to ensure 'cancelSystemTask' reverts if caller is not the task owner. - function testCancelSystemTaskRevertsIfUnauthorizedCaller() public { - testRegisterSystemTask(); - vm.expectRevert(IAutomationRegistry.UnauthorizedAccount.selector); - - vm.prank(alice); - registry.cancelSystemTask(0); - } - - /// @dev Test to ensure 'cancelSystemTask' cancels a GST. - function testCancelSystemTask() public { - testRegisterSystemTask(); - - vm.prank(bob); - registry.cancelSystemTask(0); - - assertFalse(registry.ifTaskExists(0)); - assertFalse(registry.ifSysTaskExists(0)); - assertEq(registry.totalTasks(), 0); - assertEq(registry.totalSystemTasks(), 0); - assertEq(automationCore.getSystemGasCommittedForNextCycle(), 0); - } - - /// @dev Test to ensure 'cancelSystemTask' emits event 'TaskCancelled'. - function testCancelSystemTaskEmitsEvent() public { - testRegisterSystemTask(); - - vm.expectEmit(true, true, true, false); - emit AutomationRegistry.TaskCancelled(0, bob, keccak256("txHash")); - - vm.prank(bob); - registry.cancelSystemTask(0); - } - - // :::::::::::::::::::::::::::::::::::::::::::::::::::::: Tests related to 'stopTasks' :::::::::::::::::::::::::::::::::::::::::::::::::::::: - - /// @dev Test to ensure 'stopTasks' reverts if automation is not enabled. - function testStopTasksRevertsIfAutomationNotEnabled() public { - vm.prank(admin); - controller.disableAutomation(); - - uint64[] memory taskIndexes; - vm.expectRevert(IAutomationRegistry.AutomationNotEnabled.selector); - - vm.prank(alice); - registry.stopTasks(taskIndexes); - } - - /// @dev Test to ensure 'stopTasks' reverts if input array is empty. - function testStopTasksRevertsIfInputArrayEmpty() public { - uint64[] memory taskIndexes; - vm.expectRevert(IAutomationRegistry.TaskIndexesCannotBeEmpty.selector); - - vm.prank(alice); - registry.stopTasks(taskIndexes); - } - - /// @dev Test to ensure 'stopTasks' reverts if caller is not the task owner. - function testStopTasksRevertsIfUnauthorizedCaller() public { - testRegister(); - - uint64[] memory taskIndexes = new uint64[](1); - taskIndexes[0] = 0; - - vm.expectRevert(IAutomationRegistry.UnauthorizedAccount.selector); - - vm.prank(bob); - registry.stopTasks(taskIndexes); - } - - /// @dev Test to ensure 'stopTasks' reverts if task type is not UST. - function testStopTasksRevertsIfTaskTypeNotUST() public { - testRegisterSystemTask(); - - uint64[] memory taskIndexes = new uint64[](1); - taskIndexes[0] = 0; - - vm.expectRevert(IAutomationRegistry.UnsupportedTaskOperation.selector); - - vm.prank(bob); - registry.stopTasks(taskIndexes); - } - - /// @dev Test to ensure 'stopTasks' does nothing if task does not exist. - function testStopTasksDoesNothingIfTaskDoesNotExist() public { - testRegister(); - - uint64[] memory taskIndexes = new uint64[](1); - taskIndexes[0] = 5; - - vm.prank(alice); - registry.stopTasks(taskIndexes); - - assertEq(registry.totalTasks(), 1); - assertEq(automationCore.getTotalDepositedAutomationFees(), 0.5 ether); - } - - /// @dev Test to ensure 'stopTasks' stops the input UST tasks. - function testStopTasks() public { - testRegister(); - address controllerAddr = registry.automationController(); - - uint64[] memory taskIndexes = new uint64[](1); - taskIndexes[0] = 0; - - vm.warp(2002); - vm.startPrank(vmSigner, vmSigner); - AutomationController(controllerAddr).monitorCycleEnd(); - AutomationController(controllerAddr).processTasks(2, taskIndexes); - vm.stopPrank(); - - assertEq(erc20Supra.balanceOf(address(automationCore)), 0.702 ether); - assertEq(erc20Supra.balanceOf(alice), 4.298 ether); - - vm.prank(alice); - registry.stopTasks(taskIndexes); - - assertFalse(registry.ifTaskExists(0)); - assertEq(registry.totalTasks(), 0); - assertEq(automationCore.getGasCommittedForNextCycle(), 0); - assertEq(automationCore.getTotalDepositedAutomationFees(), 0); - assertEq(erc20Supra.balanceOf(address(automationCore)), 0.18955 ether); - assertEq(erc20Supra.balanceOf(alice), 4.81045 ether); - } - - /// @dev Test to ensure 'stopTasks' emits event 'TasksStopped'. - function testStopTasksEmitsEvent() public { - testRegister(); - address controllerAddr = registry.automationController(); - - uint64[] memory taskIndexes = new uint64[](1); - taskIndexes[0] = 0; - - vm.warp(2002); - vm.startPrank(vmSigner, vmSigner); - AutomationController(controllerAddr).monitorCycleEnd(); - AutomationController(controllerAddr).processTasks(2, taskIndexes); - vm.stopPrank(); - - LibRegistry.TaskStopped[] memory stoppedTasks = new LibRegistry.TaskStopped[](1); - stoppedTasks[0] = LibRegistry.TaskStopped(0, 0.5 ether, 0.01245 ether, keccak256("txHash")); - - vm.expectEmit(true, true, false, false); - emit AutomationRegistry.TasksStopped(stoppedTasks, alice); - - vm.prank(alice); - registry.stopTasks(taskIndexes); - } - - // :::::::::::::::::::::::::::::::::::::::::::::::::::::: Tests related to 'stopSystemTasks' :::::::::::::::::::::::::::::::::::::::::::::::::::::: - - /// @dev Test to ensure 'stopSystemTasks' reverts if automation is not enabled. - function testStopSystemTasksRevertsIfAutomationNotEnabled() public { - vm.prank(admin); - controller.disableAutomation(); - - uint64[] memory taskIndexes; - vm.expectRevert(IAutomationRegistry.AutomationNotEnabled.selector); - - vm.prank(alice); - registry.stopSystemTasks(taskIndexes); - } - - /// @dev Test to ensure 'stopSystemTasks' reverts if input array is empty. - function testStopSystemTasksRevertsIfInputArrayEmpty() public { - uint64[] memory taskIndexes; - vm.expectRevert(IAutomationRegistry.TaskIndexesCannotBeEmpty.selector); - - vm.prank(alice); - registry.stopSystemTasks(taskIndexes); - } - - /// @dev Test to ensure 'stopSystemTasks' reverts if caller is not the task owner. - function testStopSystemTasksRevertsIfUnauthorizedCaller() public { - testRegisterSystemTask(); - - uint64[] memory taskIndexes = new uint64[](1); - taskIndexes[0] = 0; - - vm.expectRevert(IAutomationRegistry.UnauthorizedAccount.selector); - - vm.prank(alice); - registry.stopSystemTasks(taskIndexes); - } - - /// @dev Test to ensure 'stopSystemTasks' reverts if task type is not GST. - function testStopSystemTasksRevertsIfTaskTypeNotGST() public { - testRegister(); - - uint64[] memory taskIndexes = new uint64[](1); - taskIndexes[0] = 0; - - vm.expectRevert(IAutomationRegistry.UnsupportedTaskOperation.selector); - - vm.prank(alice); - registry.stopSystemTasks(taskIndexes); - } - - /// @dev Test to ensure 'stopSystemTasks' does nothing if task does not exist. - function testStopSystemTasksDoesNothingIfTaskDoesNotExist() public { - testRegisterSystemTask(); - - uint64[] memory taskIndexes = new uint64[](1); - taskIndexes[0] = 5; - - vm.prank(alice); - registry.stopSystemTasks(taskIndexes); - - assertEq(registry.totalTasks(), 1); - assertEq(registry.totalSystemTasks(), 1); - } - - /// @dev Test to ensure 'stopSystemTasks' stops the input GST tasks. - function testStopSystemTasks() public { - testRegisterSystemTask(); - address controllerAddr = registry.automationController(); - - uint64[] memory taskIndexes = new uint64[](1); - taskIndexes[0] = 0; - - vm.warp(2002); - vm.prank(vmSigner, vmSigner); - AutomationController(controllerAddr).monitorCycleEnd(); - - vm.prank(vmSigner); - AutomationController(controllerAddr).processTasks(2, taskIndexes); - - vm.prank(bob); - registry.stopSystemTasks(taskIndexes); - - assertFalse(registry.ifTaskExists(0)); - assertFalse(registry.ifSysTaskExists(0)); - assertEq(registry.totalTasks(), 0); - assertEq(registry.totalSystemTasks(), 0); - assertEq(automationCore.getSystemGasCommittedForNextCycle(), 1000000); - } - - /// @dev Test to ensure 'stopSystemTasks' emits event 'TasksStopped'. - function testStopSystemTasksEmitsEvent() public { - testRegisterSystemTask(); - address controllerAddr = registry.automationController(); - - uint64[] memory taskIndexes = new uint64[](1); - taskIndexes[0] = 0; - - vm.warp(2002); - vm.prank(vmSigner, vmSigner); - AutomationController(controllerAddr).monitorCycleEnd(); - - vm.prank(vmSigner); - AutomationController(controllerAddr).processTasks(2, taskIndexes); - - LibRegistry.TaskStopped[] memory stoppedTasks = new LibRegistry.TaskStopped[](1); - stoppedTasks[0] = LibRegistry.TaskStopped(0, 0, 0, keccak256("txHash")); - - vm.expectEmit(true, true, false, false); - emit AutomationRegistry.TasksStopped(stoppedTasks, bob); - - vm.prank(bob); - registry.stopSystemTasks(taskIndexes); - } - - /// @dev Test to ensure 'removeTask' reverts if caller is not AutomationController. - function testRemoveTaskRevertsIfCallerNotAutomationController() public { - vm.expectRevert(IAutomationRegistry.CallerNotController.selector); - - vm.prank(address(automationCore)); - registry.removeTask(0, false); - } - - /// @dev Test to ensure 'updateTaskState' reverts if caller is not AutomationController. - function testUpdateTaskStateRevertsIfCallerNotAutomationController() public { - vm.expectRevert(IAutomationRegistry.CallerNotController.selector); - - vm.prank(address(automationCore)); - registry.updateTaskState(0, CommonUtils.TaskState.ACTIVE); - } - - /// @dev Test to ensure 'updateTasks' reverts if caller is not AutomationController. - function testUpdateTasksRevertsIfCallerNotAutomationController() public { - vm.expectRevert(IAutomationRegistry.CallerNotController.selector); - - vm.prank(address(automationCore)); - registry.updateTaskIds(CommonUtils.CycleState.STARTED); - } - - /// @dev Test to ensure 'refundDepositAndDrop' reverts if caller is not AutomationController. - function testRefundDepositAndDropRevertsIfCallerNotAutomationController() public { - vm.expectRevert(IAutomationRegistry.CallerNotController.selector); - - vm.prank(address(automationCore)); - registry.refundDepositAndDrop( - 0, - alice, - 0.01 ether, - 0.1 ether - ); - } -} \ No newline at end of file diff --git a/solidity/supra_contracts/test/BaseDiamondTest.t.sol b/solidity/supra_contracts/test/BaseDiamondTest.t.sol new file mode 100644 index 0000000000..490e1d6b27 --- /dev/null +++ b/solidity/supra_contracts/test/BaseDiamondTest.t.sol @@ -0,0 +1,101 @@ +// SPDX-License-Identifier: MIT +pragma solidity 0.8.27; + +import {Test} from "forge-std/Test.sol"; +import {ERC20Supra} from "../src/ERC20Supra.sol"; +import {Diamond} from "../src/Diamond.sol"; +import {RegistryFacet} from "../src/facets/RegistryFacet.sol"; +import {ConfigFacet} from "../src/facets/ConfigFacet.sol"; +import {Deployment, InitParams, LibDiamondUtils} from "../src/libraries/LibDiamondUtils.sol"; +import {LibUtils} from "../src/libraries/LibUtils.sol"; + +abstract contract BaseDiamondTest is Test { + ERC20Supra erc20Supra; // ERC20Supra contract + Diamond diamond; // Diamond instance + address diamondAddr; // Diamond address + + InitParams defaultParams; // Default initialization parameters + Deployment deployment; // Struct containing deployed contract addresses + + /// @dev Address of the transaction hash precompile. + address constant TX_HASH_PRECOMPILE = 0x0000000000000000000000000000000053555001; + + address admin = address(0xA11CE); + address vmSigner = address(0x53555000); + address alice = address(0x123); + address bob = address(0x456); + + /// @dev Sets up initial state for testing. + /// @dev Sets balance of 'alice' to 100 ether. + /// @dev Deploys all the contracts and initializes the Diamond with required parameters. + function setUp() public { + vm.deal(alice, 100 ether); + + vm.startPrank(admin); + erc20Supra = new ERC20Supra(admin); + + defaultParams = LibDiamondUtils.defaultInitParams(); + deployment = LibDiamondUtils.deploy(admin); + LibDiamondUtils.executeCut(vmSigner, address(erc20Supra), defaultParams, deployment); + + diamond = Diamond(payable(deployment.diamond)); + diamondAddr = deployment.diamond; + + ConfigFacet(diamondAddr).grantAuthorization(bob); + + vm.stopPrank(); + + vm.mockCall( + TX_HASH_PRECOMPILE, + bytes(""), + abi.encode(keccak256("txHash")) + ); + } + + /// @dev Helper function to register a UST. + function registerUST() internal { + bytes[] memory auxData; + bytes memory payload = createPayload(0, address(erc20Supra)); + + vm.startPrank(alice); + erc20Supra.nativeToErc20Supra{value: 5 ether}(); + erc20Supra.approve(diamondAddr, type(uint256).max); + + RegistryFacet(diamondAddr).register( + payload, + uint64(block.timestamp + 2250), + uint128(1_000_000), + uint128(10 gwei), + uint128(0.5 ether), + 2, + auxData + ); + vm.stopPrank(); + } + + /// @dev Helper function to return payload. + /// @param _value Value to be sent along with the transaction. + /// @param _target Address of the destination smart contract. + function createPayload(uint128 _value, address _target) internal pure returns (bytes memory) { + LibUtils.AccessListEntry[] memory accessList = new LibUtils.AccessListEntry[](2); + + bytes32[] memory keys = new bytes32[](2); + keys[0] = bytes32(uint256(0)); + keys[1] = bytes32(uint256(1)); + + accessList[0] = LibUtils.AccessListEntry({ + addr: address(0x1111), + storageKeys: keys + }); + + accessList[1] = LibUtils.AccessListEntry({ + addr: address(0x2222), + storageKeys: keys + }); + + bytes memory callData = abi.encodeCall(ERC20Supra.erc20SupraToNative, 100); + bytes memory payload = abi.encode(_value, _target, callData, accessList); + + return payload; + } +} diff --git a/solidity/supra_contracts/test/BlockMeta.t.sol b/solidity/supra_contracts/test/BlockMeta.t.sol index f827763b22..d813e6faaa 100644 --- a/solidity/supra_contracts/test/BlockMeta.t.sol +++ b/solidity/supra_contracts/test/BlockMeta.t.sol @@ -6,7 +6,7 @@ import {ERC1967Proxy} from "../lib/openzeppelin-contracts/contracts/proxy/ERC196 import {OwnableUpgradeable} from"../lib/openzeppelin-contracts-upgradeable/contracts/access/Ownable2StepUpgradeable.sol"; import {BlockMeta} from "../src/BlockMeta.sol"; import {Counter} from "./Counter.sol"; -import {CommonUtils} from "../src/CommonUtils.sol"; +import {LibUtils} from "../src/libraries/LibUtils.sol"; contract BlockMetaTest is Test { BlockMeta blockMeta; // BlockMeta instance on proxy address @@ -86,14 +86,14 @@ contract BlockMetaTest is Test { /// @dev Test to ensure 'register' reverts if address(0) is passed. function testRegisterRevertsIfAddressZero() public { - vm.expectRevert(CommonUtils.AddressCannotBeZero.selector); + vm.expectRevert(LibUtils.AddressCannotBeZero.selector); register(address(0), selector); } /// @dev Test to ensure 'register' reverts if EOA is passed. function testRegisterRevertsIfEOA() public { - vm.expectRevert(CommonUtils.AddressCannotBeEOA.selector); + vm.expectRevert(LibUtils.AddressCannotBeEOA.selector); register(alice, selector); } @@ -282,7 +282,7 @@ contract BlockMetaTest is Test { executionOrder[0] = packExecution(counterAddress, selector); executionOrder[1] = packExecution(address(0), selector); - vm.expectRevert(CommonUtils.AddressCannotBeZero.selector); + vm.expectRevert(LibUtils.AddressCannotBeZero.selector); vm.prank(admin); blockMeta.updateExecutionOrder(executionOrder); @@ -294,7 +294,7 @@ contract BlockMetaTest is Test { executionOrder[0] = packExecution(counterAddress, selector); executionOrder[1] = packExecution(alice, selector); - vm.expectRevert(CommonUtils.AddressCannotBeEOA.selector); + vm.expectRevert(LibUtils.AddressCannotBeEOA.selector); vm.prank(admin); blockMeta.updateExecutionOrder(executionOrder); diff --git a/solidity/supra_contracts/test/ConfigFacet.t.sol b/solidity/supra_contracts/test/ConfigFacet.t.sol new file mode 100644 index 0000000000..b28d8a77f0 --- /dev/null +++ b/solidity/supra_contracts/test/ConfigFacet.t.sol @@ -0,0 +1,436 @@ +// SPDX-License-Identifier: MIT +pragma solidity 0.8.27; + +import {BaseDiamondTest} from "./BaseDiamondTest.t.sol"; +import {ERC20Supra} from "../src/ERC20Supra.sol"; +import {ConfigFacet} from "../src/facets/ConfigFacet.sol"; +import {IConfigFacet} from "../src/interfaces/IConfigFacet.sol"; +import {LibUtils} from "../src/libraries/LibUtils.sol"; +import {Config} from "../src/libraries/LibAppStorage.sol"; + +contract ConfigFacetTest is BaseDiamondTest { + + // :::::::::::::::::::::::::::::::::::::::::::::::::::: Tests related to 'grantAuthorization' :::::::::::::::::::::::::::::::::::::::::::::::::::: + + /// @dev Test to ensure 'grantAuthorization' grants authorization to an address. + function testGrantAuthorization() public { + vm.prank(admin); + ConfigFacet(diamondAddr).grantAuthorization(alice); + + // assertTrue(ConfigFacet(diamondAddr).isAuthorizedSubmitter(alice)); + } + + /// @dev Test to ensure 'grantAuthorization' emits event 'AuthorizationGranted'. + function testGrantAuthorizationEmitsEvent() public { + vm.expectEmit(true, true, false, false); + emit ConfigFacet.AuthorizationGranted(alice, block.timestamp); + + vm.prank(admin); + ConfigFacet(diamondAddr).grantAuthorization(alice); + } + + /// @dev Test to ensure 'grantAuthorization' reverts if address is already authorized. + function testGrantAuthorizationRevertsIfAlreadyAuthorised() public { + // Grant authorization to alice + testGrantAuthorization(); + + vm.expectRevert(IConfigFacet.AddressAlreadyExists.selector); + + vm.prank(admin); + ConfigFacet(diamondAddr).grantAuthorization(alice); + } + + /// @dev Test to ensure 'grantAuthorization' reverts if caller is not owner. + function testGrantAuthorizationRevertsIfNotOwner() public { + vm.expectRevert(bytes("LibDiamond: Must be contract owner")); + + vm.prank(alice); + ConfigFacet(diamondAddr).grantAuthorization(alice); + } + + // ::::::::::::::::::::::::::::::::::::::::::::::::::: Tests related to 'revokeAuthorization' ::::::::::::::::::::::::::::::::::::::::::::::::::: + + /// @dev Test to ensure 'revokeAuthorization' revokes authorization from an address. + function testRevokeAuthorization() public { + // Grant authorization to alice + testGrantAuthorization(); + + // Revoke authorization + vm.prank(admin); + ConfigFacet(diamondAddr).revokeAuthorization(alice); + + // assertFalse(ConfigFacet(diamondAddr).isAuthorizedSubmitter(alice)); + } + + /// @dev Test to ensure 'revokeAuthorization' emits event 'AuthorizationRevoked'. + function testRevokeAuthorizationEmitsEvent() public { + // Grant authorization to alice + testGrantAuthorization(); + + vm.expectEmit(true, true, false, false); + emit ConfigFacet.AuthorizationRevoked(alice, block.timestamp); + + vm.prank(admin); + ConfigFacet(diamondAddr).revokeAuthorization(alice); + } + + /// @dev Test to ensure 'revokeAuthorization' reverts if address is not authorised. + function testRevokeAuthorizationRevertsIfNotAuthorised() public { + vm.expectRevert(IConfigFacet.AddressDoesNotExist.selector); + + vm.prank(admin); + ConfigFacet(diamondAddr).revokeAuthorization(alice); + } + + /// @dev Test to ensure 'revokeAuthorization' reverts if caller is not owner. + function testRevokeAuthorizationRevertsIfNotOwner() public { + vm.expectRevert(bytes("LibDiamond: Must be contract owner")); + + vm.prank(alice); + ConfigFacet(diamondAddr).revokeAuthorization(alice); + } + + // :::::::::::::::::::::::::::::::::::::::::::::::::::::: Tests related to 'disableRegistration' :::::::::::::::::::::::::::::::::::::::::::::::::::::: + + /// @dev Test to ensure 'disableRegistration' disables the registration. + function testDisableRegistration() public { + vm.prank(admin); + ConfigFacet(diamondAddr).disableRegistration(); + + assertFalse(ConfigFacet(diamondAddr).isRegistrationEnabled()); + } + + /// @dev Test to ensure 'disableRegistration' emits event 'TaskRegistrationDisabled'. + function testDisableRegistrationEmitsEvent() public { + vm.expectEmit(true, false, false, false); + emit ConfigFacet.TaskRegistrationDisabled(false); + + testDisableRegistration(); + } + + /// @dev Test to ensure 'disableRegistration' reverts if registration is already disabled. + function testDisableRegistrationRevertsIfAlreadyDisabled() public { + // Disable registration + testDisableRegistration(); + + // Disable again → revert + vm.expectRevert(IConfigFacet.AlreadyDisabled.selector); + + vm.prank(admin); + ConfigFacet(diamondAddr).disableRegistration(); + } + + /// @dev Test to ensure 'disableRegistration' reverts if caller is not owner. + function testDisableRegistrationRevertsIfNotOwner() public { + vm.expectRevert(bytes("LibDiamond: Must be contract owner")); + + vm.prank(alice); + ConfigFacet(diamondAddr).disableRegistration(); + } + + // :::::::::::::::::::::::::::::::::::::::::::::::::::::: Tests related to 'enableRegistration' :::::::::::::::::::::::::::::::::::::::::::::::::::::: + + /// @dev Test to ensure 'enableRegistration' enables the registration. + function testEnableRegistration() public { + // Disable registration + testDisableRegistration(); + + // Enable registration + vm.prank(admin); + ConfigFacet(diamondAddr).enableRegistration(); + + assertTrue(ConfigFacet(diamondAddr).isRegistrationEnabled()); + } + + /// @dev Test to ensure 'enableRegistration' emits event 'TaskRegistrationEnabled'. + function testEnableRegistrationEmitsEvent() public { + // Disable registration + testDisableRegistration(); + + vm.expectEmit(true, false, false, false); + emit ConfigFacet.TaskRegistrationEnabled(true); + + // Enable registration + vm.prank(admin); + ConfigFacet(diamondAddr).enableRegistration(); + } + + /// @dev Test to ensure 'enableRegistration' reverts if registration is already enabled. + function testEnableRegistrationRevertsIfAlreadyEnabled() public { + // Already enabled in initialize() + vm.expectRevert(IConfigFacet.AlreadyEnabled.selector); + + vm.prank(admin); + ConfigFacet(diamondAddr).enableRegistration(); + } + + /// @dev Test to ensure 'enableRegistration' reverts if caller is not owner. + function testEnableRegistrationRevertsIfNotOwner() public { + vm.expectRevert(bytes("LibDiamond: Must be contract owner")); + + vm.prank(alice); + ConfigFacet(diamondAddr).enableRegistration(); + } + + // :::::::::::::::::::::::::::::::::::::::::::::::::::::: Tests related to 'setVmSigner' :::::::::::::::::::::::::::::::::::::::::::::::::::::: + + /// @dev Test to ensure 'setVmSigner' updates the VM Signer address. + function testSetVmSigner() public { + address newVmSigner = address(0x100); + + vm.prank(admin); + ConfigFacet(diamondAddr).setVmSigner(newVmSigner); + + assertEq(ConfigFacet(diamondAddr).getVmSigner(), newVmSigner); + } + + /// @dev Test to ensure 'setVmSigner' emits event 'VmSignerUpdated'. + function testSetVmSignerEmitsEvent() public { + address oldVmSigner = ConfigFacet(diamondAddr).getVmSigner(); + address newVmSigner = address(0x100); + + vm.expectEmit(true, true, false, false); + emit ConfigFacet.VmSignerUpdated(oldVmSigner, newVmSigner); + + vm.prank(admin); + ConfigFacet(diamondAddr).setVmSigner(newVmSigner); + } + + /// @dev Test to ensure 'setVmSigner' reverts if zero address is passed. + function testSetVmSignerRevertsIfZeroAddress() public { + vm.expectRevert(IConfigFacet.AddressCannotBeZero.selector); + + vm.prank(admin); + ConfigFacet(diamondAddr).setVmSigner(address(0)); + } + + /// @dev Test to ensure 'setVmSigner' reverts if caller is not owner. + function testSetVmSignerRevertsIfNotOwner() public { + vm.expectRevert(bytes("LibDiamond: Must be contract owner")); + + vm.prank(alice); + ConfigFacet(diamondAddr).setVmSigner(address(0x100)); + } + + // :::::::::::::::::::::::::::::::::::::::::::::::::::::: Tests related to 'setErc20Supra' :::::::::::::::::::::::::::::::::::::::::::::::::::::: + + /// @dev Test to ensure 'setErc20Supra' updates the ERC20Supra address. + function testSetErc20Supra() public { + ERC20Supra supraErc20 = new ERC20Supra(msg.sender); + + vm.prank(admin); + ConfigFacet(diamondAddr).setErc20Supra(address(supraErc20)); + + assertEq(ConfigFacet(diamondAddr).erc20Supra(), address(supraErc20)); + } + + /// @dev Test to ensure 'setErc20Supra' emits event 'Erc20SupraUpdated'. + function testSetErc20SupraEmitsEvent() public { + address oldAddr = ConfigFacet(diamondAddr).erc20Supra(); + ERC20Supra supraErc20 = new ERC20Supra(msg.sender); + + vm.expectEmit(true, true, false, false); + emit ConfigFacet.Erc20SupraUpdated(oldAddr, address(supraErc20)); + + vm.prank(admin); + ConfigFacet(diamondAddr).setErc20Supra(address(supraErc20)); + } + + /// @dev Test to ensure 'setErc20Supra' reverts if zero address is passed. + function testSetErc20SupraRevertsIfZeroAddress() public { + vm.expectRevert(IConfigFacet.AddressCannotBeZero.selector); + + vm.prank(admin); + ConfigFacet(diamondAddr).setErc20Supra(address(0)); + } + + /// @dev Test to ensure 'setErc20Supra' reverts if EOA is passed. + function testSetErc20SupraRevertsIfEoa() public { + vm.expectRevert(LibUtils.AddressCannotBeEOA.selector); + + vm.prank(admin); + ConfigFacet(diamondAddr).setErc20Supra(alice); + } + + /// @dev Test to ensure 'setErc20Supra' reverts if caller is not owner. + function testSetErc20SupraRevertsIfNotOwner() public { + ERC20Supra supraErc20 = new ERC20Supra(msg.sender); + + vm.expectRevert(bytes("LibDiamond: Must be contract owner")); + + vm.prank(alice); + ConfigFacet(diamondAddr).setErc20Supra(address(supraErc20)); + } + + // :::::::::::::::::::::::::::::::::::::::::::::::::::::: Tests related to 'withdrawFees' :::::::::::::::::::::::::::::::::::::::::::::::::::::: + + /// @dev Test to ensure 'withdrawFees' reverts if amount is zero. + function testWithdrawFeesRevertsIfAmountZero() public { + vm.prank(admin); + + vm.expectRevert(IConfigFacet.InvalidAmount.selector); + ConfigFacet(diamondAddr).withdrawFees(0, admin); + } + + /// @dev Test to ensure 'withdrawFees' reverts if recipient address is zero. + function testWithdrawFeesRevertsIfRecipientAddressZero() public { + vm.prank(admin); + + vm.expectRevert(IConfigFacet.AddressCannotBeZero.selector); + ConfigFacet(diamondAddr).withdrawFees(1 ether, address(0)); + } + + /// @dev Test to ensure 'withdrawFees' reverts if contract has insufficient balance. + function testWithdrawFeesRevertsIfInsufficientBalance() public { + vm.expectRevert(IConfigFacet.InsufficientBalance.selector); + + vm.prank(admin); + ConfigFacet(diamondAddr).withdrawFees(1 ether, admin); + } + + /// @dev Test to ensure 'withdrawFees' reverts if request amount exceeds the locked balance. + function testWithdrawFeesRevertsIfRequestExceedsLockedBalance() public { + registerUST(); + + vm.expectRevert(IConfigFacet.RequestExceedsLockedBalance.selector); + + vm.prank(admin); + ConfigFacet(diamondAddr).withdrawFees(0.04 ether, admin); + } + + /// @dev Test to ensure 'withdrawFees' reverts if caller is not owner. + function testWithdrawFeesRevertsIfNotOwner() public { + vm.expectRevert(bytes("LibDiamond: Must be contract owner")); + + vm.prank(alice); + ConfigFacet(diamondAddr).withdrawFees(1 ether, admin); + } + + /// @dev Test to ensure 'withdrawFees' withdraws the requested amount and updates the balance. + function testWithdrawFees() public { + registerUST(); + + assertEq(erc20Supra.balanceOf(admin), 0); + assertEq(erc20Supra.balanceOf(diamondAddr), 0.502 ether); + + vm.prank(admin); + ConfigFacet(diamondAddr).withdrawFees(0.002 ether, admin); + + assertEq(erc20Supra.balanceOf(admin), 0.002 ether); + assertEq(erc20Supra.balanceOf(diamondAddr), 0.5 ether); + } + + /// @dev Test to ensure 'withdrawFees' emits event 'RegistryFeeWithdrawn'. + function testWithdrawFeesEmitsEvent() public { + registerUST(); + + vm.expectEmit(true, true, false, false); + emit ConfigFacet.RegistryFeeWithdrawn(admin, 0.002 ether); + + vm.prank(admin); + ConfigFacet(diamondAddr).withdrawFees(0.002 ether, admin); + } + + // :::::::::::::::::::::::::::::::::::::::::::::::::::::: Tests related to 'updateConfigBuffer' :::::::::::::::::::::::::::::::::::::::::::::::::::::: + + /// @dev Helper function that returns a valid config. + function validConfig() private pure returns (Config memory cfg) { + cfg = Config({ + registryMaxGasCap: 10_000_000, + sysRegistryMaxGasCap: 5_000_000, + automationBaseFeeWeiPerSec: 0.001 ether, + flatRegistrationFeeWei: 0.002 ether, + congestionBaseFeeWeiPerSec: 0.002 ether, + taskDurationCapSecs: 3600, + sysTaskDurationCapSecs: 3600, + cycleDurationSecs: 2000, + taskCapacity: 500, + sysTaskCapacity: 500, + congestionThresholdPercentage: 55, + congestionExponent: 3 + }); + } + + /// @dev Test to ensure 'updateConfigBuffer' updates the config buffer. + function testUpdateConfigBuffer() public { + Config memory cfg = validConfig(); + + vm.prank(admin); + ConfigFacet(diamondAddr).updateConfigBuffer( + cfg.taskDurationCapSecs, + cfg.registryMaxGasCap, + cfg.automationBaseFeeWeiPerSec, + cfg.flatRegistrationFeeWei, + cfg.congestionThresholdPercentage, + cfg.congestionBaseFeeWeiPerSec, + cfg.congestionExponent, + cfg.taskCapacity, + cfg.cycleDurationSecs, + cfg.sysTaskDurationCapSecs, + cfg.sysRegistryMaxGasCap, + cfg.sysTaskCapacity + ); + + // Pending config should be updated + Config memory configBuffer = ConfigFacet(diamondAddr).getConfigBuffer(); + assertEq(configBuffer.taskDurationCapSecs, cfg.taskDurationCapSecs); + assertEq(configBuffer.registryMaxGasCap, cfg.registryMaxGasCap); + assertEq(configBuffer.automationBaseFeeWeiPerSec, cfg.automationBaseFeeWeiPerSec); + assertEq(configBuffer.flatRegistrationFeeWei, cfg.flatRegistrationFeeWei); + assertEq(configBuffer.congestionThresholdPercentage, cfg.congestionThresholdPercentage); + assertEq(configBuffer.congestionBaseFeeWeiPerSec, cfg.congestionBaseFeeWeiPerSec); + assertEq(configBuffer.congestionExponent, cfg.congestionExponent); + assertEq(configBuffer.taskCapacity, cfg.taskCapacity); + assertEq(configBuffer.cycleDurationSecs, cfg.cycleDurationSecs); + assertEq(configBuffer.sysTaskDurationCapSecs, cfg.sysTaskDurationCapSecs); + assertEq(configBuffer.sysRegistryMaxGasCap, cfg.sysRegistryMaxGasCap); + assertEq(configBuffer.sysTaskCapacity, cfg.sysTaskCapacity); + } + + /// @dev Test to ensure 'updateConfigBuffer' emits event 'ConfigBufferUpdated'. + function testUpdateConfigBufferEmitsEvent() public { + Config memory cfg = validConfig(); + + vm.expectEmit(true, false, false, false); + emit ConfigFacet.ConfigBufferUpdated(cfg); + + vm.prank(admin); + ConfigFacet(diamondAddr).updateConfigBuffer( + cfg.taskDurationCapSecs, + cfg.registryMaxGasCap, + cfg.automationBaseFeeWeiPerSec, + cfg.flatRegistrationFeeWei, + cfg.congestionThresholdPercentage, + cfg.congestionBaseFeeWeiPerSec, + cfg.congestionExponent, + cfg.taskCapacity, + cfg.cycleDurationSecs, + cfg.sysTaskDurationCapSecs, + cfg.sysRegistryMaxGasCap, + cfg.sysTaskCapacity + ); + } + + /// @dev Test to ensure 'updateConfigBuffer' reverts if caller is not owner. + function testUpdateConfigBufferRevertsIfNotOwner() public { + Config memory cfg = validConfig(); + + vm.expectRevert(bytes("LibDiamond: Must be contract owner")); + + vm.prank(alice); + ConfigFacet(diamondAddr).updateConfigBuffer( + cfg.taskDurationCapSecs, + cfg.registryMaxGasCap, + cfg.automationBaseFeeWeiPerSec, + cfg.flatRegistrationFeeWei, + cfg.congestionThresholdPercentage, + cfg.congestionBaseFeeWeiPerSec, + cfg.congestionExponent, + cfg.taskCapacity, + cfg.cycleDurationSecs, + cfg.sysTaskDurationCapSecs, + cfg.sysRegistryMaxGasCap, + cfg.sysTaskCapacity + ); + } +} \ No newline at end of file diff --git a/solidity/supra_contracts/test/CoreFacet.t.sol b/solidity/supra_contracts/test/CoreFacet.t.sol new file mode 100644 index 0000000000..f13940506c --- /dev/null +++ b/solidity/supra_contracts/test/CoreFacet.t.sol @@ -0,0 +1,440 @@ +// SPDX-License-Identifier: MIT +pragma solidity 0.8.27; + +import {BaseDiamondTest} from "./BaseDiamondTest.t.sol"; +import {ERC20Supra} from "../src/ERC20Supra.sol"; +import {RegistryFacet} from "../src/facets/RegistryFacet.sol"; +import {CoreFacet} from "../src/facets/CoreFacet.sol"; +import {ICoreFacet} from "../src/interfaces/ICoreFacet.sol"; +import {LibUtils} from "../src/libraries/LibUtils.sol"; +import {LibCore} from "../src/libraries/LibCore.sol"; +import {Deployment, InitParams, LibDiamondUtils} from "../src/libraries/LibDiamondUtils.sol"; + +contract CoreFacetTest is BaseDiamondTest { + + /// @dev Test to ensure 'monitorCycleEnd' reverts if tx.origin is not VM Signer. + function testMonitorCycleEndRevertsIfTxOriginNotVm() public { + vm.expectRevert(ICoreFacet.CallerNotVmSigner.selector); + + vm.prank(vmSigner); + CoreFacet(diamondAddr).monitorCycleEnd(); + } + + /// @dev Test to ensure 'monitorCycleEnd' does nothing before cycle expiry. + function testMonitorCycleEndDoesNothingBeforeCycleExpiry() public { + (uint64 indexBefore, uint64 startBefore, uint64 durationBefore, LibUtils.CycleState stateBefore) = CoreFacet(diamondAddr).getCycleInfo(); + + vm.prank(vmSigner, vmSigner); + CoreFacet(diamondAddr).monitorCycleEnd(); + + (uint64 indexAfter, uint64 startAfter, uint64 durationAfter, LibUtils.CycleState stateAfter) = CoreFacet(diamondAddr).getCycleInfo(); + + assertEq(indexAfter, indexBefore); + assertEq(startAfter, startBefore); + assertEq(durationAfter, durationBefore); + assertEq(uint8(stateAfter), uint8(stateBefore)); + } + + /// @dev Test to ensure 'monitorCycleEnd' does nothing if state is not STARTED. + function testMonitorCycleEndDoesNothingIfNotStarted() public { + vm.startPrank(admin); + InitParams memory initParams = InitParams({ + taskDurationCapSecs: 3600, + registryMaxGasCap: 10_000_000, + automationBaseFeeWeiPerSec: 0.001 ether, + flatRegistrationFeeWei: 0.002 ether, + congestionThresholdPercentage: 50, + congestionBaseFeeWeiPerSec: 0.002 ether, + congestionExponent: 2, + taskCapacity: 500, + cycleDurationSecs: 2000, + sysTaskDurationCapSecs: 3600, + sysRegistryMaxGasCap: 5_000_000, + sysTaskCapacity: 500, + registrationEnabled: false, + automationEnabled: false + }); + + Deployment memory deployment = LibDiamondUtils.deploy(admin); + LibDiamondUtils.executeCut(vmSigner, address(erc20Supra), initParams, deployment); + + address diamondAddr = deployment.diamond; + vm.stopPrank(); + + (uint64 indexBefore, uint64 startBefore, uint64 durationBefore, LibUtils.CycleState stateBefore) = CoreFacet(diamondAddr).getCycleInfo(); + assertEq(uint8(stateBefore), uint8(LibUtils.CycleState.READY)); + + vm.warp(startBefore + durationBefore); + + vm.prank(vmSigner, vmSigner); + CoreFacet(diamondAddr).monitorCycleEnd(); + + (uint64 indexAfter, uint64 startAfter, uint64 durationAfter, LibUtils.CycleState stateAfter) = CoreFacet(diamondAddr).getCycleInfo(); + + assertEq(indexAfter, indexBefore); + assertEq(startAfter, startBefore); + assertEq(durationAfter, durationBefore); + assertEq(uint8(stateAfter), uint8(stateBefore)); + } + + /// @dev Test to ensure 'monitorCycleEnd' moves cycle state to READY if automation is disabled and no tasks exist. + function testMonitorCycleEndWhenAutomationDisabledNoTasks() public { + // Disable automation + vm.prank(admin); + CoreFacet(diamondAddr).disableAutomation(); + + assertFalse(CoreFacet(diamondAddr).isAutomationEnabled()); + + (uint64 indexBefore, uint64 startBefore, uint64 durationBefore, LibUtils.CycleState stateBefore) = CoreFacet(diamondAddr).getCycleInfo(); + vm.warp(startBefore + durationBefore); + + vm.expectEmit(true, true, false, true); + emit LibCore.AutomationCycleEvent( + indexBefore, + LibUtils.CycleState.READY, + startBefore, + durationBefore, + stateBefore + ); + + vm.prank(vmSigner, vmSigner); + CoreFacet(diamondAddr).monitorCycleEnd(); + + (uint64 indexAfter, uint64 startAfter, uint64 durationAfter, LibUtils.CycleState stateAfter) = CoreFacet(diamondAddr).getCycleInfo(); + + assertEq(indexAfter, indexBefore); + assertEq(startAfter, startBefore); + assertEq(durationAfter, durationBefore); + assertEq(uint8(stateAfter), uint8(LibUtils.CycleState.READY)); + } + + /// @dev Test to ensure 'monitorCycleEnd' moves cycle state to STARTED if automation is enabled and no tasks exist. + function testMonitorCycleEndWhenAutomationEnabledNoTasks() public { + (uint64 indexBefore, uint64 startBefore, uint64 durationBefore, LibUtils.CycleState stateBefore) = CoreFacet(diamondAddr).getCycleInfo(); + + vm.warp(startBefore + durationBefore); + + vm.expectEmit(true, true, false, true); + emit LibCore.AutomationCycleEvent( + indexBefore + 1, + LibUtils.CycleState.STARTED, + uint64(block.timestamp), + durationBefore, + stateBefore + ); + + vm.prank(vmSigner, vmSigner); + CoreFacet(diamondAddr).monitorCycleEnd(); + + (uint64 indexAfter, uint64 startAfter, uint64 durationAfter, LibUtils.CycleState stateAfter) = CoreFacet(diamondAddr).getCycleInfo(); + + assertEq(indexAfter, indexBefore + 1); + assertEq(startAfter, block.timestamp); + assertEq(durationAfter, durationBefore); + assertEq(uint8(stateAfter), uint8(LibUtils.CycleState.STARTED)); + } + + /// @dev Test to ensure 'monitorCycleEnd' moves cycle state to FINISHED if automation is enabled and tasks exist. + function testMonitorCycleEndWhenAutomationEnabledAndTasksExist() public { + registerUST(); + + (uint64 indexBefore, uint64 startBefore, uint64 durationBefore, LibUtils.CycleState stateBefore) = CoreFacet(diamondAddr).getCycleInfo(); + vm.warp(startBefore + durationBefore); + + vm.expectEmit(true, true, false, true); + emit LibCore.AutomationCycleEvent( + indexBefore, + LibUtils.CycleState.FINISHED, + startBefore, + durationBefore, + stateBefore + ); + + vm.prank(vmSigner, vmSigner); + CoreFacet(diamondAddr).monitorCycleEnd(); + + (uint64 indexAfter, uint64 startAfter, uint64 durationAfter, LibUtils.CycleState stateAfter) = CoreFacet(diamondAddr).getCycleInfo(); + + assertEq(indexAfter, indexBefore); + assertEq(startAfter, startBefore); + assertEq(durationAfter, durationBefore); + assertEq(uint8(stateAfter), uint8(LibUtils.CycleState.FINISHED)); + + (uint64 refundDuration, uint128 automationFeePerSec) = CoreFacet(diamondAddr).getTransitionInfo(); + assertEq(refundDuration, 0); + assertEq(automationFeePerSec, 1000000000000000); + } + + /// @dev Test to ensure 'processTasks' reverts if caller is not VM Signer. + function testProcessTasksRevertsIfNotVm() public { + uint64[] memory tasks = new uint64[](1); + tasks[0] = 0; + + vm.expectRevert(ICoreFacet.CallerNotVmSigner.selector); + + vm.prank(admin); + CoreFacet(diamondAddr).processTasks(1, tasks); + } + + /// @dev Test to ensure 'processTasks' reverts if state is not FINISHED or SUSPENDED. + function testProcessTasksRevertsIfInvalidState() public { + uint64[] memory tasks = new uint64[](1); + tasks[0] = 0; + + vm.expectRevert(ICoreFacet.InvalidRegistryState.selector); + + vm.prank(vmSigner, vmSigner); + CoreFacet(diamondAddr).processTasks(1, tasks); + } + + /// @dev Test to ensure 'processTasks' works correctly when cycle state is FINISHED. + function testProcessTasksWhenCycleStateFinished() public { + registerUST(); + + ( , uint64 startTime, uint64 duration, ) = CoreFacet(diamondAddr).getCycleInfo(); + vm.warp(startTime + duration); + + vm.prank(vmSigner, vmSigner); + CoreFacet(diamondAddr).monitorCycleEnd(); + + (uint64 index, , , LibUtils.CycleState state) = CoreFacet(diamondAddr).getCycleInfo(); + assertEq(uint8(state), uint8(LibUtils.CycleState.FINISHED)); + + uint64[] memory tasks = new uint64[](1); + tasks[0] = 0; + + uint256[] memory activeTasks = new uint256[](1); + tasks[0] = 0; + + vm.expectEmit(true, false, false, false); + emit LibCore.ActiveTasks(activeTasks); + + vm.prank(vmSigner, vmSigner); + CoreFacet(diamondAddr).processTasks(index + 1, tasks); + + (uint64 newIndex, uint64 newStart, uint64 newDuration, LibUtils.CycleState newState) = CoreFacet(diamondAddr).getCycleInfo(); + assertEq(newIndex, index + 1); + assertEq(newStart, uint64(block.timestamp)); + assertEq(newDuration, 2000); + assertEq(uint8(newState), uint8(LibUtils.CycleState.STARTED)); + + assertEq(RegistryFacet(diamondAddr).getActiveTaskIds(), activeTasks); + assertEq(RegistryFacet(diamondAddr).getSystemGasCommittedForNextCycle(), 0); + assertEq(RegistryFacet(diamondAddr).getSystemGasCommittedForCurrentCycle(), 0); + assertEq(RegistryFacet(diamondAddr).getGasCommittedForNextCycle(), 0); + assertEq(RegistryFacet(diamondAddr).getGasCommittedForCurrentCycle(), 1000000); + assertEq(RegistryFacet(diamondAddr).getCycleLockedFees(), 200000000000000000); + } + + /// @dev Test to ensure 'processTasks' reverts if invalid cycle index is passed when cycle state is FINISHED. + function testProcessTasksRevertsIfInvalidCycleIndexWhenCycleStateFinished() public { + registerUST(); + + ( , uint64 startTime, uint64 duration, ) = CoreFacet(diamondAddr).getCycleInfo(); + vm.warp(startTime + duration); + + vm.prank(vmSigner, vmSigner); + CoreFacet(diamondAddr).monitorCycleEnd(); + + (uint64 index, , , LibUtils.CycleState state) = CoreFacet(diamondAddr).getCycleInfo(); + assertEq(uint8(state), uint8(LibUtils.CycleState.FINISHED)); + + uint64[] memory tasks = new uint64[](1); + tasks[0] = 0; + + vm.expectRevert(LibCore.InvalidInputCycleIndex.selector); + + vm.prank(vmSigner, vmSigner); + CoreFacet(diamondAddr).processTasks(index, tasks); + } + + /// @dev Test to ensure 'processTasks' works correctly when cycle state is SUSPENDED and automation is disabled. + function testProcessTasksWhenCycleStateSuspendedAutomationDisabled() public { + registerUST(); + + ( , uint64 start, uint64 duration, ) = CoreFacet(diamondAddr).getCycleInfo(); + vm.warp(start + duration); + + // Moves state to FINISHED + vm.prank(vmSigner, vmSigner); + CoreFacet(diamondAddr).monitorCycleEnd(); + + ( , , , LibUtils.CycleState stateBefore) = CoreFacet(diamondAddr).getCycleInfo(); + assertEq(uint8(stateBefore), uint8(LibUtils.CycleState.FINISHED)); + + // Disable automation → moves state to SUSPENDED + vm.prank(admin); + CoreFacet(diamondAddr).disableAutomation(); + + (uint64 indexAfter, , , LibUtils.CycleState stateAfter) = CoreFacet(diamondAddr).getCycleInfo(); + assertEq(uint8(stateAfter), uint8(LibUtils.CycleState.SUSPENDED)); + + uint64[] memory tasks = new uint64[](1); + tasks[0] = 0; + + vm.expectEmit(true, false, false, false); + emit LibCore.RemovedTasks(tasks); + + vm.prank(vmSigner, vmSigner); + CoreFacet(diamondAddr).processTasks(indexAfter, tasks); + + ( , , , LibUtils.CycleState newState) = CoreFacet(diamondAddr).getCycleInfo(); + assertEq(uint8(newState), uint8(LibUtils.CycleState.READY)); + assertFalse(RegistryFacet(diamondAddr).ifTaskExists(tasks[0])); + } + + /// @dev Test to ensure 'processTasks' works correctly when cycle state is SUSPENDED and automation is enabled. + function testProcessTasksWhenCycleStateSuspendedAutomationEnabled() public { + registerUST(); + + ( , uint64 start, uint64 duration, ) = CoreFacet(diamondAddr).getCycleInfo(); + vm.warp(start + duration); + + // Moves state to FINISHED + vm.prank(vmSigner, vmSigner); + CoreFacet(diamondAddr).monitorCycleEnd(); + + ( , , , LibUtils.CycleState stateBefore) = CoreFacet(diamondAddr).getCycleInfo(); + assertEq(uint8(stateBefore), uint8(LibUtils.CycleState.FINISHED)); + + // Disable automation → moves state to SUSPENDED + vm.prank(admin); + CoreFacet(diamondAddr).disableAutomation(); + + (uint64 indexAfter, , , LibUtils.CycleState stateAfter) = CoreFacet(diamondAddr).getCycleInfo(); + assertEq(uint8(stateAfter), uint8(LibUtils.CycleState.SUSPENDED)); + + // Enable automation + vm.prank(admin); + CoreFacet(diamondAddr).enableAutomation(); + + uint64[] memory tasks = new uint64[](1); + tasks[0] = 0; + + vm.expectEmit(true, false, false, false); + emit LibCore.RemovedTasks(tasks); + + vm.prank(vmSigner, vmSigner); + CoreFacet(diamondAddr).processTasks(indexAfter, tasks); + + (uint64 newIndex, uint64 newStart, uint64 newDuration, LibUtils.CycleState newState) = CoreFacet(diamondAddr).getCycleInfo(); + assertEq(newIndex, indexAfter + 1); + assertEq(newStart, uint64(block.timestamp)); + assertEq(newDuration, 2000); + assertEq(uint8(newState), uint8(LibUtils.CycleState.STARTED)); + assertFalse(RegistryFacet(diamondAddr).ifTaskExists(tasks[0])); + } + + /// @dev Test to ensure 'processTasks' reverts if invalid cycle index is passed when cycle state is SUSPENDED. + function testProcessTasksRevertsIfInvalidCycleIndexWhenCycleStateSuspended() public { + registerUST(); + + ( , uint64 start, uint64 duration, ) = CoreFacet(diamondAddr).getCycleInfo(); + vm.warp(start + duration); + + // Moves state to FINISHED + vm.prank(vmSigner, vmSigner); + CoreFacet(diamondAddr).monitorCycleEnd(); + + ( , , , LibUtils.CycleState stateBefore) = CoreFacet(diamondAddr).getCycleInfo(); + assertEq(uint8(stateBefore), uint8(LibUtils.CycleState.FINISHED)); + + // Disable automation → moves state to SUSPENDED + vm.prank(admin); + CoreFacet(diamondAddr).disableAutomation(); + + (uint64 indexAfter, , , LibUtils.CycleState stateAfter) = CoreFacet(diamondAddr).getCycleInfo(); + assertEq(uint8(stateAfter), uint8(LibUtils.CycleState.SUSPENDED)); + + uint64[] memory tasks = new uint64[](1); + tasks[0] = 0; + + vm.expectRevert(LibCore.InvalidInputCycleIndex.selector); + + vm.prank(vmSigner, vmSigner); + CoreFacet(diamondAddr).processTasks(indexAfter + 1, tasks); + } + + // :::::::::::::::::::::::::::::::::::::::::::::::::::::: Tests related to 'disableAutomation' :::::::::::::::::::::::::::::::::::::::::::::::::::::: + + /// @dev Test to ensure 'disableAutomation' disables the automation. + function testDisableAutomation() public { + // Already enabled in initialize() + vm.prank(admin); + CoreFacet(diamondAddr).disableAutomation(); + + assertFalse(CoreFacet(diamondAddr).isAutomationEnabled()); + } + + /// @dev Test to ensure 'disableAutomation' emits event 'AutomationDisabled'. + function testDisableAutomationEmitsEvent() public { + vm.expectEmit(true, false, false, false); + emit CoreFacet.AutomationDisabled(false); + + vm.prank(admin); + CoreFacet(diamondAddr).disableAutomation(); + } + + /// @dev Test to ensure 'disableAutomation' reverts if automation is already disabled. + function testDisableAutomationRevertsIfAlreadyDisabled() public { + // Disable automation + testDisableAutomation(); + + // Disable again → revert + vm.expectRevert(ICoreFacet.AlreadyDisabled.selector); + + vm.prank(admin); + CoreFacet(diamondAddr).disableAutomation(); + } + + /// @dev Test to ensure 'disableAutomation' reverts if caller is not owner. + function testDisableAutomationRevertsIfNotOwner() public { + vm.expectRevert(bytes("LibDiamond: Must be contract owner")); + + vm.prank(alice); + CoreFacet(diamondAddr).disableAutomation(); + } + + // :::::::::::::::::::::::::::::::::::::::::::::::::::::: Tests related to 'enableAutomation' :::::::::::::::::::::::::::::::::::::::::::::::::::::: + + /// @dev Test to ensure 'enableAutomation' enables the automation. + function testEnableAutomation() public { + // Disable automation + testDisableAutomation(); + + // Enable automation + vm.prank(admin); + CoreFacet(diamondAddr).enableAutomation(); + + assertTrue(CoreFacet(diamondAddr).isAutomationEnabled()); + } + + /// @dev Test to ensure 'enableAutomation' emits event 'AutomationEnabled'. + function testEnableAutomationEmitsEvent() public { + // Disable automation + testDisableAutomation(); + + vm.expectEmit(true, false, false, false); + emit CoreFacet.AutomationEnabled(true); + + vm.prank(admin); + CoreFacet(diamondAddr).enableAutomation(); + } + + /// @dev Test to ensure 'enableAutomation' reverts if automation is already enabled. + function testEnableAutomationRevertsIfAlreadyEnabled() public { + // Already enabled during initialization + vm.expectRevert(ICoreFacet.AlreadyEnabled.selector); + + vm.prank(admin); + CoreFacet(diamondAddr).enableAutomation(); + } + + /// @dev Test to ensure 'enableAutomation' reverts if caller is not owner. + function testEnableAutomationRevertsIfNotOwner() public { + vm.expectRevert(bytes("LibDiamond: Must be contract owner")); + + vm.prank(alice); + CoreFacet(diamondAddr).enableAutomation(); + } +} \ No newline at end of file diff --git a/solidity/supra_contracts/test/DiamondInit.t.sol b/solidity/supra_contracts/test/DiamondInit.t.sol new file mode 100644 index 0000000000..c005e94af9 --- /dev/null +++ b/solidity/supra_contracts/test/DiamondInit.t.sol @@ -0,0 +1,640 @@ +// SPDX-License-Identifier: MIT +pragma solidity 0.8.27; + +import {BaseDiamondTest} from "./BaseDiamondTest.t.sol"; +import {OwnershipFacet} from "../src/facets/OwnershipFacet.sol"; +import {ConfigFacet} from "../src/facets/ConfigFacet.sol"; +import {RegistryFacet} from "../src/facets/RegistryFacet.sol"; +import {CoreFacet} from "../src/facets/CoreFacet.sol"; +import {LibUtils} from "../src/libraries/LibUtils.sol"; +import {Config} from "../src/libraries/LibAppStorage.sol"; +import {Deployment, InitParams, LibDiamondUtils} from "../src/libraries/LibDiamondUtils.sol"; +import {IDiamondCut} from "../src/interfaces/IDiamondCut.sol"; +import {IDiamondLoupe} from "../src/interfaces/IDiamondLoupe.sol"; +import {IERC173} from "../src/interfaces/IERC173.sol"; +import {IERC165} from "../src/interfaces/IERC165.sol"; +import {DiamondInit} from "../src/upgradeInitializers/DiamondInit.sol"; + +contract DiamondInitTest is BaseDiamondTest { + + /// @dev Test to ensure all state variables are initialized correctly. + function testInitialize() public view { + assertEq(OwnershipFacet(diamondAddr).owner(), admin); + + (uint64 index, uint64 startTime, uint64 durationSecs, LibUtils.CycleState state) = CoreFacet(diamondAddr).getCycleInfo(); + assertEq(index, 1); + assertEq(startTime, block.timestamp); + assertEq(durationSecs, 2000); + assertEq(uint8(state), uint8(LibUtils.CycleState.STARTED)); + + assertEq(RegistryFacet(diamondAddr).getNextCycleRegistryMaxGasCap(), 10_000_000); + assertEq(RegistryFacet(diamondAddr).getNextCycleSysRegistryMaxGasCap(), 5_000_000); + assertTrue(ConfigFacet(diamondAddr).isRegistrationEnabled()); + assertTrue(CoreFacet(diamondAddr).isAutomationEnabled()); + assertEq(ConfigFacet(diamondAddr).getVmSigner(), vmSigner); + assertEq(ConfigFacet(diamondAddr).erc20Supra(), address(erc20Supra)); + + Config memory config = ConfigFacet(diamondAddr).getConfig(); + + assertEq(config.registryMaxGasCap, 10_000_000); + assertEq(config.sysRegistryMaxGasCap, 5_000_000); + assertEq(config.automationBaseFeeWeiPerSec, 0.001 ether); + assertEq(config.flatRegistrationFeeWei, 0.002 ether); + assertEq(config.congestionBaseFeeWeiPerSec, 0.002 ether); + assertEq(config.taskDurationCapSecs, 3600); + assertEq(config.sysTaskDurationCapSecs, 3600); + assertEq(config.cycleDurationSecs, 2000); + assertEq(config.taskCapacity, 500); + assertEq(config.sysTaskCapacity, 500); + assertEq(config.congestionThresholdPercentage, 50); + assertEq(config.congestionExponent, 2); + } + + function testInterfacesRegistered() public view { + assertTrue(IERC165(diamondAddr).supportsInterface(type(IERC165).interfaceId)); + assertTrue(IERC165(diamondAddr).supportsInterface(type(IDiamondCut).interfaceId)); + assertTrue(IERC165(diamondAddr).supportsInterface(type(IDiamondLoupe).interfaceId)); + assertTrue(IERC165(diamondAddr).supportsInterface(type(IERC173).interfaceId)); + } + + function testInitSelectorNotRegistered() public view { + address facet = IDiamondLoupe(diamondAddr).facetAddress(DiamondInit.init.selector); + assertEq(facet, address(0)); + } + + function testLoupeFacetAddresses() public view { + address[] memory facets = IDiamondLoupe(diamondAddr).facetAddresses(); + assertEq(facets.length, 6); // diamondCut, loupe, ownership, config, registry, core + + bool diamondCutExists; + bool loupeExists; + bool ownershipExists; + bool registryExists; + bool coreExists; + + for (uint i; i < facets.length; i++) { + if (facets[i] == deployment.diamondCutFacet) diamondCutExists = true; + if (facets[i] == deployment.loupeFacet) loupeExists = true; + if (facets[i] == deployment.ownershipFacet) ownershipExists = true; + if (facets[i] == deployment.registryFacet) registryExists = true; + if (facets[i] == deployment.coreFacet) coreExists = true; + } + + assertTrue(diamondCutExists); + assertTrue(loupeExists); + assertTrue(ownershipExists); + assertTrue(registryExists); + assertTrue(coreExists); + } + + function testSelectorRouting() public view { + assertEq( + IDiamondLoupe(diamondAddr).facetAddress(RegistryFacet.register.selector), + deployment.registryFacet + ); + + assertEq( + IDiamondLoupe(diamondAddr).facetAddress(CoreFacet.enableAutomation.selector), + deployment.coreFacet + ); + + assertEq( + IDiamondLoupe(diamondAddr).facetAddress(OwnershipFacet.transferOwnership.selector), + deployment.ownershipFacet + ); + } + + /// @dev Test to ensure Diamond reverts if 'init' is called. + function testInitReverts() public { + vm.expectRevert(bytes("Diamond: Function does not exist")); + + vm.prank(admin); + DiamondInit(diamondAddr).init( + 3600, + 10_000_000, + 0.001 ether, + 0.002 ether, + 50, + 0.002 ether, + 2, + 500, + 2000, + 3600, + 5_000_000, + 500, + vmSigner, + address(erc20Supra), + true, + true + ); + } + + function testDiamondCutRevertsIfNotOwner() public { + bytes4[] memory selectors = new bytes4[](2); + selectors[0] = RegistryFacet.register.selector; + selectors[1] = RegistryFacet.registerSystemTask.selector; + + IDiamondCut.FacetCut[] memory cut = new IDiamondCut.FacetCut[](1); + cut[0] = IDiamondCut.FacetCut({ + facetAddress: diamondAddr, + action: IDiamondCut.FacetCutAction.Add, + functionSelectors: selectors + }); + + vm.expectRevert(bytes("LibDiamond: Must be contract owner")); + + vm.prank(alice); + IDiamondCut(diamondAddr).diamondCut( + cut, + address(0), + "" + ); + } + + function testTransferOwnership() public { + vm.prank(admin); + OwnershipFacet(diamondAddr).transferOwnership(alice); + + assertEq(OwnershipFacet(diamondAddr).owner(), alice); + } + + function testTransferOwnershipRevertsIfNotOwner() public { + vm.expectRevert(bytes("LibDiamond: Must be contract owner")); + + vm.prank(alice); + OwnershipFacet(diamondAddr).transferOwnership(bob); + } + + function testUnknownSelectorReverts() public { + vm.expectRevert(bytes("Diamond: Function does not exist")); + + INonExistent(diamondAddr).nonExistent(); + } + + function testAddSelector() public { + uint256 numFacetsBefore = IDiamondLoupe(diamondAddr).facetAddresses().length; + + // Deploy mock facet + MockRegistryFacet mockRegistryFacet = new MockRegistryFacet(); + + bytes4[] memory selectors = new bytes4[](1); + selectors[0] = MockRegistryFacet.counter.selector; + + IDiamondCut.FacetCut[] memory cut = new IDiamondCut.FacetCut[](1); + cut[0] = IDiamondCut.FacetCut({ + facetAddress: address(mockRegistryFacet), + action: IDiamondCut.FacetCutAction.Add, + functionSelectors: selectors + }); + + vm.prank(admin); + IDiamondCut(diamondAddr).diamondCut(cut, address(0), ""); + + assertEq(IDiamondLoupe(diamondAddr).facetAddress(MockRegistryFacet.counter.selector), address(mockRegistryFacet)); + assertEq(IDiamondLoupe(diamondAddr).facetAddresses().length , numFacetsBefore + 1); + + assertEq(MockRegistryFacet(diamondAddr).counter(), 1); + } + + function testAddExistingSelectorReverts() public { + // Deploy mock facet + MockRegistryFacet mockRegistryFacet = new MockRegistryFacet(); + + bytes4[] memory selectors = new bytes4[](1); + selectors[0] = MockRegistryFacet.getVmSigner.selector; + + IDiamondCut.FacetCut[] memory cut = new IDiamondCut.FacetCut[](1); + cut[0] = IDiamondCut.FacetCut({ + facetAddress: address(mockRegistryFacet), + action: IDiamondCut.FacetCutAction.Add, + functionSelectors: selectors + }); + + vm.expectRevert(bytes("LibDiamondCut: Can't add function that already exists")); + + vm.prank(admin); + IDiamondCut(diamondAddr).diamondCut(cut, address(0), ""); + } + + function testAddWithEmptySelectorsReverts() public { + bytes4[] memory selectors; + + IDiamondCut.FacetCut[] memory cut = new IDiamondCut.FacetCut[](1); + cut[0] = IDiamondCut.FacetCut({ + facetAddress: deployment.registryFacet, + action: IDiamondCut.FacetCutAction.Add, + functionSelectors: selectors + }); + + vm.expectRevert(bytes("LibDiamondCut: No selectors in facet to cut")); + + vm.prank(admin); + IDiamondCut(diamondAddr).diamondCut(cut, address(0), ""); + } + + function testAddSelectorWithZeroAddressReverts() public { + bytes4[] memory selectors = new bytes4[](1); + selectors[0] = MockRegistryFacet.counter.selector; + + IDiamondCut.FacetCut[] memory cut = new IDiamondCut.FacetCut[](1); + cut[0] = IDiamondCut.FacetCut({ + facetAddress: address(0), + action: IDiamondCut.FacetCutAction.Add, + functionSelectors: selectors + }); + + vm.expectRevert(bytes("LibDiamondCut: Add facet can't be address(0)")); + + vm.prank(admin); + IDiamondCut(diamondAddr).diamondCut(cut, address(0), ""); + } + + function testRemoveSelector() public { + uint256 numSelectorsBefore = IDiamondLoupe(diamondAddr).facetFunctionSelectors(deployment.registryFacet).length; + + bytes4[] memory selectors = new bytes4[](1); + selectors[0] = RegistryFacet.cancelTask.selector; + + IDiamondCut.FacetCut[] memory cut = new IDiamondCut.FacetCut[](1); + cut[0] = IDiamondCut.FacetCut({ + facetAddress: address(0), + action: IDiamondCut.FacetCutAction.Remove, + functionSelectors: selectors + }); + + vm.prank(admin); + IDiamondCut(diamondAddr).diamondCut(cut, address(0), ""); + + // Verify selector mapping cleared + address facet = IDiamondLoupe(diamondAddr).facetAddress(RegistryFacet.cancelTask.selector); + assertEq(facet, address(0)); + + uint256 numSelectorsAfter = IDiamondLoupe(diamondAddr).facetFunctionSelectors(deployment.registryFacet).length; + assertEq(numSelectorsAfter, numSelectorsBefore - 1); + + // Verify call now reverts + vm.expectRevert(bytes("Diamond: Function does not exist")); + RegistryFacet(diamondAddr).cancelTask(0); + } + + function testRemoveNonExistingSelectorReverts() public { + bytes4[] memory selectors = new bytes4[](1); + selectors[0] = MockRegistryFacet.counter.selector; + + IDiamondCut.FacetCut[] memory cut = new IDiamondCut.FacetCut[](1); + cut[0] = IDiamondCut.FacetCut({ + facetAddress: address(0), + action: IDiamondCut.FacetCutAction.Remove, + functionSelectors: selectors + }); + + vm.expectRevert(bytes("LibDiamondCut: Can't remove function that doesn't exist")); + + vm.prank(admin); + IDiamondCut(diamondAddr).diamondCut(cut, address(0), ""); + } + + function testReplaceSelector() public { + // Deploy mock facet + MockRegistryFacet mockRegistryFacet = new MockRegistryFacet(); + + bytes4[] memory selectors = new bytes4[](1); + selectors[0] = ConfigFacet.getVmSigner.selector; + + IDiamondCut.FacetCut[] memory cut = new IDiamondCut.FacetCut[](1); + cut[0] = IDiamondCut.FacetCut({ + facetAddress: address(mockRegistryFacet), + action: IDiamondCut.FacetCutAction.Replace, + functionSelectors: selectors + }); + + vm.prank(admin); + IDiamondCut(diamondAddr).diamondCut(cut, address(0), ""); + + // Verify selector now points to mockRegistryFacet + address facet = IDiamondLoupe(diamondAddr).facetAddress(ConfigFacet.getVmSigner.selector); + assertEq(facet, address(mockRegistryFacet)); + + // Verify logic changed + assertEq(ConfigFacet(diamondAddr).getVmSigner(), address(0x999)); + } + + function testReplaceWithSameFacetReverts() public { + bytes4[] memory selectors = new bytes4[](1); + selectors[0] = ConfigFacet.getVmSigner.selector; + + IDiamondCut.FacetCut[] memory cut = new IDiamondCut.FacetCut[](1); + cut[0] = IDiamondCut.FacetCut({ + facetAddress: deployment.configFacet, + action: IDiamondCut.FacetCutAction.Replace, + functionSelectors: selectors + }); + + vm.expectRevert(bytes("LibDiamondCut: Can't replace function with same function")); + + vm.prank(admin); + IDiamondCut(diamondAddr).diamondCut(cut, address(0), ""); + } + + /// @dev Test to ensure initialization fails if zero address is passed as VM Signer. + function testInitializeRevertsIfVmSignerZero() public { + vm.startPrank(admin); + Deployment memory deployment = LibDiamondUtils.deploy(admin); + + vm.expectRevert(LibUtils.AddressCannotBeZero.selector); + + // address(0) as VM signer + LibDiamondUtils.executeCut(address(0), address(erc20Supra), defaultParams, deployment); + vm.stopPrank(); + } + + /// @dev Test to ensure initialization fails if ERC20Supra address is zero. + function testInitializeRevertsIfErc20SupraIsZero() public { + vm.startPrank(admin); + Deployment memory deployment = LibDiamondUtils.deploy(admin); + + vm.expectRevert(LibUtils.AddressCannotBeZero.selector); + + // address(0) as ERC20Supra + LibDiamondUtils.executeCut(vmSigner, address(0), defaultParams, deployment); + vm.stopPrank(); + } + + /// @dev Test to ensure initialization fails if EOA is passed as ERC20Supra address. + function testInitializeRevertsIfErc20SupraIsEoa() public { + vm.startPrank(admin); + Deployment memory deployment = LibDiamondUtils.deploy(admin); + + vm.expectRevert(LibUtils.AddressCannotBeEOA.selector); + + // EOA address as ERC20Supra + LibDiamondUtils.executeCut(vmSigner, admin, defaultParams, deployment); + vm.stopPrank(); + } + + /// @dev Test to ensure initialization fails if task duration is <= cycle duration. + function testInitializeRevertsIfInvalidTaskDuration() public { + vm.startPrank(admin); + Deployment memory deployment = LibDiamondUtils.deploy(admin); + + InitParams memory initParams = InitParams({ + taskDurationCapSecs: 2000, + registryMaxGasCap: 10_000_000, + automationBaseFeeWeiPerSec: 0.001 ether, + flatRegistrationFeeWei: 0.002 ether, + congestionThresholdPercentage: 50, + congestionBaseFeeWeiPerSec: 0.002 ether, + congestionExponent: 2, + taskCapacity: 500, + cycleDurationSecs: 2000, + sysTaskDurationCapSecs: 3600, + sysRegistryMaxGasCap: 5_000_000, + sysTaskCapacity: 500, + registrationEnabled: true, + automationEnabled: true + }); + + vm.expectRevert(LibUtils.InvalidTaskDuration.selector); + + LibDiamondUtils.executeCut(vmSigner, address(erc20Supra), initParams, deployment); + vm.stopPrank(); + } + + /// @dev Test to ensure initialization fails if registry max gas cap is zero. + function testInitializeRevertsIfRegistryMaxGasCapZero() public { + vm.startPrank(admin); + Deployment memory deployment = LibDiamondUtils.deploy(admin); + + InitParams memory initParams = InitParams({ + taskDurationCapSecs: 3600, + registryMaxGasCap: 0, + automationBaseFeeWeiPerSec: 0.001 ether, + flatRegistrationFeeWei: 0.002 ether, + congestionThresholdPercentage: 50, + congestionBaseFeeWeiPerSec: 0.002 ether, + congestionExponent: 2, + taskCapacity: 500, + cycleDurationSecs: 2000, + sysTaskDurationCapSecs: 3600, + sysRegistryMaxGasCap: 5_000_000, + sysTaskCapacity: 500, + registrationEnabled: true, + automationEnabled: true + }); + + vm.expectRevert(LibUtils.InvalidRegistryMaxGasCap.selector); + + LibDiamondUtils.executeCut(vmSigner, address(erc20Supra), initParams, deployment); + vm.stopPrank(); + } + + /// @dev Test to ensure initialization fails if congestion threshold percentage is > 100. + function testInitializeRevertsIfInvalidCongestionThreshold() public { + vm.startPrank(admin); + Deployment memory deployment = LibDiamondUtils.deploy(admin); + + InitParams memory initParams = InitParams({ + taskDurationCapSecs: 3600, + registryMaxGasCap: 10_000_000, + automationBaseFeeWeiPerSec: 0.001 ether, + flatRegistrationFeeWei: 0.002 ether, + congestionThresholdPercentage: 101, + congestionBaseFeeWeiPerSec: 0.002 ether, + congestionExponent: 2, + taskCapacity: 500, + cycleDurationSecs: 2000, + sysTaskDurationCapSecs: 3600, + sysRegistryMaxGasCap: 5_000_000, + sysTaskCapacity: 500, + registrationEnabled: true, + automationEnabled: true + }); + + vm.expectRevert(LibUtils.InvalidCongestionThreshold.selector); + + LibDiamondUtils.executeCut(vmSigner, address(erc20Supra), initParams, deployment); + vm.stopPrank(); + } + + /// @dev Test to ensure initialization fails if congestion exponent is 0. + function testInitializeRevertsIfCongestionExponentZero() public { + vm.startPrank(admin); + Deployment memory deployment = LibDiamondUtils.deploy(admin); + + InitParams memory initParams = InitParams({ + taskDurationCapSecs: 3600, + registryMaxGasCap: 10_000_000, + automationBaseFeeWeiPerSec: 0.001 ether, + flatRegistrationFeeWei: 0.002 ether, + congestionThresholdPercentage: 50, + congestionBaseFeeWeiPerSec: 0.002 ether, + congestionExponent: 0, + taskCapacity: 500, + cycleDurationSecs: 2000, + sysTaskDurationCapSecs: 3600, + sysRegistryMaxGasCap: 5_000_000, + sysTaskCapacity: 500, + registrationEnabled: true, + automationEnabled: true + }); + + vm.expectRevert(LibUtils.InvalidCongestionExponent.selector); + + LibDiamondUtils.executeCut(vmSigner, address(erc20Supra), initParams, deployment); + vm.stopPrank(); + } + + /// @dev Test to ensure initialization fails if task capacity is 0. + function testInitializeRevertsIfTaskCapacityZero() public { + vm.startPrank(admin); + Deployment memory deployment = LibDiamondUtils.deploy(admin); + + InitParams memory initParams = InitParams({ + taskDurationCapSecs: 3600, + registryMaxGasCap: 10_000_000, + automationBaseFeeWeiPerSec: 0.001 ether, + flatRegistrationFeeWei: 0.002 ether, + congestionThresholdPercentage: 50, + congestionBaseFeeWeiPerSec: 0.002 ether, + congestionExponent: 2, + taskCapacity: 0, + cycleDurationSecs: 2000, + sysTaskDurationCapSecs: 3600, + sysRegistryMaxGasCap: 5_000_000, + sysTaskCapacity: 500, + registrationEnabled: true, + automationEnabled: true + }); + + vm.expectRevert(LibUtils.InvalidTaskCapacity.selector); + + LibDiamondUtils.executeCut(vmSigner, address(erc20Supra), initParams, deployment); + vm.stopPrank(); + } + + /// @dev Test to ensure initialization fails if cycle duration is 0. + function testInitializeRevertsIfCycleDurationZero() public { + vm.startPrank(admin); + Deployment memory deployment = LibDiamondUtils.deploy(admin); + + InitParams memory initParams = InitParams({ + taskDurationCapSecs: 3600, + registryMaxGasCap: 10_000_000, + automationBaseFeeWeiPerSec: 0.001 ether, + flatRegistrationFeeWei: 0.002 ether, + congestionThresholdPercentage: 50, + congestionBaseFeeWeiPerSec: 0.002 ether, + congestionExponent: 2, + taskCapacity: 500, + cycleDurationSecs: 0, + sysTaskDurationCapSecs: 3600, + sysRegistryMaxGasCap: 5_000_000, + sysTaskCapacity: 500, + registrationEnabled: true, + automationEnabled: true + }); + + vm.expectRevert(LibUtils.InvalidCycleDuration.selector); + + LibDiamondUtils.executeCut(vmSigner, address(erc20Supra), initParams, deployment); + vm.stopPrank(); + } + + /// @dev Test to ensure initialization fails if system task duration is <= cycle duration. + function testInitializeRevertsIfInvalidSysTaskDuration() public { + vm.startPrank(admin); + Deployment memory deployment = LibDiamondUtils.deploy(admin); + + InitParams memory initParams = InitParams({ + taskDurationCapSecs: 3600, + registryMaxGasCap: 10_000_000, + automationBaseFeeWeiPerSec: 0.001 ether, + flatRegistrationFeeWei: 0.002 ether, + congestionThresholdPercentage: 50, + congestionBaseFeeWeiPerSec: 0.002 ether, + congestionExponent: 2, + taskCapacity: 500, + cycleDurationSecs: 2000, + sysTaskDurationCapSecs: 2000, + sysRegistryMaxGasCap: 5_000_000, + sysTaskCapacity: 500, + registrationEnabled: true, + automationEnabled: true + }); + + vm.expectRevert(LibUtils.InvalidSysTaskDuration.selector); + + LibDiamondUtils.executeCut(vmSigner, address(erc20Supra), initParams, deployment); + vm.stopPrank(); + } + + /// @dev Test to ensure initialization fails if system registry max gas cap is 0. + function testInitializeRevertsIfSysRegistryMaxGasCapZero() public { + vm.startPrank(admin); + Deployment memory deployment = LibDiamondUtils.deploy(admin); + + InitParams memory initParams = InitParams({ + taskDurationCapSecs: 3600, + registryMaxGasCap: 10_000_000, + automationBaseFeeWeiPerSec: 0.001 ether, + flatRegistrationFeeWei: 0.002 ether, + congestionThresholdPercentage: 50, + congestionBaseFeeWeiPerSec: 0.002 ether, + congestionExponent: 2, + taskCapacity: 500, + cycleDurationSecs: 2000, + sysTaskDurationCapSecs: 3600, + sysRegistryMaxGasCap: 0, + sysTaskCapacity: 500, + registrationEnabled: true, + automationEnabled: true + }); + + vm.expectRevert(LibUtils.InvalidSysRegistryMaxGasCap.selector); + + LibDiamondUtils.executeCut(vmSigner, address(erc20Supra), initParams, deployment); + vm.stopPrank(); + } + + /// @dev Test to ensure initialization fails if system task capacity is 0. + function testInitializeRevertsIfSysTaskCapacityZero() public { + vm.startPrank(admin); + Deployment memory deployment = LibDiamondUtils.deploy(admin); + + InitParams memory initParams = InitParams({ + taskDurationCapSecs: 3600, + registryMaxGasCap: 10_000_000, + automationBaseFeeWeiPerSec: 0.001 ether, + flatRegistrationFeeWei: 0.002 ether, + congestionThresholdPercentage: 50, + congestionBaseFeeWeiPerSec: 0.002 ether, + congestionExponent: 2, + taskCapacity: 500, + cycleDurationSecs: 2000, + sysTaskDurationCapSecs: 3600, + sysRegistryMaxGasCap: 5_000_000, + sysTaskCapacity: 0, + registrationEnabled: true, + automationEnabled: true + }); + + vm.expectRevert(LibUtils.InvalidSysTaskCapacity.selector); + + LibDiamondUtils.executeCut(vmSigner, address(erc20Supra), initParams, deployment); + vm.stopPrank(); + } +} + +interface INonExistent { + function nonExistent() external; +} + +contract MockRegistryFacet { + function getVmSigner() external pure returns (address) { + return address(0x999); + } + + function counter() external pure returns (uint256) { + return 1; + } +} diff --git a/solidity/supra_contracts/test/RegistryFacet.t.sol b/solidity/supra_contracts/test/RegistryFacet.t.sol new file mode 100644 index 0000000000..0b39beb0dc --- /dev/null +++ b/solidity/supra_contracts/test/RegistryFacet.t.sol @@ -0,0 +1,838 @@ +// SPDX-License-Identifier: MIT +pragma solidity 0.8.27; + +import {BaseDiamondTest} from "./BaseDiamondTest.t.sol"; +import {ConfigFacet} from "../src/facets/ConfigFacet.sol"; +import {RegistryFacet} from "../src/facets/RegistryFacet.sol"; +import {CoreFacet} from "../src/facets/CoreFacet.sol"; +import {IRegistryFacet} from "../src/interfaces/IRegistryFacet.sol"; +import {LibUtils} from "../src/libraries/LibUtils.sol"; +import {LibRegistry} from "../src/libraries/LibRegistry.sol"; +import {TaskMetadata} from "../src/libraries/LibAppStorage.sol"; + +contract RegistryFacetTest is BaseDiamondTest { + + // :::::::::::::::::::::::::::::::::::::::::::::::::::::: Tests related to 'register' :::::::::::::::::::::::::::::::::::::::::::::::::::::: + + /// @dev Test to ensure 'register' reverts if automation is not enabled. + function testRegisterRevertsIfAutomationNotEnabled() public { + // Disable automation + vm.prank(admin); + CoreFacet(diamondAddr).disableAutomation(); + + bytes[] memory auxData; + bytes memory payload = createPayload(0, address(erc20Supra)); + + vm.expectRevert(IRegistryFacet.AutomationNotEnabled.selector); + + vm.prank(alice); + RegistryFacet(diamondAddr).register( + payload, // payload + uint64(block.timestamp + 2250), // expiryTime + uint128(1_000_000), // maxGasAmount + uint128(10 gwei), // gasPriceCap + uint128(0.5 ether), // automationFeeCapForCycle + 0, // priority + auxData // aux data + ); + } + + /// @dev Test to ensure 'register' reverts if registration is disabled. + function testRegisterRevertsIfRegistrationDisabled() public { + // Disable registration + vm.prank(admin); + ConfigFacet(diamondAddr).disableRegistration(); + + bytes[] memory auxData; + bytes memory payload = createPayload(0, address(erc20Supra)); + + vm.expectRevert(LibRegistry.RegistrationDisabled.selector); + + vm.prank(alice); + RegistryFacet(diamondAddr).register( + payload, // payload + uint64(block.timestamp + 2250), // expiryTime + uint128(1_000_000), // maxGasAmount + uint128(10 gwei), // gasPriceCap + uint128(0.5 ether), // automationFeeCapForCycle + 0, // priority + auxData // aux data + ); + } + + /// @dev Test to ensure 'register' reverts if expiry time is equal to or less than registration time. + function testRegisterRevertsIfInvalidExpiryTime() public { + bytes[] memory auxData; + bytes memory payload = createPayload(0, address(erc20Supra)); + + vm.expectRevert(LibRegistry.InvalidExpiryTime.selector); + + vm.prank(alice); + RegistryFacet(diamondAddr).register( + payload, + uint64(block.timestamp), // Invalid expiryTime + uint128(1_000_000), + uint128(10 gwei), + uint128(0.5 ether), + 0, + auxData + ); + } + + /// @dev Test to ensure 'register' reverts if task duration is greater than the task duration cap. + function testRegisterRevertsIfInvalidTaskDuration() public { + bytes[] memory auxData; + bytes memory payload = createPayload(0, address(erc20Supra)); + + vm.expectRevert(LibRegistry.InvalidTaskDuration.selector); + + vm.prank(alice); + RegistryFacet(diamondAddr).register( + payload, + uint64(block.timestamp + 3601), // Invalid task duration + uint128(1_000_000), + uint128(10 gwei), + uint128(0.5 ether), + 0, + auxData + ); + } + + /// @dev Test to ensure 'register' reverts if task expires before the next cycle. + function testRegisterRevertsIfTaskExpiresBeforeNextCycle() public { + bytes[] memory auxData; + bytes memory payload = createPayload(0, address(erc20Supra)); + + vm.expectRevert(LibRegistry.TaskExpiresBeforeNextCycle.selector); + + vm.prank(alice); + RegistryFacet(diamondAddr).register( + payload, + uint64(block.timestamp + 2000), // Task expires before next cycle + uint128(1_000_000), + uint128(10 gwei), + uint128(0.5 ether), + 0, + auxData + ); + } + + /// @dev Test to ensure 'register' reverts if payload target address is zero. + function testRegisterRevertsIfPayloadTargetZero() public { + bytes[] memory auxData; + bytes memory payload = createPayload(0, address(0)); // Invalid address: address(0) + + vm.expectRevert(LibUtils.AddressCannotBeZero.selector); + + vm.prank(alice); + RegistryFacet(diamondAddr).register( + payload, + uint64(block.timestamp + 2250), + uint128(1_000_000), + uint128(10 gwei), + uint128(0.5 ether), + 0, + auxData + ); + } + + /// @dev Test to ensure 'register' reverts if payload target address is EOA. + function testRegisterRevertsIfPayloadTargetEoa() public { + bytes[] memory auxData; + bytes memory payload = createPayload(0, alice); // Invalid address: EOA address being passed + + vm.expectRevert(LibUtils.AddressCannotBeEOA.selector); + + vm.prank(alice); + RegistryFacet(diamondAddr).register( + payload, + uint64(block.timestamp + 2250), + uint128(1_000_000), + uint128(10 gwei), + uint128(0.5 ether), + 0, + auxData + ); + } + + /// @dev Test to ensure 'register' reverts if 0 is passed as max gas amount. + function testRegisterRevertsIfMaxGasAmountZero() public { + bytes[] memory auxData; + bytes memory payload = createPayload(0, address(erc20Supra)); + + vm.expectRevert(LibRegistry.InvalidMaxGasAmount.selector); + + vm.prank(alice); + RegistryFacet(diamondAddr).register( + payload, + uint64(block.timestamp + 2250), + uint128(0), // maxGasAmount + uint128(10 gwei), + uint128(0.5 ether), + 0, + auxData + ); + } + + /// @dev Test to ensure 'register' reverts if 0 is passed as gas price cap. + function testRegisterRevertsIfGasPriceCapZero() public { + bytes[] memory auxData; + bytes memory payload = createPayload(0, address(erc20Supra)); + + vm.expectRevert(LibRegistry.InvalidGasPriceCap.selector); + + vm.prank(alice); + RegistryFacet(diamondAddr).register( + payload, + uint64(block.timestamp + 2250), + uint128(1_000_000), + uint128(0), // gasPriceCap + uint128(0.5 ether), + 0, + auxData + ); + } + + /// @dev Test to ensure 'register' reverts if automation fee cap is less than the estimated automation fee. + function testRegisterRevertsIfAutomationFeeCapLessThanEstimated() public { + bytes[] memory auxData; + bytes memory payload = createPayload(0, address(erc20Supra)); + + vm.expectRevert(LibRegistry.InsufficientFeeCapForCycle.selector); + + vm.prank(alice); + RegistryFacet(diamondAddr).register( + payload, + uint64(block.timestamp + 2250), + uint128(1_000_000), + uint128(10 gwei), + uint128(0), // automationFeeCapForCycle + 0, + auxData + ); + } + + /// @dev Test to ensure 'register' reverts if gas committed exceeds the registry max gas cap. + function testRegisterRevertsIfGasCommittedExceedsMaxGasCap() public { + bytes[] memory auxData; + bytes memory payload = createPayload(0, address(erc20Supra)); + + vm.expectRevert(LibRegistry.GasCommittedExceedsMaxGasCap.selector); + + vm.prank(alice); + RegistryFacet(diamondAddr).register( + payload, + uint64(block.timestamp + 2250), + uint128(10_000_001), // Gas exceeds max gas cap + uint128(10 gwei), + uint128(7.01 ether), + 0, + auxData + ); + } + + /// @dev Test to ensure 'register' registers a UST. + function testRegister() public { + bytes[] memory auxData; + bytes memory payload = createPayload(0, address(erc20Supra)); + + vm.startPrank(alice); + erc20Supra.nativeToErc20Supra{value: 5 ether}(); + erc20Supra.approve(diamondAddr, type(uint256).max); + + RegistryFacet(diamondAddr).register( + payload, + uint64(block.timestamp + 2250), + uint128(1_000_000), + uint128(10 gwei), + uint128(0.5 ether), + 4, + auxData + ); + vm.stopPrank(); + + TaskMetadata memory taskMetadata = RegistryFacet(diamondAddr).getTaskDetails(0); + assertTrue(RegistryFacet(diamondAddr).ifTaskExists(0)); + assertEq(RegistryFacet(diamondAddr).totalTasks(), 1); + assertEq(RegistryFacet(diamondAddr).getNextTaskIndex(), 1); + assertEq(RegistryFacet(diamondAddr).getGasCommittedForNextCycle(), 1_000_000); + assertEq(RegistryFacet(diamondAddr).getTotalDepositedAutomationFees(), 0.5 ether); + assertEq(erc20Supra.balanceOf(diamondAddr), 0.502 ether); + assertEq(erc20Supra.balanceOf(alice), 4.498 ether); + + assertEq(taskMetadata.maxGasAmount, 1_000_000); + assertEq(taskMetadata.gasPriceCap, 10 gwei); + assertEq(taskMetadata.automationFeeCapForCycle, 0.5 ether); + assertEq(taskMetadata.depositFee, 0.5 ether); + assertEq(taskMetadata.txHash, keccak256("txHash")); + assertEq(taskMetadata.taskIndex, 0); + assertEq(taskMetadata.registrationTime, uint64(block.timestamp)); + assertEq(taskMetadata.expiryTime, uint64(block.timestamp + 2250)); + assertEq(taskMetadata.priority, 0); + assertEq(uint8(taskMetadata.taskType), 0); + assertEq(uint8(taskMetadata.taskState), 0); + assertEq(taskMetadata.owner, alice); + assertEq(taskMetadata.payloadTx, payload); + assertEq(taskMetadata.auxData, auxData); + } + + /// @dev Test to ensure 'register' emits event 'TaskRegistered'. + function testRegisterEmitsEvent() public { + bytes[] memory auxData; + bytes memory payload = createPayload(0, address(erc20Supra)); + + vm.startPrank(alice); + erc20Supra.nativeToErc20Supra{value: 5 ether}(); + erc20Supra.approve(diamondAddr, type(uint256).max); + + TaskMetadata memory taskMetadata = TaskMetadata({ + maxGasAmount: 1_000_000, + gasPriceCap: 10 gwei, + automationFeeCapForCycle: 0.5 ether, + depositFee: 0.5 ether, + txHash: keccak256("txHash"), + taskIndex: 0, + registrationTime: uint64(block.timestamp), + expiryTime: uint64(block.timestamp + 2250), + priority: 0, + owner: alice, + taskType: LibUtils.TaskType.UST, + taskState: LibUtils.TaskState.PENDING, + payloadTx: payload, + auxData: auxData + }); + + vm.expectEmit(true, true, false, true); + emit RegistryFacet.TaskRegistered(0, alice, 0.002 ether, 0.5 ether, taskMetadata); + + RegistryFacet(diamondAddr).register( + payload, + uint64(block.timestamp + 2250), + uint128(1_000_000), + uint128(10 gwei), + uint128(0.5 ether), + 0, + auxData + ); + vm.stopPrank(); + } + + // ::::::::::::::::::::::::::::::::::::::::::::::::::::: Tests related to 'registerSystemTask' ::::::::::::::::::::::::::::::::::::::::::::::::::::: + + /// @dev Test to ensure 'registerSystemTask' reverts if caller is not authorized. + function testRegisterSystemTaskRevertsIfUnauthorizedCaller() public { + bytes[] memory auxData; + bytes memory payload = createPayload(0, address(erc20Supra)); + + vm.expectRevert(IRegistryFacet.UnauthorizedAccount.selector); + + vm.prank(alice); + RegistryFacet(diamondAddr).registerSystemTask( + payload, // payload + uint64(block.timestamp + 2250), // expiryTime + uint128(1_000_000), // maxGasAmount + 2, // priority + auxData // aux data + ); + } + + /// @dev Test to ensure 'registerSystemTask' reverts if automation is not enabled. + function testRegisterSystemTaskRevertsIfAutomationNotEnabled() public { + vm.prank(admin); + CoreFacet(diamondAddr).disableAutomation(); + + bytes[] memory auxData; + bytes memory payload = createPayload(0, address(erc20Supra)); + + vm.expectRevert(IRegistryFacet.AutomationNotEnabled.selector); + + vm.prank(bob); + RegistryFacet(diamondAddr).registerSystemTask( + payload, // payload + uint64(block.timestamp + 2250), // expiryTime + uint128(1_000_000), // maxGasAmount + 2, // priority + auxData // aux data + ); + } + + /// @dev Test to ensure 'registerSystemTask' reverts if registration is disabled. + function testRegisterSystemTaskRevertsIfRegistrationDisabled() public { + vm.prank(admin); + ConfigFacet(diamondAddr).disableRegistration(); + + bytes[] memory auxData; + bytes memory payload = createPayload(0, address(erc20Supra)); + + vm.expectRevert(LibRegistry.RegistrationDisabled.selector); + + vm.prank(bob); + RegistryFacet(diamondAddr).registerSystemTask( + payload, // payload + uint64(block.timestamp + 2250), // expiryTime + uint128(1_000_000), // maxGasAmount + 2, // priority + auxData // aux data + ); + } + + /// @dev Test to ensure 'registerSystemTask' reverts if task duration is greater than system task duration cap. + function testRegisterSystemTaskRevertsIfInvalidTaskDuration() public { + bytes[] memory auxData; + bytes memory payload = createPayload(0, address(erc20Supra)); + + vm.expectRevert(LibRegistry.InvalidTaskDuration.selector); + + vm.prank(bob); + RegistryFacet(diamondAddr).registerSystemTask( + payload, + uint64(block.timestamp + 3601), // Invalid task duration + uint128(1_000_000), + 2, + auxData + ); + } + + /// @dev Test to ensure 'registerSystemTask' reverts if gas committed exceeds the system registry max gas cap. + function testRegisterSystemTaskRevertsIfGasCommittedExceedsMaxGasCap() public { + bytes[] memory auxData; + bytes memory payload = createPayload(0, address(erc20Supra)); + + vm.expectRevert(LibRegistry.GasCommittedExceedsMaxGasCap.selector); + + vm.prank(bob); + RegistryFacet(diamondAddr).registerSystemTask( + payload, + uint64(block.timestamp + 2250), + uint128(5_000_001), // Gas exceeds max gas cap + 2, + auxData + ); + } + + /// @dev Test to ensure 'registerSystemTask' registers a GST. + function testRegisterSystemTask() public { + bytes[] memory auxData; + bytes memory payload = createPayload(0, address(erc20Supra)); + + vm.prank(bob); + RegistryFacet(diamondAddr).registerSystemTask( + payload, // payload + uint64(block.timestamp + 2250), // expiryTime + uint128(1_000_000), // maxGasAmount + 2, // priority + auxData // aux data + ); + + TaskMetadata memory taskMetadata = RegistryFacet(diamondAddr).getTaskDetails(0); + assertTrue(RegistryFacet(diamondAddr).ifTaskExists(0)); + assertTrue(RegistryFacet(diamondAddr).ifSysTaskExists(0)); + assertEq(RegistryFacet(diamondAddr).totalTasks(), 1); + assertEq(RegistryFacet(diamondAddr).totalSystemTasks(), 1); + assertEq(RegistryFacet(diamondAddr).getNextTaskIndex(), 1); + assertEq(RegistryFacet(diamondAddr).getSystemGasCommittedForNextCycle(), 1_000_000); + + assertEq(taskMetadata.maxGasAmount, 1_000_000); + assertEq(taskMetadata.gasPriceCap, 0); + assertEq(taskMetadata.automationFeeCapForCycle, 0); + assertEq(taskMetadata.depositFee, 0); + assertEq(taskMetadata.txHash, keccak256("txHash")); + assertEq(taskMetadata.taskIndex, 0); + assertEq(taskMetadata.registrationTime, uint64(block.timestamp)); + assertEq(taskMetadata.expiryTime, uint64(block.timestamp + 2250)); + assertEq(taskMetadata.priority, 2); + assertEq(uint8(taskMetadata.taskType), 1); + assertEq(uint8(taskMetadata.taskState), 0); + assertEq(taskMetadata.owner, bob); + assertEq(taskMetadata.payloadTx, payload); + assertEq(taskMetadata.auxData, auxData); + } + + /// @dev Test to ensure 'registerSystemTask' emits event 'SystemTaskRegistered'. + function testRegisterSystemTaskEmitsEvent() public { + bytes[] memory auxData; + bytes memory payload = createPayload(0, address(erc20Supra)); + + TaskMetadata memory taskMetadata = TaskMetadata({ + maxGasAmount: 1_000_000, + gasPriceCap: 0, + automationFeeCapForCycle: 0, + depositFee: 0, + txHash: keccak256("txHash"), + taskIndex: 0, + registrationTime: uint64(block.timestamp), + expiryTime: uint64(block.timestamp + 2250), + priority: 2, + owner: bob, + taskType: LibUtils.TaskType.GST, + taskState: LibUtils.TaskState.PENDING, + payloadTx: payload, + auxData: auxData + }); + + vm.expectEmit(true, true, false, true); + emit RegistryFacet.SystemTaskRegistered(0, bob, block.timestamp, taskMetadata); + + vm.prank(bob); + RegistryFacet(diamondAddr).registerSystemTask( + payload, // payload + uint64(block.timestamp + 2250), // expiryTime + uint128(1_000_000), // maxGasAmount + 2, // priority + auxData // aux data + ); + } + + // :::::::::::::::::::::::::::::::::::::::::::::::::::::: Tests related to 'cancelTask' :::::::::::::::::::::::::::::::::::::::::::::::::::::: + + /// @dev Test to ensure 'cancelTask' reverts if automation is not enabled. + function testCancelTaskRevertsIfAutomationNotEnabled() public { + vm.prank(admin); + CoreFacet(diamondAddr).disableAutomation(); + + vm.expectRevert(IRegistryFacet.AutomationNotEnabled.selector); + + vm.prank(alice); + RegistryFacet(diamondAddr).cancelTask(0); + } + + /// @dev Test to ensure 'cancelTask' reverts if task does not exist. + function testCancelTaskRevertsIfTaskDoesNotExist() public { + vm.expectRevert(IRegistryFacet.TaskDoesNotExist.selector); + + vm.prank(alice); + RegistryFacet(diamondAddr).cancelTask(0); + } + + /// @dev Test to ensure 'cancelTask' reverts if task type is not UST. + function testCancelTaskRevertsIfTaskTypeNotUST() public { + testRegisterSystemTask(); + vm.expectRevert(IRegistryFacet.UnsupportedTaskOperation.selector); + + vm.prank(bob); + RegistryFacet(diamondAddr).cancelTask(0); + } + + /// @dev Test to ensure 'cancelTask' reverts if caller is not the task owner. + function testCancelTaskRevertsIfUnauthorizedCaller() public { + testRegister(); + vm.expectRevert(IRegistryFacet.UnauthorizedAccount.selector); + + vm.prank(bob); + RegistryFacet(diamondAddr).cancelTask(0); + } + + /// @dev Test to ensure 'cancelTask' cancels a UST. + function testCancelTask() public { + testRegister(); + + vm.prank(alice); + RegistryFacet(diamondAddr).cancelTask(0); + + assertFalse(RegistryFacet(diamondAddr).ifTaskExists(0)); + assertEq(RegistryFacet(diamondAddr).totalTasks(), 0); + assertEq(RegistryFacet(diamondAddr).getGasCommittedForNextCycle(), 0); + assertEq(RegistryFacet(diamondAddr).getTotalDepositedAutomationFees(), 0); + assertEq(erc20Supra.balanceOf(diamondAddr), 0.252 ether); + assertEq(erc20Supra.balanceOf(alice), 4.748 ether); + } + + /// @dev Test to ensure 'cancelTask' emits event 'TaskCancelled'. + function testCancelTaskEmitsEvent() public { + testRegister(); + + vm.expectEmit(true, true, true, false); + emit RegistryFacet.TaskCancelled(0, alice, keccak256("txHash")); + + vm.prank(alice); + RegistryFacet(diamondAddr).cancelTask(0); + } + + // :::::::::::::::::::::::::::::::::::::::::::::::::::::: Tests related to 'cancelSystemTask' :::::::::::::::::::::::::::::::::::::::::::::::::::::: + + /// @dev Test to ensure 'cancelSystemTask' reverts if automation is not enabled. + function testCancelSystemTaskRevertsIfAutomationNotEnabled() public { + vm.prank(admin); + CoreFacet(diamondAddr).disableAutomation(); + + vm.expectRevert(IRegistryFacet.AutomationNotEnabled.selector); + + vm.prank(alice); + RegistryFacet(diamondAddr).cancelSystemTask(0); + } + + /// @dev Test to ensure 'cancelSystemTask' reverts if task does not exist. + function testCancelSystemTaskRevertsIfTaskDoesNotExist() public { + vm.expectRevert(IRegistryFacet.TaskDoesNotExist.selector); + + vm.prank(alice); + RegistryFacet(diamondAddr).cancelSystemTask(0); + } + + /// @dev Test to ensure 'cancelSystemTask' reverts if task does not exist in system tasks. + function testCancelSystemTaskRevertsIfSystemTaskDoesNotExist() public { + testRegister(); + vm.expectRevert(IRegistryFacet.SystemTaskDoesNotExist.selector); + + vm.prank(alice); + RegistryFacet(diamondAddr).cancelSystemTask(0); + } + + /// @dev Test to ensure 'cancelSystemTask' reverts if caller is not the task owner. + function testCancelSystemTaskRevertsIfUnauthorizedCaller() public { + testRegisterSystemTask(); + vm.expectRevert(IRegistryFacet.UnauthorizedAccount.selector); + + vm.prank(alice); + RegistryFacet(diamondAddr).cancelSystemTask(0); + } + + /// @dev Test to ensure 'cancelSystemTask' cancels a GST. + function testCancelSystemTask() public { + testRegisterSystemTask(); + + vm.prank(bob); + RegistryFacet(diamondAddr).cancelSystemTask(0); + + assertFalse(RegistryFacet(diamondAddr).ifTaskExists(0)); + assertFalse(RegistryFacet(diamondAddr).ifSysTaskExists(0)); + assertEq(RegistryFacet(diamondAddr).totalTasks(), 0); + assertEq(RegistryFacet(diamondAddr).totalSystemTasks(), 0); + assertEq(RegistryFacet(diamondAddr).getSystemGasCommittedForNextCycle(), 0); + } + + /// @dev Test to ensure 'cancelSystemTask' emits event 'TaskCancelled'. + function testCancelSystemTaskEmitsEvent() public { + testRegisterSystemTask(); + + vm.expectEmit(true, true, true, false); + emit RegistryFacet.TaskCancelled(0, bob, keccak256("txHash")); + + vm.prank(bob); + RegistryFacet(diamondAddr).cancelSystemTask(0); + } + + // :::::::::::::::::::::::::::::::::::::::::::::::::::::: Tests related to 'stopTasks' :::::::::::::::::::::::::::::::::::::::::::::::::::::: + + /// @dev Test to ensure 'stopTasks' reverts if automation is not enabled. + function testStopTasksRevertsIfAutomationNotEnabled() public { + vm.prank(admin); + CoreFacet(diamondAddr).disableAutomation(); + + uint64[] memory taskIndexes; + vm.expectRevert(IRegistryFacet.AutomationNotEnabled.selector); + + vm.prank(alice); + RegistryFacet(diamondAddr).stopTasks(taskIndexes); + } + + /// @dev Test to ensure 'stopTasks' reverts if input array is empty. + function testStopTasksRevertsIfInputArrayEmpty() public { + uint64[] memory taskIndexes; + vm.expectRevert(IRegistryFacet.TaskIndexesCannotBeEmpty.selector); + + vm.prank(alice); + RegistryFacet(diamondAddr).stopTasks(taskIndexes); + } + + /// @dev Test to ensure 'stopTasks' reverts if caller is not the task owner. + function testStopTasksRevertsIfUnauthorizedCaller() public { + testRegister(); + + uint64[] memory taskIndexes = new uint64[](1); + taskIndexes[0] = 0; + + vm.expectRevert(IRegistryFacet.UnauthorizedAccount.selector); + + vm.prank(bob); + RegistryFacet(diamondAddr).stopTasks(taskIndexes); + } + + /// @dev Test to ensure 'stopTasks' reverts if task type is not UST. + function testStopTasksRevertsIfTaskTypeNotUST() public { + testRegisterSystemTask(); + + uint64[] memory taskIndexes = new uint64[](1); + taskIndexes[0] = 0; + + vm.expectRevert(IRegistryFacet.UnsupportedTaskOperation.selector); + + vm.prank(bob); + RegistryFacet(diamondAddr).stopTasks(taskIndexes); + } + + /// @dev Test to ensure 'stopTasks' does nothing if task does not exist. + function testStopTasksDoesNothingIfTaskDoesNotExist() public { + testRegister(); + + uint64[] memory taskIndexes = new uint64[](1); + taskIndexes[0] = 5; + + vm.prank(alice); + RegistryFacet(diamondAddr).stopTasks(taskIndexes); + + assertEq(RegistryFacet(diamondAddr).totalTasks(), 1); + assertEq(RegistryFacet(diamondAddr).getTotalDepositedAutomationFees(), 0.5 ether); + } + + /// @dev Test to ensure 'stopTasks' stops the input UST tasks. + function testStopTasks() public { + testRegister(); + + uint64[] memory taskIndexes = new uint64[](1); + taskIndexes[0] = 0; + + vm.warp(2002); + vm.startPrank(vmSigner, vmSigner); + CoreFacet(diamondAddr).monitorCycleEnd(); + CoreFacet(diamondAddr).processTasks(2, taskIndexes); + vm.stopPrank(); + + assertEq(erc20Supra.balanceOf(diamondAddr), 0.702 ether); + assertEq(erc20Supra.balanceOf(alice), 4.298 ether); + + vm.prank(alice); + RegistryFacet(diamondAddr).stopTasks(taskIndexes); + + assertFalse(RegistryFacet(diamondAddr).ifTaskExists(0)); + assertEq(RegistryFacet(diamondAddr).totalTasks(), 0); + assertEq(RegistryFacet(diamondAddr).getGasCommittedForNextCycle(), 0); + assertEq(RegistryFacet(diamondAddr).getTotalDepositedAutomationFees(), 0); + assertEq(erc20Supra.balanceOf(diamondAddr), 0.18955 ether); + assertEq(erc20Supra.balanceOf(alice), 4.81045 ether); + } + + /// @dev Test to ensure 'stopTasks' emits event 'TasksStopped'. + function testStopTasksEmitsEvent() public { + testRegister(); + + uint64[] memory taskIndexes = new uint64[](1); + taskIndexes[0] = 0; + + vm.warp(2002); + vm.startPrank(vmSigner, vmSigner); + CoreFacet(diamondAddr).monitorCycleEnd(); + CoreFacet(diamondAddr).processTasks(2, taskIndexes); + vm.stopPrank(); + + LibUtils.TaskStopped[] memory stoppedTasks = new LibUtils.TaskStopped[](1); + stoppedTasks[0] = LibUtils.TaskStopped(0, 0.5 ether, 0.01245 ether, keccak256("txHash")); + + vm.expectEmit(true, true, false, false); + emit RegistryFacet.TasksStopped(stoppedTasks, alice); + + vm.prank(alice); + RegistryFacet(diamondAddr).stopTasks(taskIndexes); + } + + // :::::::::::::::::::::::::::::::::::::::::::::::::::::: Tests related to 'stopSystemTasks' :::::::::::::::::::::::::::::::::::::::::::::::::::::: + + /// @dev Test to ensure 'stopSystemTasks' reverts if automation is not enabled. + function testStopSystemTasksRevertsIfAutomationNotEnabled() public { + vm.prank(admin); + CoreFacet(diamondAddr).disableAutomation(); + + uint64[] memory taskIndexes; + vm.expectRevert(IRegistryFacet.AutomationNotEnabled.selector); + + vm.prank(alice); + RegistryFacet(diamondAddr).stopSystemTasks(taskIndexes); + } + + /// @dev Test to ensure 'stopSystemTasks' reverts if input array is empty. + function testStopSystemTasksRevertsIfInputArrayEmpty() public { + uint64[] memory taskIndexes; + vm.expectRevert(IRegistryFacet.TaskIndexesCannotBeEmpty.selector); + + vm.prank(alice); + RegistryFacet(diamondAddr).stopSystemTasks(taskIndexes); + } + + /// @dev Test to ensure 'stopSystemTasks' reverts if caller is not the task owner. + function testStopSystemTasksRevertsIfUnauthorizedCaller() public { + testRegisterSystemTask(); + + uint64[] memory taskIndexes = new uint64[](1); + taskIndexes[0] = 0; + + vm.expectRevert(IRegistryFacet.UnauthorizedAccount.selector); + + vm.prank(alice); + RegistryFacet(diamondAddr).stopSystemTasks(taskIndexes); + } + + /// @dev Test to ensure 'stopSystemTasks' reverts if task type is not GST. + function testStopSystemTasksRevertsIfTaskTypeNotGST() public { + testRegister(); + + uint64[] memory taskIndexes = new uint64[](1); + taskIndexes[0] = 0; + + vm.expectRevert(IRegistryFacet.UnsupportedTaskOperation.selector); + + vm.prank(alice); + RegistryFacet(diamondAddr).stopSystemTasks(taskIndexes); + } + + /// @dev Test to ensure 'stopSystemTasks' does nothing if task does not exist. + function testStopSystemTasksDoesNothingIfTaskDoesNotExist() public { + testRegisterSystemTask(); + + uint64[] memory taskIndexes = new uint64[](1); + taskIndexes[0] = 5; + + vm.prank(alice); + RegistryFacet(diamondAddr).stopSystemTasks(taskIndexes); + + assertEq(RegistryFacet(diamondAddr).totalTasks(), 1); + assertEq(RegistryFacet(diamondAddr).totalSystemTasks(), 1); + } + + /// @dev Test to ensure 'stopSystemTasks' stops the input GST tasks. + function testStopSystemTasks() public { + testRegisterSystemTask(); + + uint64[] memory taskIndexes = new uint64[](1); + taskIndexes[0] = 0; + + vm.warp(2002); + vm.prank(vmSigner, vmSigner); + CoreFacet(diamondAddr).monitorCycleEnd(); + + vm.prank(vmSigner); + CoreFacet(diamondAddr).processTasks(2, taskIndexes); + + vm.prank(bob); + RegistryFacet(diamondAddr).stopSystemTasks(taskIndexes); + + assertFalse(RegistryFacet(diamondAddr).ifTaskExists(0)); + assertFalse(RegistryFacet(diamondAddr).ifSysTaskExists(0)); + assertEq(RegistryFacet(diamondAddr).totalTasks(), 0); + assertEq(RegistryFacet(diamondAddr).totalSystemTasks(), 0); + assertEq(RegistryFacet(diamondAddr).getSystemGasCommittedForNextCycle(), 1000000); + } + + /// @dev Test to ensure 'stopSystemTasks' emits event 'TasksStopped'. + function testStopSystemTasksEmitsEvent() public { + testRegisterSystemTask(); + + uint64[] memory taskIndexes = new uint64[](1); + taskIndexes[0] = 0; + + vm.warp(2002); + vm.prank(vmSigner, vmSigner); + CoreFacet(diamondAddr).monitorCycleEnd(); + + vm.prank(vmSigner); + CoreFacet(diamondAddr).processTasks(2, taskIndexes); + + LibUtils.TaskStopped[] memory stoppedTasks = new LibUtils.TaskStopped[](1); + stoppedTasks[0] = LibUtils.TaskStopped(0, 0, 0, keccak256("txHash")); + + vm.expectEmit(true, true, false, false); + emit RegistryFacet.TasksStopped(stoppedTasks, bob); + + vm.prank(bob); + RegistryFacet(diamondAddr).stopSystemTasks(taskIndexes); + } +} \ No newline at end of file From 1c0b7437c4d00d0bb83700f0d7288f9908322ff7 Mon Sep 17 00:00:00 2001 From: Udit Yadav Date: Tue, 17 Feb 2026 14:40:51 +0530 Subject: [PATCH 2/8] minor fixes --- .../script/DeployDiamond.s.sol | 7 - .../src/facets/ConfigFacet.sol | 20 +-- .../src/facets/RegistryFacet.sol | 84 ++++++------ .../src/interfaces/IRegistryFacet.sol | 3 +- .../src/libraries/LibAppStorage.sol | 4 +- .../supra_contracts/src/libraries/LibCore.sol | 3 +- .../src/libraries/LibDiamondUtils.sol | 3 +- .../src/libraries/LibUtils.sol | 18 +-- .../test/BaseDiamondTest.t.sol | 3 +- solidity/supra_contracts/test/BlockMeta.t.sol | 11 +- .../supra_contracts/test/ConfigFacet.t.sol | 6 +- solidity/supra_contracts/test/CoreFacet.t.sol | 39 +++--- .../supra_contracts/test/DiamondInit.t.sol | 124 ++++++++++-------- .../supra_contracts/test/RegistryFacet.t.sol | 26 +++- 14 files changed, 189 insertions(+), 162 deletions(-) diff --git a/solidity/supra_contracts/script/DeployDiamond.s.sol b/solidity/supra_contracts/script/DeployDiamond.s.sol index c1f899a886..a4c4a92c97 100644 --- a/solidity/supra_contracts/script/DeployDiamond.s.sol +++ b/solidity/supra_contracts/script/DeployDiamond.s.sol @@ -2,14 +2,7 @@ pragma solidity 0.8.27; import {Script, console} from "forge-std/Script.sol"; -import {Diamond} from "../src/Diamond.sol"; -import {IDiamondCut} from "../src/interfaces/IDiamondCut.sol"; -import {DiamondCutFacet} from "../src/facets/DiamondCutFacet.sol"; -import {DiamondLoupeFacet} from "../src/facets/DiamondLoupeFacet.sol"; import {OwnershipFacet} from "../src/facets/OwnershipFacet.sol"; -import {RegistryFacet} from "../src/facets/RegistryFacet.sol"; -import {CoreFacet} from "../src/facets/CoreFacet.sol"; -import {DiamondInit} from "../src/upgradeInitializers/DiamondInit.sol"; import {Deployment, InitParams, LibDiamondUtils} from "../src/libraries/LibDiamondUtils.sol"; contract DeployDiamond is Script { diff --git a/solidity/supra_contracts/src/facets/ConfigFacet.sol b/solidity/supra_contracts/src/facets/ConfigFacet.sol index b33bbc5003..497045585a 100644 --- a/solidity/supra_contracts/src/facets/ConfigFacet.sol +++ b/solidity/supra_contracts/src/facets/ConfigFacet.sol @@ -64,7 +64,7 @@ contract ConfigFacet is IConfigFacet { function enableRegistration() external { LibDiamond.enforceIsContractOwner(); - if(s.registrationEnabled) { revert AlreadyEnabled(); } + if (s.registrationEnabled) { revert AlreadyEnabled(); } s.registrationEnabled = true; emit TaskRegistrationEnabled(s.registrationEnabled); @@ -74,7 +74,7 @@ contract ConfigFacet is IConfigFacet { function disableRegistration() external { LibDiamond.enforceIsContractOwner(); - if(!s.registrationEnabled) { revert AlreadyDisabled(); } + if (!s.registrationEnabled) { revert AlreadyDisabled(); } s.registrationEnabled = false; emit TaskRegistrationDisabled(s.registrationEnabled); @@ -85,7 +85,7 @@ contract ConfigFacet is IConfigFacet { function setVmSigner(address _vmSigner) external { LibDiamond.enforceIsContractOwner(); - if(_vmSigner == address(0)) { revert AddressCannotBeZero(); } + if (_vmSigner == address(0)) { revert AddressCannotBeZero(); } address oldVmSigner = s.vmSigner; s.vmSigner = _vmSigner; @@ -112,15 +112,15 @@ contract ConfigFacet is IConfigFacet { function withdrawFees(uint256 _amount, address _recipient) external { LibDiamond.enforceIsContractOwner(); - if(_amount == 0) { revert InvalidAmount(); } - if(_recipient == address(0)) { revert AddressCannotBeZero(); } + if (_amount == 0) { revert InvalidAmount(); } + if (_recipient == address(0)) { revert AddressCannotBeZero(); } uint256 balance = IERC20(s.erc20Supra).balanceOf(address(this)); - if(balance < _amount) { revert InsufficientBalance(); } - if(balance - _amount < s.registryState.cycleLockedFees + s.registryState.totalDepositedAutomationFees) { revert RequestExceedsLockedBalance(); } + if (balance < _amount) { revert InsufficientBalance(); } + if (balance - _amount < s.registryState.cycleLockedFees + s.registryState.totalDepositedAutomationFees) { revert RequestExceedsLockedBalance(); } bool sent = IERC20(s.erc20Supra).transfer(_recipient, _amount); - if(!sent) { revert TransferFailed(); } + if (!sent) { revert TransferFailed(); } emit RegistryFeeWithdrawn(_recipient, _amount); } @@ -154,8 +154,8 @@ contract ConfigFacet is IConfigFacet { _sysTaskCapacity ); - if(s.registryState.gasCommittedForNextCycle > _registryMaxGasCap) { revert UnacceptableRegistryMaxGasCap(); } - if(s.registryState.sysGasCommittedForNextCycle > _sysRegistryMaxGasCap) { revert UnacceptableSysRegistryMaxGasCap(); } + if (s.registryState.gasCommittedForNextCycle > _registryMaxGasCap) { revert UnacceptableRegistryMaxGasCap(); } + if (s.registryState.sysGasCommittedForNextCycle > _sysRegistryMaxGasCap) { revert UnacceptableSysRegistryMaxGasCap(); } // Add new config to the buffer Config memory configBuffer = Config({ diff --git a/solidity/supra_contracts/src/facets/RegistryFacet.sol b/solidity/supra_contracts/src/facets/RegistryFacet.sol index f540b953fe..3b566dd63b 100644 --- a/solidity/supra_contracts/src/facets/RegistryFacet.sol +++ b/solidity/supra_contracts/src/facets/RegistryFacet.sol @@ -107,6 +107,7 @@ contract RegistryFacet is IRegistryFacet { s.registryState.tasks[taskIndex] = taskMetadata; require(s.registryState.taskIdList.add(taskIndex), TaskIndexNotUnique()); + require(s.registryState.userTasks[msg.sender].add(taskIndex), TaskIndexNotUnique()); s.registryState.currentIndex += 1; s.registryState.totalDepositedAutomationFees += _automationFeeCapForCycle; @@ -130,7 +131,7 @@ contract RegistryFacet is IRegistryFacet { uint64 _priority, bytes[] memory _auxData ) external { - if(!isAuthorizedSubmitter(msg.sender)) { revert UnauthorizedAccount(); } + if (!isAuthorizedSubmitter(msg.sender)) { revert UnauthorizedAccount(); } uint64 regTime = uint64(block.timestamp); LibRegistry.updateStateForValidRegistration( @@ -165,6 +166,7 @@ contract RegistryFacet is IRegistryFacet { s.registryState.tasks[taskIndex] = taskMetadata; require(s.registryState.taskIdList.add(taskIndex), TaskIndexNotUnique()); + require(s.registryState.userTasks[msg.sender].add(taskIndex), TaskIndexNotUnique()); require(s.registryState.sysTaskIds.add(taskIndex), TaskIndexNotUnique()); s.registryState.currentIndex += 1; @@ -185,25 +187,25 @@ contract RegistryFacet is IRegistryFacet { // Check if automation is enabled if (!s.automationEnabled) { revert AutomationNotEnabled(); } - if(!LibRegistry.isCycleStarted()) { revert CycleTransitionInProgress(); } - if(!LibRegistry.ifTaskExists(_taskIndex)) { revert TaskDoesNotExist(); } + if (!LibRegistry.isCycleStarted()) { revert CycleTransitionInProgress(); } + if (!LibRegistry.ifTaskExists(_taskIndex)) { revert TaskDoesNotExist(); } TaskMetadata memory task = s.registryState.tasks[_taskIndex]; - if(task.taskType == LibUtils.TaskType.GST) { revert UnsupportedTaskOperation(); } - if(task.owner != msg.sender) { revert UnauthorizedAccount(); } - if(task.taskState == LibUtils.TaskState.CANCELLED) { revert AlreadyCancelled(); } + if (task.taskType == LibUtils.TaskType.GST) { revert UnsupportedTaskOperation(); } + if (task.owner != msg.sender) { revert UnauthorizedAccount(); } + if (task.taskState == LibUtils.TaskState.CANCELLED) { revert AlreadyCancelled(); } if (task.taskState == LibUtils.TaskState.PENDING) { // When Pending tasks are cancelled, refund of the deposit fee is done with penalty - _removeTask(_taskIndex, false); + removeTask(_taskIndex, task.owner, false); bool result = LibRegistry.safeDepositRefund( _taskIndex, task.owner, task.depositFee / REFUND_FACTOR, task.depositFee ); - if(!result) { revert ErrorDepositRefund(); } + if (!result) { revert ErrorDepositRefund(); } } else { // It is safe not to check the state as above, the cancelled tasks are already rejected. // Active tasks will be refunded the deposited amount fully at the end of the cycle. @@ -233,27 +235,27 @@ contract RegistryFacet is IRegistryFacet { // Check if automation is enabled if (!s.automationEnabled) { revert AutomationNotEnabled(); } - if(!LibRegistry.isCycleStarted()) { revert CycleTransitionInProgress(); } - if(!LibRegistry.ifTaskExists(_taskIndex)) { revert TaskDoesNotExist(); } - if(!LibRegistry.ifSysTaskExists(_taskIndex)) { revert SystemTaskDoesNotExist(); } + if (!LibRegistry.isCycleStarted()) { revert CycleTransitionInProgress(); } + if (!LibRegistry.ifTaskExists(_taskIndex)) { revert TaskDoesNotExist(); } + if (!LibRegistry.ifSysTaskExists(_taskIndex)) { revert SystemTaskDoesNotExist(); } TaskMetadata memory task = s.registryState.tasks[_taskIndex]; // Check if GST - if(task.taskType == LibUtils.TaskType.UST) { revert UnsupportedTaskOperation(); } + if (task.taskType == LibUtils.TaskType.UST) { revert UnsupportedTaskOperation(); } - if(task.owner != msg.sender) { revert UnauthorizedAccount(); } - if(task.taskState == LibUtils.TaskState.CANCELLED) { revert AlreadyCancelled(); } + if (task.owner != msg.sender) { revert UnauthorizedAccount(); } + if (task.taskState == LibUtils.TaskState.CANCELLED) { revert AlreadyCancelled(); } - if(task.taskState == LibUtils.TaskState.PENDING) { - _removeTask(_taskIndex, true); + if (task.taskState == LibUtils.TaskState.PENDING) { + removeTask(_taskIndex, task.owner, true); } else { s.registryState.tasks[_taskIndex].taskState = LibUtils.TaskState.CANCELLED; } // This check means the task was expected to be executed in the next cycle, but it has been cancelled. // We need to remove its gas commitment from `gasCommittedForNextCycle` for this particular task. - if(task.expiryTime > LibCore.getCycleEndTime()) { + if (task.expiryTime > LibCore.getCycleEndTime()) { LibRegistry.updateGasCommittedForNextCycle(task.taskType, task.maxGasAmount); } @@ -272,8 +274,8 @@ contract RegistryFacet is IRegistryFacet { // Check if automation is enabled if (!s.automationEnabled) { revert AutomationNotEnabled(); } - if(!LibRegistry.isCycleStarted()) { revert CycleTransitionInProgress(); } - if(_taskIndexes.length == 0) { revert TaskIndexesCannotBeEmpty(); } + if (!LibRegistry.isCycleStarted()) { revert CycleTransitionInProgress(); } + if (_taskIndexes.length == 0) { revert TaskIndexesCannotBeEmpty(); } LibUtils.TaskStopped[] memory stoppedTaskDetails = new LibUtils.TaskStopped[](_taskIndexes.length); uint256 counter = 0; @@ -287,24 +289,24 @@ contract RegistryFacet is IRegistryFacet { // Loop through each task index to validate and stop the task for (uint256 i = 0; i < _taskIndexes.length; i++) { - if(LibRegistry.ifTaskExists(_taskIndexes[i])) { + if (LibRegistry.ifTaskExists(_taskIndexes[i])) { TaskMetadata memory task = s.registryState.tasks[_taskIndexes[i]]; // Check if authorised - if(msg.sender != task.owner) { revert UnauthorizedAccount(); } + if (msg.sender != task.owner) { revert UnauthorizedAccount(); } // Check if UST - if(task.taskType == LibUtils.TaskType.GST) { revert UnsupportedTaskOperation(); } + if (task.taskType == LibUtils.TaskType.GST) { revert UnsupportedTaskOperation(); } // Remove task from the registry - _removeTask(_taskIndexes[i], false); + removeTask(_taskIndexes[i], task.owner, false); // Remove from active tasks require(s.registryState.activeTaskIds.remove(_taskIndexes[i]), TaskIndexNotFound()); // This check means the task was expected to be executed in the next cycle, but it has been stopped. // We need to remove its gas commitment from `gasCommittedForNextCycle` for this particular task. // Also it checks that task should not be cancelled. - if(task.taskState != LibUtils.TaskState.CANCELLED && task.expiryTime > cycleEndTime) { + if (task.taskState != LibUtils.TaskState.CANCELLED && task.expiryTime > cycleEndTime) { // Reduce committed gas by the stopped task's max gas LibRegistry.updateGasCommittedForNextCycle(task.taskType, task.maxGasAmount); } @@ -334,7 +336,7 @@ contract RegistryFacet is IRegistryFacet { } // Refund and emit event if any tasks were stopped - if(stoppedTaskDetails.length > 0) { + if (stoppedTaskDetails.length > 0) { LibRegistry.refund(msg.sender, totalRefundFee); // Emit task stopped event @@ -357,28 +359,28 @@ contract RegistryFacet is IRegistryFacet { // Check if automation is enabled if (!s.automationEnabled) { revert AutomationNotEnabled(); } - if(!LibRegistry.isCycleStarted()) { revert CycleTransitionInProgress(); } + if (!LibRegistry.isCycleStarted()) { revert CycleTransitionInProgress(); } // Ensure that task indexes are provided - if(_taskIndexes.length == 0) { revert TaskIndexesCannotBeEmpty(); } + if (_taskIndexes.length == 0) { revert TaskIndexesCannotBeEmpty(); } LibUtils.TaskStopped[] memory stoppedTaskDetails = new LibUtils.TaskStopped[](_taskIndexes.length); uint256 counter = 0; // Loop through each task index to validate and stop the task for (uint256 i = 0; i < _taskIndexes.length; i++) { - if(LibRegistry.ifTaskExists(_taskIndexes[i])) { + if (LibRegistry.ifTaskExists(_taskIndexes[i])) { TaskMetadata memory task = s.registryState.tasks[_taskIndexes[i]]; - if(task.owner != msg.sender) { revert UnauthorizedAccount(); } + if (task.owner != msg.sender) { revert UnauthorizedAccount(); } // Check if GST - if(task.taskType == LibUtils.TaskType.UST) { revert UnsupportedTaskOperation(); } - _removeTask(_taskIndexes[i], true); + if (task.taskType == LibUtils.TaskType.UST) { revert UnsupportedTaskOperation(); } + removeTask(_taskIndexes[i], task.owner, true); // Remove from active tasks require(s.registryState.activeTaskIds.remove(_taskIndexes[i]), TaskIndexNotFound()); - if(task.taskState != LibUtils.TaskState.CANCELLED && task.expiryTime > LibCore.getCycleEndTime()) { + if (task.taskState != LibUtils.TaskState.CANCELLED && task.expiryTime > LibCore.getCycleEndTime()) { LibRegistry.updateGasCommittedForNextCycle(task.taskType, task.maxGasAmount); } @@ -394,7 +396,7 @@ contract RegistryFacet is IRegistryFacet { } } - if(stoppedTaskDetails.length > 0) { + if (stoppedTaskDetails.length > 0) { // Emit task stopped event emit TasksStopped( stoppedTaskDetails, @@ -414,15 +416,17 @@ contract RegistryFacet is IRegistryFacet { } /// @notice Function to remove a task from the registry. - /// @param _taskIndex Index of the task to remove. + /// @param _taskIndex Index of the task to remove. + /// @param _owner Address of the task owner. /// @param _removeFromSysReg Wheather to remove from system task registry. - function _removeTask(uint64 _taskIndex, bool _removeFromSysReg) private { - if(_removeFromSysReg) { + function removeTask(uint64 _taskIndex, address _owner, bool _removeFromSysReg) private { + if (_removeFromSysReg) { require(s.registryState.sysTaskIds.remove(_taskIndex), TaskIndexNotFound()); } delete s.registryState.tasks[_taskIndex]; require(s.registryState.taskIdList.remove(_taskIndex), TaskIndexNotFound()); + require(s.registryState.userTasks[_owner].remove(_taskIndex), TaskIndexNotFound()); } // :::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::: VIEW FUNCTIONS :::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::: @@ -432,6 +436,12 @@ contract RegistryFacet is IRegistryFacet { return s.registryState.taskIdList.values(); } + /// @notice Returns all the automation tasks registered by a user. + /// @param _user Address of the user to fetch registered tasks for. + function getUserTasks(address _user) external view returns (uint256[] memory) { + return s.registryState.userTasks[_user].values(); + } + /// @notice Returns all the system tasks available in the registry. function getSystemTaskIds() external view returns (uint256[] memory) { return s.registryState.sysTaskIds.values(); @@ -484,7 +494,7 @@ contract RegistryFacet is IRegistryFacet { uint256 exists; for (uint256 i = 0; i < count; i++) { - if(LibRegistry.ifTaskExists(_taskIndexes[i])) { + if (LibRegistry.ifTaskExists(_taskIndexes[i])) { temp[exists] = s.registryState.tasks[_taskIndexes[i]]; exists += 1; } diff --git a/solidity/supra_contracts/src/interfaces/IRegistryFacet.sol b/solidity/supra_contracts/src/interfaces/IRegistryFacet.sol index c37dd8c008..1d9b795c43 100644 --- a/solidity/supra_contracts/src/interfaces/IRegistryFacet.sol +++ b/solidity/supra_contracts/src/interfaces/IRegistryFacet.sol @@ -2,7 +2,7 @@ pragma solidity 0.8.27; import {LibUtils} from "../libraries/LibUtils.sol"; -import {Config, TaskMetadata} from "../libraries/LibAppStorage.sol"; +import {TaskMetadata} from "../libraries/LibAppStorage.sol"; interface IRegistryFacet { // Custom errors @@ -45,6 +45,7 @@ interface IRegistryFacet { function getTotalActiveTasks() external view returns (uint256); function getTotalDepositedAutomationFees() external view returns (uint256); function getTotalLockedBalance() external view returns (uint256); + function getUserTasks(address _user) external view returns (uint256[] memory); function hasActiveSystemTask(address _account, uint64 _taskIndex) external view returns (bool); function hasActiveTaskOfType(address _account, uint64 _taskIndex, LibUtils.TaskType _type) external view returns (bool); function hasActiveUserTask(address _account, uint64 _taskIndex) external view returns (bool); diff --git a/solidity/supra_contracts/src/libraries/LibAppStorage.sol b/solidity/supra_contracts/src/libraries/LibAppStorage.sol index ad26928925..d3538404b8 100644 --- a/solidity/supra_contracts/src/libraries/LibAppStorage.sol +++ b/solidity/supra_contracts/src/libraries/LibAppStorage.sol @@ -65,9 +65,9 @@ struct RegistryState { uint64 currentIndex; EnumerableSet.UintSet activeTaskIds; EnumerableSet.UintSet taskIdList; - mapping(uint64 => TaskMetadata) tasks; EnumerableSet.UintSet sysTaskIds; - // mapping(address => uint64[]) userTasks TO_DO: user to their tasks, need to decide on this + mapping(uint64 => TaskMetadata) tasks; + mapping(address => EnumerableSet.UintSet) userTasks; } /// @notice Central AppStorage layout for Diamond proxy diff --git a/solidity/supra_contracts/src/libraries/LibCore.sol b/solidity/supra_contracts/src/libraries/LibCore.sol index a9105d2ca2..14983a1403 100644 --- a/solidity/supra_contracts/src/libraries/LibCore.sol +++ b/solidity/supra_contracts/src/libraries/LibCore.sol @@ -69,7 +69,7 @@ library LibCore { function removeTask(uint64 _taskIndex, bool _removeFromSysReg) private { AppStorage storage s = LibAppStorage.appStorage(); - if(_removeFromSysReg) { + if (_removeFromSysReg) { require(s.registryState.sysTaskIds.remove(_taskIndex), TaskIndexNotFound()); } @@ -423,7 +423,6 @@ library LibCore { if (userBalance < _fee) { // If the user does not have enough balance, remove the task, DON'T refund the locked deposit, but simply unlock it and emit an event. - // require(unlocked, UnlockLockedDepositFailed()); LibRegistry.safeUnlockLockedDeposit(_taskIndex, _lockedFeeForNextCycle); removeTask(_taskIndex, false); diff --git a/solidity/supra_contracts/src/libraries/LibDiamondUtils.sol b/solidity/supra_contracts/src/libraries/LibDiamondUtils.sol index dc3d083ac6..a166e14dc7 100644 --- a/solidity/supra_contracts/src/libraries/LibDiamondUtils.sol +++ b/solidity/supra_contracts/src/libraries/LibDiamondUtils.sol @@ -229,7 +229,7 @@ library LibDiamondUtils { // RegistryFacet // ------------------------------------------------------------ { - bytes4[] memory selectors = new bytes4[](35); + bytes4[] memory selectors = new bytes4[](36); selectors[0] = RegistryFacet.register.selector; selectors[1] = RegistryFacet.registerSystemTask.selector; selectors[2] = RegistryFacet.cancelTask.selector; @@ -266,6 +266,7 @@ library LibDiamondUtils { selectors[32] = RegistryFacet.estimateAutomationFeeWithCommittedOccupancy.selector; selectors[33] = RegistryFacet.ifTaskExists.selector; selectors[34] = RegistryFacet.ifSysTaskExists.selector; + selectors[35] = RegistryFacet.getUserTasks.selector; cut[3] = IDiamondCut.FacetCut({ facetAddress: registryFacet, diff --git a/solidity/supra_contracts/src/libraries/LibUtils.sol b/solidity/supra_contracts/src/libraries/LibUtils.sol index 2ce4b8e709..f6cca6282a 100644 --- a/solidity/supra_contracts/src/libraries/LibUtils.sol +++ b/solidity/supra_contracts/src/libraries/LibUtils.sol @@ -115,15 +115,15 @@ library LibUtils { uint128 _sysRegistryMaxGasCap, uint16 _sysTaskCapacity ) internal pure { - if(_taskDurationCapSecs <= _cycleDurationSecs) { revert InvalidTaskDuration(); } - if(_registryMaxGasCap == 0) { revert InvalidRegistryMaxGasCap(); } - if(_congestionThresholdPercentage > 100) { revert InvalidCongestionThreshold(); } - if(_congestionExponent == 0) { revert InvalidCongestionExponent(); } - if(_taskCapacity == 0) { revert InvalidTaskCapacity(); } - if(_cycleDurationSecs == 0) { revert InvalidCycleDuration(); } - if(_sysTaskDurationCapSecs <= _cycleDurationSecs) { revert InvalidSysTaskDuration(); } - if(_sysRegistryMaxGasCap == 0) { revert InvalidSysRegistryMaxGasCap(); } - if(_sysTaskCapacity == 0) { revert InvalidSysTaskCapacity(); } + if (_taskDurationCapSecs <= _cycleDurationSecs) { revert InvalidTaskDuration(); } + if (_registryMaxGasCap == 0) { revert InvalidRegistryMaxGasCap(); } + if (_congestionThresholdPercentage > 100) { revert InvalidCongestionThreshold(); } + if (_congestionExponent == 0) { revert InvalidCongestionExponent(); } + if (_taskCapacity == 0) { revert InvalidTaskCapacity(); } + if (_cycleDurationSecs == 0) { revert InvalidCycleDuration(); } + if (_sysTaskDurationCapSecs <= _cycleDurationSecs) { revert InvalidSysTaskDuration(); } + if (_sysRegistryMaxGasCap == 0) { revert InvalidSysRegistryMaxGasCap(); } + if (_sysTaskCapacity == 0) { revert InvalidSysTaskCapacity(); } } /// @notice Helper function to sort an array. diff --git a/solidity/supra_contracts/test/BaseDiamondTest.t.sol b/solidity/supra_contracts/test/BaseDiamondTest.t.sol index 490e1d6b27..581e689693 100644 --- a/solidity/supra_contracts/test/BaseDiamondTest.t.sol +++ b/solidity/supra_contracts/test/BaseDiamondTest.t.sol @@ -21,7 +21,6 @@ abstract contract BaseDiamondTest is Test { address constant TX_HASH_PRECOMPILE = 0x0000000000000000000000000000000053555001; address admin = address(0xA11CE); - address vmSigner = address(0x53555000); address alice = address(0x123); address bob = address(0x456); @@ -36,7 +35,7 @@ abstract contract BaseDiamondTest is Test { defaultParams = LibDiamondUtils.defaultInitParams(); deployment = LibDiamondUtils.deploy(admin); - LibDiamondUtils.executeCut(vmSigner, address(erc20Supra), defaultParams, deployment); + LibDiamondUtils.executeCut(LibUtils.VM_SIGNER, address(erc20Supra), defaultParams, deployment); diamond = Diamond(payable(deployment.diamond)); diamondAddr = deployment.diamond; diff --git a/solidity/supra_contracts/test/BlockMeta.t.sol b/solidity/supra_contracts/test/BlockMeta.t.sol index d813e6faaa..10648ecab3 100644 --- a/solidity/supra_contracts/test/BlockMeta.t.sol +++ b/solidity/supra_contracts/test/BlockMeta.t.sol @@ -18,9 +18,6 @@ contract BlockMetaTest is Test { address vmAddress = address(0x99); address alice = address(0x123); - // Address of the VM Signer: SUP0 - address constant VM_SIGNER = address(0x53555000); - /// @dev Sets up initial state for testing. /// @dev Deploys and initializes BlockMeta and AutomationController contracts. function setUp() public { @@ -360,7 +357,7 @@ contract BlockMetaTest is Test { assertEq(counter.counter(), 0); testRegister(); - vm.prank(VM_SIGNER); + vm.prank(LibUtils.VM_SIGNER); blockMeta.blockPrologue(); assertEq(counter.counter(), 1); } @@ -384,7 +381,7 @@ contract BlockMetaTest is Test { vm.expectEmit(true, true, false, true); emit BlockMeta.CallFailed(address(failingContract), failSelector, abi.encodeWithSignature("Fail()")); - vm.prank(VM_SIGNER); + vm.prank(LibUtils.VM_SIGNER); blockMeta.blockPrologue(); } @@ -395,7 +392,7 @@ contract BlockMetaTest is Test { vm.expectEmit(true, true, false, false); emit BlockMeta.CallSucceeded(counterAddress, selector); - vm.prank(VM_SIGNER); + vm.prank(LibUtils.VM_SIGNER); blockMeta.blockPrologue(); } @@ -417,7 +414,7 @@ contract BlockMetaTest is Test { vm.expectEmit(true, true, false, false); emit BlockMeta.CallSucceeded(counterAddress, selector); - vm.prank(VM_SIGNER); + vm.prank(LibUtils.VM_SIGNER); blockMeta.blockPrologue(); // Counter must still be incremented even though the first call failed diff --git a/solidity/supra_contracts/test/ConfigFacet.t.sol b/solidity/supra_contracts/test/ConfigFacet.t.sol index b28d8a77f0..c23c4e645f 100644 --- a/solidity/supra_contracts/test/ConfigFacet.t.sol +++ b/solidity/supra_contracts/test/ConfigFacet.t.sol @@ -5,6 +5,7 @@ import {BaseDiamondTest} from "./BaseDiamondTest.t.sol"; import {ERC20Supra} from "../src/ERC20Supra.sol"; import {ConfigFacet} from "../src/facets/ConfigFacet.sol"; import {IConfigFacet} from "../src/interfaces/IConfigFacet.sol"; +import {IRegistryFacet} from "../src/interfaces/IRegistryFacet.sol"; import {LibUtils} from "../src/libraries/LibUtils.sol"; import {Config} from "../src/libraries/LibAppStorage.sol"; @@ -17,7 +18,7 @@ contract ConfigFacetTest is BaseDiamondTest { vm.prank(admin); ConfigFacet(diamondAddr).grantAuthorization(alice); - // assertTrue(ConfigFacet(diamondAddr).isAuthorizedSubmitter(alice)); + assertTrue(IRegistryFacet(diamondAddr).isAuthorizedSubmitter(alice)); } /// @dev Test to ensure 'grantAuthorization' emits event 'AuthorizationGranted'. @@ -59,7 +60,7 @@ contract ConfigFacetTest is BaseDiamondTest { vm.prank(admin); ConfigFacet(diamondAddr).revokeAuthorization(alice); - // assertFalse(ConfigFacet(diamondAddr).isAuthorizedSubmitter(alice)); + assertFalse(IRegistryFacet(diamondAddr).isAuthorizedSubmitter(alice)); } /// @dev Test to ensure 'revokeAuthorization' emits event 'AuthorizationRevoked'. @@ -157,7 +158,6 @@ contract ConfigFacetTest is BaseDiamondTest { /// @dev Test to ensure 'enableRegistration' reverts if registration is already enabled. function testEnableRegistrationRevertsIfAlreadyEnabled() public { - // Already enabled in initialize() vm.expectRevert(IConfigFacet.AlreadyEnabled.selector); vm.prank(admin); diff --git a/solidity/supra_contracts/test/CoreFacet.t.sol b/solidity/supra_contracts/test/CoreFacet.t.sol index f13940506c..ed6ed20ac0 100644 --- a/solidity/supra_contracts/test/CoreFacet.t.sol +++ b/solidity/supra_contracts/test/CoreFacet.t.sol @@ -2,7 +2,6 @@ pragma solidity 0.8.27; import {BaseDiamondTest} from "./BaseDiamondTest.t.sol"; -import {ERC20Supra} from "../src/ERC20Supra.sol"; import {RegistryFacet} from "../src/facets/RegistryFacet.sol"; import {CoreFacet} from "../src/facets/CoreFacet.sol"; import {ICoreFacet} from "../src/interfaces/ICoreFacet.sol"; @@ -16,7 +15,7 @@ contract CoreFacetTest is BaseDiamondTest { function testMonitorCycleEndRevertsIfTxOriginNotVm() public { vm.expectRevert(ICoreFacet.CallerNotVmSigner.selector); - vm.prank(vmSigner); + vm.prank(LibUtils.VM_SIGNER); CoreFacet(diamondAddr).monitorCycleEnd(); } @@ -24,7 +23,7 @@ contract CoreFacetTest is BaseDiamondTest { function testMonitorCycleEndDoesNothingBeforeCycleExpiry() public { (uint64 indexBefore, uint64 startBefore, uint64 durationBefore, LibUtils.CycleState stateBefore) = CoreFacet(diamondAddr).getCycleInfo(); - vm.prank(vmSigner, vmSigner); + vm.prank(LibUtils.VM_SIGNER, LibUtils.VM_SIGNER); CoreFacet(diamondAddr).monitorCycleEnd(); (uint64 indexAfter, uint64 startAfter, uint64 durationAfter, LibUtils.CycleState stateAfter) = CoreFacet(diamondAddr).getCycleInfo(); @@ -56,7 +55,7 @@ contract CoreFacetTest is BaseDiamondTest { }); Deployment memory deployment = LibDiamondUtils.deploy(admin); - LibDiamondUtils.executeCut(vmSigner, address(erc20Supra), initParams, deployment); + LibDiamondUtils.executeCut(LibUtils.VM_SIGNER, address(erc20Supra), initParams, deployment); address diamondAddr = deployment.diamond; vm.stopPrank(); @@ -66,7 +65,7 @@ contract CoreFacetTest is BaseDiamondTest { vm.warp(startBefore + durationBefore); - vm.prank(vmSigner, vmSigner); + vm.prank(LibUtils.VM_SIGNER, LibUtils.VM_SIGNER); CoreFacet(diamondAddr).monitorCycleEnd(); (uint64 indexAfter, uint64 startAfter, uint64 durationAfter, LibUtils.CycleState stateAfter) = CoreFacet(diamondAddr).getCycleInfo(); @@ -97,7 +96,7 @@ contract CoreFacetTest is BaseDiamondTest { stateBefore ); - vm.prank(vmSigner, vmSigner); + vm.prank(LibUtils.VM_SIGNER, LibUtils.VM_SIGNER); CoreFacet(diamondAddr).monitorCycleEnd(); (uint64 indexAfter, uint64 startAfter, uint64 durationAfter, LibUtils.CycleState stateAfter) = CoreFacet(diamondAddr).getCycleInfo(); @@ -123,7 +122,7 @@ contract CoreFacetTest is BaseDiamondTest { stateBefore ); - vm.prank(vmSigner, vmSigner); + vm.prank(LibUtils.VM_SIGNER, LibUtils.VM_SIGNER); CoreFacet(diamondAddr).monitorCycleEnd(); (uint64 indexAfter, uint64 startAfter, uint64 durationAfter, LibUtils.CycleState stateAfter) = CoreFacet(diamondAddr).getCycleInfo(); @@ -150,7 +149,7 @@ contract CoreFacetTest is BaseDiamondTest { stateBefore ); - vm.prank(vmSigner, vmSigner); + vm.prank(LibUtils.VM_SIGNER, LibUtils.VM_SIGNER); CoreFacet(diamondAddr).monitorCycleEnd(); (uint64 indexAfter, uint64 startAfter, uint64 durationAfter, LibUtils.CycleState stateAfter) = CoreFacet(diamondAddr).getCycleInfo(); @@ -183,7 +182,7 @@ contract CoreFacetTest is BaseDiamondTest { vm.expectRevert(ICoreFacet.InvalidRegistryState.selector); - vm.prank(vmSigner, vmSigner); + vm.prank(LibUtils.VM_SIGNER, LibUtils.VM_SIGNER); CoreFacet(diamondAddr).processTasks(1, tasks); } @@ -194,7 +193,7 @@ contract CoreFacetTest is BaseDiamondTest { ( , uint64 startTime, uint64 duration, ) = CoreFacet(diamondAddr).getCycleInfo(); vm.warp(startTime + duration); - vm.prank(vmSigner, vmSigner); + vm.prank(LibUtils.VM_SIGNER, LibUtils.VM_SIGNER); CoreFacet(diamondAddr).monitorCycleEnd(); (uint64 index, , , LibUtils.CycleState state) = CoreFacet(diamondAddr).getCycleInfo(); @@ -209,7 +208,7 @@ contract CoreFacetTest is BaseDiamondTest { vm.expectEmit(true, false, false, false); emit LibCore.ActiveTasks(activeTasks); - vm.prank(vmSigner, vmSigner); + vm.prank(LibUtils.VM_SIGNER, LibUtils.VM_SIGNER); CoreFacet(diamondAddr).processTasks(index + 1, tasks); (uint64 newIndex, uint64 newStart, uint64 newDuration, LibUtils.CycleState newState) = CoreFacet(diamondAddr).getCycleInfo(); @@ -233,7 +232,7 @@ contract CoreFacetTest is BaseDiamondTest { ( , uint64 startTime, uint64 duration, ) = CoreFacet(diamondAddr).getCycleInfo(); vm.warp(startTime + duration); - vm.prank(vmSigner, vmSigner); + vm.prank(LibUtils.VM_SIGNER, LibUtils.VM_SIGNER); CoreFacet(diamondAddr).monitorCycleEnd(); (uint64 index, , , LibUtils.CycleState state) = CoreFacet(diamondAddr).getCycleInfo(); @@ -244,7 +243,7 @@ contract CoreFacetTest is BaseDiamondTest { vm.expectRevert(LibCore.InvalidInputCycleIndex.selector); - vm.prank(vmSigner, vmSigner); + vm.prank(LibUtils.VM_SIGNER, LibUtils.VM_SIGNER); CoreFacet(diamondAddr).processTasks(index, tasks); } @@ -256,7 +255,7 @@ contract CoreFacetTest is BaseDiamondTest { vm.warp(start + duration); // Moves state to FINISHED - vm.prank(vmSigner, vmSigner); + vm.prank(LibUtils.VM_SIGNER, LibUtils.VM_SIGNER); CoreFacet(diamondAddr).monitorCycleEnd(); ( , , , LibUtils.CycleState stateBefore) = CoreFacet(diamondAddr).getCycleInfo(); @@ -275,7 +274,7 @@ contract CoreFacetTest is BaseDiamondTest { vm.expectEmit(true, false, false, false); emit LibCore.RemovedTasks(tasks); - vm.prank(vmSigner, vmSigner); + vm.prank(LibUtils.VM_SIGNER, LibUtils.VM_SIGNER); CoreFacet(diamondAddr).processTasks(indexAfter, tasks); ( , , , LibUtils.CycleState newState) = CoreFacet(diamondAddr).getCycleInfo(); @@ -291,7 +290,7 @@ contract CoreFacetTest is BaseDiamondTest { vm.warp(start + duration); // Moves state to FINISHED - vm.prank(vmSigner, vmSigner); + vm.prank(LibUtils.VM_SIGNER, LibUtils.VM_SIGNER); CoreFacet(diamondAddr).monitorCycleEnd(); ( , , , LibUtils.CycleState stateBefore) = CoreFacet(diamondAddr).getCycleInfo(); @@ -314,7 +313,7 @@ contract CoreFacetTest is BaseDiamondTest { vm.expectEmit(true, false, false, false); emit LibCore.RemovedTasks(tasks); - vm.prank(vmSigner, vmSigner); + vm.prank(LibUtils.VM_SIGNER, LibUtils.VM_SIGNER); CoreFacet(diamondAddr).processTasks(indexAfter, tasks); (uint64 newIndex, uint64 newStart, uint64 newDuration, LibUtils.CycleState newState) = CoreFacet(diamondAddr).getCycleInfo(); @@ -333,7 +332,7 @@ contract CoreFacetTest is BaseDiamondTest { vm.warp(start + duration); // Moves state to FINISHED - vm.prank(vmSigner, vmSigner); + vm.prank(LibUtils.VM_SIGNER, LibUtils.VM_SIGNER); CoreFacet(diamondAddr).monitorCycleEnd(); ( , , , LibUtils.CycleState stateBefore) = CoreFacet(diamondAddr).getCycleInfo(); @@ -351,7 +350,7 @@ contract CoreFacetTest is BaseDiamondTest { vm.expectRevert(LibCore.InvalidInputCycleIndex.selector); - vm.prank(vmSigner, vmSigner); + vm.prank(LibUtils.VM_SIGNER, LibUtils.VM_SIGNER); CoreFacet(diamondAddr).processTasks(indexAfter + 1, tasks); } @@ -359,7 +358,6 @@ contract CoreFacetTest is BaseDiamondTest { /// @dev Test to ensure 'disableAutomation' disables the automation. function testDisableAutomation() public { - // Already enabled in initialize() vm.prank(admin); CoreFacet(diamondAddr).disableAutomation(); @@ -423,7 +421,6 @@ contract CoreFacetTest is BaseDiamondTest { /// @dev Test to ensure 'enableAutomation' reverts if automation is already enabled. function testEnableAutomationRevertsIfAlreadyEnabled() public { - // Already enabled during initialization vm.expectRevert(ICoreFacet.AlreadyEnabled.selector); vm.prank(admin); diff --git a/solidity/supra_contracts/test/DiamondInit.t.sol b/solidity/supra_contracts/test/DiamondInit.t.sol index c005e94af9..fef76c5c3d 100644 --- a/solidity/supra_contracts/test/DiamondInit.t.sol +++ b/solidity/supra_contracts/test/DiamondInit.t.sol @@ -31,7 +31,7 @@ contract DiamondInitTest is BaseDiamondTest { assertEq(RegistryFacet(diamondAddr).getNextCycleSysRegistryMaxGasCap(), 5_000_000); assertTrue(ConfigFacet(diamondAddr).isRegistrationEnabled()); assertTrue(CoreFacet(diamondAddr).isAutomationEnabled()); - assertEq(ConfigFacet(diamondAddr).getVmSigner(), vmSigner); + assertEq(ConfigFacet(diamondAddr).getVmSigner(), LibUtils.VM_SIGNER); assertEq(ConfigFacet(diamondAddr).erc20Supra(), address(erc20Supra)); Config memory config = ConfigFacet(diamondAddr).getConfig(); @@ -50,6 +50,7 @@ contract DiamondInitTest is BaseDiamondTest { assertEq(config.congestionExponent, 2); } + /// @dev Test to ensure all interfaces are registered. function testInterfacesRegistered() public view { assertTrue(IERC165(diamondAddr).supportsInterface(type(IERC165).interfaceId)); assertTrue(IERC165(diamondAddr).supportsInterface(type(IDiamondCut).interfaceId)); @@ -57,11 +58,45 @@ contract DiamondInitTest is BaseDiamondTest { assertTrue(IERC165(diamondAddr).supportsInterface(type(IERC173).interfaceId)); } + /// @dev Test to ensure 'init' selector is not registered. function testInitSelectorNotRegistered() public view { address facet = IDiamondLoupe(diamondAddr).facetAddress(DiamondInit.init.selector); assertEq(facet, address(0)); } + /// @dev Test to ensure Diamond reverts if 'init' is called. + function testInitReverts() public { + vm.expectRevert(bytes("Diamond: Function does not exist")); + + vm.prank(admin); + DiamondInit(diamondAddr).init( + 3600, + 10_000_000, + 0.001 ether, + 0.002 ether, + 50, + 0.002 ether, + 2, + 500, + 2000, + 3600, + 5_000_000, + 500, + LibUtils.VM_SIGNER, + address(erc20Supra), + true, + true + ); + } + + /// @dev Test to ensure Diamond reverts if an unknown selector is called. + function testUnknownSelectorReverts() public { + vm.expectRevert(bytes("Diamond: Function does not exist")); + + INonExistent(diamondAddr).nonExistent(); + } + + /// @dev Test to ensure 'facetAddresses' returns the address of all the facets. function testLoupeFacetAddresses() public view { address[] memory facets = IDiamondLoupe(diamondAddr).facetAddresses(); assertEq(facets.length, 6); // diamondCut, loupe, ownership, config, registry, core @@ -87,6 +122,7 @@ contract DiamondInitTest is BaseDiamondTest { assertTrue(coreExists); } + /// @dev Test to ensure 'facetAddress' points to correct facet for a selector. function testSelectorRouting() public view { assertEq( IDiamondLoupe(diamondAddr).facetAddress(RegistryFacet.register.selector), @@ -104,31 +140,23 @@ contract DiamondInitTest is BaseDiamondTest { ); } - /// @dev Test to ensure Diamond reverts if 'init' is called. - function testInitReverts() public { - vm.expectRevert(bytes("Diamond: Function does not exist")); - + /// @dev Test to ensure 'transferOwnership' transfers the ownership. + function testTransferOwnership() public { vm.prank(admin); - DiamondInit(diamondAddr).init( - 3600, - 10_000_000, - 0.001 ether, - 0.002 ether, - 50, - 0.002 ether, - 2, - 500, - 2000, - 3600, - 5_000_000, - 500, - vmSigner, - address(erc20Supra), - true, - true - ); + OwnershipFacet(diamondAddr).transferOwnership(alice); + + assertEq(OwnershipFacet(diamondAddr).owner(), alice); + } + + /// @dev Test to ensure 'transferOwnership' reverts if caller is not owner. + function testTransferOwnershipRevertsIfNotOwner() public { + vm.expectRevert(bytes("LibDiamond: Must be contract owner")); + + vm.prank(alice); + OwnershipFacet(diamondAddr).transferOwnership(bob); } + /// @dev Test to ensure 'diamondCut' reverts if caller is not owner. function testDiamondCutRevertsIfNotOwner() public { bytes4[] memory selectors = new bytes4[](2); selectors[0] = RegistryFacet.register.selector; @@ -151,26 +179,7 @@ contract DiamondInitTest is BaseDiamondTest { ); } - function testTransferOwnership() public { - vm.prank(admin); - OwnershipFacet(diamondAddr).transferOwnership(alice); - - assertEq(OwnershipFacet(diamondAddr).owner(), alice); - } - - function testTransferOwnershipRevertsIfNotOwner() public { - vm.expectRevert(bytes("LibDiamond: Must be contract owner")); - - vm.prank(alice); - OwnershipFacet(diamondAddr).transferOwnership(bob); - } - - function testUnknownSelectorReverts() public { - vm.expectRevert(bytes("Diamond: Function does not exist")); - - INonExistent(diamondAddr).nonExistent(); - } - + /// @dev Test to ensure adding a selector works correctly. function testAddSelector() public { uint256 numFacetsBefore = IDiamondLoupe(diamondAddr).facetAddresses().length; @@ -196,6 +205,7 @@ contract DiamondInitTest is BaseDiamondTest { assertEq(MockRegistryFacet(diamondAddr).counter(), 1); } + /// @dev Test to ensure adding an existing selector reverts. function testAddExistingSelectorReverts() public { // Deploy mock facet MockRegistryFacet mockRegistryFacet = new MockRegistryFacet(); @@ -216,6 +226,7 @@ contract DiamondInitTest is BaseDiamondTest { IDiamondCut(diamondAddr).diamondCut(cut, address(0), ""); } + /// @dev Test to ensure 'diamondCut' reverts if empty array of selectors is passed as selectors to be added. function testAddWithEmptySelectorsReverts() public { bytes4[] memory selectors; @@ -232,6 +243,7 @@ contract DiamondInitTest is BaseDiamondTest { IDiamondCut(diamondAddr).diamondCut(cut, address(0), ""); } + /// @dev Test to ensure 'diamondCut' reverts if address(0) is passed as facet address. function testAddSelectorWithZeroAddressReverts() public { bytes4[] memory selectors = new bytes4[](1); selectors[0] = MockRegistryFacet.counter.selector; @@ -249,6 +261,7 @@ contract DiamondInitTest is BaseDiamondTest { IDiamondCut(diamondAddr).diamondCut(cut, address(0), ""); } + /// @dev Test to ensure removing a selector works correclty. function testRemoveSelector() public { uint256 numSelectorsBefore = IDiamondLoupe(diamondAddr).facetFunctionSelectors(deployment.registryFacet).length; @@ -277,6 +290,7 @@ contract DiamondInitTest is BaseDiamondTest { RegistryFacet(diamondAddr).cancelTask(0); } + /// @dev Test to ensure 'diamondCut' reverts if tried to remove a selector that doesn't exist. function testRemoveNonExistingSelectorReverts() public { bytes4[] memory selectors = new bytes4[](1); selectors[0] = MockRegistryFacet.counter.selector; @@ -294,6 +308,7 @@ contract DiamondInitTest is BaseDiamondTest { IDiamondCut(diamondAddr).diamondCut(cut, address(0), ""); } + /// @dev Test to ensure replacing a selector works correclty. function testReplaceSelector() public { // Deploy mock facet MockRegistryFacet mockRegistryFacet = new MockRegistryFacet(); @@ -319,6 +334,7 @@ contract DiamondInitTest is BaseDiamondTest { assertEq(ConfigFacet(diamondAddr).getVmSigner(), address(0x999)); } + /// @dev Test to ensure replacing a selector with same facet address reverts. function testReplaceWithSameFacetReverts() public { bytes4[] memory selectors = new bytes4[](1); selectors[0] = ConfigFacet.getVmSigner.selector; @@ -356,7 +372,7 @@ contract DiamondInitTest is BaseDiamondTest { vm.expectRevert(LibUtils.AddressCannotBeZero.selector); // address(0) as ERC20Supra - LibDiamondUtils.executeCut(vmSigner, address(0), defaultParams, deployment); + LibDiamondUtils.executeCut(LibUtils.VM_SIGNER, address(0), defaultParams, deployment); vm.stopPrank(); } @@ -368,7 +384,7 @@ contract DiamondInitTest is BaseDiamondTest { vm.expectRevert(LibUtils.AddressCannotBeEOA.selector); // EOA address as ERC20Supra - LibDiamondUtils.executeCut(vmSigner, admin, defaultParams, deployment); + LibDiamondUtils.executeCut(LibUtils.VM_SIGNER, admin, defaultParams, deployment); vm.stopPrank(); } @@ -396,7 +412,7 @@ contract DiamondInitTest is BaseDiamondTest { vm.expectRevert(LibUtils.InvalidTaskDuration.selector); - LibDiamondUtils.executeCut(vmSigner, address(erc20Supra), initParams, deployment); + LibDiamondUtils.executeCut(LibUtils.VM_SIGNER, address(erc20Supra), initParams, deployment); vm.stopPrank(); } @@ -424,7 +440,7 @@ contract DiamondInitTest is BaseDiamondTest { vm.expectRevert(LibUtils.InvalidRegistryMaxGasCap.selector); - LibDiamondUtils.executeCut(vmSigner, address(erc20Supra), initParams, deployment); + LibDiamondUtils.executeCut(LibUtils.VM_SIGNER, address(erc20Supra), initParams, deployment); vm.stopPrank(); } @@ -452,7 +468,7 @@ contract DiamondInitTest is BaseDiamondTest { vm.expectRevert(LibUtils.InvalidCongestionThreshold.selector); - LibDiamondUtils.executeCut(vmSigner, address(erc20Supra), initParams, deployment); + LibDiamondUtils.executeCut(LibUtils.VM_SIGNER, address(erc20Supra), initParams, deployment); vm.stopPrank(); } @@ -480,7 +496,7 @@ contract DiamondInitTest is BaseDiamondTest { vm.expectRevert(LibUtils.InvalidCongestionExponent.selector); - LibDiamondUtils.executeCut(vmSigner, address(erc20Supra), initParams, deployment); + LibDiamondUtils.executeCut(LibUtils.VM_SIGNER, address(erc20Supra), initParams, deployment); vm.stopPrank(); } @@ -508,7 +524,7 @@ contract DiamondInitTest is BaseDiamondTest { vm.expectRevert(LibUtils.InvalidTaskCapacity.selector); - LibDiamondUtils.executeCut(vmSigner, address(erc20Supra), initParams, deployment); + LibDiamondUtils.executeCut(LibUtils.VM_SIGNER, address(erc20Supra), initParams, deployment); vm.stopPrank(); } @@ -536,7 +552,7 @@ contract DiamondInitTest is BaseDiamondTest { vm.expectRevert(LibUtils.InvalidCycleDuration.selector); - LibDiamondUtils.executeCut(vmSigner, address(erc20Supra), initParams, deployment); + LibDiamondUtils.executeCut(LibUtils.VM_SIGNER, address(erc20Supra), initParams, deployment); vm.stopPrank(); } @@ -564,7 +580,7 @@ contract DiamondInitTest is BaseDiamondTest { vm.expectRevert(LibUtils.InvalidSysTaskDuration.selector); - LibDiamondUtils.executeCut(vmSigner, address(erc20Supra), initParams, deployment); + LibDiamondUtils.executeCut(LibUtils.VM_SIGNER, address(erc20Supra), initParams, deployment); vm.stopPrank(); } @@ -592,7 +608,7 @@ contract DiamondInitTest is BaseDiamondTest { vm.expectRevert(LibUtils.InvalidSysRegistryMaxGasCap.selector); - LibDiamondUtils.executeCut(vmSigner, address(erc20Supra), initParams, deployment); + LibDiamondUtils.executeCut(LibUtils.VM_SIGNER, address(erc20Supra), initParams, deployment); vm.stopPrank(); } @@ -620,7 +636,7 @@ contract DiamondInitTest is BaseDiamondTest { vm.expectRevert(LibUtils.InvalidSysTaskCapacity.selector); - LibDiamondUtils.executeCut(vmSigner, address(erc20Supra), initParams, deployment); + LibDiamondUtils.executeCut(LibUtils.VM_SIGNER, address(erc20Supra), initParams, deployment); vm.stopPrank(); } } diff --git a/solidity/supra_contracts/test/RegistryFacet.t.sol b/solidity/supra_contracts/test/RegistryFacet.t.sol index 0b39beb0dc..08ef2f776e 100644 --- a/solidity/supra_contracts/test/RegistryFacet.t.sol +++ b/solidity/supra_contracts/test/RegistryFacet.t.sol @@ -254,6 +254,11 @@ contract RegistryFacetTest is BaseDiamondTest { TaskMetadata memory taskMetadata = RegistryFacet(diamondAddr).getTaskDetails(0); assertTrue(RegistryFacet(diamondAddr).ifTaskExists(0)); assertEq(RegistryFacet(diamondAddr).totalTasks(), 1); + + uint256[] memory userTasks = RegistryFacet(diamondAddr).getUserTasks(alice); + assertEq(userTasks.length, 1); + assertEq(userTasks[0], 0); + assertEq(RegistryFacet(diamondAddr).getNextTaskIndex(), 1); assertEq(RegistryFacet(diamondAddr).getGasCommittedForNextCycle(), 1_000_000); assertEq(RegistryFacet(diamondAddr).getTotalDepositedAutomationFees(), 0.5 ether); @@ -429,6 +434,11 @@ contract RegistryFacetTest is BaseDiamondTest { assertTrue(RegistryFacet(diamondAddr).ifSysTaskExists(0)); assertEq(RegistryFacet(diamondAddr).totalTasks(), 1); assertEq(RegistryFacet(diamondAddr).totalSystemTasks(), 1); + + uint256[] memory userTasks = RegistryFacet(diamondAddr).getUserTasks(bob); + assertEq(userTasks.length, 1); + assertEq(userTasks[0], 0); + assertEq(RegistryFacet(diamondAddr).getNextTaskIndex(), 1); assertEq(RegistryFacet(diamondAddr).getSystemGasCommittedForNextCycle(), 1_000_000); @@ -531,6 +541,7 @@ contract RegistryFacetTest is BaseDiamondTest { assertFalse(RegistryFacet(diamondAddr).ifTaskExists(0)); assertEq(RegistryFacet(diamondAddr).totalTasks(), 0); + assertEq(RegistryFacet(diamondAddr).getUserTasks(alice).length, 0); assertEq(RegistryFacet(diamondAddr).getGasCommittedForNextCycle(), 0); assertEq(RegistryFacet(diamondAddr).getTotalDepositedAutomationFees(), 0); assertEq(erc20Supra.balanceOf(diamondAddr), 0.252 ether); @@ -596,6 +607,7 @@ contract RegistryFacetTest is BaseDiamondTest { assertFalse(RegistryFacet(diamondAddr).ifTaskExists(0)); assertFalse(RegistryFacet(diamondAddr).ifSysTaskExists(0)); + assertEq(RegistryFacet(diamondAddr).getUserTasks(bob).length, 0); assertEq(RegistryFacet(diamondAddr).totalTasks(), 0); assertEq(RegistryFacet(diamondAddr).totalSystemTasks(), 0); assertEq(RegistryFacet(diamondAddr).getSystemGasCommittedForNextCycle(), 0); @@ -683,7 +695,7 @@ contract RegistryFacetTest is BaseDiamondTest { taskIndexes[0] = 0; vm.warp(2002); - vm.startPrank(vmSigner, vmSigner); + vm.startPrank(LibUtils.VM_SIGNER, LibUtils.VM_SIGNER); CoreFacet(diamondAddr).monitorCycleEnd(); CoreFacet(diamondAddr).processTasks(2, taskIndexes); vm.stopPrank(); @@ -696,6 +708,7 @@ contract RegistryFacetTest is BaseDiamondTest { assertFalse(RegistryFacet(diamondAddr).ifTaskExists(0)); assertEq(RegistryFacet(diamondAddr).totalTasks(), 0); + assertEq(RegistryFacet(diamondAddr).getUserTasks(alice).length, 0); assertEq(RegistryFacet(diamondAddr).getGasCommittedForNextCycle(), 0); assertEq(RegistryFacet(diamondAddr).getTotalDepositedAutomationFees(), 0); assertEq(erc20Supra.balanceOf(diamondAddr), 0.18955 ether); @@ -710,7 +723,7 @@ contract RegistryFacetTest is BaseDiamondTest { taskIndexes[0] = 0; vm.warp(2002); - vm.startPrank(vmSigner, vmSigner); + vm.startPrank(LibUtils.VM_SIGNER, LibUtils.VM_SIGNER); CoreFacet(diamondAddr).monitorCycleEnd(); CoreFacet(diamondAddr).processTasks(2, taskIndexes); vm.stopPrank(); @@ -796,10 +809,10 @@ contract RegistryFacetTest is BaseDiamondTest { taskIndexes[0] = 0; vm.warp(2002); - vm.prank(vmSigner, vmSigner); + vm.prank(LibUtils.VM_SIGNER, LibUtils.VM_SIGNER); CoreFacet(diamondAddr).monitorCycleEnd(); - vm.prank(vmSigner); + vm.prank(LibUtils.VM_SIGNER); CoreFacet(diamondAddr).processTasks(2, taskIndexes); vm.prank(bob); @@ -807,6 +820,7 @@ contract RegistryFacetTest is BaseDiamondTest { assertFalse(RegistryFacet(diamondAddr).ifTaskExists(0)); assertFalse(RegistryFacet(diamondAddr).ifSysTaskExists(0)); + assertEq(RegistryFacet(diamondAddr).getUserTasks(bob).length, 0); assertEq(RegistryFacet(diamondAddr).totalTasks(), 0); assertEq(RegistryFacet(diamondAddr).totalSystemTasks(), 0); assertEq(RegistryFacet(diamondAddr).getSystemGasCommittedForNextCycle(), 1000000); @@ -820,10 +834,10 @@ contract RegistryFacetTest is BaseDiamondTest { taskIndexes[0] = 0; vm.warp(2002); - vm.prank(vmSigner, vmSigner); + vm.prank(LibUtils.VM_SIGNER, LibUtils.VM_SIGNER); CoreFacet(diamondAddr).monitorCycleEnd(); - vm.prank(vmSigner); + vm.prank(LibUtils.VM_SIGNER); CoreFacet(diamondAddr).processTasks(2, taskIndexes); LibUtils.TaskStopped[] memory stoppedTasks = new LibUtils.TaskStopped[](1); From cb855fec11ddb87bc366ea37ca9853d0145c997a Mon Sep 17 00:00:00 2001 From: Udit Yadav Date: Wed, 18 Feb 2026 12:03:53 +0530 Subject: [PATCH 3/8] refactored code --- .../script/DeployDiamond.s.sol | 1 + .../src/facets/ConfigFacet.sol | 26 -- .../supra_contracts/src/facets/CoreFacet.sol | 8 - .../src/facets/RegistryFacet.sol | 87 +------ .../src/interfaces/IConfigFacet.sol | 61 ++++- .../src/interfaces/ICoreFacet.sol | 65 ++++- .../src/interfaces/IRegistryFacet.sol | 104 +++++++- .../supra_contracts/src/libraries/LibCore.sol | 57 +---- .../src/libraries/LibRegistry.sol | 118 ++++----- .../src/libraries/LibUtils.sol | 2 +- .../supra_contracts/test/ConfigFacet.t.sol | 99 ++++---- solidity/supra_contracts/test/CoreFacet.t.sol | 139 ++++++----- .../supra_contracts/test/RegistryFacet.t.sol | 223 +++++++++--------- 13 files changed, 513 insertions(+), 477 deletions(-) diff --git a/solidity/supra_contracts/script/DeployDiamond.s.sol b/solidity/supra_contracts/script/DeployDiamond.s.sol index a4c4a92c97..f46549e1aa 100644 --- a/solidity/supra_contracts/script/DeployDiamond.s.sol +++ b/solidity/supra_contracts/script/DeployDiamond.s.sol @@ -50,6 +50,7 @@ contract DeployDiamond is Script { console.log("DiamondCutFacet deployed at:", address(deployment.diamondCutFacet)); console.log("DiamondLoupeFacet deployed at:", address(deployment.loupeFacet)); console.log("OwnershipFacet deployed at:", address(deployment.ownershipFacet)); + console.log("ConfigFacet deployed at:", address(deployment.configFacet)); console.log("RegistryFacet deployed at:", address(deployment.registryFacet)); console.log("CoreFacet deployed at:", address(deployment.coreFacet)); console.log("DiamondInit deployed at:", address(deployment.diamondInit)); diff --git a/solidity/supra_contracts/src/facets/ConfigFacet.sol b/solidity/supra_contracts/src/facets/ConfigFacet.sol index 497045585a..50f63b8e79 100644 --- a/solidity/supra_contracts/src/facets/ConfigFacet.sol +++ b/solidity/supra_contracts/src/facets/ConfigFacet.sol @@ -14,32 +14,6 @@ contract ConfigFacet is IConfigFacet { /// @dev State variables AppStorage internal s; - // ::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::: EVENTS ::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::: - - /// @notice Emitted when an account is authorized as submitter for system tasks. - event AuthorizationGranted(address indexed account, uint256 indexed timestamp); - - /// @notice Emitted when authorization is revoked for an account to submit system tasks. - event AuthorizationRevoked(address indexed account, uint256 indexed timestamp); - - /// @notice Emitted when task registration is enabled. - event TaskRegistrationEnabled(bool indexed status); - - /// @notice Emitted when task registration is disabled. - event TaskRegistrationDisabled(bool indexed status); - - /// @notice Emitted when the VM Signer address is updated. - event VmSignerUpdated(address indexed oldVmSigner, address indexed newVmSigner); - - /// @notice Emitted when the ERC20Supra address is updated. - event Erc20SupraUpdated(address indexed oldErc20Supra, address indexed newErc20Supra); - - /// @notice Emitted when the registry fees is withdrawn by the admin. - event RegistryFeeWithdrawn(address indexed recipient, uint256 indexed feesWithdrawn); - - /// @notice Emitted when a new config is added. - event ConfigBufferUpdated(Config indexed pendingConfig); - // ::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::: ADMIN FUNCTIONS ::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::: /// @notice Grants authorization to the input account to submit system automation tasks. diff --git a/solidity/supra_contracts/src/facets/CoreFacet.sol b/solidity/supra_contracts/src/facets/CoreFacet.sol index 46a737b081..f0f5334a1e 100644 --- a/solidity/supra_contracts/src/facets/CoreFacet.sol +++ b/solidity/supra_contracts/src/facets/CoreFacet.sol @@ -10,14 +10,6 @@ import {LibDiamond} from "../libraries/LibDiamond.sol"; contract CoreFacet is ICoreFacet { /// @dev State variables AppStorage internal s; - - // ::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::: EVENTS ::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::: - - /// @notice Emitted when automation is enabled. - event AutomationEnabled(bool indexed status); - - /// @notice Emitted when automation is disabled. - event AutomationDisabled(bool indexed status); // :::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::: VM FUNCTIONS :::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::: diff --git a/solidity/supra_contracts/src/facets/RegistryFacet.sol b/solidity/supra_contracts/src/facets/RegistryFacet.sol index 3b566dd63b..eebe345608 100644 --- a/solidity/supra_contracts/src/facets/RegistryFacet.sol +++ b/solidity/supra_contracts/src/facets/RegistryFacet.sol @@ -10,50 +10,10 @@ import {IRegistryFacet} from "../interfaces/IRegistryFacet.sol"; contract RegistryFacet is IRegistryFacet { using EnumerableSet for *; - using LibRegistry for *; - - /// @dev Defines divisor for refunds of deposit fees with penalty - /// Factor of `2` suggests that `1/2` of the deposit will be refunded. - uint8 constant REFUND_FACTOR = 2; - - /// @notice Address of the transaction hash precompile. - address public constant TX_HASH_PRECOMPILE = 0x0000000000000000000000000000000053555001; /// @dev State variables AppStorage internal s; - // ::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::: EVENTS ::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::: - - /// @notice Emitted when a user task is registered. - event TaskRegistered( - uint64 indexed taskIndex, - address indexed owner, - uint128 registrationFee, - uint128 lockedDepositFee, - TaskMetadata taskMetadata - ); - - /// @notice Emitted when a system task is registered. - event SystemTaskRegistered( - uint64 indexed taskIndex, - address indexed owner, - uint256 timestamp, - TaskMetadata taskMetadata - ); - - /// @notice Emitted when a task is cancelled. - event TaskCancelled( - uint64 indexed taskIndex, - address indexed owner, - bytes32 indexed regHash - ); - - /// @notice Emitted when a task is stopped. - event TasksStopped( - LibUtils.TaskStopped[] indexed stoppedTasks, - address indexed owner - ); - // ::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::: TASKS RELATED FUNCTIONS ::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::: /// @notice Function used to register a user task for automation. @@ -93,7 +53,7 @@ contract RegistryFacet is IRegistryFacet { gasPriceCap: _gasPriceCap, automationFeeCapForCycle: _automationFeeCapForCycle, depositFee: _automationFeeCapForCycle, - txHash: readTxHash(), + txHash: LibRegistry.readTxHash(), taskIndex: taskIndex, registrationTime: regTime, expiryTime: _expiryTime, @@ -152,7 +112,7 @@ contract RegistryFacet is IRegistryFacet { gasPriceCap: 0, automationFeeCapForCycle: 0, depositFee: 0, - txHash: readTxHash(), + txHash: LibRegistry.readTxHash(), taskIndex: taskIndex, registrationTime: regTime, expiryTime: _expiryTime, @@ -198,11 +158,11 @@ contract RegistryFacet is IRegistryFacet { if (task.taskState == LibUtils.TaskState.PENDING) { // When Pending tasks are cancelled, refund of the deposit fee is done with penalty - removeTask(_taskIndex, task.owner, false); + LibRegistry.removeTask(_taskIndex, task.owner, false); bool result = LibRegistry.safeDepositRefund( _taskIndex, task.owner, - task.depositFee / REFUND_FACTOR, + task.depositFee / LibRegistry.REFUND_FACTOR, task.depositFee ); if (!result) { revert ErrorDepositRefund(); } @@ -248,7 +208,7 @@ contract RegistryFacet is IRegistryFacet { if (task.taskState == LibUtils.TaskState.CANCELLED) { revert AlreadyCancelled(); } if (task.taskState == LibUtils.TaskState.PENDING) { - removeTask(_taskIndex, task.owner, true); + LibRegistry.removeTask(_taskIndex, task.owner, true); } else { s.registryState.tasks[_taskIndex].taskState = LibUtils.TaskState.CANCELLED; } @@ -299,7 +259,7 @@ contract RegistryFacet is IRegistryFacet { if (task.taskType == LibUtils.TaskType.GST) { revert UnsupportedTaskOperation(); } // Remove task from the registry - removeTask(_taskIndexes[i], task.owner, false); + LibRegistry.removeTask(_taskIndexes[i], task.owner, false); // Remove from active tasks require(s.registryState.activeTaskIds.remove(_taskIndexes[i]), TaskIndexNotFound()); @@ -336,7 +296,7 @@ contract RegistryFacet is IRegistryFacet { } // Refund and emit event if any tasks were stopped - if (stoppedTaskDetails.length > 0) { + if (counter > 0) { LibRegistry.refund(msg.sender, totalRefundFee); // Emit task stopped event @@ -366,7 +326,8 @@ contract RegistryFacet is IRegistryFacet { LibUtils.TaskStopped[] memory stoppedTaskDetails = new LibUtils.TaskStopped[](_taskIndexes.length); uint256 counter = 0; - + uint64 cycleEndTime = LibCore.getCycleEndTime(); + // Loop through each task index to validate and stop the task for (uint256 i = 0; i < _taskIndexes.length; i++) { if (LibRegistry.ifTaskExists(_taskIndexes[i])) { @@ -376,11 +337,11 @@ contract RegistryFacet is IRegistryFacet { // Check if GST if (task.taskType == LibUtils.TaskType.UST) { revert UnsupportedTaskOperation(); } - removeTask(_taskIndexes[i], task.owner, true); + LibRegistry.removeTask(_taskIndexes[i], task.owner, true); // Remove from active tasks require(s.registryState.activeTaskIds.remove(_taskIndexes[i]), TaskIndexNotFound()); - if (task.taskState != LibUtils.TaskState.CANCELLED && task.expiryTime > LibCore.getCycleEndTime()) { + if (task.taskState != LibUtils.TaskState.CANCELLED && task.expiryTime > cycleEndTime) { LibRegistry.updateGasCommittedForNextCycle(task.taskType, task.maxGasAmount); } @@ -396,7 +357,7 @@ contract RegistryFacet is IRegistryFacet { } } - if (stoppedTaskDetails.length > 0) { + if (counter > 0) { // Emit task stopped event emit TasksStopped( stoppedTaskDetails, @@ -405,30 +366,6 @@ contract RegistryFacet is IRegistryFacet { } } - // :::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::: HELPER FUNCTIONS :::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::: - - /// @notice Read tx hash via precompile. Reverts if precompile missing/fails. - function readTxHash() private view returns (bytes32) { - (bool ok, bytes memory out) = TX_HASH_PRECOMPILE.staticcall(""); - require(ok, FailedToCallTxHashPrecompile()); - require(out.length == 32, TxnHashLengthShouldBe32(uint64(out.length))); - return abi.decode(out, (bytes32)); - } - - /// @notice Function to remove a task from the registry. - /// @param _taskIndex Index of the task to remove. - /// @param _owner Address of the task owner. - /// @param _removeFromSysReg Wheather to remove from system task registry. - function removeTask(uint64 _taskIndex, address _owner, bool _removeFromSysReg) private { - if (_removeFromSysReg) { - require(s.registryState.sysTaskIds.remove(_taskIndex), TaskIndexNotFound()); - } - - delete s.registryState.tasks[_taskIndex]; - require(s.registryState.taskIdList.remove(_taskIndex), TaskIndexNotFound()); - require(s.registryState.userTasks[_owner].remove(_taskIndex), TaskIndexNotFound()); - } - // :::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::: VIEW FUNCTIONS :::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::: /// @notice Returns all the automation tasks available in the registry. diff --git a/solidity/supra_contracts/src/interfaces/IConfigFacet.sol b/solidity/supra_contracts/src/interfaces/IConfigFacet.sol index db1f70dfc4..25793edf1f 100644 --- a/solidity/supra_contracts/src/interfaces/IConfigFacet.sol +++ b/solidity/supra_contracts/src/interfaces/IConfigFacet.sol @@ -4,7 +4,37 @@ pragma solidity 0.8.27; import {Config} from "../libraries/LibAppStorage.sol"; interface IConfigFacet { - // Custom errors + // ============================================================= + // Events + // ============================================================= + /// @notice Emitted when an account is authorized as submitter for system tasks. + event AuthorizationGranted(address indexed account, uint256 indexed timestamp); + + /// @notice Emitted when authorization is revoked for an account to submit system tasks. + event AuthorizationRevoked(address indexed account, uint256 indexed timestamp); + + /// @notice Emitted when task registration is enabled. + event TaskRegistrationEnabled(bool indexed status); + + /// @notice Emitted when task registration is disabled. + event TaskRegistrationDisabled(bool indexed status); + + /// @notice Emitted when the VM Signer address is updated. + event VmSignerUpdated(address indexed oldVmSigner, address indexed newVmSigner); + + /// @notice Emitted when the ERC20Supra address is updated. + event Erc20SupraUpdated(address indexed oldErc20Supra, address indexed newErc20Supra); + + /// @notice Emitted when the registry fees is withdrawn by the admin. + event RegistryFeeWithdrawn(address indexed recipient, uint256 indexed feesWithdrawn); + + /// @notice Emitted when a new config is added. + event ConfigBufferUpdated(Config indexed pendingConfig); + + + // ============================================================= + // Custom errors + // ============================================================= error AddressAlreadyExists(); error AddressCannotBeZero(); error AddressDoesNotExist(); @@ -17,10 +47,37 @@ interface IConfigFacet { error UnacceptableRegistryMaxGasCap(); error UnacceptableSysRegistryMaxGasCap(); - // View functions + // ============================================================= + // View functions + // ============================================================= function erc20Supra() external view returns (address); function getConfig() external view returns (Config memory); function getConfigBuffer() external view returns (Config memory); function getVmSigner() external view returns (address); function isRegistrationEnabled() external view returns (bool); + + // ============================================================= + // State update functions + // ============================================================= + function grantAuthorization(address _account) external; + function revokeAuthorization(address _account) external; + function enableRegistration() external; + function disableRegistration() external; + function setVmSigner(address _vmSigner) external; + function setErc20Supra(address _erc20Supra) external; + function withdrawFees(uint256 _amount, address _recipient) external; + function updateConfigBuffer( + uint64 _taskDurationCapSecs, + uint128 _registryMaxGasCap, + uint128 _automationBaseFeeWeiPerSec, + uint128 _flatRegistrationFeeWei, + uint8 _congestionThresholdPercentage, + uint128 _congestionBaseFeeWeiPerSec, + uint8 _congestionExponent, + uint16 _taskCapacity, + uint64 _cycleDurationSecs, + uint64 _sysTaskDurationCapSecs, + uint128 _sysRegistryMaxGasCap, + uint16 _sysTaskCapacity + ) external; } diff --git a/solidity/supra_contracts/src/interfaces/ICoreFacet.sol b/solidity/supra_contracts/src/interfaces/ICoreFacet.sol index 68d2d4f2ef..abf7a0df6b 100644 --- a/solidity/supra_contracts/src/interfaces/ICoreFacet.sol +++ b/solidity/supra_contracts/src/interfaces/ICoreFacet.sol @@ -4,18 +4,77 @@ pragma solidity 0.8.27; import {LibUtils} from "../libraries/LibUtils.sol"; interface ICoreFacet { - // Custom errors + // ============================================================= + // Events + // ============================================================= + /// @notice Emitted when automation is enabled. + event AutomationEnabled(bool indexed status); + + /// @notice Emitted when automation is disabled. + event AutomationDisabled(bool indexed status); + + /// @notice Event emitted on cycle transition containing active task indexes for the new cycle. + event ActiveTasks(uint256[] indexed taskIndexes); + + /// @notice Event emitted on cycle transition containing removed task indexes. + event RemovedTasks(uint64[] indexed taskIndexes); + + /// @notice Emitted when the cycle state transitions. + event AutomationCycleEvent( + uint64 indexed index, + LibUtils.CycleState indexed state, + uint64 startTime, + uint64 durationSecs, + LibUtils.CycleState indexed oldState + ); + + /// @notice Emitted when an automation fee is charged for an automation task for the cycle. + event TaskCycleFeeWithdraw( + uint64 indexed taskIndex, + address indexed owner, + uint128 fee + ); + + /// @notice Emitted when a task is removed as fee exceeds task's automation fee cap for the cycle. + event TaskCancelledCapacitySurpassed( + uint64 indexed taskIndex, + address indexed owner, + uint128 fee, + uint128 automationFeeCapForCycle, + bytes32 registrationHash + ); + + /// @notice Emitted when a task is removed due to insufficient balance. + event TaskCancelledInsufficentBalance( + uint64 indexed taskIndex, + address indexed owner, + uint128 fee, + uint256 balance, + bytes32 registrationHash + ); + + + // ============================================================= + // Custom errors + // ============================================================= error AlreadyDisabled(); error AlreadyEnabled(); error CallerNotVmSigner(); error InvalidRegistryState(); - // View functions + // ============================================================= + // View functions + // ============================================================= function getCycleInfo() external view returns (uint64, uint64, uint64, LibUtils.CycleState); function getCycleDuration() external view returns (uint64); function getTransitionInfo() external view returns (uint64, uint128); function isAutomationEnabled() external view returns (bool); - // State update functions + // ============================================================= + // State update functions + // ============================================================= function monitorCycleEnd() external; + function processTasks(uint64 _cycleIndex, uint64[] memory _taskIndexes) external; + function enableAutomation() external; + function disableAutomation() external; } diff --git a/solidity/supra_contracts/src/interfaces/IRegistryFacet.sol b/solidity/supra_contracts/src/interfaces/IRegistryFacet.sol index 1d9b795c43..b38e13803b 100644 --- a/solidity/supra_contracts/src/interfaces/IRegistryFacet.sol +++ b/solidity/supra_contracts/src/interfaces/IRegistryFacet.sol @@ -5,22 +5,94 @@ import {LibUtils} from "../libraries/LibUtils.sol"; import {TaskMetadata} from "../libraries/LibAppStorage.sol"; interface IRegistryFacet { - // Custom errors + // ============================================================= + // Events + // ============================================================= + /// @notice Emitted when a user task is registered. + event TaskRegistered( + uint64 indexed taskIndex, + address indexed owner, + uint128 registrationFee, + uint128 lockedDepositFee, + TaskMetadata indexed taskMetadata + ); + + /// @notice Emitted when a system task is registered. + event SystemTaskRegistered( + uint64 indexed taskIndex, + address indexed owner, + uint256 timestamp, + TaskMetadata taskMetadata + ); + + /// @notice Emitted when a task is cancelled. + event TaskCancelled( + uint64 indexed taskIndex, + address indexed owner, + bytes32 indexed regHash + ); + + /// @notice Emitted when a task is stopped. + event TasksStopped( + LibUtils.TaskStopped[] indexed stoppedTasks, + address indexed owner + ); + + /// @notice Emitted when an automation fee is refunded for an automation task at the end of the cycle for excessive + /// duration paid at the beginning of the cycle due to cycle duration reduction by governance. + event TaskFeeRefund( + uint64 indexed taskIndex, + address indexed owner, + uint64 indexed amount + ); + + /// @notice Emitted when a deposit fee is refunded for an automation task. + event TaskDepositFeeRefund(uint64 indexed taskIndex, address indexed owner, uint128 indexed amount); + + /// @notice Emitted when a task cycle fee is being refunded but locked cycle fees is less than the requested refund. + event ErrorUnlockTaskCycleFee( + uint64 indexed taskIndex, + uint256 indexed lockedCycleFees, + uint64 indexed refund + ); + + /// @notice Emitted during cycle transition when refunds to be paid is not possible due to insufficient contract balance. + /// Type of the refund can be related either to the deposit paid during registration (0), or to cycle fee caused by + /// the shortening of the cycle (1) + event ErrorInsufficientBalanceToRefund( + uint64 indexed _taskIndex, + address indexed _owner, + uint8 indexed _refundType, + uint128 _amount + ); + + /// @notice Emitted when deposit fee is being refunded but total locked deposits is less than the locked deposit for the task. + event ErrorUnlockTaskDepositFee( + uint64 indexed taskIndex, + uint256 indexed totalDepositedAutomationFees, + uint128 indexed lockedDeposit + ); + + + // ============================================================= + // Custom errors + // ============================================================= error AlreadyCancelled(); error AutomationNotEnabled(); error CycleTransitionInProgress(); error ErrorDepositRefund(); - error FailedToCallTxHashPrecompile(); error SystemTaskDoesNotExist(); error TaskDoesNotExist(); error TaskIndexesCannotBeEmpty(); error TaskIndexNotFound(); error TaskIndexNotUnique(); - error TxnHashLengthShouldBe32(uint64); error UnauthorizedAccount(); error UnsupportedTaskOperation(); - // View functions + + // ============================================================= + // View functions + // ============================================================= function calculateAutomationFeeMultiplierForCommittedOccupancy(uint128 _totalCommittedMaxGas) external view returns (uint128); function calculateAutomationFeeMultiplierForCurrentCycle() external view returns (uint128); function estimateAutomationFee(uint128 _taskOccupancy) external view returns (uint128); @@ -51,4 +123,28 @@ interface IRegistryFacet { function hasActiveUserTask(address _account, uint64 _taskIndex) external view returns (bool); function totalSystemTasks() external view returns (uint256); function totalTasks() external view returns (uint256); + + // ============================================================= + // State update functions + // ============================================================= + function register( + bytes memory _payloadTx, + uint64 _expiryTime, + uint128 _maxGasAmount, + uint128 _gasPriceCap, + uint128 _automationFeeCapForCycle, + uint64 _priority, + bytes[] memory _auxData + ) external; + function registerSystemTask( + bytes memory _payloadTx, + uint64 _expiryTime, + uint128 _maxGasAmount, + uint64 _priority, + bytes[] memory _auxData + ) external; + function cancelTask(uint64 _taskIndex) external; + function cancelSystemTask(uint64 _taskIndex) external; + function stopTasks(uint64[] memory _taskIndexes) external; + function stopSystemTasks(uint64[] memory _taskIndexes) external; } diff --git a/solidity/supra_contracts/src/libraries/LibCore.sol b/solidity/supra_contracts/src/libraries/LibCore.sol index 14983a1403..df27be4c0f 100644 --- a/solidity/supra_contracts/src/libraries/LibCore.sol +++ b/solidity/supra_contracts/src/libraries/LibCore.sol @@ -4,6 +4,7 @@ pragma solidity 0.8.27; import {LibUtils} from "./LibUtils.sol"; import {AppStorage, LibAppStorage, TaskMetadata} from "./LibAppStorage.sol"; import {LibRegistry} from "./LibRegistry.sol"; +import {ICoreFacet} from "../interfaces/ICoreFacet.sol"; import {IERC20} from "../../lib/openzeppelin-contracts/contracts/token/ERC20/IERC20.sol"; import {EnumerableSet} from "../../lib/openzeppelin-contracts/contracts/utils/structs/EnumerableSet.sol"; @@ -19,48 +20,6 @@ library LibCore { error OutOfOrderTaskProcessingRequest(); error TaskIndexNotFound(); - // ::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::: EVENTS ::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::: - - /// @notice Event emitted on cycle transition containing active task indexes for the new cycle. - event ActiveTasks(uint256[] indexed taskIndexes); - - /// @notice Event emitted on cycle transition containing removed task indexes. - event RemovedTasks(uint64[] indexed taskIndexes); - - /// @notice Emitted when the cycle state transitions. - event AutomationCycleEvent( - uint64 indexed index, - LibUtils.CycleState indexed state, - uint64 startTime, - uint64 durationSecs, - LibUtils.CycleState indexed oldState - ); - - /// @notice Emitted when an automation fee is charged for an automation task for the cycle. - event TaskCycleFeeWithdraw( - uint64 indexed taskIndex, - address indexed owner, - uint128 fee - ); - - /// @notice Emitted when a task is removed as fee exceeds task's automation fee cap for the cycle. - event TaskCancelledCapacitySurpassed( - uint64 indexed taskIndex, - address indexed owner, - uint128 fee, - uint128 automationFeeCapForCycle, - bytes32 registrationHash - ); - - /// @notice Emitted when a task is removed due to insufficient balance. - event TaskCancelledInsufficentBalance( - uint64 indexed taskIndex, - address indexed owner, - uint128 fee, - uint256 balance, - bytes32 registrationHash - ); - // ::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::: HELPER FUNCTIONS ::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::: /// @notice Function to remove a task from the registry. @@ -132,7 +91,7 @@ library LibCore { LibUtils.CycleState oldState = s.cycleState; s.cycleState = _state; - emit AutomationCycleEvent ( + emit ICoreFacet.AutomationCycleEvent ( s.index, s.cycleState, s.startTime, @@ -259,7 +218,7 @@ library LibCore { moveToStartedState(); if (LibRegistry.getTotalActiveTasks() > 0 ) { uint256[] memory activeTasks = LibRegistry.getAllActiveTaskIds(); - emit ActiveTasks(activeTasks); + emit ICoreFacet.ActiveTasks(activeTasks); } } } @@ -411,7 +370,7 @@ library LibCore { isRemoved = true; - emit TaskCancelledCapacitySurpassed( + emit ICoreFacet.TaskCancelledCapacitySurpassed( _taskIndex, _owner, _fee, @@ -428,7 +387,7 @@ library LibCore { isRemoved = true; - emit TaskCancelledInsufficentBalance( + emit ICoreFacet.TaskCancelledInsufficentBalance( _taskIndex, _owner, _fee, @@ -442,7 +401,7 @@ library LibCore { fees = _fee; } - emit TaskCycleFeeWithdraw( + emit ICoreFacet.TaskCycleFeeWithdraw( _taskIndex, _owner, _fee @@ -512,7 +471,7 @@ library LibCore { updateCycleTransitionStateFromFinished(); if (intermediateState.removedTasks.length > 0) { - emit RemovedTasks(intermediateState.removedTasks); + emit ICoreFacet.RemovedTasks(intermediateState.removedTasks); } } @@ -556,7 +515,7 @@ library LibCore { } updateCycleTransitionStateFromSuspended(); - emit RemovedTasks(removedTasks); + emit ICoreFacet.RemovedTasks(removedTasks); } /// @notice Helper function called when cycle end is identified. diff --git a/solidity/supra_contracts/src/libraries/LibRegistry.sol b/solidity/supra_contracts/src/libraries/LibRegistry.sol index add1e4f616..30407c7547 100644 --- a/solidity/supra_contracts/src/libraries/LibRegistry.sol +++ b/solidity/supra_contracts/src/libraries/LibRegistry.sol @@ -3,8 +3,9 @@ pragma solidity 0.8.27; import {LibUtils} from "./LibUtils.sol"; import {AppStorage, LibAppStorage, TaskMetadata} from "./LibAppStorage.sol"; -import {EnumerableSet} from "../../lib/openzeppelin-contracts/contracts/utils/structs/EnumerableSet.sol"; +import {IRegistryFacet} from "../interfaces/IRegistryFacet.sol"; import {IERC20} from "../../lib/openzeppelin-contracts/contracts/token/ERC20/IERC20.sol"; +import {EnumerableSet} from "../../lib/openzeppelin-contracts/contracts/utils/structs/EnumerableSet.sol"; library LibRegistry { using LibUtils for *; @@ -20,86 +21,37 @@ library LibRegistry { /// @dev Refund fraction uint8 constant REFUND_FRACTION = 2; + /// @dev Defines divisor for refunds of deposit fees with penalty + /// Factor of `2` suggests that `1/2` of the deposit will be refunded. + uint8 constant REFUND_FACTOR = 2; + + /// @notice Address of the transaction hash precompile. + address public constant TX_HASH_PRECOMPILE = 0x0000000000000000000000000000000053555001; + // :::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::: ERRORS :::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::: error TransferFailed(); error ErrorCycleFeeRefund(); error RegistrationDisabled(); - error AddressAlreadyExists(); - error AddressDoesNotExist(); error AutomationNotEnabled(); - error CallerNotController(); - error UnauthorizedAccount(); error CycleTransitionInProgress(); error TaskDoesNotExist(); - error UnsupportedTaskOperation(); - error AlreadyCancelled(); error ErrorDepositRefund(); - error SystemTaskDoesNotExist(); - error TaskIndexesCannotBeEmpty(); error RegisteredTaskInvalidType(); error TaskIndexNotFound(); - error TaskIndexNotUnique(); error FailedToCallTxHashPrecompile(); error TxnHashLengthShouldBe32(uint64); error InvalidMaxGasAmount(); error GasCommittedExceedsMaxGasCap(); error GasCommittedValueUnderflow(); - error InsufficientBalance(); error InsufficientFeeCapForCycle(); error InsufficientBalanceForRefund(); - error InvalidCongestionExponent(); - error InvalidCongestionThreshold(); - error InvalidCycleDuration(); error InvalidExpiryTime(); error InvalidGasPriceCap(); - error InvalidRegistryMaxGasCap(); - error InvalidSysRegistryMaxGasCap(); - error InvalidSysTaskCapacity(); - error InvalidSysTaskDuration(); - error InvalidTaskCapacity(); error InvalidTaskDuration(); - error RequestExceedsLockedBalance(); error TaskCapacityReached(); error TaskExpiresBeforeNextCycle(); - // :::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::: EVENTS :::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::: - - /// @notice Emitted when an automation fee is refunded for an automation task at the end of the cycle for excessive - /// duration paid at the beginning of the cycle due to cycle duration reduction by governance. - event TaskFeeRefund( - uint64 indexed taskIndex, - address indexed owner, - uint64 indexed amount - ); - - /// @notice Emitted when a deposit fee is refunded for an automation task. - event TaskDepositFeeRefund(uint64 indexed taskIndex, address owner, uint128 amount); - - /// @notice Emitted when a task cycle fee is being refunded but locked cycle fees is less than the requested refund. - event ErrorUnlockTaskCycleFee( - uint64 indexed taskIndex, - uint256 indexed lockedCycleFees, - uint64 indexed refund - ); - - /// @notice Emitted during cycle transition when refunds to be paid is not possible due to insufficient contract balance. - /// Type of the refund can be related either to the deposit paid during registration (0), or to cycle fee caused by - /// the shortening of the cycle (1) - event ErrorInsufficientBalanceToRefund( - uint64 indexed _taskIndex, - address indexed _owner, - uint8 indexed _refundType, - uint128 _amount - ); - - /// @notice Emitted when deposit fee is being refunded but total locked deposits is less than the locked deposit for the task. - event ErrorUnlockTaskDepositFee( - uint64 indexed taskIndex, - uint256 indexed totalDepositedAutomationFees, - uint128 indexed lockedDeposit - ); - // :::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::: VIEW FUNCTIONS :::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::: /// @notice Returns the total number of active tasks. @@ -155,20 +107,6 @@ library LibRegistry { task = s.registryState.tasks[_taskIndex]; } - /// @notice Function to remove a task from the registry. - /// @param _taskIndex Index of the task to remove. - /// @param _removeFromSysReg Wheather to remove from system task registry. - function removeTask(uint64 _taskIndex, bool _removeFromSysReg) internal { - AppStorage storage s = LibAppStorage.appStorage(); - - if (_removeFromSysReg) { - require(s.registryState.sysTaskIds.remove(_taskIndex), TaskIndexNotFound()); - } - - delete s.registryState.tasks[_taskIndex]; - require(s.registryState.taskIdList.remove(_taskIndex), TaskIndexNotFound()); - } - /// @notice Refunds the specified amount of deposit to the task owner and unlocks full deposit from the total automation fees deposited. /// @param _taskIndex Index of the task. /// @param _taskOwner Owner of the task. @@ -188,7 +126,7 @@ library LibRegistry { result = safeRefund(_taskIndex, _taskOwner, _refundableDeposit, DEPOSIT_CYCLE_FEE); - if (result) { emit TaskDepositFeeRefund(_taskIndex, _taskOwner, _refundableDeposit); } + if (result) { emit IRegistryFacet.TaskDepositFeeRefund(_taskIndex, _taskOwner, _refundableDeposit); } return result; } @@ -308,7 +246,7 @@ library LibRegistry { // Unlock the refunded amount _cycleLockedFees = _cycleLockedFees - _refundableFee; } else { - emit ErrorUnlockTaskCycleFee(_taskIndex, _cycleLockedFees, _refundableFee); + emit IRegistryFacet.ErrorUnlockTaskCycleFee(_taskIndex, _cycleLockedFees, _refundableFee); } return (hasLockedFee, _cycleLockedFees); } @@ -343,7 +281,7 @@ library LibRegistry { address erc20Supra = s.erc20Supra; uint256 balance = IERC20(erc20Supra).balanceOf(address(this)); if (balance < _refundableAmount) { - emit ErrorInsufficientBalanceToRefund(_taskIndex, _taskOwner, _refundType, _refundableAmount); + emit IRegistryFacet.ErrorInsufficientBalanceToRefund(_taskIndex, _taskOwner, _refundType, _refundableAmount); return false; } else { return _refund(erc20Supra, _taskOwner, _refundableAmount); @@ -366,7 +304,7 @@ library LibRegistry { if (!result) { return (result, remainingLockedFees); } result = safeRefund( _taskIndex, _taskOwner, _refundableFee, CYCLE_FEE); - if (result) { emit TaskFeeRefund(_taskIndex, _taskOwner, _refundableFee); } + if (result) { emit IRegistryFacet.TaskFeeRefund(_taskIndex, _taskOwner, _refundableFee); } return (result, remainingLockedFees); } @@ -398,7 +336,7 @@ library LibRegistry { return true; } - emit ErrorUnlockTaskDepositFee(_taskIndex, totalDeposited, _lockedDeposit); + emit IRegistryFacet.ErrorUnlockTaskDepositFee(_taskIndex, totalDeposited, _lockedDeposit); return false; } @@ -531,7 +469,7 @@ library LibRegistry { if (s.registryState.tasks[_taskIndex].taskType == LibUtils.TaskType.GST) { revert RegisteredTaskInvalidType(); } // Remove task from the registry state - removeTask(_taskIndex, false); + removeTask(_taskIndex, _taskOwner,false); // Refund safeDepositRefund( @@ -708,4 +646,30 @@ library LibRegistry { return (cycleFeeRefund, depositRefund); } + + // :::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::: HELPER FUNCTIONS :::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::: + + /// @notice Function to remove a task from the registry. + /// @param _taskIndex Index of the task to remove. + /// @param _owner Address of the task owner. + /// @param _removeFromSysReg Wheather to remove from system task registry. + function removeTask(uint64 _taskIndex, address _owner, bool _removeFromSysReg) internal { + AppStorage storage s = LibAppStorage.appStorage(); + + if (_removeFromSysReg) { + require(s.registryState.sysTaskIds.remove(_taskIndex), TaskIndexNotFound()); + } + + delete s.registryState.tasks[_taskIndex]; + require(s.registryState.taskIdList.remove(_taskIndex), TaskIndexNotFound()); + require(s.registryState.userTasks[_owner].remove(_taskIndex), TaskIndexNotFound()); + } + + /// @notice Read tx hash via precompile. Reverts if precompile missing/fails. + function readTxHash() internal view returns (bytes32) { + (bool ok, bytes memory out) = TX_HASH_PRECOMPILE.staticcall(""); + require(ok, FailedToCallTxHashPrecompile()); + require(out.length == 32, TxnHashLengthShouldBe32(uint64(out.length))); + return abi.decode(out, (bytes32)); + } } diff --git a/solidity/supra_contracts/src/libraries/LibUtils.sol b/solidity/supra_contracts/src/libraries/LibUtils.sol index f6cca6282a..ae256dc18d 100644 --- a/solidity/supra_contracts/src/libraries/LibUtils.sol +++ b/solidity/supra_contracts/src/libraries/LibUtils.sol @@ -1,7 +1,7 @@ // SPDX-License-Identifier: MIT pragma solidity 0.8.27; -// Helper library used by supra contracts +// Helper library used by Supra contracts library LibUtils { // Custom errors diff --git a/solidity/supra_contracts/test/ConfigFacet.t.sol b/solidity/supra_contracts/test/ConfigFacet.t.sol index c23c4e645f..c0aa999819 100644 --- a/solidity/supra_contracts/test/ConfigFacet.t.sol +++ b/solidity/supra_contracts/test/ConfigFacet.t.sol @@ -3,7 +3,6 @@ pragma solidity 0.8.27; import {BaseDiamondTest} from "./BaseDiamondTest.t.sol"; import {ERC20Supra} from "../src/ERC20Supra.sol"; -import {ConfigFacet} from "../src/facets/ConfigFacet.sol"; import {IConfigFacet} from "../src/interfaces/IConfigFacet.sol"; import {IRegistryFacet} from "../src/interfaces/IRegistryFacet.sol"; import {LibUtils} from "../src/libraries/LibUtils.sol"; @@ -16,7 +15,7 @@ contract ConfigFacetTest is BaseDiamondTest { /// @dev Test to ensure 'grantAuthorization' grants authorization to an address. function testGrantAuthorization() public { vm.prank(admin); - ConfigFacet(diamondAddr).grantAuthorization(alice); + IConfigFacet(diamondAddr).grantAuthorization(alice); assertTrue(IRegistryFacet(diamondAddr).isAuthorizedSubmitter(alice)); } @@ -24,10 +23,10 @@ contract ConfigFacetTest is BaseDiamondTest { /// @dev Test to ensure 'grantAuthorization' emits event 'AuthorizationGranted'. function testGrantAuthorizationEmitsEvent() public { vm.expectEmit(true, true, false, false); - emit ConfigFacet.AuthorizationGranted(alice, block.timestamp); + emit IConfigFacet.AuthorizationGranted(alice, block.timestamp); vm.prank(admin); - ConfigFacet(diamondAddr).grantAuthorization(alice); + IConfigFacet(diamondAddr).grantAuthorization(alice); } /// @dev Test to ensure 'grantAuthorization' reverts if address is already authorized. @@ -38,7 +37,7 @@ contract ConfigFacetTest is BaseDiamondTest { vm.expectRevert(IConfigFacet.AddressAlreadyExists.selector); vm.prank(admin); - ConfigFacet(diamondAddr).grantAuthorization(alice); + IConfigFacet(diamondAddr).grantAuthorization(alice); } /// @dev Test to ensure 'grantAuthorization' reverts if caller is not owner. @@ -46,7 +45,7 @@ contract ConfigFacetTest is BaseDiamondTest { vm.expectRevert(bytes("LibDiamond: Must be contract owner")); vm.prank(alice); - ConfigFacet(diamondAddr).grantAuthorization(alice); + IConfigFacet(diamondAddr).grantAuthorization(alice); } // ::::::::::::::::::::::::::::::::::::::::::::::::::: Tests related to 'revokeAuthorization' ::::::::::::::::::::::::::::::::::::::::::::::::::: @@ -58,7 +57,7 @@ contract ConfigFacetTest is BaseDiamondTest { // Revoke authorization vm.prank(admin); - ConfigFacet(diamondAddr).revokeAuthorization(alice); + IConfigFacet(diamondAddr).revokeAuthorization(alice); assertFalse(IRegistryFacet(diamondAddr).isAuthorizedSubmitter(alice)); } @@ -69,10 +68,10 @@ contract ConfigFacetTest is BaseDiamondTest { testGrantAuthorization(); vm.expectEmit(true, true, false, false); - emit ConfigFacet.AuthorizationRevoked(alice, block.timestamp); + emit IConfigFacet.AuthorizationRevoked(alice, block.timestamp); vm.prank(admin); - ConfigFacet(diamondAddr).revokeAuthorization(alice); + IConfigFacet(diamondAddr).revokeAuthorization(alice); } /// @dev Test to ensure 'revokeAuthorization' reverts if address is not authorised. @@ -80,7 +79,7 @@ contract ConfigFacetTest is BaseDiamondTest { vm.expectRevert(IConfigFacet.AddressDoesNotExist.selector); vm.prank(admin); - ConfigFacet(diamondAddr).revokeAuthorization(alice); + IConfigFacet(diamondAddr).revokeAuthorization(alice); } /// @dev Test to ensure 'revokeAuthorization' reverts if caller is not owner. @@ -88,7 +87,7 @@ contract ConfigFacetTest is BaseDiamondTest { vm.expectRevert(bytes("LibDiamond: Must be contract owner")); vm.prank(alice); - ConfigFacet(diamondAddr).revokeAuthorization(alice); + IConfigFacet(diamondAddr).revokeAuthorization(alice); } // :::::::::::::::::::::::::::::::::::::::::::::::::::::: Tests related to 'disableRegistration' :::::::::::::::::::::::::::::::::::::::::::::::::::::: @@ -96,15 +95,15 @@ contract ConfigFacetTest is BaseDiamondTest { /// @dev Test to ensure 'disableRegistration' disables the registration. function testDisableRegistration() public { vm.prank(admin); - ConfigFacet(diamondAddr).disableRegistration(); + IConfigFacet(diamondAddr).disableRegistration(); - assertFalse(ConfigFacet(diamondAddr).isRegistrationEnabled()); + assertFalse(IConfigFacet(diamondAddr).isRegistrationEnabled()); } /// @dev Test to ensure 'disableRegistration' emits event 'TaskRegistrationDisabled'. function testDisableRegistrationEmitsEvent() public { vm.expectEmit(true, false, false, false); - emit ConfigFacet.TaskRegistrationDisabled(false); + emit IConfigFacet.TaskRegistrationDisabled(false); testDisableRegistration(); } @@ -118,7 +117,7 @@ contract ConfigFacetTest is BaseDiamondTest { vm.expectRevert(IConfigFacet.AlreadyDisabled.selector); vm.prank(admin); - ConfigFacet(diamondAddr).disableRegistration(); + IConfigFacet(diamondAddr).disableRegistration(); } /// @dev Test to ensure 'disableRegistration' reverts if caller is not owner. @@ -126,7 +125,7 @@ contract ConfigFacetTest is BaseDiamondTest { vm.expectRevert(bytes("LibDiamond: Must be contract owner")); vm.prank(alice); - ConfigFacet(diamondAddr).disableRegistration(); + IConfigFacet(diamondAddr).disableRegistration(); } // :::::::::::::::::::::::::::::::::::::::::::::::::::::: Tests related to 'enableRegistration' :::::::::::::::::::::::::::::::::::::::::::::::::::::: @@ -138,9 +137,9 @@ contract ConfigFacetTest is BaseDiamondTest { // Enable registration vm.prank(admin); - ConfigFacet(diamondAddr).enableRegistration(); + IConfigFacet(diamondAddr).enableRegistration(); - assertTrue(ConfigFacet(diamondAddr).isRegistrationEnabled()); + assertTrue(IConfigFacet(diamondAddr).isRegistrationEnabled()); } /// @dev Test to ensure 'enableRegistration' emits event 'TaskRegistrationEnabled'. @@ -149,11 +148,11 @@ contract ConfigFacetTest is BaseDiamondTest { testDisableRegistration(); vm.expectEmit(true, false, false, false); - emit ConfigFacet.TaskRegistrationEnabled(true); + emit IConfigFacet.TaskRegistrationEnabled(true); // Enable registration vm.prank(admin); - ConfigFacet(diamondAddr).enableRegistration(); + IConfigFacet(diamondAddr).enableRegistration(); } /// @dev Test to ensure 'enableRegistration' reverts if registration is already enabled. @@ -161,7 +160,7 @@ contract ConfigFacetTest is BaseDiamondTest { vm.expectRevert(IConfigFacet.AlreadyEnabled.selector); vm.prank(admin); - ConfigFacet(diamondAddr).enableRegistration(); + IConfigFacet(diamondAddr).enableRegistration(); } /// @dev Test to ensure 'enableRegistration' reverts if caller is not owner. @@ -169,7 +168,7 @@ contract ConfigFacetTest is BaseDiamondTest { vm.expectRevert(bytes("LibDiamond: Must be contract owner")); vm.prank(alice); - ConfigFacet(diamondAddr).enableRegistration(); + IConfigFacet(diamondAddr).enableRegistration(); } // :::::::::::::::::::::::::::::::::::::::::::::::::::::: Tests related to 'setVmSigner' :::::::::::::::::::::::::::::::::::::::::::::::::::::: @@ -179,21 +178,21 @@ contract ConfigFacetTest is BaseDiamondTest { address newVmSigner = address(0x100); vm.prank(admin); - ConfigFacet(diamondAddr).setVmSigner(newVmSigner); + IConfigFacet(diamondAddr).setVmSigner(newVmSigner); - assertEq(ConfigFacet(diamondAddr).getVmSigner(), newVmSigner); + assertEq(IConfigFacet(diamondAddr).getVmSigner(), newVmSigner); } /// @dev Test to ensure 'setVmSigner' emits event 'VmSignerUpdated'. function testSetVmSignerEmitsEvent() public { - address oldVmSigner = ConfigFacet(diamondAddr).getVmSigner(); + address oldVmSigner = IConfigFacet(diamondAddr).getVmSigner(); address newVmSigner = address(0x100); vm.expectEmit(true, true, false, false); - emit ConfigFacet.VmSignerUpdated(oldVmSigner, newVmSigner); + emit IConfigFacet.VmSignerUpdated(oldVmSigner, newVmSigner); vm.prank(admin); - ConfigFacet(diamondAddr).setVmSigner(newVmSigner); + IConfigFacet(diamondAddr).setVmSigner(newVmSigner); } /// @dev Test to ensure 'setVmSigner' reverts if zero address is passed. @@ -201,7 +200,7 @@ contract ConfigFacetTest is BaseDiamondTest { vm.expectRevert(IConfigFacet.AddressCannotBeZero.selector); vm.prank(admin); - ConfigFacet(diamondAddr).setVmSigner(address(0)); + IConfigFacet(diamondAddr).setVmSigner(address(0)); } /// @dev Test to ensure 'setVmSigner' reverts if caller is not owner. @@ -209,7 +208,7 @@ contract ConfigFacetTest is BaseDiamondTest { vm.expectRevert(bytes("LibDiamond: Must be contract owner")); vm.prank(alice); - ConfigFacet(diamondAddr).setVmSigner(address(0x100)); + IConfigFacet(diamondAddr).setVmSigner(address(0x100)); } // :::::::::::::::::::::::::::::::::::::::::::::::::::::: Tests related to 'setErc20Supra' :::::::::::::::::::::::::::::::::::::::::::::::::::::: @@ -219,21 +218,21 @@ contract ConfigFacetTest is BaseDiamondTest { ERC20Supra supraErc20 = new ERC20Supra(msg.sender); vm.prank(admin); - ConfigFacet(diamondAddr).setErc20Supra(address(supraErc20)); + IConfigFacet(diamondAddr).setErc20Supra(address(supraErc20)); - assertEq(ConfigFacet(diamondAddr).erc20Supra(), address(supraErc20)); + assertEq(IConfigFacet(diamondAddr).erc20Supra(), address(supraErc20)); } /// @dev Test to ensure 'setErc20Supra' emits event 'Erc20SupraUpdated'. function testSetErc20SupraEmitsEvent() public { - address oldAddr = ConfigFacet(diamondAddr).erc20Supra(); + address oldAddr = IConfigFacet(diamondAddr).erc20Supra(); ERC20Supra supraErc20 = new ERC20Supra(msg.sender); vm.expectEmit(true, true, false, false); - emit ConfigFacet.Erc20SupraUpdated(oldAddr, address(supraErc20)); + emit IConfigFacet.Erc20SupraUpdated(oldAddr, address(supraErc20)); vm.prank(admin); - ConfigFacet(diamondAddr).setErc20Supra(address(supraErc20)); + IConfigFacet(diamondAddr).setErc20Supra(address(supraErc20)); } /// @dev Test to ensure 'setErc20Supra' reverts if zero address is passed. @@ -241,7 +240,7 @@ contract ConfigFacetTest is BaseDiamondTest { vm.expectRevert(IConfigFacet.AddressCannotBeZero.selector); vm.prank(admin); - ConfigFacet(diamondAddr).setErc20Supra(address(0)); + IConfigFacet(diamondAddr).setErc20Supra(address(0)); } /// @dev Test to ensure 'setErc20Supra' reverts if EOA is passed. @@ -249,7 +248,7 @@ contract ConfigFacetTest is BaseDiamondTest { vm.expectRevert(LibUtils.AddressCannotBeEOA.selector); vm.prank(admin); - ConfigFacet(diamondAddr).setErc20Supra(alice); + IConfigFacet(diamondAddr).setErc20Supra(alice); } /// @dev Test to ensure 'setErc20Supra' reverts if caller is not owner. @@ -259,7 +258,7 @@ contract ConfigFacetTest is BaseDiamondTest { vm.expectRevert(bytes("LibDiamond: Must be contract owner")); vm.prank(alice); - ConfigFacet(diamondAddr).setErc20Supra(address(supraErc20)); + IConfigFacet(diamondAddr).setErc20Supra(address(supraErc20)); } // :::::::::::::::::::::::::::::::::::::::::::::::::::::: Tests related to 'withdrawFees' :::::::::::::::::::::::::::::::::::::::::::::::::::::: @@ -269,7 +268,7 @@ contract ConfigFacetTest is BaseDiamondTest { vm.prank(admin); vm.expectRevert(IConfigFacet.InvalidAmount.selector); - ConfigFacet(diamondAddr).withdrawFees(0, admin); + IConfigFacet(diamondAddr).withdrawFees(0, admin); } /// @dev Test to ensure 'withdrawFees' reverts if recipient address is zero. @@ -277,7 +276,7 @@ contract ConfigFacetTest is BaseDiamondTest { vm.prank(admin); vm.expectRevert(IConfigFacet.AddressCannotBeZero.selector); - ConfigFacet(diamondAddr).withdrawFees(1 ether, address(0)); + IConfigFacet(diamondAddr).withdrawFees(1 ether, address(0)); } /// @dev Test to ensure 'withdrawFees' reverts if contract has insufficient balance. @@ -285,7 +284,7 @@ contract ConfigFacetTest is BaseDiamondTest { vm.expectRevert(IConfigFacet.InsufficientBalance.selector); vm.prank(admin); - ConfigFacet(diamondAddr).withdrawFees(1 ether, admin); + IConfigFacet(diamondAddr).withdrawFees(1 ether, admin); } /// @dev Test to ensure 'withdrawFees' reverts if request amount exceeds the locked balance. @@ -295,7 +294,7 @@ contract ConfigFacetTest is BaseDiamondTest { vm.expectRevert(IConfigFacet.RequestExceedsLockedBalance.selector); vm.prank(admin); - ConfigFacet(diamondAddr).withdrawFees(0.04 ether, admin); + IConfigFacet(diamondAddr).withdrawFees(0.04 ether, admin); } /// @dev Test to ensure 'withdrawFees' reverts if caller is not owner. @@ -303,7 +302,7 @@ contract ConfigFacetTest is BaseDiamondTest { vm.expectRevert(bytes("LibDiamond: Must be contract owner")); vm.prank(alice); - ConfigFacet(diamondAddr).withdrawFees(1 ether, admin); + IConfigFacet(diamondAddr).withdrawFees(1 ether, admin); } /// @dev Test to ensure 'withdrawFees' withdraws the requested amount and updates the balance. @@ -314,7 +313,7 @@ contract ConfigFacetTest is BaseDiamondTest { assertEq(erc20Supra.balanceOf(diamondAddr), 0.502 ether); vm.prank(admin); - ConfigFacet(diamondAddr).withdrawFees(0.002 ether, admin); + IConfigFacet(diamondAddr).withdrawFees(0.002 ether, admin); assertEq(erc20Supra.balanceOf(admin), 0.002 ether); assertEq(erc20Supra.balanceOf(diamondAddr), 0.5 ether); @@ -325,10 +324,10 @@ contract ConfigFacetTest is BaseDiamondTest { registerUST(); vm.expectEmit(true, true, false, false); - emit ConfigFacet.RegistryFeeWithdrawn(admin, 0.002 ether); + emit IConfigFacet.RegistryFeeWithdrawn(admin, 0.002 ether); vm.prank(admin); - ConfigFacet(diamondAddr).withdrawFees(0.002 ether, admin); + IConfigFacet(diamondAddr).withdrawFees(0.002 ether, admin); } // :::::::::::::::::::::::::::::::::::::::::::::::::::::: Tests related to 'updateConfigBuffer' :::::::::::::::::::::::::::::::::::::::::::::::::::::: @@ -356,7 +355,7 @@ contract ConfigFacetTest is BaseDiamondTest { Config memory cfg = validConfig(); vm.prank(admin); - ConfigFacet(diamondAddr).updateConfigBuffer( + IConfigFacet(diamondAddr).updateConfigBuffer( cfg.taskDurationCapSecs, cfg.registryMaxGasCap, cfg.automationBaseFeeWeiPerSec, @@ -372,7 +371,7 @@ contract ConfigFacetTest is BaseDiamondTest { ); // Pending config should be updated - Config memory configBuffer = ConfigFacet(diamondAddr).getConfigBuffer(); + Config memory configBuffer = IConfigFacet(diamondAddr).getConfigBuffer(); assertEq(configBuffer.taskDurationCapSecs, cfg.taskDurationCapSecs); assertEq(configBuffer.registryMaxGasCap, cfg.registryMaxGasCap); assertEq(configBuffer.automationBaseFeeWeiPerSec, cfg.automationBaseFeeWeiPerSec); @@ -392,10 +391,10 @@ contract ConfigFacetTest is BaseDiamondTest { Config memory cfg = validConfig(); vm.expectEmit(true, false, false, false); - emit ConfigFacet.ConfigBufferUpdated(cfg); + emit IConfigFacet.ConfigBufferUpdated(cfg); vm.prank(admin); - ConfigFacet(diamondAddr).updateConfigBuffer( + IConfigFacet(diamondAddr).updateConfigBuffer( cfg.taskDurationCapSecs, cfg.registryMaxGasCap, cfg.automationBaseFeeWeiPerSec, @@ -418,7 +417,7 @@ contract ConfigFacetTest is BaseDiamondTest { vm.expectRevert(bytes("LibDiamond: Must be contract owner")); vm.prank(alice); - ConfigFacet(diamondAddr).updateConfigBuffer( + IConfigFacet(diamondAddr).updateConfigBuffer( cfg.taskDurationCapSecs, cfg.registryMaxGasCap, cfg.automationBaseFeeWeiPerSec, diff --git a/solidity/supra_contracts/test/CoreFacet.t.sol b/solidity/supra_contracts/test/CoreFacet.t.sol index ed6ed20ac0..7158850400 100644 --- a/solidity/supra_contracts/test/CoreFacet.t.sol +++ b/solidity/supra_contracts/test/CoreFacet.t.sol @@ -3,7 +3,6 @@ pragma solidity 0.8.27; import {BaseDiamondTest} from "./BaseDiamondTest.t.sol"; import {RegistryFacet} from "../src/facets/RegistryFacet.sol"; -import {CoreFacet} from "../src/facets/CoreFacet.sol"; import {ICoreFacet} from "../src/interfaces/ICoreFacet.sol"; import {LibUtils} from "../src/libraries/LibUtils.sol"; import {LibCore} from "../src/libraries/LibCore.sol"; @@ -16,17 +15,17 @@ contract CoreFacetTest is BaseDiamondTest { vm.expectRevert(ICoreFacet.CallerNotVmSigner.selector); vm.prank(LibUtils.VM_SIGNER); - CoreFacet(diamondAddr).monitorCycleEnd(); + ICoreFacet(diamondAddr).monitorCycleEnd(); } /// @dev Test to ensure 'monitorCycleEnd' does nothing before cycle expiry. function testMonitorCycleEndDoesNothingBeforeCycleExpiry() public { - (uint64 indexBefore, uint64 startBefore, uint64 durationBefore, LibUtils.CycleState stateBefore) = CoreFacet(diamondAddr).getCycleInfo(); + (uint64 indexBefore, uint64 startBefore, uint64 durationBefore, LibUtils.CycleState stateBefore) = ICoreFacet(diamondAddr).getCycleInfo(); vm.prank(LibUtils.VM_SIGNER, LibUtils.VM_SIGNER); - CoreFacet(diamondAddr).monitorCycleEnd(); + ICoreFacet(diamondAddr).monitorCycleEnd(); - (uint64 indexAfter, uint64 startAfter, uint64 durationAfter, LibUtils.CycleState stateAfter) = CoreFacet(diamondAddr).getCycleInfo(); + (uint64 indexAfter, uint64 startAfter, uint64 durationAfter, LibUtils.CycleState stateAfter) = ICoreFacet(diamondAddr).getCycleInfo(); assertEq(indexAfter, indexBefore); assertEq(startAfter, startBefore); @@ -60,15 +59,15 @@ contract CoreFacetTest is BaseDiamondTest { address diamondAddr = deployment.diamond; vm.stopPrank(); - (uint64 indexBefore, uint64 startBefore, uint64 durationBefore, LibUtils.CycleState stateBefore) = CoreFacet(diamondAddr).getCycleInfo(); + (uint64 indexBefore, uint64 startBefore, uint64 durationBefore, LibUtils.CycleState stateBefore) = ICoreFacet(diamondAddr).getCycleInfo(); assertEq(uint8(stateBefore), uint8(LibUtils.CycleState.READY)); vm.warp(startBefore + durationBefore); vm.prank(LibUtils.VM_SIGNER, LibUtils.VM_SIGNER); - CoreFacet(diamondAddr).monitorCycleEnd(); + ICoreFacet(diamondAddr).monitorCycleEnd(); - (uint64 indexAfter, uint64 startAfter, uint64 durationAfter, LibUtils.CycleState stateAfter) = CoreFacet(diamondAddr).getCycleInfo(); + (uint64 indexAfter, uint64 startAfter, uint64 durationAfter, LibUtils.CycleState stateAfter) = ICoreFacet(diamondAddr).getCycleInfo(); assertEq(indexAfter, indexBefore); assertEq(startAfter, startBefore); @@ -80,15 +79,15 @@ contract CoreFacetTest is BaseDiamondTest { function testMonitorCycleEndWhenAutomationDisabledNoTasks() public { // Disable automation vm.prank(admin); - CoreFacet(diamondAddr).disableAutomation(); + ICoreFacet(diamondAddr).disableAutomation(); - assertFalse(CoreFacet(diamondAddr).isAutomationEnabled()); + assertFalse(ICoreFacet(diamondAddr).isAutomationEnabled()); - (uint64 indexBefore, uint64 startBefore, uint64 durationBefore, LibUtils.CycleState stateBefore) = CoreFacet(diamondAddr).getCycleInfo(); + (uint64 indexBefore, uint64 startBefore, uint64 durationBefore, LibUtils.CycleState stateBefore) = ICoreFacet(diamondAddr).getCycleInfo(); vm.warp(startBefore + durationBefore); vm.expectEmit(true, true, false, true); - emit LibCore.AutomationCycleEvent( + emit ICoreFacet.AutomationCycleEvent( indexBefore, LibUtils.CycleState.READY, startBefore, @@ -97,9 +96,9 @@ contract CoreFacetTest is BaseDiamondTest { ); vm.prank(LibUtils.VM_SIGNER, LibUtils.VM_SIGNER); - CoreFacet(diamondAddr).monitorCycleEnd(); + ICoreFacet(diamondAddr).monitorCycleEnd(); - (uint64 indexAfter, uint64 startAfter, uint64 durationAfter, LibUtils.CycleState stateAfter) = CoreFacet(diamondAddr).getCycleInfo(); + (uint64 indexAfter, uint64 startAfter, uint64 durationAfter, LibUtils.CycleState stateAfter) = ICoreFacet(diamondAddr).getCycleInfo(); assertEq(indexAfter, indexBefore); assertEq(startAfter, startBefore); @@ -109,12 +108,12 @@ contract CoreFacetTest is BaseDiamondTest { /// @dev Test to ensure 'monitorCycleEnd' moves cycle state to STARTED if automation is enabled and no tasks exist. function testMonitorCycleEndWhenAutomationEnabledNoTasks() public { - (uint64 indexBefore, uint64 startBefore, uint64 durationBefore, LibUtils.CycleState stateBefore) = CoreFacet(diamondAddr).getCycleInfo(); + (uint64 indexBefore, uint64 startBefore, uint64 durationBefore, LibUtils.CycleState stateBefore) = ICoreFacet(diamondAddr).getCycleInfo(); vm.warp(startBefore + durationBefore); vm.expectEmit(true, true, false, true); - emit LibCore.AutomationCycleEvent( + emit ICoreFacet.AutomationCycleEvent( indexBefore + 1, LibUtils.CycleState.STARTED, uint64(block.timestamp), @@ -123,9 +122,9 @@ contract CoreFacetTest is BaseDiamondTest { ); vm.prank(LibUtils.VM_SIGNER, LibUtils.VM_SIGNER); - CoreFacet(diamondAddr).monitorCycleEnd(); + ICoreFacet(diamondAddr).monitorCycleEnd(); - (uint64 indexAfter, uint64 startAfter, uint64 durationAfter, LibUtils.CycleState stateAfter) = CoreFacet(diamondAddr).getCycleInfo(); + (uint64 indexAfter, uint64 startAfter, uint64 durationAfter, LibUtils.CycleState stateAfter) = ICoreFacet(diamondAddr).getCycleInfo(); assertEq(indexAfter, indexBefore + 1); assertEq(startAfter, block.timestamp); @@ -137,11 +136,11 @@ contract CoreFacetTest is BaseDiamondTest { function testMonitorCycleEndWhenAutomationEnabledAndTasksExist() public { registerUST(); - (uint64 indexBefore, uint64 startBefore, uint64 durationBefore, LibUtils.CycleState stateBefore) = CoreFacet(diamondAddr).getCycleInfo(); + (uint64 indexBefore, uint64 startBefore, uint64 durationBefore, LibUtils.CycleState stateBefore) = ICoreFacet(diamondAddr).getCycleInfo(); vm.warp(startBefore + durationBefore); vm.expectEmit(true, true, false, true); - emit LibCore.AutomationCycleEvent( + emit ICoreFacet.AutomationCycleEvent( indexBefore, LibUtils.CycleState.FINISHED, startBefore, @@ -150,16 +149,16 @@ contract CoreFacetTest is BaseDiamondTest { ); vm.prank(LibUtils.VM_SIGNER, LibUtils.VM_SIGNER); - CoreFacet(diamondAddr).monitorCycleEnd(); + ICoreFacet(diamondAddr).monitorCycleEnd(); - (uint64 indexAfter, uint64 startAfter, uint64 durationAfter, LibUtils.CycleState stateAfter) = CoreFacet(diamondAddr).getCycleInfo(); + (uint64 indexAfter, uint64 startAfter, uint64 durationAfter, LibUtils.CycleState stateAfter) = ICoreFacet(diamondAddr).getCycleInfo(); assertEq(indexAfter, indexBefore); assertEq(startAfter, startBefore); assertEq(durationAfter, durationBefore); assertEq(uint8(stateAfter), uint8(LibUtils.CycleState.FINISHED)); - (uint64 refundDuration, uint128 automationFeePerSec) = CoreFacet(diamondAddr).getTransitionInfo(); + (uint64 refundDuration, uint128 automationFeePerSec) = ICoreFacet(diamondAddr).getTransitionInfo(); assertEq(refundDuration, 0); assertEq(automationFeePerSec, 1000000000000000); } @@ -172,7 +171,7 @@ contract CoreFacetTest is BaseDiamondTest { vm.expectRevert(ICoreFacet.CallerNotVmSigner.selector); vm.prank(admin); - CoreFacet(diamondAddr).processTasks(1, tasks); + ICoreFacet(diamondAddr).processTasks(1, tasks); } /// @dev Test to ensure 'processTasks' reverts if state is not FINISHED or SUSPENDED. @@ -183,20 +182,20 @@ contract CoreFacetTest is BaseDiamondTest { vm.expectRevert(ICoreFacet.InvalidRegistryState.selector); vm.prank(LibUtils.VM_SIGNER, LibUtils.VM_SIGNER); - CoreFacet(diamondAddr).processTasks(1, tasks); + ICoreFacet(diamondAddr).processTasks(1, tasks); } /// @dev Test to ensure 'processTasks' works correctly when cycle state is FINISHED. function testProcessTasksWhenCycleStateFinished() public { registerUST(); - ( , uint64 startTime, uint64 duration, ) = CoreFacet(diamondAddr).getCycleInfo(); + ( , uint64 startTime, uint64 duration, ) = ICoreFacet(diamondAddr).getCycleInfo(); vm.warp(startTime + duration); vm.prank(LibUtils.VM_SIGNER, LibUtils.VM_SIGNER); - CoreFacet(diamondAddr).monitorCycleEnd(); + ICoreFacet(diamondAddr).monitorCycleEnd(); - (uint64 index, , , LibUtils.CycleState state) = CoreFacet(diamondAddr).getCycleInfo(); + (uint64 index, , , LibUtils.CycleState state) = ICoreFacet(diamondAddr).getCycleInfo(); assertEq(uint8(state), uint8(LibUtils.CycleState.FINISHED)); uint64[] memory tasks = new uint64[](1); @@ -206,12 +205,12 @@ contract CoreFacetTest is BaseDiamondTest { tasks[0] = 0; vm.expectEmit(true, false, false, false); - emit LibCore.ActiveTasks(activeTasks); + emit ICoreFacet.ActiveTasks(activeTasks); vm.prank(LibUtils.VM_SIGNER, LibUtils.VM_SIGNER); - CoreFacet(diamondAddr).processTasks(index + 1, tasks); + ICoreFacet(diamondAddr).processTasks(index + 1, tasks); - (uint64 newIndex, uint64 newStart, uint64 newDuration, LibUtils.CycleState newState) = CoreFacet(diamondAddr).getCycleInfo(); + (uint64 newIndex, uint64 newStart, uint64 newDuration, LibUtils.CycleState newState) = ICoreFacet(diamondAddr).getCycleInfo(); assertEq(newIndex, index + 1); assertEq(newStart, uint64(block.timestamp)); assertEq(newDuration, 2000); @@ -229,13 +228,13 @@ contract CoreFacetTest is BaseDiamondTest { function testProcessTasksRevertsIfInvalidCycleIndexWhenCycleStateFinished() public { registerUST(); - ( , uint64 startTime, uint64 duration, ) = CoreFacet(diamondAddr).getCycleInfo(); + ( , uint64 startTime, uint64 duration, ) = ICoreFacet(diamondAddr).getCycleInfo(); vm.warp(startTime + duration); vm.prank(LibUtils.VM_SIGNER, LibUtils.VM_SIGNER); - CoreFacet(diamondAddr).monitorCycleEnd(); + ICoreFacet(diamondAddr).monitorCycleEnd(); - (uint64 index, , , LibUtils.CycleState state) = CoreFacet(diamondAddr).getCycleInfo(); + (uint64 index, , , LibUtils.CycleState state) = ICoreFacet(diamondAddr).getCycleInfo(); assertEq(uint8(state), uint8(LibUtils.CycleState.FINISHED)); uint64[] memory tasks = new uint64[](1); @@ -244,40 +243,40 @@ contract CoreFacetTest is BaseDiamondTest { vm.expectRevert(LibCore.InvalidInputCycleIndex.selector); vm.prank(LibUtils.VM_SIGNER, LibUtils.VM_SIGNER); - CoreFacet(diamondAddr).processTasks(index, tasks); + ICoreFacet(diamondAddr).processTasks(index, tasks); } /// @dev Test to ensure 'processTasks' works correctly when cycle state is SUSPENDED and automation is disabled. function testProcessTasksWhenCycleStateSuspendedAutomationDisabled() public { registerUST(); - ( , uint64 start, uint64 duration, ) = CoreFacet(diamondAddr).getCycleInfo(); + ( , uint64 start, uint64 duration, ) = ICoreFacet(diamondAddr).getCycleInfo(); vm.warp(start + duration); // Moves state to FINISHED vm.prank(LibUtils.VM_SIGNER, LibUtils.VM_SIGNER); - CoreFacet(diamondAddr).monitorCycleEnd(); + ICoreFacet(diamondAddr).monitorCycleEnd(); - ( , , , LibUtils.CycleState stateBefore) = CoreFacet(diamondAddr).getCycleInfo(); + ( , , , LibUtils.CycleState stateBefore) = ICoreFacet(diamondAddr).getCycleInfo(); assertEq(uint8(stateBefore), uint8(LibUtils.CycleState.FINISHED)); // Disable automation → moves state to SUSPENDED vm.prank(admin); - CoreFacet(diamondAddr).disableAutomation(); + ICoreFacet(diamondAddr).disableAutomation(); - (uint64 indexAfter, , , LibUtils.CycleState stateAfter) = CoreFacet(diamondAddr).getCycleInfo(); + (uint64 indexAfter, , , LibUtils.CycleState stateAfter) = ICoreFacet(diamondAddr).getCycleInfo(); assertEq(uint8(stateAfter), uint8(LibUtils.CycleState.SUSPENDED)); uint64[] memory tasks = new uint64[](1); tasks[0] = 0; vm.expectEmit(true, false, false, false); - emit LibCore.RemovedTasks(tasks); + emit ICoreFacet.RemovedTasks(tasks); vm.prank(LibUtils.VM_SIGNER, LibUtils.VM_SIGNER); - CoreFacet(diamondAddr).processTasks(indexAfter, tasks); + ICoreFacet(diamondAddr).processTasks(indexAfter, tasks); - ( , , , LibUtils.CycleState newState) = CoreFacet(diamondAddr).getCycleInfo(); + ( , , , LibUtils.CycleState newState) = ICoreFacet(diamondAddr).getCycleInfo(); assertEq(uint8(newState), uint8(LibUtils.CycleState.READY)); assertFalse(RegistryFacet(diamondAddr).ifTaskExists(tasks[0])); } @@ -286,37 +285,37 @@ contract CoreFacetTest is BaseDiamondTest { function testProcessTasksWhenCycleStateSuspendedAutomationEnabled() public { registerUST(); - ( , uint64 start, uint64 duration, ) = CoreFacet(diamondAddr).getCycleInfo(); + ( , uint64 start, uint64 duration, ) = ICoreFacet(diamondAddr).getCycleInfo(); vm.warp(start + duration); // Moves state to FINISHED vm.prank(LibUtils.VM_SIGNER, LibUtils.VM_SIGNER); - CoreFacet(diamondAddr).monitorCycleEnd(); + ICoreFacet(diamondAddr).monitorCycleEnd(); - ( , , , LibUtils.CycleState stateBefore) = CoreFacet(diamondAddr).getCycleInfo(); + ( , , , LibUtils.CycleState stateBefore) = ICoreFacet(diamondAddr).getCycleInfo(); assertEq(uint8(stateBefore), uint8(LibUtils.CycleState.FINISHED)); // Disable automation → moves state to SUSPENDED vm.prank(admin); - CoreFacet(diamondAddr).disableAutomation(); + ICoreFacet(diamondAddr).disableAutomation(); - (uint64 indexAfter, , , LibUtils.CycleState stateAfter) = CoreFacet(diamondAddr).getCycleInfo(); + (uint64 indexAfter, , , LibUtils.CycleState stateAfter) = ICoreFacet(diamondAddr).getCycleInfo(); assertEq(uint8(stateAfter), uint8(LibUtils.CycleState.SUSPENDED)); // Enable automation vm.prank(admin); - CoreFacet(diamondAddr).enableAutomation(); + ICoreFacet(diamondAddr).enableAutomation(); uint64[] memory tasks = new uint64[](1); tasks[0] = 0; vm.expectEmit(true, false, false, false); - emit LibCore.RemovedTasks(tasks); + emit ICoreFacet.RemovedTasks(tasks); vm.prank(LibUtils.VM_SIGNER, LibUtils.VM_SIGNER); - CoreFacet(diamondAddr).processTasks(indexAfter, tasks); + ICoreFacet(diamondAddr).processTasks(indexAfter, tasks); - (uint64 newIndex, uint64 newStart, uint64 newDuration, LibUtils.CycleState newState) = CoreFacet(diamondAddr).getCycleInfo(); + (uint64 newIndex, uint64 newStart, uint64 newDuration, LibUtils.CycleState newState) = ICoreFacet(diamondAddr).getCycleInfo(); assertEq(newIndex, indexAfter + 1); assertEq(newStart, uint64(block.timestamp)); assertEq(newDuration, 2000); @@ -328,21 +327,21 @@ contract CoreFacetTest is BaseDiamondTest { function testProcessTasksRevertsIfInvalidCycleIndexWhenCycleStateSuspended() public { registerUST(); - ( , uint64 start, uint64 duration, ) = CoreFacet(diamondAddr).getCycleInfo(); + ( , uint64 start, uint64 duration, ) = ICoreFacet(diamondAddr).getCycleInfo(); vm.warp(start + duration); // Moves state to FINISHED vm.prank(LibUtils.VM_SIGNER, LibUtils.VM_SIGNER); - CoreFacet(diamondAddr).monitorCycleEnd(); + ICoreFacet(diamondAddr).monitorCycleEnd(); - ( , , , LibUtils.CycleState stateBefore) = CoreFacet(diamondAddr).getCycleInfo(); + ( , , , LibUtils.CycleState stateBefore) = ICoreFacet(diamondAddr).getCycleInfo(); assertEq(uint8(stateBefore), uint8(LibUtils.CycleState.FINISHED)); // Disable automation → moves state to SUSPENDED vm.prank(admin); - CoreFacet(diamondAddr).disableAutomation(); + ICoreFacet(diamondAddr).disableAutomation(); - (uint64 indexAfter, , , LibUtils.CycleState stateAfter) = CoreFacet(diamondAddr).getCycleInfo(); + (uint64 indexAfter, , , LibUtils.CycleState stateAfter) = ICoreFacet(diamondAddr).getCycleInfo(); assertEq(uint8(stateAfter), uint8(LibUtils.CycleState.SUSPENDED)); uint64[] memory tasks = new uint64[](1); @@ -351,7 +350,7 @@ contract CoreFacetTest is BaseDiamondTest { vm.expectRevert(LibCore.InvalidInputCycleIndex.selector); vm.prank(LibUtils.VM_SIGNER, LibUtils.VM_SIGNER); - CoreFacet(diamondAddr).processTasks(indexAfter + 1, tasks); + ICoreFacet(diamondAddr).processTasks(indexAfter + 1, tasks); } // :::::::::::::::::::::::::::::::::::::::::::::::::::::: Tests related to 'disableAutomation' :::::::::::::::::::::::::::::::::::::::::::::::::::::: @@ -359,18 +358,18 @@ contract CoreFacetTest is BaseDiamondTest { /// @dev Test to ensure 'disableAutomation' disables the automation. function testDisableAutomation() public { vm.prank(admin); - CoreFacet(diamondAddr).disableAutomation(); + ICoreFacet(diamondAddr).disableAutomation(); - assertFalse(CoreFacet(diamondAddr).isAutomationEnabled()); + assertFalse(ICoreFacet(diamondAddr).isAutomationEnabled()); } /// @dev Test to ensure 'disableAutomation' emits event 'AutomationDisabled'. function testDisableAutomationEmitsEvent() public { vm.expectEmit(true, false, false, false); - emit CoreFacet.AutomationDisabled(false); + emit ICoreFacet.AutomationDisabled(false); vm.prank(admin); - CoreFacet(diamondAddr).disableAutomation(); + ICoreFacet(diamondAddr).disableAutomation(); } /// @dev Test to ensure 'disableAutomation' reverts if automation is already disabled. @@ -382,7 +381,7 @@ contract CoreFacetTest is BaseDiamondTest { vm.expectRevert(ICoreFacet.AlreadyDisabled.selector); vm.prank(admin); - CoreFacet(diamondAddr).disableAutomation(); + ICoreFacet(diamondAddr).disableAutomation(); } /// @dev Test to ensure 'disableAutomation' reverts if caller is not owner. @@ -390,7 +389,7 @@ contract CoreFacetTest is BaseDiamondTest { vm.expectRevert(bytes("LibDiamond: Must be contract owner")); vm.prank(alice); - CoreFacet(diamondAddr).disableAutomation(); + ICoreFacet(diamondAddr).disableAutomation(); } // :::::::::::::::::::::::::::::::::::::::::::::::::::::: Tests related to 'enableAutomation' :::::::::::::::::::::::::::::::::::::::::::::::::::::: @@ -402,9 +401,9 @@ contract CoreFacetTest is BaseDiamondTest { // Enable automation vm.prank(admin); - CoreFacet(diamondAddr).enableAutomation(); + ICoreFacet(diamondAddr).enableAutomation(); - assertTrue(CoreFacet(diamondAddr).isAutomationEnabled()); + assertTrue(ICoreFacet(diamondAddr).isAutomationEnabled()); } /// @dev Test to ensure 'enableAutomation' emits event 'AutomationEnabled'. @@ -413,10 +412,10 @@ contract CoreFacetTest is BaseDiamondTest { testDisableAutomation(); vm.expectEmit(true, false, false, false); - emit CoreFacet.AutomationEnabled(true); + emit ICoreFacet.AutomationEnabled(true); vm.prank(admin); - CoreFacet(diamondAddr).enableAutomation(); + ICoreFacet(diamondAddr).enableAutomation(); } /// @dev Test to ensure 'enableAutomation' reverts if automation is already enabled. @@ -424,7 +423,7 @@ contract CoreFacetTest is BaseDiamondTest { vm.expectRevert(ICoreFacet.AlreadyEnabled.selector); vm.prank(admin); - CoreFacet(diamondAddr).enableAutomation(); + ICoreFacet(diamondAddr).enableAutomation(); } /// @dev Test to ensure 'enableAutomation' reverts if caller is not owner. @@ -432,6 +431,6 @@ contract CoreFacetTest is BaseDiamondTest { vm.expectRevert(bytes("LibDiamond: Must be contract owner")); vm.prank(alice); - CoreFacet(diamondAddr).enableAutomation(); + ICoreFacet(diamondAddr).enableAutomation(); } } \ No newline at end of file diff --git a/solidity/supra_contracts/test/RegistryFacet.t.sol b/solidity/supra_contracts/test/RegistryFacet.t.sol index 08ef2f776e..483e9a236a 100644 --- a/solidity/supra_contracts/test/RegistryFacet.t.sol +++ b/solidity/supra_contracts/test/RegistryFacet.t.sol @@ -2,9 +2,8 @@ pragma solidity 0.8.27; import {BaseDiamondTest} from "./BaseDiamondTest.t.sol"; -import {ConfigFacet} from "../src/facets/ConfigFacet.sol"; -import {RegistryFacet} from "../src/facets/RegistryFacet.sol"; -import {CoreFacet} from "../src/facets/CoreFacet.sol"; +import {IConfigFacet} from "../src/interfaces/IConfigFacet.sol"; +import {ICoreFacet} from "../src/interfaces/ICoreFacet.sol"; import {IRegistryFacet} from "../src/interfaces/IRegistryFacet.sol"; import {LibUtils} from "../src/libraries/LibUtils.sol"; import {LibRegistry} from "../src/libraries/LibRegistry.sol"; @@ -18,7 +17,7 @@ contract RegistryFacetTest is BaseDiamondTest { function testRegisterRevertsIfAutomationNotEnabled() public { // Disable automation vm.prank(admin); - CoreFacet(diamondAddr).disableAutomation(); + ICoreFacet(diamondAddr).disableAutomation(); bytes[] memory auxData; bytes memory payload = createPayload(0, address(erc20Supra)); @@ -26,7 +25,7 @@ contract RegistryFacetTest is BaseDiamondTest { vm.expectRevert(IRegistryFacet.AutomationNotEnabled.selector); vm.prank(alice); - RegistryFacet(diamondAddr).register( + IRegistryFacet(diamondAddr).register( payload, // payload uint64(block.timestamp + 2250), // expiryTime uint128(1_000_000), // maxGasAmount @@ -41,7 +40,7 @@ contract RegistryFacetTest is BaseDiamondTest { function testRegisterRevertsIfRegistrationDisabled() public { // Disable registration vm.prank(admin); - ConfigFacet(diamondAddr).disableRegistration(); + IConfigFacet(diamondAddr).disableRegistration(); bytes[] memory auxData; bytes memory payload = createPayload(0, address(erc20Supra)); @@ -49,7 +48,7 @@ contract RegistryFacetTest is BaseDiamondTest { vm.expectRevert(LibRegistry.RegistrationDisabled.selector); vm.prank(alice); - RegistryFacet(diamondAddr).register( + IRegistryFacet(diamondAddr).register( payload, // payload uint64(block.timestamp + 2250), // expiryTime uint128(1_000_000), // maxGasAmount @@ -68,7 +67,7 @@ contract RegistryFacetTest is BaseDiamondTest { vm.expectRevert(LibRegistry.InvalidExpiryTime.selector); vm.prank(alice); - RegistryFacet(diamondAddr).register( + IRegistryFacet(diamondAddr).register( payload, uint64(block.timestamp), // Invalid expiryTime uint128(1_000_000), @@ -87,7 +86,7 @@ contract RegistryFacetTest is BaseDiamondTest { vm.expectRevert(LibRegistry.InvalidTaskDuration.selector); vm.prank(alice); - RegistryFacet(diamondAddr).register( + IRegistryFacet(diamondAddr).register( payload, uint64(block.timestamp + 3601), // Invalid task duration uint128(1_000_000), @@ -106,7 +105,7 @@ contract RegistryFacetTest is BaseDiamondTest { vm.expectRevert(LibRegistry.TaskExpiresBeforeNextCycle.selector); vm.prank(alice); - RegistryFacet(diamondAddr).register( + IRegistryFacet(diamondAddr).register( payload, uint64(block.timestamp + 2000), // Task expires before next cycle uint128(1_000_000), @@ -125,7 +124,7 @@ contract RegistryFacetTest is BaseDiamondTest { vm.expectRevert(LibUtils.AddressCannotBeZero.selector); vm.prank(alice); - RegistryFacet(diamondAddr).register( + IRegistryFacet(diamondAddr).register( payload, uint64(block.timestamp + 2250), uint128(1_000_000), @@ -144,7 +143,7 @@ contract RegistryFacetTest is BaseDiamondTest { vm.expectRevert(LibUtils.AddressCannotBeEOA.selector); vm.prank(alice); - RegistryFacet(diamondAddr).register( + IRegistryFacet(diamondAddr).register( payload, uint64(block.timestamp + 2250), uint128(1_000_000), @@ -163,7 +162,7 @@ contract RegistryFacetTest is BaseDiamondTest { vm.expectRevert(LibRegistry.InvalidMaxGasAmount.selector); vm.prank(alice); - RegistryFacet(diamondAddr).register( + IRegistryFacet(diamondAddr).register( payload, uint64(block.timestamp + 2250), uint128(0), // maxGasAmount @@ -182,7 +181,7 @@ contract RegistryFacetTest is BaseDiamondTest { vm.expectRevert(LibRegistry.InvalidGasPriceCap.selector); vm.prank(alice); - RegistryFacet(diamondAddr).register( + IRegistryFacet(diamondAddr).register( payload, uint64(block.timestamp + 2250), uint128(1_000_000), @@ -201,7 +200,7 @@ contract RegistryFacetTest is BaseDiamondTest { vm.expectRevert(LibRegistry.InsufficientFeeCapForCycle.selector); vm.prank(alice); - RegistryFacet(diamondAddr).register( + IRegistryFacet(diamondAddr).register( payload, uint64(block.timestamp + 2250), uint128(1_000_000), @@ -220,7 +219,7 @@ contract RegistryFacetTest is BaseDiamondTest { vm.expectRevert(LibRegistry.GasCommittedExceedsMaxGasCap.selector); vm.prank(alice); - RegistryFacet(diamondAddr).register( + IRegistryFacet(diamondAddr).register( payload, uint64(block.timestamp + 2250), uint128(10_000_001), // Gas exceeds max gas cap @@ -240,7 +239,7 @@ contract RegistryFacetTest is BaseDiamondTest { erc20Supra.nativeToErc20Supra{value: 5 ether}(); erc20Supra.approve(diamondAddr, type(uint256).max); - RegistryFacet(diamondAddr).register( + IRegistryFacet(diamondAddr).register( payload, uint64(block.timestamp + 2250), uint128(1_000_000), @@ -251,17 +250,17 @@ contract RegistryFacetTest is BaseDiamondTest { ); vm.stopPrank(); - TaskMetadata memory taskMetadata = RegistryFacet(diamondAddr).getTaskDetails(0); - assertTrue(RegistryFacet(diamondAddr).ifTaskExists(0)); - assertEq(RegistryFacet(diamondAddr).totalTasks(), 1); + TaskMetadata memory taskMetadata = IRegistryFacet(diamondAddr).getTaskDetails(0); + assertTrue(IRegistryFacet(diamondAddr).ifTaskExists(0)); + assertEq(IRegistryFacet(diamondAddr).totalTasks(), 1); - uint256[] memory userTasks = RegistryFacet(diamondAddr).getUserTasks(alice); + uint256[] memory userTasks = IRegistryFacet(diamondAddr).getUserTasks(alice); assertEq(userTasks.length, 1); assertEq(userTasks[0], 0); - assertEq(RegistryFacet(diamondAddr).getNextTaskIndex(), 1); - assertEq(RegistryFacet(diamondAddr).getGasCommittedForNextCycle(), 1_000_000); - assertEq(RegistryFacet(diamondAddr).getTotalDepositedAutomationFees(), 0.5 ether); + assertEq(IRegistryFacet(diamondAddr).getNextTaskIndex(), 1); + assertEq(IRegistryFacet(diamondAddr).getGasCommittedForNextCycle(), 1_000_000); + assertEq(IRegistryFacet(diamondAddr).getTotalDepositedAutomationFees(), 0.5 ether); assertEq(erc20Supra.balanceOf(diamondAddr), 0.502 ether); assertEq(erc20Supra.balanceOf(alice), 4.498 ether); @@ -308,9 +307,9 @@ contract RegistryFacetTest is BaseDiamondTest { }); vm.expectEmit(true, true, false, true); - emit RegistryFacet.TaskRegistered(0, alice, 0.002 ether, 0.5 ether, taskMetadata); + emit IRegistryFacet.TaskRegistered(0, alice, 0.002 ether, 0.5 ether, taskMetadata); - RegistryFacet(diamondAddr).register( + IRegistryFacet(diamondAddr).register( payload, uint64(block.timestamp + 2250), uint128(1_000_000), @@ -332,7 +331,7 @@ contract RegistryFacetTest is BaseDiamondTest { vm.expectRevert(IRegistryFacet.UnauthorizedAccount.selector); vm.prank(alice); - RegistryFacet(diamondAddr).registerSystemTask( + IRegistryFacet(diamondAddr).registerSystemTask( payload, // payload uint64(block.timestamp + 2250), // expiryTime uint128(1_000_000), // maxGasAmount @@ -344,7 +343,7 @@ contract RegistryFacetTest is BaseDiamondTest { /// @dev Test to ensure 'registerSystemTask' reverts if automation is not enabled. function testRegisterSystemTaskRevertsIfAutomationNotEnabled() public { vm.prank(admin); - CoreFacet(diamondAddr).disableAutomation(); + ICoreFacet(diamondAddr).disableAutomation(); bytes[] memory auxData; bytes memory payload = createPayload(0, address(erc20Supra)); @@ -352,7 +351,7 @@ contract RegistryFacetTest is BaseDiamondTest { vm.expectRevert(IRegistryFacet.AutomationNotEnabled.selector); vm.prank(bob); - RegistryFacet(diamondAddr).registerSystemTask( + IRegistryFacet(diamondAddr).registerSystemTask( payload, // payload uint64(block.timestamp + 2250), // expiryTime uint128(1_000_000), // maxGasAmount @@ -364,7 +363,7 @@ contract RegistryFacetTest is BaseDiamondTest { /// @dev Test to ensure 'registerSystemTask' reverts if registration is disabled. function testRegisterSystemTaskRevertsIfRegistrationDisabled() public { vm.prank(admin); - ConfigFacet(diamondAddr).disableRegistration(); + IConfigFacet(diamondAddr).disableRegistration(); bytes[] memory auxData; bytes memory payload = createPayload(0, address(erc20Supra)); @@ -372,7 +371,7 @@ contract RegistryFacetTest is BaseDiamondTest { vm.expectRevert(LibRegistry.RegistrationDisabled.selector); vm.prank(bob); - RegistryFacet(diamondAddr).registerSystemTask( + IRegistryFacet(diamondAddr).registerSystemTask( payload, // payload uint64(block.timestamp + 2250), // expiryTime uint128(1_000_000), // maxGasAmount @@ -389,7 +388,7 @@ contract RegistryFacetTest is BaseDiamondTest { vm.expectRevert(LibRegistry.InvalidTaskDuration.selector); vm.prank(bob); - RegistryFacet(diamondAddr).registerSystemTask( + IRegistryFacet(diamondAddr).registerSystemTask( payload, uint64(block.timestamp + 3601), // Invalid task duration uint128(1_000_000), @@ -406,7 +405,7 @@ contract RegistryFacetTest is BaseDiamondTest { vm.expectRevert(LibRegistry.GasCommittedExceedsMaxGasCap.selector); vm.prank(bob); - RegistryFacet(diamondAddr).registerSystemTask( + IRegistryFacet(diamondAddr).registerSystemTask( payload, uint64(block.timestamp + 2250), uint128(5_000_001), // Gas exceeds max gas cap @@ -421,7 +420,7 @@ contract RegistryFacetTest is BaseDiamondTest { bytes memory payload = createPayload(0, address(erc20Supra)); vm.prank(bob); - RegistryFacet(diamondAddr).registerSystemTask( + IRegistryFacet(diamondAddr).registerSystemTask( payload, // payload uint64(block.timestamp + 2250), // expiryTime uint128(1_000_000), // maxGasAmount @@ -429,18 +428,18 @@ contract RegistryFacetTest is BaseDiamondTest { auxData // aux data ); - TaskMetadata memory taskMetadata = RegistryFacet(diamondAddr).getTaskDetails(0); - assertTrue(RegistryFacet(diamondAddr).ifTaskExists(0)); - assertTrue(RegistryFacet(diamondAddr).ifSysTaskExists(0)); - assertEq(RegistryFacet(diamondAddr).totalTasks(), 1); - assertEq(RegistryFacet(diamondAddr).totalSystemTasks(), 1); + TaskMetadata memory taskMetadata = IRegistryFacet(diamondAddr).getTaskDetails(0); + assertTrue(IRegistryFacet(diamondAddr).ifTaskExists(0)); + assertTrue(IRegistryFacet(diamondAddr).ifSysTaskExists(0)); + assertEq(IRegistryFacet(diamondAddr).totalTasks(), 1); + assertEq(IRegistryFacet(diamondAddr).totalSystemTasks(), 1); - uint256[] memory userTasks = RegistryFacet(diamondAddr).getUserTasks(bob); + uint256[] memory userTasks = IRegistryFacet(diamondAddr).getUserTasks(bob); assertEq(userTasks.length, 1); assertEq(userTasks[0], 0); - assertEq(RegistryFacet(diamondAddr).getNextTaskIndex(), 1); - assertEq(RegistryFacet(diamondAddr).getSystemGasCommittedForNextCycle(), 1_000_000); + assertEq(IRegistryFacet(diamondAddr).getNextTaskIndex(), 1); + assertEq(IRegistryFacet(diamondAddr).getSystemGasCommittedForNextCycle(), 1_000_000); assertEq(taskMetadata.maxGasAmount, 1_000_000); assertEq(taskMetadata.gasPriceCap, 0); @@ -481,10 +480,10 @@ contract RegistryFacetTest is BaseDiamondTest { }); vm.expectEmit(true, true, false, true); - emit RegistryFacet.SystemTaskRegistered(0, bob, block.timestamp, taskMetadata); + emit IRegistryFacet.SystemTaskRegistered(0, bob, block.timestamp, taskMetadata); vm.prank(bob); - RegistryFacet(diamondAddr).registerSystemTask( + IRegistryFacet(diamondAddr).registerSystemTask( payload, // payload uint64(block.timestamp + 2250), // expiryTime uint128(1_000_000), // maxGasAmount @@ -498,12 +497,12 @@ contract RegistryFacetTest is BaseDiamondTest { /// @dev Test to ensure 'cancelTask' reverts if automation is not enabled. function testCancelTaskRevertsIfAutomationNotEnabled() public { vm.prank(admin); - CoreFacet(diamondAddr).disableAutomation(); + ICoreFacet(diamondAddr).disableAutomation(); vm.expectRevert(IRegistryFacet.AutomationNotEnabled.selector); vm.prank(alice); - RegistryFacet(diamondAddr).cancelTask(0); + IRegistryFacet(diamondAddr).cancelTask(0); } /// @dev Test to ensure 'cancelTask' reverts if task does not exist. @@ -511,7 +510,7 @@ contract RegistryFacetTest is BaseDiamondTest { vm.expectRevert(IRegistryFacet.TaskDoesNotExist.selector); vm.prank(alice); - RegistryFacet(diamondAddr).cancelTask(0); + IRegistryFacet(diamondAddr).cancelTask(0); } /// @dev Test to ensure 'cancelTask' reverts if task type is not UST. @@ -520,7 +519,7 @@ contract RegistryFacetTest is BaseDiamondTest { vm.expectRevert(IRegistryFacet.UnsupportedTaskOperation.selector); vm.prank(bob); - RegistryFacet(diamondAddr).cancelTask(0); + IRegistryFacet(diamondAddr).cancelTask(0); } /// @dev Test to ensure 'cancelTask' reverts if caller is not the task owner. @@ -529,7 +528,7 @@ contract RegistryFacetTest is BaseDiamondTest { vm.expectRevert(IRegistryFacet.UnauthorizedAccount.selector); vm.prank(bob); - RegistryFacet(diamondAddr).cancelTask(0); + IRegistryFacet(diamondAddr).cancelTask(0); } /// @dev Test to ensure 'cancelTask' cancels a UST. @@ -537,13 +536,13 @@ contract RegistryFacetTest is BaseDiamondTest { testRegister(); vm.prank(alice); - RegistryFacet(diamondAddr).cancelTask(0); + IRegistryFacet(diamondAddr).cancelTask(0); - assertFalse(RegistryFacet(diamondAddr).ifTaskExists(0)); - assertEq(RegistryFacet(diamondAddr).totalTasks(), 0); - assertEq(RegistryFacet(diamondAddr).getUserTasks(alice).length, 0); - assertEq(RegistryFacet(diamondAddr).getGasCommittedForNextCycle(), 0); - assertEq(RegistryFacet(diamondAddr).getTotalDepositedAutomationFees(), 0); + assertFalse(IRegistryFacet(diamondAddr).ifTaskExists(0)); + assertEq(IRegistryFacet(diamondAddr).totalTasks(), 0); + assertEq(IRegistryFacet(diamondAddr).getUserTasks(alice).length, 0); + assertEq(IRegistryFacet(diamondAddr).getGasCommittedForNextCycle(), 0); + assertEq(IRegistryFacet(diamondAddr).getTotalDepositedAutomationFees(), 0); assertEq(erc20Supra.balanceOf(diamondAddr), 0.252 ether); assertEq(erc20Supra.balanceOf(alice), 4.748 ether); } @@ -553,10 +552,10 @@ contract RegistryFacetTest is BaseDiamondTest { testRegister(); vm.expectEmit(true, true, true, false); - emit RegistryFacet.TaskCancelled(0, alice, keccak256("txHash")); + emit IRegistryFacet.TaskCancelled(0, alice, keccak256("txHash")); vm.prank(alice); - RegistryFacet(diamondAddr).cancelTask(0); + IRegistryFacet(diamondAddr).cancelTask(0); } // :::::::::::::::::::::::::::::::::::::::::::::::::::::: Tests related to 'cancelSystemTask' :::::::::::::::::::::::::::::::::::::::::::::::::::::: @@ -564,12 +563,12 @@ contract RegistryFacetTest is BaseDiamondTest { /// @dev Test to ensure 'cancelSystemTask' reverts if automation is not enabled. function testCancelSystemTaskRevertsIfAutomationNotEnabled() public { vm.prank(admin); - CoreFacet(diamondAddr).disableAutomation(); + ICoreFacet(diamondAddr).disableAutomation(); vm.expectRevert(IRegistryFacet.AutomationNotEnabled.selector); vm.prank(alice); - RegistryFacet(diamondAddr).cancelSystemTask(0); + IRegistryFacet(diamondAddr).cancelSystemTask(0); } /// @dev Test to ensure 'cancelSystemTask' reverts if task does not exist. @@ -577,7 +576,7 @@ contract RegistryFacetTest is BaseDiamondTest { vm.expectRevert(IRegistryFacet.TaskDoesNotExist.selector); vm.prank(alice); - RegistryFacet(diamondAddr).cancelSystemTask(0); + IRegistryFacet(diamondAddr).cancelSystemTask(0); } /// @dev Test to ensure 'cancelSystemTask' reverts if task does not exist in system tasks. @@ -586,7 +585,7 @@ contract RegistryFacetTest is BaseDiamondTest { vm.expectRevert(IRegistryFacet.SystemTaskDoesNotExist.selector); vm.prank(alice); - RegistryFacet(diamondAddr).cancelSystemTask(0); + IRegistryFacet(diamondAddr).cancelSystemTask(0); } /// @dev Test to ensure 'cancelSystemTask' reverts if caller is not the task owner. @@ -595,7 +594,7 @@ contract RegistryFacetTest is BaseDiamondTest { vm.expectRevert(IRegistryFacet.UnauthorizedAccount.selector); vm.prank(alice); - RegistryFacet(diamondAddr).cancelSystemTask(0); + IRegistryFacet(diamondAddr).cancelSystemTask(0); } /// @dev Test to ensure 'cancelSystemTask' cancels a GST. @@ -603,14 +602,14 @@ contract RegistryFacetTest is BaseDiamondTest { testRegisterSystemTask(); vm.prank(bob); - RegistryFacet(diamondAddr).cancelSystemTask(0); + IRegistryFacet(diamondAddr).cancelSystemTask(0); - assertFalse(RegistryFacet(diamondAddr).ifTaskExists(0)); - assertFalse(RegistryFacet(diamondAddr).ifSysTaskExists(0)); - assertEq(RegistryFacet(diamondAddr).getUserTasks(bob).length, 0); - assertEq(RegistryFacet(diamondAddr).totalTasks(), 0); - assertEq(RegistryFacet(diamondAddr).totalSystemTasks(), 0); - assertEq(RegistryFacet(diamondAddr).getSystemGasCommittedForNextCycle(), 0); + assertFalse(IRegistryFacet(diamondAddr).ifTaskExists(0)); + assertFalse(IRegistryFacet(diamondAddr).ifSysTaskExists(0)); + assertEq(IRegistryFacet(diamondAddr).getUserTasks(bob).length, 0); + assertEq(IRegistryFacet(diamondAddr).totalTasks(), 0); + assertEq(IRegistryFacet(diamondAddr).totalSystemTasks(), 0); + assertEq(IRegistryFacet(diamondAddr).getSystemGasCommittedForNextCycle(), 0); } /// @dev Test to ensure 'cancelSystemTask' emits event 'TaskCancelled'. @@ -618,10 +617,10 @@ contract RegistryFacetTest is BaseDiamondTest { testRegisterSystemTask(); vm.expectEmit(true, true, true, false); - emit RegistryFacet.TaskCancelled(0, bob, keccak256("txHash")); + emit IRegistryFacet.TaskCancelled(0, bob, keccak256("txHash")); vm.prank(bob); - RegistryFacet(diamondAddr).cancelSystemTask(0); + IRegistryFacet(diamondAddr).cancelSystemTask(0); } // :::::::::::::::::::::::::::::::::::::::::::::::::::::: Tests related to 'stopTasks' :::::::::::::::::::::::::::::::::::::::::::::::::::::: @@ -629,13 +628,13 @@ contract RegistryFacetTest is BaseDiamondTest { /// @dev Test to ensure 'stopTasks' reverts if automation is not enabled. function testStopTasksRevertsIfAutomationNotEnabled() public { vm.prank(admin); - CoreFacet(diamondAddr).disableAutomation(); + ICoreFacet(diamondAddr).disableAutomation(); uint64[] memory taskIndexes; vm.expectRevert(IRegistryFacet.AutomationNotEnabled.selector); vm.prank(alice); - RegistryFacet(diamondAddr).stopTasks(taskIndexes); + IRegistryFacet(diamondAddr).stopTasks(taskIndexes); } /// @dev Test to ensure 'stopTasks' reverts if input array is empty. @@ -644,7 +643,7 @@ contract RegistryFacetTest is BaseDiamondTest { vm.expectRevert(IRegistryFacet.TaskIndexesCannotBeEmpty.selector); vm.prank(alice); - RegistryFacet(diamondAddr).stopTasks(taskIndexes); + IRegistryFacet(diamondAddr).stopTasks(taskIndexes); } /// @dev Test to ensure 'stopTasks' reverts if caller is not the task owner. @@ -657,7 +656,7 @@ contract RegistryFacetTest is BaseDiamondTest { vm.expectRevert(IRegistryFacet.UnauthorizedAccount.selector); vm.prank(bob); - RegistryFacet(diamondAddr).stopTasks(taskIndexes); + IRegistryFacet(diamondAddr).stopTasks(taskIndexes); } /// @dev Test to ensure 'stopTasks' reverts if task type is not UST. @@ -670,7 +669,7 @@ contract RegistryFacetTest is BaseDiamondTest { vm.expectRevert(IRegistryFacet.UnsupportedTaskOperation.selector); vm.prank(bob); - RegistryFacet(diamondAddr).stopTasks(taskIndexes); + IRegistryFacet(diamondAddr).stopTasks(taskIndexes); } /// @dev Test to ensure 'stopTasks' does nothing if task does not exist. @@ -681,10 +680,10 @@ contract RegistryFacetTest is BaseDiamondTest { taskIndexes[0] = 5; vm.prank(alice); - RegistryFacet(diamondAddr).stopTasks(taskIndexes); + IRegistryFacet(diamondAddr).stopTasks(taskIndexes); - assertEq(RegistryFacet(diamondAddr).totalTasks(), 1); - assertEq(RegistryFacet(diamondAddr).getTotalDepositedAutomationFees(), 0.5 ether); + assertEq(IRegistryFacet(diamondAddr).totalTasks(), 1); + assertEq(IRegistryFacet(diamondAddr).getTotalDepositedAutomationFees(), 0.5 ether); } /// @dev Test to ensure 'stopTasks' stops the input UST tasks. @@ -696,21 +695,21 @@ contract RegistryFacetTest is BaseDiamondTest { vm.warp(2002); vm.startPrank(LibUtils.VM_SIGNER, LibUtils.VM_SIGNER); - CoreFacet(diamondAddr).monitorCycleEnd(); - CoreFacet(diamondAddr).processTasks(2, taskIndexes); + ICoreFacet(diamondAddr).monitorCycleEnd(); + ICoreFacet(diamondAddr).processTasks(2, taskIndexes); vm.stopPrank(); assertEq(erc20Supra.balanceOf(diamondAddr), 0.702 ether); assertEq(erc20Supra.balanceOf(alice), 4.298 ether); vm.prank(alice); - RegistryFacet(diamondAddr).stopTasks(taskIndexes); + IRegistryFacet(diamondAddr).stopTasks(taskIndexes); - assertFalse(RegistryFacet(diamondAddr).ifTaskExists(0)); - assertEq(RegistryFacet(diamondAddr).totalTasks(), 0); - assertEq(RegistryFacet(diamondAddr).getUserTasks(alice).length, 0); - assertEq(RegistryFacet(diamondAddr).getGasCommittedForNextCycle(), 0); - assertEq(RegistryFacet(diamondAddr).getTotalDepositedAutomationFees(), 0); + assertFalse(IRegistryFacet(diamondAddr).ifTaskExists(0)); + assertEq(IRegistryFacet(diamondAddr).totalTasks(), 0); + assertEq(IRegistryFacet(diamondAddr).getUserTasks(alice).length, 0); + assertEq(IRegistryFacet(diamondAddr).getGasCommittedForNextCycle(), 0); + assertEq(IRegistryFacet(diamondAddr).getTotalDepositedAutomationFees(), 0); assertEq(erc20Supra.balanceOf(diamondAddr), 0.18955 ether); assertEq(erc20Supra.balanceOf(alice), 4.81045 ether); } @@ -724,18 +723,18 @@ contract RegistryFacetTest is BaseDiamondTest { vm.warp(2002); vm.startPrank(LibUtils.VM_SIGNER, LibUtils.VM_SIGNER); - CoreFacet(diamondAddr).monitorCycleEnd(); - CoreFacet(diamondAddr).processTasks(2, taskIndexes); + ICoreFacet(diamondAddr).monitorCycleEnd(); + ICoreFacet(diamondAddr).processTasks(2, taskIndexes); vm.stopPrank(); LibUtils.TaskStopped[] memory stoppedTasks = new LibUtils.TaskStopped[](1); stoppedTasks[0] = LibUtils.TaskStopped(0, 0.5 ether, 0.01245 ether, keccak256("txHash")); vm.expectEmit(true, true, false, false); - emit RegistryFacet.TasksStopped(stoppedTasks, alice); + emit IRegistryFacet.TasksStopped(stoppedTasks, alice); vm.prank(alice); - RegistryFacet(diamondAddr).stopTasks(taskIndexes); + IRegistryFacet(diamondAddr).stopTasks(taskIndexes); } // :::::::::::::::::::::::::::::::::::::::::::::::::::::: Tests related to 'stopSystemTasks' :::::::::::::::::::::::::::::::::::::::::::::::::::::: @@ -743,13 +742,13 @@ contract RegistryFacetTest is BaseDiamondTest { /// @dev Test to ensure 'stopSystemTasks' reverts if automation is not enabled. function testStopSystemTasksRevertsIfAutomationNotEnabled() public { vm.prank(admin); - CoreFacet(diamondAddr).disableAutomation(); + ICoreFacet(diamondAddr).disableAutomation(); uint64[] memory taskIndexes; vm.expectRevert(IRegistryFacet.AutomationNotEnabled.selector); vm.prank(alice); - RegistryFacet(diamondAddr).stopSystemTasks(taskIndexes); + IRegistryFacet(diamondAddr).stopSystemTasks(taskIndexes); } /// @dev Test to ensure 'stopSystemTasks' reverts if input array is empty. @@ -758,7 +757,7 @@ contract RegistryFacetTest is BaseDiamondTest { vm.expectRevert(IRegistryFacet.TaskIndexesCannotBeEmpty.selector); vm.prank(alice); - RegistryFacet(diamondAddr).stopSystemTasks(taskIndexes); + IRegistryFacet(diamondAddr).stopSystemTasks(taskIndexes); } /// @dev Test to ensure 'stopSystemTasks' reverts if caller is not the task owner. @@ -771,7 +770,7 @@ contract RegistryFacetTest is BaseDiamondTest { vm.expectRevert(IRegistryFacet.UnauthorizedAccount.selector); vm.prank(alice); - RegistryFacet(diamondAddr).stopSystemTasks(taskIndexes); + IRegistryFacet(diamondAddr).stopSystemTasks(taskIndexes); } /// @dev Test to ensure 'stopSystemTasks' reverts if task type is not GST. @@ -784,7 +783,7 @@ contract RegistryFacetTest is BaseDiamondTest { vm.expectRevert(IRegistryFacet.UnsupportedTaskOperation.selector); vm.prank(alice); - RegistryFacet(diamondAddr).stopSystemTasks(taskIndexes); + IRegistryFacet(diamondAddr).stopSystemTasks(taskIndexes); } /// @dev Test to ensure 'stopSystemTasks' does nothing if task does not exist. @@ -795,10 +794,10 @@ contract RegistryFacetTest is BaseDiamondTest { taskIndexes[0] = 5; vm.prank(alice); - RegistryFacet(diamondAddr).stopSystemTasks(taskIndexes); + IRegistryFacet(diamondAddr).stopSystemTasks(taskIndexes); - assertEq(RegistryFacet(diamondAddr).totalTasks(), 1); - assertEq(RegistryFacet(diamondAddr).totalSystemTasks(), 1); + assertEq(IRegistryFacet(diamondAddr).totalTasks(), 1); + assertEq(IRegistryFacet(diamondAddr).totalSystemTasks(), 1); } /// @dev Test to ensure 'stopSystemTasks' stops the input GST tasks. @@ -810,20 +809,20 @@ contract RegistryFacetTest is BaseDiamondTest { vm.warp(2002); vm.prank(LibUtils.VM_SIGNER, LibUtils.VM_SIGNER); - CoreFacet(diamondAddr).monitorCycleEnd(); + ICoreFacet(diamondAddr).monitorCycleEnd(); vm.prank(LibUtils.VM_SIGNER); - CoreFacet(diamondAddr).processTasks(2, taskIndexes); + ICoreFacet(diamondAddr).processTasks(2, taskIndexes); vm.prank(bob); - RegistryFacet(diamondAddr).stopSystemTasks(taskIndexes); + IRegistryFacet(diamondAddr).stopSystemTasks(taskIndexes); - assertFalse(RegistryFacet(diamondAddr).ifTaskExists(0)); - assertFalse(RegistryFacet(diamondAddr).ifSysTaskExists(0)); - assertEq(RegistryFacet(diamondAddr).getUserTasks(bob).length, 0); - assertEq(RegistryFacet(diamondAddr).totalTasks(), 0); - assertEq(RegistryFacet(diamondAddr).totalSystemTasks(), 0); - assertEq(RegistryFacet(diamondAddr).getSystemGasCommittedForNextCycle(), 1000000); + assertFalse(IRegistryFacet(diamondAddr).ifTaskExists(0)); + assertFalse(IRegistryFacet(diamondAddr).ifSysTaskExists(0)); + assertEq(IRegistryFacet(diamondAddr).getUserTasks(bob).length, 0); + assertEq(IRegistryFacet(diamondAddr).totalTasks(), 0); + assertEq(IRegistryFacet(diamondAddr).totalSystemTasks(), 0); + assertEq(IRegistryFacet(diamondAddr).getSystemGasCommittedForNextCycle(), 1000000); } /// @dev Test to ensure 'stopSystemTasks' emits event 'TasksStopped'. @@ -835,18 +834,18 @@ contract RegistryFacetTest is BaseDiamondTest { vm.warp(2002); vm.prank(LibUtils.VM_SIGNER, LibUtils.VM_SIGNER); - CoreFacet(diamondAddr).monitorCycleEnd(); + ICoreFacet(diamondAddr).monitorCycleEnd(); vm.prank(LibUtils.VM_SIGNER); - CoreFacet(diamondAddr).processTasks(2, taskIndexes); + ICoreFacet(diamondAddr).processTasks(2, taskIndexes); LibUtils.TaskStopped[] memory stoppedTasks = new LibUtils.TaskStopped[](1); stoppedTasks[0] = LibUtils.TaskStopped(0, 0, 0, keccak256("txHash")); vm.expectEmit(true, true, false, false); - emit RegistryFacet.TasksStopped(stoppedTasks, bob); + emit IRegistryFacet.TasksStopped(stoppedTasks, bob); vm.prank(bob); - RegistryFacet(diamondAddr).stopSystemTasks(taskIndexes); + IRegistryFacet(diamondAddr).stopSystemTasks(taskIndexes); } } \ No newline at end of file From 399296232d787390a1e0f502d64fcbd26a146534 Mon Sep 17 00:00:00 2001 From: Udit Yadav Date: Wed, 18 Feb 2026 16:44:21 +0530 Subject: [PATCH 4/8] updated scripts --- .../deploy_automation_registry.sh | 34 ++-- solidity/supra_contracts/getTaskDetails.js | 37 ----- solidity/supra_contracts/package-lock.json | 131 --------------- solidity/supra_contracts/package.json | 7 - solidity/supra_contracts/run.sh | 149 ++++++++++++------ .../test/BaseDiamondTest.t.sol | 8 +- solidity/supra_contracts/test/CoreFacet.t.sol | 18 +-- 7 files changed, 132 insertions(+), 252 deletions(-) delete mode 100755 solidity/supra_contracts/getTaskDetails.js delete mode 100644 solidity/supra_contracts/package-lock.json delete mode 100644 solidity/supra_contracts/package.json diff --git a/solidity/supra_contracts/deploy_automation_registry.sh b/solidity/supra_contracts/deploy_automation_registry.sh index b2ce8d19cd..9e1eb1bdff 100755 --- a/solidity/supra_contracts/deploy_automation_registry.sh +++ b/solidity/supra_contracts/deploy_automation_registry.sh @@ -39,7 +39,7 @@ fi export ERC20_SUPRA -forge script script/DeployAutomationRegistry.s.sol:DeployAutomationRegistry \ +forge script script/DeployDiamond.s.sol:DeployDiamond \ --rpc-url "$RPC_URL" \ --private-key "$PRIVATE_KEY" \ --broadcast \ @@ -54,12 +54,15 @@ echo "Deployment logs saved to $DEPLOY_LOG" echo "" echo "=== Extracting deployed addresses ===" -AUTOMATION_CORE_IMPL=$(extract "AutomationCore implementation deployed at:") -AUTOMATION_CORE_PROXY=$(extract "AutomationCore proxy deployed at:") -AUTOMATION_REGISTRY_IMPL=$(extract "AutomationRegistry implementation deployed at:") -AUTOMATION_REGISTRY_PROXY=$(extract "AutomationRegistry proxy deployed at:") -AUTOMATION_CONTROLLER_IMPL=$(extract "AutomationController implementation deployed at:") -AUTOMATION_CONTROLLER_PROXY=$(extract "AutomationController proxy deployed at:") +DIAMOND_OWNER=$(extract "Diamond owner:") +DIAMOND=$(extract "Diamond deployed at:") +DIAMOND_CUT_FACET=$(extract "DiamondCutFacet deployed at:") +DIAMOND_LOUPE_FACET=$(extract "DiamondLoupeFacet deployed at:") +OWNERSHIP_FACET=$(extract "OwnershipFacet deployed at:") +CONFIG_FACET=$(extract "ConfigFacet deployed at:") +REGISTRY_FACET=$(extract "RegistryFacet deployed at:") +CORE_FACET=$(extract "CoreFacet deployed at:") +DIAMOND_INIT=$(extract "DiamondInit deployed at:") # ------------------------------------------------------------ # WRITE TO .env @@ -73,14 +76,15 @@ cat < "$ENV_FILE" ERC20_SUPRA=$ERC20_SUPRA -AUTOMATION_CORE_IMPL=$AUTOMATION_CORE_IMPL -AUTOMATION_CORE_PROXY=$AUTOMATION_CORE_PROXY - -AUTOMATION_REGISTRY_IMPL=$AUTOMATION_REGISTRY_IMPL -AUTOMATION_REGISTRY_PROXY=$AUTOMATION_REGISTRY_PROXY - -AUTOMATION_CONTROLLER_IMPL=$AUTOMATION_CONTROLLER_IMPL -AUTOMATION_CONTROLLER_PROXY=$AUTOMATION_CONTROLLER_PROXY +DIAMOND_OWNER=$DIAMOND_OWNER +DIAMOND=$DIAMOND +DIAMOND_CUT_FACET=$DIAMOND_CUT_FACET +DIAMOND_LOUPE_FACET=$DIAMOND_LOUPE_FACET +OWNERSHIP_FACET=$OWNERSHIP_FACET +CONFIG_FACET=$CONFIG_FACET +REGISTRY_FACET=$REGISTRY_FACET +CORE_FACET=$CORE_FACET +DIAMOND_INIT=$DIAMOND_INIT EOF cat "$ENV_FILE" diff --git a/solidity/supra_contracts/getTaskDetails.js b/solidity/supra_contracts/getTaskDetails.js deleted file mode 100755 index d83b44f7b1..0000000000 --- a/solidity/supra_contracts/getTaskDetails.js +++ /dev/null @@ -1,37 +0,0 @@ -#!/usr/bin/env node -import { ethers } from "ethers"; - -const [registryAddress, taskIndex, rpcUrl] = process.argv.slice(2); - -if (!registryAddress || !taskIndex || !rpcUrl) { - console.error("Usage: node getTaskDetails.js "); - process.exit(1); -} - -// Replace with your contract ABI (minimal, only getTaskDetails) -const registryAbi = [ - "function getTaskDetails(uint64 _taskIndex) view returns (tuple(uint128 maxGasAmount,uint128 gasPriceCap,uint128 automationFeeCapForCycle,uint128 lockedFeeForNextCycle,bytes32 txHash,uint64 taskIndex,uint64 registrationTime,uint64 expiryTime,uint64 priority,uint8 taskType,uint8 state,address owner,bytes payloadTx,bytes[] auxData))" -]; - -const provider = new ethers.JsonRpcProvider(rpcUrl); -const registry = new ethers.Contract(registryAddress, registryAbi, provider); - -async function main() { - try { - const task = await registry.getTaskDetails(taskIndex); - console.log(`taskIndex: ${task.taskIndex}`); - console.log(`owner: ${task.owner}`); - console.log(`state: ${["PENDING","ACTIVE","CANCELLED"][task.state]}`); - console.log(`expiryTime: ${task.expiryTime}`); - console.log(`payloadTx: ${task.payloadTx}`); - console.log(`auxData: ${task.auxData}`); - console.log(`maxGasAmount: ${task.maxGasAmount}`); - console.log(`gasPriceCap: ${task.gasPriceCap}`); - console.log(`automationFeeCapForCycle: ${task.automationFeeCapForCycle}`); - console.log(`lockedFeeForNextCycle: ${task.lockedFeeForNextCycle}`); - } catch (e) { - console.error("Error fetching task:", e.message); - } -} - -main(); diff --git a/solidity/supra_contracts/package-lock.json b/solidity/supra_contracts/package-lock.json deleted file mode 100644 index 765ea6dd30..0000000000 --- a/solidity/supra_contracts/package-lock.json +++ /dev/null @@ -1,131 +0,0 @@ -{ - "name": "supra_contracts", - "lockfileVersion": 3, - "requires": true, - "packages": { - "": { - "dependencies": { - "dotenv": "^17.2.3", - "ethers": "^6.16.0" - } - }, - "node_modules/@adraffy/ens-normalize": { - "version": "1.10.1", - "resolved": "https://registry.npmjs.org/@adraffy/ens-normalize/-/ens-normalize-1.10.1.tgz", - "integrity": "sha512-96Z2IP3mYmF1Xg2cDm8f1gWGf/HUVedQ3FMifV4kG/PQ4yEP51xDtRAEfhVNt5f/uzpNkZHwWQuUcu6D6K+Ekw==", - "license": "MIT" - }, - "node_modules/@noble/curves": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/@noble/curves/-/curves-1.2.0.tgz", - "integrity": "sha512-oYclrNgRaM9SsBUBVbb8M6DTV7ZHRTKugureoYEncY5c65HOmRzvSiTE3y5CYaPYJA/GVkrhXEoF0M3Ya9PMnw==", - "license": "MIT", - "dependencies": { - "@noble/hashes": "1.3.2" - }, - "funding": { - "url": "https://paulmillr.com/funding/" - } - }, - "node_modules/@noble/hashes": { - "version": "1.3.2", - "resolved": "https://registry.npmjs.org/@noble/hashes/-/hashes-1.3.2.tgz", - "integrity": "sha512-MVC8EAQp7MvEcm30KWENFjgR+Mkmf+D189XJTkFIlwohU5hcBbn1ZkKq7KVTi2Hme3PMGF390DaL52beVrIihQ==", - "license": "MIT", - "engines": { - "node": ">= 16" - }, - "funding": { - "url": "https://paulmillr.com/funding/" - } - }, - "node_modules/@types/node": { - "version": "22.7.5", - "resolved": "https://registry.npmjs.org/@types/node/-/node-22.7.5.tgz", - "integrity": "sha512-jML7s2NAzMWc//QSJ1a3prpk78cOPchGvXJsC3C6R6PSMoooztvRVQEz89gmBTBY1SPMaqo5teB4uNHPdetShQ==", - "license": "MIT", - "dependencies": { - "undici-types": "~6.19.2" - } - }, - "node_modules/aes-js": { - "version": "4.0.0-beta.5", - "resolved": "https://registry.npmjs.org/aes-js/-/aes-js-4.0.0-beta.5.tgz", - "integrity": "sha512-G965FqalsNyrPqgEGON7nIx1e/OVENSgiEIzyC63haUMuvNnwIgIjMs52hlTCKhkBny7A2ORNlfY9Zu+jmGk1Q==", - "license": "MIT" - }, - "node_modules/dotenv": { - "version": "17.2.3", - "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-17.2.3.tgz", - "integrity": "sha512-JVUnt+DUIzu87TABbhPmNfVdBDt18BLOWjMUFJMSi/Qqg7NTYtabbvSNJGOJ7afbRuv9D/lngizHtP7QyLQ+9w==", - "license": "BSD-2-Clause", - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://dotenvx.com" - } - }, - "node_modules/ethers": { - "version": "6.16.0", - "resolved": "https://registry.npmjs.org/ethers/-/ethers-6.16.0.tgz", - "integrity": "sha512-U1wulmetNymijEhpSEQ7Ct/P/Jw9/e7R1j5XIbPRydgV2DjLVMsULDlNksq3RQnFgKoLlZf88ijYtWEXcPa07A==", - "funding": [ - { - "type": "individual", - "url": "https://github.com/sponsors/ethers-io/" - }, - { - "type": "individual", - "url": "https://www.buymeacoffee.com/ricmoo" - } - ], - "license": "MIT", - "dependencies": { - "@adraffy/ens-normalize": "1.10.1", - "@noble/curves": "1.2.0", - "@noble/hashes": "1.3.2", - "@types/node": "22.7.5", - "aes-js": "4.0.0-beta.5", - "tslib": "2.7.0", - "ws": "8.17.1" - }, - "engines": { - "node": ">=14.0.0" - } - }, - "node_modules/tslib": { - "version": "2.7.0", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.7.0.tgz", - "integrity": "sha512-gLXCKdN1/j47AiHiOkJN69hJmcbGTHI0ImLmbYLHykhgeN0jVGola9yVjFgzCUklsZQMW55o+dW7IXv3RCXDzA==", - "license": "0BSD" - }, - "node_modules/undici-types": { - "version": "6.19.8", - "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.19.8.tgz", - "integrity": "sha512-ve2KP6f/JnbPBFyobGHuerC9g1FYGn/F8n1LWTwNxCEzd6IfqTwUQcNXgEtmmQ6DlRrC1hrSrBnCZPokRrDHjw==", - "license": "MIT" - }, - "node_modules/ws": { - "version": "8.17.1", - "resolved": "https://registry.npmjs.org/ws/-/ws-8.17.1.tgz", - "integrity": "sha512-6XQFvXTkbfUOZOKKILFG1PDK2NDQs4azKQl26T0YS5CxqWLgXajbPZ+h4gZekJyRqFU8pvnbAbbs/3TgRPy+GQ==", - "license": "MIT", - "engines": { - "node": ">=10.0.0" - }, - "peerDependencies": { - "bufferutil": "^4.0.1", - "utf-8-validate": ">=5.0.2" - }, - "peerDependenciesMeta": { - "bufferutil": { - "optional": true - }, - "utf-8-validate": { - "optional": true - } - } - } - } -} diff --git a/solidity/supra_contracts/package.json b/solidity/supra_contracts/package.json deleted file mode 100644 index db925d0be9..0000000000 --- a/solidity/supra_contracts/package.json +++ /dev/null @@ -1,7 +0,0 @@ -{ - "dependencies": { - "dotenv": "^17.2.3", - "ethers": "^6.16.0" - }, - "type": "module" -} diff --git a/solidity/supra_contracts/run.sh b/solidity/supra_contracts/run.sh index 1a8da402ec..9d28d75e1a 100755 --- a/solidity/supra_contracts/run.sh +++ b/solidity/supra_contracts/run.sh @@ -23,29 +23,25 @@ source deployed.env # Validate env variables # ------------------------------- : "${ERC20_SUPRA:?Missing ERC20_SUPRA in deployed.env}" -: "${AUTOMATION_CORE_PROXY:?Missing AUTOMATION_CORE_PROXY in deployed.env}" -: "${AUTOMATION_REGISTRY_PROXY:?Missing AUTOMATION_REGISTRY_PROXY in deployed.env}" +: "${DIAMOND:?Missing DIAMOND in deployed.env}" echo "" echo "Contracts Loaded:" echo "ERC20_SUPRA: $ERC20_SUPRA" -echo "AUTOMATION_CORE: $AUTOMATION_CORE_PROXY" -echo "AUTOMATION_REGISTRY: $AUTOMATION_REGISTRY_PROXY" +echo "DIAMOND: $DIAMOND" echo "" echo "=== Starting Automation CLI ===" ERC20_SUPRA="$ERC20_SUPRA" -AUTOMATION_CORE="$AUTOMATION_CORE_PROXY" -REGISTRY="$AUTOMATION_REGISTRY_PROXY" +DIAMOND="$DIAMOND" ADDRESS=$(cast wallet address --private-key "$PRIVATE_KEY") echo "" echo "Using RPC: $RPC_URL" echo "Wallet: $ADDRESS" echo "ERC20 Supra: $ERC20_SUPRA" -echo "Automation Core proxy: $AUTOMATION_CORE" -echo "Automation Registry proxy: $REGISTRY" +echo "Diamond: $DIAMOND" echo "" # ------------------------------- @@ -78,7 +74,7 @@ get_erc20Supra_balance() { } get_allowance() { - RAW=$(cast erc20-token allowance "$ERC20_SUPRA" "$ADDRESS" "$AUTOMATION_CORE" --rpc-url "$RPC_URL" 2>/dev/null) + RAW=$(cast erc20-token allowance "$ERC20_SUPRA" "$ADDRESS" "$DIAMOND" --rpc-url "$RPC_URL" 2>/dev/null) DEC_WEI=$(echo "$RAW" | awk '{print $1}') DEC_WEI=${DEC_WEI:-0} SUPRA=$(cast --from-wei "$DEC_WEI") @@ -94,26 +90,55 @@ view_task_details() { read -r index echo "" echo "=== Task Details ===" - node getTaskDetails.js "$REGISTRY" "$index" "$RPC_URL" + + RAW=$(cast call "$DIAMOND" \ + "getTaskDetails(uint64)((uint128,uint128,uint128,uint128,bytes32,uint64,uint64,uint64,uint64,address,uint8,uint8,bytes,bytes[]))" \ + "$index" \ + --rpc-url "$RPC_URL" \ + --json 2>/dev/null || true) + + if [ -z "$RAW" ] || [ "$RAW" = "null" ]; then + echo "❌ Task $index does not exist" + echo "" + return + fi + + echo "$RAW" | jq '.[0] | { + maxGasAmount: .[0], + gasPriceCap: ((.[1] | tonumber) / 1e9 | tostring + " Gwei"), + automationFeeCapForCycle: ((.[2] | tonumber) / 1e18 | tostring + " SUPRA"), + depositFee: ((.[3] | tonumber) / 1e18 | tostring + " SUPRA"), + txHash: .[4], + taskIndex: .[5], + registrationTime: .[6], + expiryTime: .[7], + priority: .[8], + owner: .[9], + taskType: (if .[10]==0 then "UST" elif .[10]==1 then "GST" else "UNKNOWN" end), + taskState: (if .[11]==0 then "PENDING" elif .[11]==1 then "ACTIVE" elif .[11]==2 then "CANCELLED" else "UNKNOWN" end), + payloadTx: .[12], + auxData: .[13] + }' + echo "" } is_authorized_submitter() { echo -n "Enter address: " read -r address - RAW=$(cast call "$REGISTRY" "isAuthorizedSubmitter(address)(bool)" $address --rpc-url "$RPC_URL") + RAW=$(cast call "$DIAMOND" "isAuthorizedSubmitter(address)(bool)" $address --rpc-url "$RPC_URL") echo "Is submitter?: $RAW" } view_registry_locked_balance() { - RAW=$(cast call "$REGISTRY" "getTotalLockedBalance()(uint256)" --rpc-url "$RPC_URL") + RAW=$(cast call "$DIAMOND" "getTotalLockedBalance()(uint256)" --rpc-url "$RPC_URL") DEC=$(echo "$RAW" | awk '{print $1}') SUPRA=$(cast --from-wei "$DEC") echo "Registry Locked SUPRA: $SUPRA SUPRA" } view_registry_erc20Supra_balance() { - RAW=$(cast erc20-token balance "$ERC20_SUPRA" "$AUTOMATION_CORE" --rpc-url "$RPC_URL") + RAW=$(cast erc20-token balance "$ERC20_SUPRA" "$DIAMOND" --rpc-url "$RPC_URL") DEC=$(echo "$RAW" | awk '{print $1}') SUPRA=$(cast --from-wei "$DEC") @@ -122,7 +147,7 @@ view_registry_erc20Supra_balance() { } view_task_list() { - RAW=$(cast call "$REGISTRY" "getTaskIdList()(uint256[])" --rpc-url "$RPC_URL") + RAW=$(cast call "$DIAMOND" "getTaskIds()(uint256[])" --rpc-url "$RPC_URL") echo "" echo "=== Task IDs ===" echo "$RAW" @@ -130,10 +155,45 @@ view_task_list() { } view_total_tasks() { - RAW=$(cast call "$REGISTRY" "totalTasks()(uint256)" --rpc-url "$RPC_URL") + RAW=$(cast call "$DIAMOND" "totalTasks()(uint256)" --rpc-url "$RPC_URL") echo "Total Task Count: $RAW" } +view_user_tasks() { + echo -n "Enter user address: " + read -r user + + RAW=$(cast call "$DIAMOND" \ + "getUserTasks(address)(uint256[])" \ + "$user" \ + --rpc-url "$RPC_URL") + + echo "" + echo "=== User Task IDs ===" + echo "$RAW" + echo "" +} + +check_task_exists() { + echo -n "Enter task index: " + read -r index + + RAW=$(cast call "$DIAMOND" \ + "ifTaskExists(uint64)(bool)" \ + "$index" \ + --rpc-url "$RPC_URL") + + echo "" + + if [ "$RAW" = "true" ]; then + echo "✅ Task $index EXISTS" + else + echo "❌ Task $index does NOT exist" + fi + + echo "" +} + # ------------------------------- # Main menu # ------------------------------- @@ -162,6 +222,8 @@ while true; do echo " registry-balance View ERC20Supra balance of registry contract" echo " task-list View all task IDs" echo " total-tasks View number of tasks" + echo " user-tasks View tasks of a user" + echo " task-exists Check if a task exists" echo " exit Quit" echo -n "Command> " read -r CMD @@ -181,22 +243,22 @@ while true; do ;; nativeToErc20SupraWithAllowance) - echo "Enter: " - read -r depositEth spender allowanceEth + echo "Enter: " + read -r depositAmount allowance - if [ -z "$depositEth" ] || [ -z "$spender" ] || [ -z "$allowanceEth" ]; then - echo "Invalid input. Expected: " + if [ -z "$depositAmount" ] || [ -z "$allowance" ]; then + echo "Invalid input. Expected: " exit 1 fi - depositWei=$(cast --to-wei "$depositEth") - allowanceWei=$(cast --to-wei "$allowanceEth") + depositWei=$(cast --to-wei "$depositAmount") + allowanceWei=$(cast --to-wei "$allowance") - echo "Depositing $depositEth ETH, and approving $spender for $allowanceEth ERC20Supra..." + echo "Depositing $depositAmount SUPRA, and approving $DIAMOND for $allowance ERC20Supra..." send_tx "$ERC20_SUPRA" \ "nativeToErc20SupraWithAllowance(address,uint256)" \ - "$spender" "$allowanceWei" \ + "$DIAMOND" "$allowanceWei" \ --value "$depositWei" ;; @@ -205,7 +267,7 @@ while true; do read -r ethAmount weiAmount=$(cast --to-wei "$ethAmount") echo "Approving $ethAmount SUPRA..." - cast erc20-token approve "$ERC20_SUPRA" "$AUTOMATION_CORE" "$weiAmount" --rpc-url "$RPC_URL" --private-key "$PRIVATE_KEY" + cast erc20-token approve "$ERC20_SUPRA" "$DIAMOND" "$weiAmount" --rpc-url "$RPC_URL" --private-key "$PRIVATE_KEY" ;; register) @@ -219,9 +281,6 @@ while true; do expiryTime=$(("$now" + "$duration")) echo "Computed expiryTime = $expiryTime" - echo -n "txHash (0x...): " - read -r txHash - echo -n "maxGasAmount: " read -r maxGas @@ -229,7 +288,6 @@ while true; do read -r gasPriceCap gasPriceCapWei=$(cast --to-wei "$gasPriceCap" gwei) # convert GWEI to wei - echo -n "Automation fee cap for cycle (ETH): " read -r feeCap feeCapWei=$(cast --to-wei "$feeCap") # convert ETH to wei @@ -237,14 +295,11 @@ while true; do echo -n "Priority (uint64): " read -r priority - echo -n "Type (uint8): " - read -r taskType - aux_json="[]" - send_tx "$REGISTRY" \ - "register(bytes,uint64,bytes32,uint128,uint128,uint128,uint64,uint8,bytes[])" \ - "$payloadTx" "$expiryTime" "$txHash" "$maxGas" "$gasPriceCapWei" "$feeCapWei" "$priority" "$taskType" "$aux_json" + send_tx "$DIAMOND" \ + "register(bytes,uint64,uint128,uint128,uint128,uint64,bytes[])" \ + "$payloadTx" "$expiryTime" "$maxGas" "$gasPriceCapWei" "$feeCapWei" "$priority" "$aux_json" ;; register-system) @@ -258,53 +313,47 @@ while true; do expiryTime=$(("$now" + "$duration")) echo "Computed expiryTime = $expiryTime" - echo -n "txHash (0x...): " - read -r txHash - echo -n "maxGasAmount: " read -r maxGas echo -n "Priority (uint64): " read -r priority - echo -n "Type (uint8): " - read -r taskType - aux_json="[]" - send_tx "$REGISTRY" \ - "registerSystemTask(bytes,uint64,bytes32,uint128,uint64,uint8,bytes[])" \ - "$payloadTx" "$expiryTime" "$txHash" "$maxGas" "$priority" "$taskType" "$aux_json" + send_tx "$DIAMOND" \ + "registerSystemTask(bytes,uint64,uint128,uint64,bytes[])" \ + "$payloadTx" "$expiryTime" "$maxGas" "$priority" "$aux_json" ;; cancel) echo -n "Task index: " read -r index - send_tx "$REGISTRY" "cancelTask(uint64)" "$index" + send_tx "$DIAMOND" "cancelTask(uint64)" "$index" ;; cancel-system) echo -n "System task index: " read -r index - send_tx "$REGISTRY" "cancelSystemTask(uint64)" "$index" + send_tx "$DIAMOND" "cancelSystemTask(uint64)" "$index" ;; stop) echo -n "Enter task indexes array (e.g. [0,1,2,3]): " read -r indexes - send_tx "$REGISTRY" "stopTasks(uint64[])" "$indexes" + send_tx "$DIAMOND" "stopTasks(uint64[])" "$indexes" ;; stop-system) echo -n "System task indexes array (e.g. [0,1,2,3]): " read -r indexes - send_tx "$REGISTRY" "stopSystemTasks(uint64[])" "$indexes" + send_tx "$DIAMOND" "stopSystemTasks(uint64[])" "$indexes" ;; grant-authorization) echo -n "Address to grant authorization to: " read -r -a address - cast send "$REGISTRY" "grantAuthorization(address)" "$address" \ + cast send "$DIAMOND" "grantAuthorization(address)" "$address" \ --rpc-url "$RPC_URL" \ --private-key "$ADMIN_PRIVATE_KEY" \ --gas-limit 3000000 @@ -313,7 +362,7 @@ while true; do revoke-authorization) echo -n "Address to revoke authorization on: " read -r -a address - cast send "$REGISTRY" "revokeAuthorization(address)" "$address" \ + cast send "$DIAMOND" "revokeAuthorization(address)" "$address" \ --rpc-url "$RPC_URL" \ --private-key "$ADMIN_PRIVATE_KEY" \ --gas-limit 3000000 @@ -325,6 +374,8 @@ while true; do registry-balance) view_registry_erc20Supra_balance ;; task-list) view_task_list ;; total-tasks) view_total_tasks ;; + user-tasks) view_user_tasks ;; + task-exists) check_task_exists ;; exit) echo "Exiting." diff --git a/solidity/supra_contracts/test/BaseDiamondTest.t.sol b/solidity/supra_contracts/test/BaseDiamondTest.t.sol index 581e689693..22fa253bbb 100644 --- a/solidity/supra_contracts/test/BaseDiamondTest.t.sol +++ b/solidity/supra_contracts/test/BaseDiamondTest.t.sol @@ -4,8 +4,8 @@ pragma solidity 0.8.27; import {Test} from "forge-std/Test.sol"; import {ERC20Supra} from "../src/ERC20Supra.sol"; import {Diamond} from "../src/Diamond.sol"; -import {RegistryFacet} from "../src/facets/RegistryFacet.sol"; -import {ConfigFacet} from "../src/facets/ConfigFacet.sol"; +import {IConfigFacet} from "../src/interfaces/IConfigFacet.sol"; +import {IRegistryFacet} from "../src/interfaces/IRegistryFacet.sol"; import {Deployment, InitParams, LibDiamondUtils} from "../src/libraries/LibDiamondUtils.sol"; import {LibUtils} from "../src/libraries/LibUtils.sol"; @@ -40,7 +40,7 @@ abstract contract BaseDiamondTest is Test { diamond = Diamond(payable(deployment.diamond)); diamondAddr = deployment.diamond; - ConfigFacet(diamondAddr).grantAuthorization(bob); + IConfigFacet(diamondAddr).grantAuthorization(bob); vm.stopPrank(); @@ -60,7 +60,7 @@ abstract contract BaseDiamondTest is Test { erc20Supra.nativeToErc20Supra{value: 5 ether}(); erc20Supra.approve(diamondAddr, type(uint256).max); - RegistryFacet(diamondAddr).register( + IRegistryFacet(diamondAddr).register( payload, uint64(block.timestamp + 2250), uint128(1_000_000), diff --git a/solidity/supra_contracts/test/CoreFacet.t.sol b/solidity/supra_contracts/test/CoreFacet.t.sol index 7158850400..788faca320 100644 --- a/solidity/supra_contracts/test/CoreFacet.t.sol +++ b/solidity/supra_contracts/test/CoreFacet.t.sol @@ -2,7 +2,7 @@ pragma solidity 0.8.27; import {BaseDiamondTest} from "./BaseDiamondTest.t.sol"; -import {RegistryFacet} from "../src/facets/RegistryFacet.sol"; +import {IRegistryFacet} from "../src/interfaces/IRegistryFacet.sol"; import {ICoreFacet} from "../src/interfaces/ICoreFacet.sol"; import {LibUtils} from "../src/libraries/LibUtils.sol"; import {LibCore} from "../src/libraries/LibCore.sol"; @@ -216,12 +216,12 @@ contract CoreFacetTest is BaseDiamondTest { assertEq(newDuration, 2000); assertEq(uint8(newState), uint8(LibUtils.CycleState.STARTED)); - assertEq(RegistryFacet(diamondAddr).getActiveTaskIds(), activeTasks); - assertEq(RegistryFacet(diamondAddr).getSystemGasCommittedForNextCycle(), 0); - assertEq(RegistryFacet(diamondAddr).getSystemGasCommittedForCurrentCycle(), 0); - assertEq(RegistryFacet(diamondAddr).getGasCommittedForNextCycle(), 0); - assertEq(RegistryFacet(diamondAddr).getGasCommittedForCurrentCycle(), 1000000); - assertEq(RegistryFacet(diamondAddr).getCycleLockedFees(), 200000000000000000); + assertEq(IRegistryFacet(diamondAddr).getActiveTaskIds(), activeTasks); + assertEq(IRegistryFacet(diamondAddr).getSystemGasCommittedForNextCycle(), 0); + assertEq(IRegistryFacet(diamondAddr).getSystemGasCommittedForCurrentCycle(), 0); + assertEq(IRegistryFacet(diamondAddr).getGasCommittedForNextCycle(), 0); + assertEq(IRegistryFacet(diamondAddr).getGasCommittedForCurrentCycle(), 1000000); + assertEq(IRegistryFacet(diamondAddr).getCycleLockedFees(), 200000000000000000); } /// @dev Test to ensure 'processTasks' reverts if invalid cycle index is passed when cycle state is FINISHED. @@ -278,7 +278,7 @@ contract CoreFacetTest is BaseDiamondTest { ( , , , LibUtils.CycleState newState) = ICoreFacet(diamondAddr).getCycleInfo(); assertEq(uint8(newState), uint8(LibUtils.CycleState.READY)); - assertFalse(RegistryFacet(diamondAddr).ifTaskExists(tasks[0])); + assertFalse(IRegistryFacet(diamondAddr).ifTaskExists(tasks[0])); } /// @dev Test to ensure 'processTasks' works correctly when cycle state is SUSPENDED and automation is enabled. @@ -320,7 +320,7 @@ contract CoreFacetTest is BaseDiamondTest { assertEq(newStart, uint64(block.timestamp)); assertEq(newDuration, 2000); assertEq(uint8(newState), uint8(LibUtils.CycleState.STARTED)); - assertFalse(RegistryFacet(diamondAddr).ifTaskExists(tasks[0])); + assertFalse(IRegistryFacet(diamondAddr).ifTaskExists(tasks[0])); } /// @dev Test to ensure 'processTasks' reverts if invalid cycle index is passed when cycle state is SUSPENDED. From 3f6f2dfec218502d1e9940053d23e07b223561de Mon Sep 17 00:00:00 2001 From: Udit Yadav Date: Thu, 19 Feb 2026 13:26:57 +0530 Subject: [PATCH 5/8] refactored --- .../src/SupraContractsBindings.sol | 4 +- .../src/facets/ConfigFacet.sol | 3 +- .../supra_contracts/src/facets/CoreFacet.sol | 16 +- .../src/facets/RegistryFacet.sol | 118 ++-- .../src/interfaces/ICoreFacet.sol | 8 +- .../src/interfaces/IRegistryFacet.sol | 6 +- .../src/libraries/LibAccounting.sol | 462 ++++++++++++++ .../src/libraries/LibAppStorage.sol | 8 +- .../src/libraries/LibCommon.sol | 177 +++++ .../supra_contracts/src/libraries/LibCore.sol | 136 ++-- .../src/libraries/LibRegistry.sol | 604 ++---------------- .../src/libraries/LibUtils.sol | 117 ---- .../src/upgradeInitializers/DiamondInit.sol | 9 +- .../test/BaseDiamondTest.t.sol | 11 +- solidity/supra_contracts/test/CoreFacet.t.sol | 79 +-- .../supra_contracts/test/DiamondInit.t.sol | 65 +- .../supra_contracts/test/RegistryFacet.t.sol | 17 +- 17 files changed, 917 insertions(+), 923 deletions(-) create mode 100644 solidity/supra_contracts/src/libraries/LibAccounting.sol create mode 100644 solidity/supra_contracts/src/libraries/LibCommon.sol diff --git a/solidity/supra_contracts/src/SupraContractsBindings.sol b/solidity/supra_contracts/src/SupraContractsBindings.sol index d865178526..e3f085b5d8 100644 --- a/solidity/supra_contracts/src/SupraContractsBindings.sol +++ b/solidity/supra_contracts/src/SupraContractsBindings.sol @@ -1,7 +1,7 @@ // SPDX-License-Identifier: MIT pragma solidity 0.8.27; -import {LibUtils} from "./libraries/LibUtils.sol"; +import {LibCommon} from "./libraries/LibCommon.sol"; import {TaskMetadata} from "./libraries/LibAppStorage.sol"; interface SupraContractsBindings { @@ -15,7 +15,7 @@ interface SupraContractsBindings { // View functions of CoreFacet function isAutomationEnabled() external view returns (bool); - function getCycleInfo() external view returns (uint64, uint64, uint64, LibUtils.CycleState); + function getCycleInfo() external view returns (uint64, uint64, uint64, LibCommon.CycleState); function getTransitionInfo() external view returns (uint64, uint128); // Entry function to be called by node runtime for bookkeeping diff --git a/solidity/supra_contracts/src/facets/ConfigFacet.sol b/solidity/supra_contracts/src/facets/ConfigFacet.sol index 50f63b8e79..1dbee9f4d5 100644 --- a/solidity/supra_contracts/src/facets/ConfigFacet.sol +++ b/solidity/supra_contracts/src/facets/ConfigFacet.sol @@ -3,6 +3,7 @@ pragma solidity 0.8.27; import {EnumerableSet} from "../../lib/openzeppelin-contracts/contracts/utils/structs/EnumerableSet.sol"; import {AppStorage, Config} from "../libraries/LibAppStorage.sol"; +import {LibCommon} from "../libraries/LibCommon.sol"; import {LibUtils} from "../libraries/LibUtils.sol"; import {IConfigFacet} from "../interfaces/IConfigFacet.sol"; import {LibDiamond} from "../libraries/LibDiamond.sol"; @@ -116,7 +117,7 @@ contract ConfigFacet is IConfigFacet { ) external { LibDiamond.enforceIsContractOwner(); - LibUtils.validateConfigParameters( + LibCommon.validateConfigParameters( _taskDurationCapSecs, _registryMaxGasCap, _congestionThresholdPercentage, diff --git a/solidity/supra_contracts/src/facets/CoreFacet.sol b/solidity/supra_contracts/src/facets/CoreFacet.sol index f0f5334a1e..66e96e2191 100644 --- a/solidity/supra_contracts/src/facets/CoreFacet.sol +++ b/solidity/supra_contracts/src/facets/CoreFacet.sol @@ -2,7 +2,7 @@ pragma solidity 0.8.27; import {AppStorage} from "../libraries/LibAppStorage.sol"; -import {LibUtils} from "../libraries/LibUtils.sol"; +import {LibCommon} from "../libraries/LibCommon.sol"; import {LibCore} from "../libraries/LibCore.sol"; import {ICoreFacet} from "../interfaces/ICoreFacet.sol"; import {LibDiamond} from "../libraries/LibDiamond.sol"; @@ -20,11 +20,11 @@ contract CoreFacet is ICoreFacet { // Check caller is VM Signer if (msg.sender != s.vmSigner) { revert CallerNotVmSigner(); } - LibUtils.CycleState state = s.cycleState; - if (state == LibUtils.CycleState.FINISHED) { + LibCommon.CycleState state = s.cycleState; + if (state == LibCommon.CycleState.FINISHED) { LibCore.onCycleTransition(_cycleIndex, _taskIndexes); } else { - if (state != LibUtils.CycleState.SUSPENDED) { revert InvalidRegistryState(); } + if (state != LibCommon.CycleState.SUSPENDED) { revert InvalidRegistryState(); } LibCore.onCycleSuspend(_cycleIndex, _taskIndexes); } } @@ -33,7 +33,7 @@ contract CoreFacet is ICoreFacet { function monitorCycleEnd() external { if (tx.origin != s.vmSigner) { revert CallerNotVmSigner(); } - if (!LibCore.isCycleStarted() || LibCore.getCycleEndTime() > block.timestamp) { + if (!LibCommon.isCycleStarted() || LibCommon.getCycleEndTime() > block.timestamp) { return; } @@ -49,7 +49,7 @@ contract CoreFacet is ICoreFacet { if (s.automationEnabled) { revert AlreadyEnabled(); } s.automationEnabled = true; - if (s.cycleState == LibUtils.CycleState.READY) { + if (s.cycleState == LibCommon.CycleState.READY) { LibCore.moveToStartedState(); LibCore.updateConfigFromBuffer(); } @@ -64,7 +64,7 @@ contract CoreFacet is ICoreFacet { if (!s.automationEnabled) { revert AlreadyDisabled(); } s.automationEnabled = false; - if (s.cycleState == LibUtils.CycleState.FINISHED && !LibCore.isTransitionInProgress()) { + if (s.cycleState == LibCommon.CycleState.FINISHED && !LibCore.isTransitionInProgress()) { LibCore.tryMoveToSuspendedState(); } @@ -74,7 +74,7 @@ contract CoreFacet is ICoreFacet { // :::::::::::::::::::::::::::::::::::::::::::::::::::::::::::: VIEW FUNCTIONS :::::::::::::::::::::::::::::::::::::::::::::::::::::::::::: /// @notice Returns the index, start time, duration and state of the current cycle. - function getCycleInfo() external view returns (uint64, uint64, uint64, LibUtils.CycleState) { + function getCycleInfo() external view returns (uint64, uint64, uint64, LibCommon.CycleState) { return (s.index, s.startTime, s.durationSecs, s.cycleState); } diff --git a/solidity/supra_contracts/src/facets/RegistryFacet.sol b/solidity/supra_contracts/src/facets/RegistryFacet.sol index eebe345608..b2582073b2 100644 --- a/solidity/supra_contracts/src/facets/RegistryFacet.sol +++ b/solidity/supra_contracts/src/facets/RegistryFacet.sol @@ -3,9 +3,9 @@ pragma solidity 0.8.27; import {EnumerableSet} from "../../lib/openzeppelin-contracts/contracts/utils/structs/EnumerableSet.sol"; import {AppStorage, TaskMetadata} from "../libraries/LibAppStorage.sol"; -import {LibUtils} from "../libraries/LibUtils.sol"; +import {LibAccounting} from "../libraries/LibAccounting.sol"; +import {LibCommon} from "../libraries/LibCommon.sol"; import {LibRegistry} from "../libraries/LibRegistry.sol"; -import {LibCore} from "../libraries/LibCore.sol"; import {IRegistryFacet} from "../interfaces/IRegistryFacet.sol"; contract RegistryFacet is IRegistryFacet { @@ -39,7 +39,7 @@ contract RegistryFacet is IRegistryFacet { totalTasks(), regTime, _expiryTime, - LibUtils.TaskType.UST, + LibCommon.TaskType.UST, _payloadTx, _maxGasAmount, _gasPriceCap, @@ -59,8 +59,8 @@ contract RegistryFacet is IRegistryFacet { expiryTime: _expiryTime, priority: taskIndex, // priority set to taskIndex by default owner: msg.sender, - taskType: LibUtils.TaskType.UST, - taskState: LibUtils.TaskState.PENDING, + taskType: LibCommon.TaskType.UST, + taskState: LibCommon.TaskState.PENDING, payloadTx: _payloadTx, auxData: _auxData }); @@ -73,7 +73,7 @@ contract RegistryFacet is IRegistryFacet { uint128 flatRegistrationFee = s.activeConfig.flatRegistrationFeeWei; uint128 fee = flatRegistrationFee + _automationFeeCapForCycle; - LibRegistry.chargeFees(msg.sender, fee); + LibAccounting.chargeFees(msg.sender, fee); emit TaskRegistered(taskIndex, msg.sender, flatRegistrationFee, _automationFeeCapForCycle, s.registryState.tasks[taskIndex]); } @@ -98,7 +98,7 @@ contract RegistryFacet is IRegistryFacet { totalSystemTasks(), regTime, _expiryTime, - LibUtils.TaskType.GST, + LibCommon.TaskType.GST, _payloadTx, _maxGasAmount, 0, @@ -118,8 +118,8 @@ contract RegistryFacet is IRegistryFacet { expiryTime: _expiryTime, priority: taskPriority, owner: msg.sender, - taskType: LibUtils.TaskType.GST, - taskState: LibUtils.TaskState.PENDING, + taskType: LibCommon.TaskType.GST, + taskState: LibCommon.TaskState.PENDING, payloadTx: _payloadTx, auxData: _auxData }); @@ -147,34 +147,34 @@ contract RegistryFacet is IRegistryFacet { // Check if automation is enabled if (!s.automationEnabled) { revert AutomationNotEnabled(); } - if (!LibRegistry.isCycleStarted()) { revert CycleTransitionInProgress(); } - if (!LibRegistry.ifTaskExists(_taskIndex)) { revert TaskDoesNotExist(); } + if (!LibCommon.isCycleStarted()) { revert CycleTransitionInProgress(); } + if (!LibCommon.ifTaskExists(_taskIndex)) { revert TaskDoesNotExist(); } TaskMetadata memory task = s.registryState.tasks[_taskIndex]; - if (task.taskType == LibUtils.TaskType.GST) { revert UnsupportedTaskOperation(); } + if (task.taskType == LibCommon.TaskType.GST) { revert UnsupportedTaskOperation(); } if (task.owner != msg.sender) { revert UnauthorizedAccount(); } - if (task.taskState == LibUtils.TaskState.CANCELLED) { revert AlreadyCancelled(); } + if (task.taskState == LibCommon.TaskState.CANCELLED) { revert AlreadyCancelled(); } - if (task.taskState == LibUtils.TaskState.PENDING) { + if (task.taskState == LibCommon.TaskState.PENDING) { // When Pending tasks are cancelled, refund of the deposit fee is done with penalty - LibRegistry.removeTask(_taskIndex, task.owner, false); - bool result = LibRegistry.safeDepositRefund( + LibCommon.removeTask(_taskIndex, task.owner, false); + bool result = LibAccounting.safeDepositRefund( _taskIndex, task.owner, - task.depositFee / LibRegistry.REFUND_FACTOR, + task.depositFee / LibAccounting.REFUND_FACTOR, task.depositFee ); if (!result) { revert ErrorDepositRefund(); } } else { // It is safe not to check the state as above, the cancelled tasks are already rejected. // Active tasks will be refunded the deposited amount fully at the end of the cycle. - s.registryState.tasks[_taskIndex].taskState = LibUtils.TaskState.CANCELLED; + s.registryState.tasks[_taskIndex].taskState = LibCommon.TaskState.CANCELLED; } // This check means the task was expected to be executed in the next cycle, but it has been cancelled. // We need to remove its gas commitment from `gasCommittedForNextCycle` for this particular task. - if (task.expiryTime > LibCore.getCycleEndTime()) { + if (task.expiryTime > LibCommon.getCycleEndTime()) { LibRegistry.updateGasCommittedForNextCycle(task.taskType, task.maxGasAmount); } @@ -195,27 +195,27 @@ contract RegistryFacet is IRegistryFacet { // Check if automation is enabled if (!s.automationEnabled) { revert AutomationNotEnabled(); } - if (!LibRegistry.isCycleStarted()) { revert CycleTransitionInProgress(); } - if (!LibRegistry.ifTaskExists(_taskIndex)) { revert TaskDoesNotExist(); } - if (!LibRegistry.ifSysTaskExists(_taskIndex)) { revert SystemTaskDoesNotExist(); } + if (!LibCommon.isCycleStarted()) { revert CycleTransitionInProgress(); } + if (!LibCommon.ifTaskExists(_taskIndex)) { revert TaskDoesNotExist(); } + if (!ifSysTaskExists(_taskIndex)) { revert SystemTaskDoesNotExist(); } TaskMetadata memory task = s.registryState.tasks[_taskIndex]; // Check if GST - if (task.taskType == LibUtils.TaskType.UST) { revert UnsupportedTaskOperation(); } + if (task.taskType == LibCommon.TaskType.UST) { revert UnsupportedTaskOperation(); } if (task.owner != msg.sender) { revert UnauthorizedAccount(); } - if (task.taskState == LibUtils.TaskState.CANCELLED) { revert AlreadyCancelled(); } + if (task.taskState == LibCommon.TaskState.CANCELLED) { revert AlreadyCancelled(); } - if (task.taskState == LibUtils.TaskState.PENDING) { - LibRegistry.removeTask(_taskIndex, task.owner, true); + if (task.taskState == LibCommon.TaskState.PENDING) { + LibCommon.removeTask(_taskIndex, task.owner, true); } else { - s.registryState.tasks[_taskIndex].taskState = LibUtils.TaskState.CANCELLED; + s.registryState.tasks[_taskIndex].taskState = LibCommon.TaskState.CANCELLED; } // This check means the task was expected to be executed in the next cycle, but it has been cancelled. // We need to remove its gas commitment from `gasCommittedForNextCycle` for this particular task. - if (task.expiryTime > LibCore.getCycleEndTime()) { + if (task.expiryTime > LibCommon.getCycleEndTime()) { LibRegistry.updateGasCommittedForNextCycle(task.taskType, task.maxGasAmount); } @@ -234,44 +234,44 @@ contract RegistryFacet is IRegistryFacet { // Check if automation is enabled if (!s.automationEnabled) { revert AutomationNotEnabled(); } - if (!LibRegistry.isCycleStarted()) { revert CycleTransitionInProgress(); } + if (!LibCommon.isCycleStarted()) { revert CycleTransitionInProgress(); } if (_taskIndexes.length == 0) { revert TaskIndexesCannotBeEmpty(); } - LibUtils.TaskStopped[] memory stoppedTaskDetails = new LibUtils.TaskStopped[](_taskIndexes.length); + LibCommon.TaskStopped[] memory stoppedTaskDetails = new LibCommon.TaskStopped[](_taskIndexes.length); uint256 counter = 0; uint128 totalRefundFee = 0; // Calculate refundable fee for this remaining time task in current cycle uint64 currentTime = uint64(block.timestamp); - uint64 cycleEndTime = LibCore.getCycleEndTime(); + uint64 cycleEndTime = LibCommon.getCycleEndTime(); uint64 residualInterval = cycleEndTime <= currentTime ? 0 : (cycleEndTime - currentTime); // Loop through each task index to validate and stop the task for (uint256 i = 0; i < _taskIndexes.length; i++) { - if (LibRegistry.ifTaskExists(_taskIndexes[i])) { + if (LibCommon.ifTaskExists(_taskIndexes[i])) { TaskMetadata memory task = s.registryState.tasks[_taskIndexes[i]]; // Check if authorised if (msg.sender != task.owner) { revert UnauthorizedAccount(); } // Check if UST - if (task.taskType == LibUtils.TaskType.GST) { revert UnsupportedTaskOperation(); } + if (task.taskType == LibCommon.TaskType.GST) { revert UnsupportedTaskOperation(); } // Remove task from the registry - LibRegistry.removeTask(_taskIndexes[i], task.owner, false); + LibCommon.removeTask(_taskIndexes[i], task.owner, false); // Remove from active tasks require(s.registryState.activeTaskIds.remove(_taskIndexes[i]), TaskIndexNotFound()); // This check means the task was expected to be executed in the next cycle, but it has been stopped. // We need to remove its gas commitment from `gasCommittedForNextCycle` for this particular task. // Also it checks that task should not be cancelled. - if (task.taskState != LibUtils.TaskState.CANCELLED && task.expiryTime > cycleEndTime) { + if (task.taskState != LibCommon.TaskState.CANCELLED && task.expiryTime > cycleEndTime) { // Reduce committed gas by the stopped task's max gas LibRegistry.updateGasCommittedForNextCycle(task.taskType, task.maxGasAmount); } - (uint128 cycleFeeRefund, uint128 depositRefund) = LibRegistry.unlockDepositAndCycleFee( + (uint128 cycleFeeRefund, uint128 depositRefund) = LibAccounting.unlockDepositAndCycleFee( _taskIndexes[i], task.taskState, task.expiryTime, @@ -284,7 +284,7 @@ contract RegistryFacet is IRegistryFacet { // Add to stopped tasks - LibUtils.TaskStopped memory taskStopped = LibUtils.TaskStopped( + LibCommon.TaskStopped memory taskStopped = LibCommon.TaskStopped( _taskIndexes[i], depositRefund, cycleFeeRefund, @@ -297,7 +297,7 @@ contract RegistryFacet is IRegistryFacet { // Refund and emit event if any tasks were stopped if (counter > 0) { - LibRegistry.refund(msg.sender, totalRefundFee); + LibAccounting.refund(msg.sender, totalRefundFee); // Emit task stopped event emit TasksStopped( @@ -319,34 +319,34 @@ contract RegistryFacet is IRegistryFacet { // Check if automation is enabled if (!s.automationEnabled) { revert AutomationNotEnabled(); } - if (!LibRegistry.isCycleStarted()) { revert CycleTransitionInProgress(); } + if (!LibCommon.isCycleStarted()) { revert CycleTransitionInProgress(); } // Ensure that task indexes are provided if (_taskIndexes.length == 0) { revert TaskIndexesCannotBeEmpty(); } - LibUtils.TaskStopped[] memory stoppedTaskDetails = new LibUtils.TaskStopped[](_taskIndexes.length); + LibCommon.TaskStopped[] memory stoppedTaskDetails = new LibCommon.TaskStopped[](_taskIndexes.length); uint256 counter = 0; - uint64 cycleEndTime = LibCore.getCycleEndTime(); + uint64 cycleEndTime = LibCommon.getCycleEndTime(); // Loop through each task index to validate and stop the task for (uint256 i = 0; i < _taskIndexes.length; i++) { - if (LibRegistry.ifTaskExists(_taskIndexes[i])) { + if (LibCommon.ifTaskExists(_taskIndexes[i])) { TaskMetadata memory task = s.registryState.tasks[_taskIndexes[i]]; if (task.owner != msg.sender) { revert UnauthorizedAccount(); } // Check if GST - if (task.taskType == LibUtils.TaskType.UST) { revert UnsupportedTaskOperation(); } - LibRegistry.removeTask(_taskIndexes[i], task.owner, true); + if (task.taskType == LibCommon.TaskType.UST) { revert UnsupportedTaskOperation(); } + LibCommon.removeTask(_taskIndexes[i], task.owner, true); // Remove from active tasks require(s.registryState.activeTaskIds.remove(_taskIndexes[i]), TaskIndexNotFound()); - if (task.taskState != LibUtils.TaskState.CANCELLED && task.expiryTime > cycleEndTime) { + if (task.taskState != LibCommon.TaskState.CANCELLED && task.expiryTime > cycleEndTime) { LibRegistry.updateGasCommittedForNextCycle(task.taskType, task.maxGasAmount); } // Add to stopped tasks - LibUtils.TaskStopped memory taskStopped = LibUtils.TaskStopped( + LibCommon.TaskStopped memory taskStopped = LibCommon.TaskStopped( _taskIndexes[i], 0, 0, @@ -408,18 +408,18 @@ contract RegistryFacet is IRegistryFacet { /// @notice Returns if a task exists in the registry. /// @param _taskIndex Task index to check existence for. function ifTaskExists(uint64 _taskIndex) external view returns (bool) { - return LibRegistry.ifTaskExists(_taskIndex); + return LibCommon.ifTaskExists(_taskIndex); } /// @notice Returns if a system task exists in the registry. /// @param _taskIndex Task index of the system task to check existence for. - function ifSysTaskExists(uint64 _taskIndex) external view returns (bool) { - return LibRegistry.ifSysTaskExists(_taskIndex); + function ifSysTaskExists(uint64 _taskIndex) public view returns (bool) { + return s.registryState.sysTaskIds.contains(_taskIndex); } /// @notice Returns the details of a task. Reverts if task doesn't exist. function getTaskDetails(uint64 _taskIndex) external view returns (TaskMetadata memory) { - return LibRegistry.getTask(_taskIndex); + return LibCommon.getTask(_taskIndex); } /// @notice Retrieves the details of automation tasks by their task index. Skips a task if it doesn't exist. @@ -431,7 +431,7 @@ contract RegistryFacet is IRegistryFacet { uint256 exists; for (uint256 i = 0; i < count; i++) { - if (LibRegistry.ifTaskExists(_taskIndexes[i])) { + if (LibCommon.ifTaskExists(_taskIndexes[i])) { temp[exists] = s.registryState.tasks[_taskIndexes[i]]; exists += 1; } @@ -462,19 +462,19 @@ contract RegistryFacet is IRegistryFacet { /// @notice Checks whether there is an active task in registry with specified input task index. function hasActiveUserTask(address _account, uint64 _taskIndex) external view returns (bool) { - return hasActiveTaskOfType(_account, _taskIndex, LibUtils.TaskType.UST); + return hasActiveTaskOfType(_account, _taskIndex, LibCommon.TaskType.UST); } /// @notice Checks whether there is an active system task in registry with specified input task index. function hasActiveSystemTask(address _account, uint64 _taskIndex) external view returns (bool) { - return hasActiveTaskOfType(_account, _taskIndex, LibUtils.TaskType.GST); + return hasActiveTaskOfType(_account, _taskIndex, LibCommon.TaskType.GST); } /// @notice Checks whether there is an active task in registry with specified input task index of the input type. /// The type can be either 0 for user submitted tasks, and 1 for governance authorized tasks. - function hasActiveTaskOfType(address _account, uint64 _taskIndex, LibUtils.TaskType _type) public view returns (bool) { + function hasActiveTaskOfType(address _account, uint64 _taskIndex, LibCommon.TaskType _type) public view returns (bool) { TaskMetadata storage task = s.registryState.tasks[_taskIndex]; - return task.owner == _account && task.taskState != LibUtils.TaskState.PENDING && task.taskType == _type; + return task.owner == _account && task.taskState != LibCommon.TaskState.PENDING && task.taskType == _type; } /// @notice Returns the gas committed for the next cycle. @@ -526,19 +526,19 @@ contract RegistryFacet is IRegistryFacet { /// referencing the current automation registry fee parameters, specified total/committed occupancy and current registry /// maximum allowed occupancy. function calculateAutomationFeeMultiplierForCommittedOccupancy(uint128 _totalCommittedMaxGas) external view returns (uint128) { - return LibRegistry.calculateAutomationFeeMultiplierForCommittedOccupancy(_totalCommittedMaxGas); + return LibAccounting.calculateAutomationFeeMultiplierForCommittedOccupancy(_totalCommittedMaxGas); } /// @notice Calculates the automation fee multiplier for current cycle. function calculateAutomationFeeMultiplierForCurrentCycle() external view returns (uint128) { - return LibRegistry.calculateAutomationFeeMultiplierForCurrentCycle(); + return LibAccounting.calculateAutomationFeeMultiplierForCurrentCycle(); } /// @notice Estimates automation fee for the next cycle for specified task occupancy for the configured cycle-interval /// referencing the current automation registry fee parameters, current total occupancy and registry maximum allowed /// occupancy for the next cycle. function estimateAutomationFee(uint128 _taskOccupancy) external view returns (uint128) { - return LibRegistry.estimateAutomationFeeWithCommittedOccupancyInternal(_taskOccupancy, s.registryState.gasCommittedForNextCycle); + return LibAccounting.estimateAutomationFeeWithCommittedOccupancyInternal(_taskOccupancy, s.registryState.gasCommittedForNextCycle); } /// @notice Estimates automation fee the next cycle for specified task occupancy for the configured cycle-interval @@ -548,7 +548,7 @@ contract RegistryFacet is IRegistryFacet { uint128 _taskOccupancy, uint128 _committedOccupancy ) external view returns (uint128) { - return LibRegistry.estimateAutomationFeeWithCommittedOccupancyInternal( + return LibAccounting.estimateAutomationFeeWithCommittedOccupancyInternal( _taskOccupancy, _committedOccupancy ); diff --git a/solidity/supra_contracts/src/interfaces/ICoreFacet.sol b/solidity/supra_contracts/src/interfaces/ICoreFacet.sol index abf7a0df6b..f8c1214088 100644 --- a/solidity/supra_contracts/src/interfaces/ICoreFacet.sol +++ b/solidity/supra_contracts/src/interfaces/ICoreFacet.sol @@ -1,7 +1,7 @@ // SPDX-License-Identifier: MIT pragma solidity 0.8.27; -import {LibUtils} from "../libraries/LibUtils.sol"; +import {LibCommon} from "../libraries/LibCommon.sol"; interface ICoreFacet { // ============================================================= @@ -22,10 +22,10 @@ interface ICoreFacet { /// @notice Emitted when the cycle state transitions. event AutomationCycleEvent( uint64 indexed index, - LibUtils.CycleState indexed state, + LibCommon.CycleState indexed state, uint64 startTime, uint64 durationSecs, - LibUtils.CycleState indexed oldState + LibCommon.CycleState indexed oldState ); /// @notice Emitted when an automation fee is charged for an automation task for the cycle. @@ -65,7 +65,7 @@ interface ICoreFacet { // ============================================================= // View functions // ============================================================= - function getCycleInfo() external view returns (uint64, uint64, uint64, LibUtils.CycleState); + function getCycleInfo() external view returns (uint64, uint64, uint64, LibCommon.CycleState); function getCycleDuration() external view returns (uint64); function getTransitionInfo() external view returns (uint64, uint128); function isAutomationEnabled() external view returns (bool); diff --git a/solidity/supra_contracts/src/interfaces/IRegistryFacet.sol b/solidity/supra_contracts/src/interfaces/IRegistryFacet.sol index b38e13803b..c39b68cbec 100644 --- a/solidity/supra_contracts/src/interfaces/IRegistryFacet.sol +++ b/solidity/supra_contracts/src/interfaces/IRegistryFacet.sol @@ -1,7 +1,7 @@ // SPDX-License-Identifier: MIT pragma solidity 0.8.27; -import {LibUtils} from "../libraries/LibUtils.sol"; +import {LibCommon} from "../libraries/LibCommon.sol"; import {TaskMetadata} from "../libraries/LibAppStorage.sol"; interface IRegistryFacet { @@ -34,7 +34,7 @@ interface IRegistryFacet { /// @notice Emitted when a task is stopped. event TasksStopped( - LibUtils.TaskStopped[] indexed stoppedTasks, + LibCommon.TaskStopped[] indexed stoppedTasks, address indexed owner ); @@ -119,7 +119,7 @@ interface IRegistryFacet { function getTotalLockedBalance() external view returns (uint256); function getUserTasks(address _user) external view returns (uint256[] memory); function hasActiveSystemTask(address _account, uint64 _taskIndex) external view returns (bool); - function hasActiveTaskOfType(address _account, uint64 _taskIndex, LibUtils.TaskType _type) external view returns (bool); + function hasActiveTaskOfType(address _account, uint64 _taskIndex, LibCommon.TaskType _type) external view returns (bool); function hasActiveUserTask(address _account, uint64 _taskIndex) external view returns (bool); function totalSystemTasks() external view returns (uint256); function totalTasks() external view returns (uint256); diff --git a/solidity/supra_contracts/src/libraries/LibAccounting.sol b/solidity/supra_contracts/src/libraries/LibAccounting.sol new file mode 100644 index 0000000000..034489ee4a --- /dev/null +++ b/solidity/supra_contracts/src/libraries/LibAccounting.sol @@ -0,0 +1,462 @@ +// SPDX-License-Identifier: MIT +pragma solidity 0.8.27; + +import {AppStorage, LibAppStorage, TaskMetadata} from "./LibAppStorage.sol"; +import {LibCommon} from "./LibCommon.sol"; +import {IRegistryFacet} from "../interfaces/IRegistryFacet.sol"; +import {IERC20} from "../../lib/openzeppelin-contracts/contracts/token/ERC20/IERC20.sol"; + +library LibAccounting { + + /// @dev Constant for 10^8 + uint256 constant DECIMAL = 100_000_000; + + /// @dev Constants describing REFUND TYPE + uint8 constant DEPOSIT_CYCLE_FEE = 0; + uint8 constant CYCLE_FEE = 1; + + /// @dev Refund fraction + uint8 constant REFUND_FRACTION = 2; + + /// @dev Defines divisor for refunds of deposit fees with penalty + /// Factor of `2` suggests that `1/2` of the deposit will be refunded. + uint8 constant REFUND_FACTOR = 2; + + // :::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::: ERRORS :::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::: + + error ErrorCycleFeeRefund(); + error ErrorDepositRefund(); + error InsufficientBalanceForRefund(); + error RegisteredTaskInvalidType(); + error TransferFailed(); + + // :::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::: PRIVATE FUNCTIONS :::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::: + + /// @notice Refunds fee paid by the task for the cycle to the task owner. + /// Note that here we do not unlock the fee, as on cycle change locked cycle-fees for the ended cycle are + /// automatically unlocked. + function safeFeeRefund( + uint64 _taskIndex, + address _taskOwner, + uint256 _cycleLockedFees, + uint64 _refundableFee + ) private returns (bool, uint256) { + bool result; + uint256 remainingLockedFees; + + (result, remainingLockedFees) = safeUnlockLockedCycleFee(_cycleLockedFees, _refundableFee, _taskIndex); + if (!result) { return (result, remainingLockedFees); } + + result = safeRefund( _taskIndex, _taskOwner, _refundableFee, CYCLE_FEE); + if (result) { emit IRegistryFacet.TaskFeeRefund(_taskIndex, _taskOwner, _refundableFee); } + return (result, remainingLockedFees); + } + + /// @notice Refunds the specified amount to the task owner. + /// @dev Error event is emitted if the registry contract does not have sufficient balance. + /// @param _taskIndex Index of the task. + /// @param _taskOwner Owner of the task. + /// @param _refundableAmount Amount to refund. + /// @param _refundType Type of refund. + /// @return Bool representing if refund was successful. + function safeRefund( + uint64 _taskIndex, + address _taskOwner, + uint128 _refundableAmount, + uint8 _refundType + ) private returns (bool) { + AppStorage storage s = LibAppStorage.appStorage(); + + address erc20Supra = s.erc20Supra; + uint256 balance = IERC20(erc20Supra).balanceOf(address(this)); + if (balance < _refundableAmount) { + emit IRegistryFacet.ErrorInsufficientBalanceToRefund(_taskIndex, _taskOwner, _refundType, _refundableAmount); + return false; + } else { + return _refund(erc20Supra, _taskOwner, _refundableAmount); + } + } + + /// @notice Helper function to transfer refunds. + /// @param _erc20Supra Address of the ERC20Supra token. + /// @param _to Recipeint of the refund + /// @param _amount Amount to refund + /// @return Bool representing if refund was successful. + function _refund(address _erc20Supra, address _to, uint128 _amount) private returns (bool) { + bool sent = IERC20(_erc20Supra).transfer(_to, _amount); + if (!sent) { revert TransferFailed(); } + + return sent; + } + + /// @notice Calculates the automation fee multiplier for cycle. + /// @param _totalCommittedGas Total committed gas. + /// @param _registryMaxGasCap Registry max gas cap. + /// @param _automationBaseFeeWeiPerSec Automation base fee in wei per sec. + function calculateAutomationFeeMultiplierForCycle( + uint128 _totalCommittedGas, + uint128 _registryMaxGasCap, + uint128 _automationBaseFeeWeiPerSec + ) private view returns (uint128) { + uint128 congesionFee = calculateAutomationCongestionFee(_totalCommittedGas, _registryMaxGasCap); + return (congesionFee + _automationBaseFeeWeiPerSec); + } + + /// @notice Function to calculate the automation congestion fee. + /// @param _totalCommittedGas Total committed gas. + /// @param _registryMaxGasCap Registry max gas cap. + /// @return Returns the automation congestion fee. + function calculateAutomationCongestionFee( + uint128 _totalCommittedGas, + uint128 _registryMaxGasCap + ) private view returns (uint128) { + AppStorage storage s = LibAppStorage.appStorage(); + if (s.activeConfig.congestionThresholdPercentage == 100 || s.activeConfig.congestionBaseFeeWeiPerSec == 0) { return 0; } + + // thresholdUsage = (totalCommittedGas / maxGasCap) * 100 + uint256 thresholdUsageScaled = (uint256(_totalCommittedGas) * DECIMAL * 100) / uint256(_registryMaxGasCap); + + uint256 thresholdPercentageScaled = uint256(s.activeConfig.congestionThresholdPercentage) * DECIMAL; + + // If usage is below threshold → no congestion fee + if (thresholdUsageScaled <= thresholdPercentageScaled) { + return 0; + } else { + // Calculate how much usage exceeds threshold + uint256 surplusScaled = (thresholdUsageScaled - thresholdPercentageScaled) / 100; + + + // Ensure threshold + threshold surplus does not exceed 1 (1 in scaled terms) + uint256 thresholdScaledAsFraction = thresholdPercentageScaled / 100; // DECIMAL-scaled fraction + uint256 surplusClipped = thresholdScaledAsFraction + surplusScaled > DECIMAL ? DECIMAL - thresholdScaledAsFraction : surplusScaled; + + uint256 baseScaled = DECIMAL + surplusClipped; // (1 + base) + uint256 resultScaled = DECIMAL; + for (uint8 i = 0; i < s.activeConfig.congestionExponent; i++) { + resultScaled = (resultScaled * baseScaled) / DECIMAL; + } + uint256 exponentResult = resultScaled - DECIMAL; // subtract 1 + + + // Multiply base fee (wei/sec) with exponentResult and downscale by DECIMAL + uint256 acf = (uint256(s.activeConfig.congestionBaseFeeWeiPerSec) * exponentResult) / DECIMAL; + + return uint128(acf); + } + } + + /// @notice Unlocks the locked fee paid by the task for cycle. + /// Error event is emitted if the cycle locked fee amount is inconsistent with the requested unlock amount. + /// @param _cycleLockedFees Locked cycle fees + /// @param _refundableFee Refundable fees + /// @param _taskIndex Index of the task + /// @return Bool if _refundableFee can be unlocked safely. + /// @return Updated _cycleLockedFees after unlocking _refundableFee. + function safeUnlockLockedCycleFee( + uint256 _cycleLockedFees, + uint64 _refundableFee, + uint64 _taskIndex + ) private returns (bool, uint256) { + // This check makes sure that more than locked amount of the fees will be not be refunded. + // Any attempt means internal bug. + bool hasLockedFee = _cycleLockedFees >= _refundableFee; + if (hasLockedFee) { + // Unlock the refunded amount + _cycleLockedFees = _cycleLockedFees - _refundableFee; + } else { + emit IRegistryFacet.ErrorUnlockTaskCycleFee(_taskIndex, _cycleLockedFees, _refundableFee); + } + return (hasLockedFee, _cycleLockedFees); + } + + /// @notice Calculates automation task fees for a single task at the time of new cycle. + /// This is supposed to be called only after removing expired task and must not be called for expired task. + function calculateAutomationFeeForInterval( + uint64 _duration, + uint128 _taskOccupancy, + uint128 _automationFeePerSec, + uint128 _registryMaxGasCap + ) private pure returns (uint128) { + uint256 taskOccupancyRatioByDuration = (uint256(_duration) * uint256(_taskOccupancy) * DECIMAL) / uint256(_registryMaxGasCap); + + uint256 automationFeeForInterval = _automationFeePerSec * taskOccupancyRatioByDuration; + + return uint128(automationFeeForInterval / DECIMAL); + } + + // :::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::: INTERNAL FUNCTIONS :::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::: + + /// @notice Refunds the deposit fee and any autoamtion fees of the task. + function refundTaskFees( + uint64 _currentTime, + uint64 _refundDuration, + uint128 _automationFeePerSec, + TaskMetadata memory _task + ) internal { + AppStorage storage s = LibAppStorage.appStorage(); + + // Do not attempt fee refund if remaining duration is 0 + if (_task.taskState != LibCommon.TaskState.PENDING && _refundDuration != 0) { + uint128 _refundFee = calculateTaskFee( + _task.taskState, + _task.expiryTime, + _task.maxGasAmount, + _refundDuration, + _currentTime, + _automationFeePerSec + ); + ( , uint256 remainingCycleLockedFees) = safeFeeRefund( + _task.taskIndex, + _task.owner, + s.registryState.cycleLockedFees, + uint64(_refundFee) + ); + s.registryState.cycleLockedFees = remainingCycleLockedFees; + } + + safeDepositRefund( + _task.taskIndex, + _task.owner, + _task.depositFee, + _task.depositFee + ); + } + + /// @notice Refunds the specified amount of deposit to the task owner and unlocks full deposit from the total automation fees deposited. + /// @param _taskIndex Index of the task. + /// @param _taskOwner Owner of the task. + /// @param _refundableDeposit Refundable amount of deposit. + /// @param _lockedDeposit Total locked deposit. + function safeDepositRefund( + uint64 _taskIndex, + address _taskOwner, + uint128 _refundableDeposit, + uint128 _lockedDeposit + ) internal returns (bool) { + // Ensures that amount to unlock is not more than the total automation fees deposited. + bool result = safeUnlockLockedDeposit(_taskIndex, _lockedDeposit); + if (!result) { + return result; + } + + result = safeRefund(_taskIndex, _taskOwner, _refundableDeposit, DEPOSIT_CYCLE_FEE); + + if (result) { emit IRegistryFacet.TaskDepositFeeRefund(_taskIndex, _taskOwner, _refundableDeposit); } + return result; + } + + /// @notice Refunds the deposit fee of the task and removes from the registry. + /// @param _taskIndex Index of the task. + /// @param _taskOwner Owner of the task. + /// @param _refundableDeposit Refundable amount of deposit. + /// @param _lockedDeposit Total locked deposit. + function refundDepositAndDrop( + uint64 _taskIndex, + address _taskOwner, + uint128 _refundableDeposit, + uint128 _lockedDeposit + ) internal { + AppStorage storage s = LibAppStorage.appStorage(); + + // Check if task is UST + if (s.registryState.tasks[_taskIndex].taskType == LibCommon.TaskType.GST) { revert RegisteredTaskInvalidType(); } + + // Remove task from the registry state + LibCommon.removeTask(_taskIndex, _taskOwner,false); + + // Refund + safeDepositRefund( + _taskIndex, + _taskOwner, + _refundableDeposit, + _lockedDeposit + ); + } + + /// @notice Internally calls _refund, reverts if caller is not AutomationRegistry. + function refund(address _to, uint128 _amount) internal { + AppStorage storage s = LibAppStorage.appStorage(); + + address erc20Supra = s.erc20Supra; + uint256 balance = IERC20(erc20Supra).balanceOf(address(this)); + if (balance < _amount) { revert InsufficientBalanceForRefund(); } + _refund(erc20Supra, _to, _amount); + } + + /// @notice Calculates the automation fee multiplier for current cycle. + function calculateAutomationFeeMultiplierForCurrentCycle() internal view returns (uint128) { + AppStorage storage s = LibAppStorage.appStorage(); + + // Compute the automation fee multiplier for this cycle + return calculateAutomationFeeMultiplierForCycle( + s.registryState.gasCommittedForThisCycle, + s.activeConfig.registryMaxGasCap, + s.activeConfig.automationBaseFeeWeiPerSec + ); + } + + /// @notice Calculates automation fee per second for the specified task occupancy + /// referencing the current automation registry fee parameters, specified total/committed occupancy and current registry + /// maximum allowed occupancy. + function calculateAutomationFeeMultiplierForCommittedOccupancy( + uint128 _totalCommittedMaxGas + ) internal view returns (uint128) { + AppStorage storage s = LibAppStorage.appStorage(); + + // Compute the automation fee multiplier for cycle + return calculateAutomationFeeMultiplierForCycle( + _totalCommittedMaxGas, + s.activeConfig.registryMaxGasCap, + s.activeConfig.automationBaseFeeWeiPerSec + ); + } + + /// @notice Estimates automation fee the next cycle for specified task occupancy for the configured cycle interval + /// referencing the current automation registry fee parameters, specified total/committed occupancy and registry + /// maximum allowed occupancy for the next cycle. + /// Note it is expected that committed_occupancy does not include current task's occupancy. + function estimateAutomationFeeWithCommittedOccupancyInternal( + uint128 _taskOccupancy, + uint128 _committedOccupancy + ) internal view returns (uint128) { + AppStorage storage s = LibAppStorage.appStorage(); + + uint128 totalCommittedGas = _taskOccupancy + _committedOccupancy; + + uint128 automationFeePerSec = calculateAutomationFeeMultiplierForCycle( + totalCommittedGas, + s.registryState.nextCycleRegistryMaxGasCap, + s.activeConfig.automationBaseFeeWeiPerSec + ); + + if (automationFeePerSec == 0) return 0; + return calculateAutomationFeeForInterval(s.durationSecs, _taskOccupancy, automationFeePerSec, s.registryState.nextCycleRegistryMaxGasCap); + } + + /// @notice Helper function to unlock locked deposit and cycle fees when stopTasks is called. + function unlockDepositAndCycleFee( + uint64 _taskIndex, + LibCommon.TaskState _taskState, + uint64 _expiryTime, + uint128 _maxGasAmount, + uint64 _residualInterval, + uint64 _currentTime, + uint128 _depositFee + ) internal returns (uint128, uint128) { + AppStorage storage s = LibAppStorage.appStorage(); + + uint128 cycleFeeRefund; + uint128 depositRefund; + + if (_taskState != LibCommon.TaskState.PENDING) { + // Compute the automation fee multiplier for cycle + uint128 automationFeePerSec = calculateAutomationFeeMultiplierForCycle( + s.registryState.gasCommittedForThisCycle, + s.activeConfig.registryMaxGasCap, + s.activeConfig.automationBaseFeeWeiPerSec + ); + + uint128 taskFee = calculateTaskFee( + _taskState, + _expiryTime, + _maxGasAmount, + _residualInterval, + _currentTime, + automationFeePerSec + ); + + // Refund full deposit and the half of the remaining run-time fee when task is active or cancelled stage + cycleFeeRefund = taskFee / REFUND_FRACTION; + depositRefund = _depositFee; + } else { + cycleFeeRefund = 0; + depositRefund = _depositFee / REFUND_FRACTION; + } + + bool result = safeUnlockLockedDeposit(_taskIndex, _depositFee); + if (!result) { revert ErrorDepositRefund(); } + + (bool hasLockedFee, uint256 remainingCycleLockedFees ) = safeUnlockLockedCycleFee(s.registryState.cycleLockedFees, uint64(cycleFeeRefund), _taskIndex); + if (!hasLockedFee) { revert ErrorCycleFeeRefund(); } + + s.registryState.cycleLockedFees = remainingCycleLockedFees; + + return (cycleFeeRefund, depositRefund); + } + + /// @notice Unlocks the deposit paid by the task from the total automation fees deposited. + /// @dev Error event is emitted if the total automation fees deposited is less than the requested unlock amount. + /// @param _taskIndex Index of the task. + /// @param _lockedDeposit Locked deposit amount to be unlocked. + /// @return Bool if _lockedDeposit can be unlocked safely. + function safeUnlockLockedDeposit( + uint64 _taskIndex, + uint128 _lockedDeposit + ) internal returns (bool) { + AppStorage storage s = LibAppStorage.appStorage(); + + uint256 totalDeposited = s.registryState.totalDepositedAutomationFees; + + if (totalDeposited >= _lockedDeposit) { + s.registryState.totalDepositedAutomationFees = totalDeposited - _lockedDeposit; + return true; + } + + emit IRegistryFacet.ErrorUnlockTaskDepositFee(_taskIndex, totalDeposited, _lockedDeposit); + return false; + } + + /// @notice Calculates automation task fees for a single task at the time of new cycle. + /// This is supposed to be called only after removing expired task and must not be called for expired task. + /// @param _state State of the task. + /// @param _expiryTime Task expiry time. + /// @param _maxGasAmount Task's max gas amount + /// @param _potentialFeeTimeframe Potential time frame to calculate task fees for. + /// @param _currentTime Current time + /// @param _automationFeePerSec Automation fee per sec + /// @return Calculated task fee for the interval the task will be active. + function calculateTaskFee( + LibCommon.TaskState _state, + uint64 _expiryTime, + uint128 _maxGasAmount, + uint64 _potentialFeeTimeframe, + uint64 _currentTime, + uint128 _automationFeePerSec + ) internal view returns (uint128) { + if (_automationFeePerSec == 0) { return 0; } + if (_expiryTime <= _currentTime) { return 0; } + + uint64 taskActiveTimeframe = _expiryTime - _currentTime; + + // If the task is a new task i.e. in Pending state, then it is charged always for + // the input _potentialFeeTimeframe(which is cycle-interval), + // For the new tasks which active-timeframe is less than cycle-interval + // it would mean it is their first and only cycle and we charge the fee for entire cycle. + // Note that although the new short tasks are charged for entire cycle, the refunding logic remains the same for + // them as for the long tasks. + // This way bad-actors will be discourged to submit small and short tasks with big occupancy by blocking other + // good-actors register tasks. + uint64 actualFeeTimeframe; + if (_state == LibCommon.TaskState.PENDING) { + actualFeeTimeframe = _potentialFeeTimeframe; + } else { + actualFeeTimeframe = taskActiveTimeframe < _potentialFeeTimeframe ? taskActiveTimeframe : _potentialFeeTimeframe; + } + + AppStorage storage s = LibAppStorage.appStorage(); + return calculateAutomationFeeForInterval( + actualFeeTimeframe, + _maxGasAmount, + _automationFeePerSec, + s.activeConfig.registryMaxGasCap + ); + } + + /// @notice Helper function to charge fees from the user. + function chargeFees(address _from, uint256 _amount) internal { + AppStorage storage s = LibAppStorage.appStorage(); + + bool sent = IERC20(s.erc20Supra).transferFrom(_from, address(this), _amount); + if (!sent) { revert TransferFailed(); } + } +} \ No newline at end of file diff --git a/solidity/supra_contracts/src/libraries/LibAppStorage.sol b/solidity/supra_contracts/src/libraries/LibAppStorage.sol index d3538404b8..8bced62abd 100644 --- a/solidity/supra_contracts/src/libraries/LibAppStorage.sol +++ b/solidity/supra_contracts/src/libraries/LibAppStorage.sol @@ -1,7 +1,7 @@ // SPDX-License-Identifier: MIT pragma solidity 0.8.27; -import {LibUtils} from "../libraries/LibUtils.sol"; +import {LibCommon} from "../libraries/LibCommon.sol"; import {EnumerableSet} from "../../lib/openzeppelin-contracts/contracts/utils/structs/EnumerableSet.sol"; /// @notice Struct representing Automation Registry configuration. @@ -45,8 +45,8 @@ struct TaskMetadata { uint64 expiryTime; uint64 priority; address owner; - LibUtils.TaskType taskType; - LibUtils.TaskState taskState; + LibCommon.TaskType taskType; + LibCommon.TaskState taskState; bytes payloadTx; bytes[] auxData; } @@ -98,7 +98,7 @@ struct AppStorage { uint64 index; uint64 startTime; uint64 durationSecs; - LibUtils.CycleState cycleState; + LibCommon.CycleState cycleState; bool ifTransitionStateExists; TransitionState transitionState; diff --git a/solidity/supra_contracts/src/libraries/LibCommon.sol b/solidity/supra_contracts/src/libraries/LibCommon.sol new file mode 100644 index 0000000000..bb45a22e88 --- /dev/null +++ b/solidity/supra_contracts/src/libraries/LibCommon.sol @@ -0,0 +1,177 @@ +// SPDX-License-Identifier: MIT +pragma solidity 0.8.27; + +import {AppStorage, LibAppStorage, TaskMetadata} from "./LibAppStorage.sol"; +import {EnumerableSet} from "../../lib/openzeppelin-contracts/contracts/utils/structs/EnumerableSet.sol"; + +library LibCommon { + using EnumerableSet for EnumerableSet.UintSet; + + /// @notice Enum describing state of the cycle. + enum CycleState { + READY, + STARTED, + FINISHED, + SUSPENDED + } + + /// @notice Enum describing state of a task. + enum TaskState { + PENDING, + ACTIVE, + CANCELLED + } + + /// @notice Enum describing task type. + enum TaskType { + UST, + GST + } + + /// @notice Represents intermediate state of the registry on cycle change. + struct IntermediateStateOfCycleChange { + uint256 cycleLockedFees; + uint128 gasCommittedForNextCycle; + uint128 sysGasCommittedForNextCycle; + uint64[] removedTasks; + } + + /// @notice Struct representing transition result. + struct TransitionResult { + uint128 fees; + uint128 gas; + uint128 sysGas; + bool isRemoved; + } + + /// @notice Struct representing a stopped task. + struct TaskStopped { + uint64 taskIndex; + uint128 depositRefund; + uint128 cycleFeeRefund; + bytes32 txHash; + } + + /// @notice Struct representing an entry in access list. + struct AccessListEntry { + address addr; + bytes32[] storageKeys; + } + + // :::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::: ERRORS :::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::: + + error InvalidTaskDuration(); + error InvalidRegistryMaxGasCap(); + error InvalidCongestionThreshold(); + error InvalidCongestionExponent(); + error InvalidTaskCapacity(); + error InvalidCycleDuration(); + error InvalidSysTaskDuration(); + error InvalidSysRegistryMaxGasCap(); + error InvalidSysTaskCapacity(); + error TaskDoesNotExist(); + error TaskIndexNotFound(); + + // :::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::: INTERNAL FUNCTIONS :::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::: + + /// @notice Helper function to validate the registry configuration parameters. + function validateConfigParameters( + uint64 _taskDurationCapSecs, + uint128 _registryMaxGasCap, + uint8 _congestionThresholdPercentage, + uint8 _congestionExponent, + uint16 _taskCapacity, + uint64 _cycleDurationSecs, + uint64 _sysTaskDurationCapSecs, + uint128 _sysRegistryMaxGasCap, + uint16 _sysTaskCapacity + ) internal pure { + if (_taskDurationCapSecs <= _cycleDurationSecs) { revert InvalidTaskDuration(); } + if (_registryMaxGasCap == 0) { revert InvalidRegistryMaxGasCap(); } + if (_congestionThresholdPercentage > 100) { revert InvalidCongestionThreshold(); } + if (_congestionExponent == 0) { revert InvalidCongestionExponent(); } + if (_taskCapacity == 0) { revert InvalidTaskCapacity(); } + if (_cycleDurationSecs == 0) { revert InvalidCycleDuration(); } + if (_sysTaskDurationCapSecs <= _cycleDurationSecs) { revert InvalidSysTaskDuration(); } + if (_sysRegistryMaxGasCap == 0) { revert InvalidSysRegistryMaxGasCap(); } + if (_sysTaskCapacity == 0) { revert InvalidSysTaskCapacity(); } + } + + /// @notice Helper function to sort an array. + /// @param arr Input array to sort. + /// @return Returns the sorted array. + function sortUint64(uint64[] memory arr) internal pure returns (uint64[] memory) { + uint256 length = arr.length; + for (uint256 i = 0; i < length; i++) { + for (uint256 j = 0; j < length - 1; j++) { + if (arr[j] > arr[j + 1]) { + uint64 temp = arr[j]; + arr[j] = arr[j + 1]; + arr[j + 1] = temp; + } + } + } + return arr; + } + + /// @notice Helper function to sort an array. + /// @param arr Input array to sort. + /// @return Returns the sorted array. + function sortUint256(uint256[] memory arr) internal pure returns (uint256[] memory) { + uint256 length = arr.length; + for (uint256 i = 0; i < length; i++) { + for (uint256 j = 0; j < length - 1; j++) { + if (arr[j] > arr[j + 1]) { + uint256 temp = arr[j]; + arr[j] = arr[j + 1]; + arr[j + 1] = temp; + } + } + } + return arr; + } + + /// @notice Checks whether cycle is in STARTED state. + function isCycleStarted() internal view returns (bool) { + AppStorage storage s = LibAppStorage.appStorage(); + return s.cycleState == LibCommon.CycleState.STARTED; + } + + /// @notice Returns the cycle end time. + function getCycleEndTime() internal view returns (uint64 cycleEndTime) { + AppStorage storage s = LibAppStorage.appStorage(); + cycleEndTime = s.startTime + s.durationSecs; + } + + /// @notice Checks if a task exist. + /// @param _taskIndex Task index to check if a task exists against it. + function ifTaskExists(uint64 _taskIndex) internal view returns (bool) { + AppStorage storage s = LibAppStorage.appStorage(); + return s.registryState.tasks[_taskIndex].owner != address(0) && s.registryState.taskIdList.contains(_taskIndex); + } + + /// @notice Returns the details of a task. Reverts if task doesn't exist. + /// @param _taskIndex Task index to get details for. + function getTask(uint64 _taskIndex) internal view returns (TaskMetadata storage task) { + AppStorage storage s = LibAppStorage.appStorage(); + + if (!ifTaskExists(_taskIndex)) { revert TaskDoesNotExist(); } + task = s.registryState.tasks[_taskIndex]; + } + + /// @notice Function to remove a task from the registry. + /// @param _taskIndex Index of the task to remove. + /// @param _owner Address of the task owner. + /// @param _removeFromSysReg Wheather to remove from system task registry. + function removeTask(uint64 _taskIndex, address _owner, bool _removeFromSysReg) internal { + AppStorage storage s = LibAppStorage.appStorage(); + + if (_removeFromSysReg) { + require(s.registryState.sysTaskIds.remove(_taskIndex), TaskIndexNotFound()); + } + + delete s.registryState.tasks[_taskIndex]; + require(s.registryState.taskIdList.remove(_taskIndex), TaskIndexNotFound()); + require(s.registryState.userTasks[_owner].remove(_taskIndex), TaskIndexNotFound()); + } +} diff --git a/solidity/supra_contracts/src/libraries/LibCore.sol b/solidity/supra_contracts/src/libraries/LibCore.sol index df27be4c0f..e2e9f99d03 100644 --- a/solidity/supra_contracts/src/libraries/LibCore.sol +++ b/solidity/supra_contracts/src/libraries/LibCore.sol @@ -1,14 +1,16 @@ // SPDX-License-Identifier: MIT pragma solidity 0.8.27; +import {LibAccounting} from "./LibAccounting.sol"; +import {LibCommon} from "./LibCommon.sol"; import {LibUtils} from "./LibUtils.sol"; import {AppStorage, LibAppStorage, TaskMetadata} from "./LibAppStorage.sol"; -import {LibRegistry} from "./LibRegistry.sol"; import {ICoreFacet} from "../interfaces/ICoreFacet.sol"; import {IERC20} from "../../lib/openzeppelin-contracts/contracts/token/ERC20/IERC20.sol"; import {EnumerableSet} from "../../lib/openzeppelin-contracts/contracts/utils/structs/EnumerableSet.sol"; library LibCore { + using LibCommon for *; using LibUtils for *; using EnumerableSet for EnumerableSet.UintSet; @@ -20,20 +22,18 @@ library LibCore { error OutOfOrderTaskProcessingRequest(); error TaskIndexNotFound(); - // ::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::: HELPER FUNCTIONS ::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::: + // ::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::: PRIVATE FUNCTIONS ::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::: - /// @notice Function to remove a task from the registry. - /// @param _taskIndex Index of the task to remove. - /// @param _removeFromSysReg Wheather to remove from system task registry. - function removeTask(uint64 _taskIndex, bool _removeFromSysReg) private { + /// @notice Returns the number of total tasks. + function totalTasks() private view returns (uint256) { AppStorage storage s = LibAppStorage.appStorage(); - - if (_removeFromSysReg) { - require(s.registryState.sysTaskIds.remove(_taskIndex), TaskIndexNotFound()); - } - - delete s.registryState.tasks[_taskIndex]; - require(s.registryState.taskIdList.remove(_taskIndex), TaskIndexNotFound()); + return s.registryState.taskIdList.length(); + } + + /// @notice Returns all the automation tasks available in the registry. + function getTaskIdList() private view returns (uint256[] memory) { + AppStorage storage s = LibAppStorage.appStorage(); + return s.registryState.taskIdList.values(); } /// @notice Function to update the cycle locked fees, gas committed and tasks lists. @@ -47,7 +47,7 @@ library LibCore { uint128 _sysGasCommittedForNextCycle, uint128 _gasCommittedForNextCycle, uint128 _gasCommittedForNewCycle, - LibUtils.CycleState _state + LibCommon.CycleState _state ) private { AppStorage storage s = LibAppStorage.appStorage(); @@ -58,7 +58,7 @@ library LibCore { s.registryState.gasCommittedForThisCycle = _gasCommittedForNewCycle; s.registryState.activeTaskIds.clear(); - if (_state == LibUtils.CycleState.FINISHED) { + if (_state == LibCommon.CycleState.FINISHED) { uint256[] memory taskIds = s.registryState.taskIdList.values(); for (uint256 i = 0; i < taskIds.length; i++) { s.registryState.activeTaskIds.add(taskIds[i]); @@ -85,10 +85,10 @@ library LibCore { /// @notice Updates the state of the cycle. /// @param _state Input state to update cycle state with. - function updateCycleStateTo(LibUtils.CycleState _state) private { + function updateCycleStateTo(LibCommon.CycleState _state) private { AppStorage storage s = LibAppStorage.appStorage(); - LibUtils.CycleState oldState = s.cycleState; + LibCommon.CycleState oldState = s.cycleState; s.cycleState = _state; emit ICoreFacet.AutomationCycleEvent ( @@ -142,7 +142,7 @@ library LibCore { s.transitionState.expectedTasksToBeProcessed.clear(); } } - updateCycleStateTo(LibUtils.CycleState.READY); + updateCycleStateTo(LibCommon.CycleState.READY); } /// @notice Updates the cycle state if the transition is identified to be finalized. @@ -164,7 +164,7 @@ library LibCore { return; } - updateRegistryState(0, 0, 0, 0, LibUtils.CycleState.SUSPENDED); + updateRegistryState(0, 0, 0, 0, LibCommon.CycleState.SUSPENDED); // Check if automation is enabled if (s.automationEnabled) { @@ -202,7 +202,7 @@ library LibCore { if (!s.ifTransitionStateExists) { revert InvalidRegistryState(); } if (isTransitionFinalized()) { - if (!s.automationEnabled && s.cycleState == LibUtils.CycleState.FINISHED) { + if (!s.automationEnabled && s.cycleState == LibCommon.CycleState.FINISHED) { tryMoveToSuspendedState(); } else { updateRegistryState( @@ -210,14 +210,14 @@ library LibCore { s.transitionState.sysGasCommittedForNextCycle, s.transitionState.gasCommittedForNextCycle, s.transitionState.gasCommittedForNewCycle, - LibUtils.CycleState.FINISHED + LibCommon.CycleState.FINISHED ); // Set current timestamp as cycle start time // Increment the cycle and update the state to STARTED moveToStartedState(); - if (LibRegistry.getTotalActiveTasks() > 0 ) { - uint256[] memory activeTasks = LibRegistry.getAllActiveTaskIds(); + if (s.registryState.activeTaskIds.length() > 0 ) { + uint256[] memory activeTasks = s.registryState.activeTaskIds.values(); emit ICoreFacet.ActiveTasks(activeTasks); } } @@ -229,7 +229,7 @@ library LibCore { /// @return intermediateState Returns the intermediate state. function dropOrChargeTasks( uint64[] memory _taskIndexes - ) private returns (LibUtils.IntermediateStateOfCycleChange memory intermediateState) { + ) private returns (LibCommon.IntermediateStateOfCycleChange memory intermediateState) { AppStorage storage s = LibAppStorage.appStorage(); uint64 currentTime = uint64(block.timestamp); @@ -243,7 +243,7 @@ library LibCore { // Process each active task and calculate fee for the cycle for the tasks for (uint256 i = 0; i < taskIndexes.length; i++) { - LibUtils.TransitionResult memory result = dropOrChargeTask( + LibCommon.TransitionResult memory result = dropOrChargeTask( taskIndexes[i], currentTime, currentCycleEndTime @@ -275,22 +275,22 @@ library LibCore { uint64 _taskIndex, uint64 _currentTime, uint64 _currentCycleEndTime - ) private returns (LibUtils.TransitionResult memory result) { + ) private returns (LibCommon.TransitionResult memory result) { AppStorage storage s = LibAppStorage.appStorage(); - if (LibRegistry.ifTaskExists(_taskIndex)) { + if (LibCommon.ifTaskExists(_taskIndex)) { markTaskProcessed(_taskIndex); - TaskMetadata memory task = LibRegistry.getTask(_taskIndex); - bool isUst = task.taskType == LibUtils.TaskType.UST; + TaskMetadata memory task = LibCommon.getTask(_taskIndex); + bool isUst = task.taskType == LibCommon.TaskType.UST; // Task is cancelled or expired - if (task.taskState == LibUtils.TaskState.CANCELLED || _currentTime >= task.expiryTime) { + if (task.taskState == LibCommon.TaskState.CANCELLED || _currentTime >= task.expiryTime) { if (isUst) { - LibRegistry.refundDepositAndDrop(_taskIndex, task.owner, task.depositFee, task.depositFee); + LibAccounting.refundDepositAndDrop(_taskIndex, task.owner, task.depositFee, task.depositFee); } else { // Remove the task from registry and system registry - removeTask(_taskIndex, true); + LibCommon.removeTask(_taskIndex, task.owner, true); } result.isRemoved = true; } else if (!isUst) { @@ -298,10 +298,10 @@ library LibCore { // Governance submitted tasks are not charged result.sysGas = task.maxGasAmount; - s.registryState.tasks[_taskIndex].taskState = LibUtils.TaskState.ACTIVE; + s.registryState.tasks[_taskIndex].taskState = LibCommon.TaskState.ACTIVE; } else { // Active UST - uint128 fee = LibRegistry.calculateTaskFee( + uint128 fee = LibAccounting.calculateTaskFee( task.taskState, task.expiryTime, task.maxGasAmount, @@ -316,7 +316,7 @@ library LibCore { // as the fee calculation for them will be different based on their active duration in the cycle. // For more details see calculateTaskFee function. - s.registryState.tasks[_taskIndex].taskState = LibUtils.TaskState.ACTIVE; + s.registryState.tasks[_taskIndex].taskState = LibCommon.TaskState.ACTIVE; (result.isRemoved, result.gas, result.fees) = tryWithdrawTaskAutomationFee( _taskIndex, task.owner, @@ -366,7 +366,7 @@ library LibCore { uint128 gas; uint128 fees; if (_fee > _automationFeeCapForCycle) { - LibRegistry.refundDepositAndDrop(_taskIndex, _owner, _lockedFeeForNextCycle, _lockedFeeForNextCycle); + LibAccounting.refundDepositAndDrop(_taskIndex, _owner, _lockedFeeForNextCycle, _lockedFeeForNextCycle); isRemoved = true; @@ -382,8 +382,8 @@ library LibCore { if (userBalance < _fee) { // If the user does not have enough balance, remove the task, DON'T refund the locked deposit, but simply unlock it and emit an event. - LibRegistry.safeUnlockLockedDeposit(_taskIndex, _lockedFeeForNextCycle); - removeTask(_taskIndex, false); + LibAccounting.safeUnlockLockedDeposit(_taskIndex, _lockedFeeForNextCycle); + LibCommon.removeTask(_taskIndex, _owner, false); isRemoved = true; @@ -397,7 +397,7 @@ library LibCore { } else { if (_fee != 0) { // Charge the fee - LibRegistry.chargeFees(_owner, _fee); + LibAccounting.chargeFees(_owner, _fee); fees = _fee; } @@ -417,19 +417,7 @@ library LibCore { return (isRemoved, gas, fees); } - // :::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::: INTERNAL FUNCTIONS :::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::: - - /// @notice Returns the cycle end time. - function getCycleEndTime() internal view returns (uint64 cycleEndTime) { - AppStorage storage s = LibAppStorage.appStorage(); - cycleEndTime = s.startTime + s.durationSecs; - } - - /// @notice Checks whether cycle is in STARTED state. - function isCycleStarted() internal view returns (bool) { - AppStorage storage s = LibAppStorage.appStorage(); - return s.cycleState == LibUtils.CycleState.STARTED; - } + // ::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::: INTERNAL FUNCTIONS ::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::: /// @notice Checks if the cycle transition is finalized. /// @return Bool representing if the cycle transition is finalized. @@ -457,13 +445,13 @@ library LibCore { if (_taskIndexes.length == 0) { return; } - if (s.cycleState != LibUtils.CycleState.FINISHED) { revert InvalidRegistryState(); } + if (s.cycleState != LibCommon.CycleState.FINISHED) { revert InvalidRegistryState(); } // Check if transition state exists if (!s.ifTransitionStateExists) { revert InvalidRegistryState(); } if (s.index + 1 != _cycleIndex) { revert InvalidInputCycleIndex(); } - LibUtils.IntermediateStateOfCycleChange memory intermediateState = dropOrChargeTasks(_taskIndexes); + LibCommon.IntermediateStateOfCycleChange memory intermediateState = dropOrChargeTasks(_taskIndexes); s.transitionState.lockedFees += intermediateState.cycleLockedFees; s.transitionState.gasCommittedForNextCycle += intermediateState.gasCommittedForNextCycle; @@ -486,7 +474,7 @@ library LibCore { if (_taskIndexes.length == 0) { return; } - if (s.cycleState != LibUtils.CycleState.SUSPENDED) { revert InvalidRegistryState(); } + if (s.cycleState != LibCommon.CycleState.SUSPENDED) { revert InvalidRegistryState(); } if (s.index != _cycleIndex) { revert InvalidInputCycleIndex(); } // Check if transition state exists if (!s.ifTransitionStateExists) { revert InvalidRegistryState(); } @@ -499,17 +487,17 @@ library LibCore { uint64 removedCounter; for (uint i = 0; i < taskIndexes.length; i++) { - if (LibRegistry.ifTaskExists(taskIndexes[i])) { - TaskMetadata memory task = LibRegistry.getTask(taskIndexes[i]); + if (LibCommon.ifTaskExists(taskIndexes[i])) { + TaskMetadata memory task = LibCommon.getTask(taskIndexes[i]); - removeTask(taskIndexes[i], false); + LibCommon.removeTask(taskIndexes[i], task.owner, false); removedTasks[removedCounter++] = taskIndexes[i]; markTaskProcessed(taskIndexes[i]); // Nothing to refund for GST tasks - if (task.taskType == LibUtils.TaskType.UST) { - LibRegistry.refundTaskFees(currentTime, s.transitionState.refundDuration, s.transitionState.automationFeePerSec, task); + if (task.taskType == LibCommon.TaskType.UST) { + LibAccounting.refundTaskFees(currentTime, s.transitionState.refundDuration, s.transitionState.automationFeePerSec, task); } } } @@ -525,12 +513,12 @@ library LibCore { if (!s.automationEnabled) { tryMoveToSuspendedState(); } else { - if (LibRegistry.totalTasks() == 0) { + if (totalTasks() == 0) { // Registry is empty update config buffer and move to STARTED state directly updateConfigFromBuffer(); moveToStartedState(); } else { - uint256[] memory expectedTasksToBeProcessed = LibRegistry.getTaskIdList().sortUint256(); + uint256[] memory expectedTasksToBeProcessed = getTaskIdList().sortUint256(); // Updates transition state s.transitionState.refundDuration = 0; @@ -550,8 +538,8 @@ library LibCore { // Calculate automation fee per second for the new cycle only after configuration is updated. // As we already know the committed gas for the new cycle it is being calculated using updated fee parameters // and will be used to charge tasks during transition process. - s.transitionState.automationFeePerSec = LibRegistry.calculateAutomationFeeMultiplierForCommittedOccupancy(s.transitionState.gasCommittedForNewCycle); - updateCycleStateTo(LibUtils.CycleState.FINISHED); + s.transitionState.automationFeePerSec = LibAccounting.calculateAutomationFeeMultiplierForCommittedOccupancy(s.transitionState.gasCommittedForNewCycle); + updateCycleStateTo(LibCommon.CycleState.FINISHED); } } } @@ -573,9 +561,9 @@ library LibCore { function tryMoveToSuspendedState() internal { AppStorage storage s = LibAppStorage.appStorage(); - if (LibRegistry.totalTasks() == 0) { + if (totalTasks() == 0) { // Registry is empty move to ready state directly - updateCycleStateTo(LibUtils.CycleState.READY); + updateCycleStateTo(LibCommon.CycleState.READY); } else if (!s.ifTransitionStateExists) { // Indicates that cycle was in STARTED state when suspention has been identified. // It is safe to assert that cycleEndTime will always be greater than current chain time as @@ -587,18 +575,18 @@ library LibCore { // As in this case we will first transition to the STARTED state and only then to SUSPENDED. // And when transition to STARTED state we update the cycle start-time to be the current-chain-time. uint64 currentTime = uint64(block.timestamp); - uint64 cycleEndTime = getCycleEndTime(); + uint64 cycleEndTime = LibCommon.getCycleEndTime(); if (currentTime < s.startTime) { revert InvalidRegistryState(); } if (currentTime >= cycleEndTime) { revert InvalidRegistryState(); } - if (!isCycleStarted()) { revert InvalidRegistryState(); } + if (!LibCommon.isCycleStarted()) { revert InvalidRegistryState(); } - uint256[] memory tasksIdList = LibRegistry.getTaskIdList(); + uint256[] memory tasksIdList = getTaskIdList(); uint256[] memory expectedTasksToBeProcessed = tasksIdList.sortUint256(); s.transitionState.refundDuration = cycleEndTime - currentTime; s.transitionState.newCycleDuration = s.durationSecs; - s.transitionState.automationFeePerSec = LibRegistry.calculateAutomationFeeMultiplierForCurrentCycle(); + s.transitionState.automationFeePerSec = LibAccounting.calculateAutomationFeeMultiplierForCurrentCycle(); s.transitionState.gasCommittedForNewCycle = 0; s.transitionState.gasCommittedForNextCycle = 0; s.transitionState.sysGasCommittedForNextCycle = 0; @@ -608,9 +596,9 @@ library LibCore { updateExpectedTasks(expectedTasksToBeProcessed); s.ifTransitionStateExists = true; - updateCycleStateTo(LibUtils.CycleState.SUSPENDED); + updateCycleStateTo(LibCommon.CycleState.SUSPENDED); } else { - if (s.cycleState != LibUtils.CycleState.FINISHED) { revert InvalidRegistryState(); } + if (s.cycleState != LibCommon.CycleState.FINISHED) { revert InvalidRegistryState(); } if (isTransitionInProgress()) { revert InvalidRegistryState(); } // Did not manage to charge cycle fee, so automationFeePerSec will be 0 along with remaining duration @@ -619,7 +607,7 @@ library LibCore { s.transitionState.automationFeePerSec = 0; s.transitionState.gasCommittedForNewCycle = 0; - updateCycleStateTo(LibUtils.CycleState.SUSPENDED); + updateCycleStateTo(LibCommon.CycleState.SUSPENDED); } } @@ -635,7 +623,7 @@ library LibCore { s.durationSecs = s.transitionState.newCycleDuration; } - updateCycleStateTo(LibUtils.CycleState.STARTED); + updateCycleStateTo(LibCommon.CycleState.STARTED); } /// @notice Function to update the registry config structure with values extracted from the buffer, if the buffer exists. diff --git a/solidity/supra_contracts/src/libraries/LibRegistry.sol b/solidity/supra_contracts/src/libraries/LibRegistry.sol index 30407c7547..1da78bbbec 100644 --- a/solidity/supra_contracts/src/libraries/LibRegistry.sol +++ b/solidity/supra_contracts/src/libraries/LibRegistry.sol @@ -1,411 +1,67 @@ // SPDX-License-Identifier: MIT pragma solidity 0.8.27; +import {LibAccounting} from "./LibAccounting.sol"; +import {LibCommon} from "./LibCommon.sol"; import {LibUtils} from "./LibUtils.sol"; -import {AppStorage, LibAppStorage, TaskMetadata} from "./LibAppStorage.sol"; -import {IRegistryFacet} from "../interfaces/IRegistryFacet.sol"; -import {IERC20} from "../../lib/openzeppelin-contracts/contracts/token/ERC20/IERC20.sol"; -import {EnumerableSet} from "../../lib/openzeppelin-contracts/contracts/utils/structs/EnumerableSet.sol"; +import {AppStorage, LibAppStorage} from "./LibAppStorage.sol"; library LibRegistry { using LibUtils for *; - using EnumerableSet for EnumerableSet.UintSet; - - /// @dev Constant for 10^8 - uint256 constant DECIMAL = 100_000_000; - - /// @dev Constants describing REFUND TYPE - uint8 constant DEPOSIT_CYCLE_FEE = 0; - uint8 constant CYCLE_FEE = 1; - - /// @dev Refund fraction - uint8 constant REFUND_FRACTION = 2; - - /// @dev Defines divisor for refunds of deposit fees with penalty - /// Factor of `2` suggests that `1/2` of the deposit will be refunded. - uint8 constant REFUND_FACTOR = 2; /// @notice Address of the transaction hash precompile. address public constant TX_HASH_PRECOMPILE = 0x0000000000000000000000000000000053555001; - // :::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::: ERRORS :::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::: + // :::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::: ERRORS :::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::: - error TransferFailed(); - error ErrorCycleFeeRefund(); error RegistrationDisabled(); error AutomationNotEnabled(); error CycleTransitionInProgress(); - error TaskDoesNotExist(); - error ErrorDepositRefund(); - error RegisteredTaskInvalidType(); - error TaskIndexNotFound(); error FailedToCallTxHashPrecompile(); error TxnHashLengthShouldBe32(uint64); error InvalidMaxGasAmount(); error GasCommittedExceedsMaxGasCap(); error GasCommittedValueUnderflow(); error InsufficientFeeCapForCycle(); - error InsufficientBalanceForRefund(); error InvalidExpiryTime(); error InvalidGasPriceCap(); error InvalidTaskDuration(); error TaskCapacityReached(); error TaskExpiresBeforeNextCycle(); - // :::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::: VIEW FUNCTIONS :::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::: - - /// @notice Returns the total number of active tasks. - function getTotalActiveTasks() internal view returns (uint256) { - AppStorage storage s = LibAppStorage.appStorage(); - return s.registryState.activeTaskIds.length(); - } - - /// @notice Returns all the active task indexes. - function getAllActiveTaskIds() internal view returns (uint256[] memory) { - AppStorage storage s = LibAppStorage.appStorage(); - return s.registryState.activeTaskIds.values(); - } - - /// @notice Returns the number of total tasks. - function totalTasks() internal view returns (uint256) { - AppStorage storage s = LibAppStorage.appStorage(); - return s.registryState.taskIdList.length(); - } - - /// @notice Returns all the automation tasks available in the registry. - function getTaskIdList() internal view returns (uint256[] memory) { - AppStorage storage s = LibAppStorage.appStorage(); - return s.registryState.taskIdList.values(); - } - - /// @notice Checks whether cycle is in STARTED state. - function isCycleStarted() internal view returns (bool) { - AppStorage storage s = LibAppStorage.appStorage(); - return s.cycleState == LibUtils.CycleState.STARTED; - } - - /// @notice Checks if a task exist. - /// @param _taskIndex Task index to check if a task exists against it. - function ifTaskExists(uint64 _taskIndex) internal view returns (bool) { - AppStorage storage s = LibAppStorage.appStorage(); - return s.registryState.tasks[_taskIndex].owner != address(0) && s.registryState.taskIdList.contains(_taskIndex); - } - - /// @notice Checks if a system task exist. - /// @param _taskIndex Task index to check if a system task exists against it. - function ifSysTaskExists(uint64 _taskIndex) internal view returns (bool) { - AppStorage storage s = LibAppStorage.appStorage(); - return s.registryState.sysTaskIds.contains(_taskIndex); - } - - /// @notice Returns the details of a task. Reverts if task doesn't exist. - /// @param _taskIndex Task index to get details for. - function getTask(uint64 _taskIndex) internal view returns (TaskMetadata storage task) { - AppStorage storage s = LibAppStorage.appStorage(); - - if (!ifTaskExists(_taskIndex)) { revert TaskDoesNotExist(); } - task = s.registryState.tasks[_taskIndex]; - } - - /// @notice Refunds the specified amount of deposit to the task owner and unlocks full deposit from the total automation fees deposited. - /// @param _taskIndex Index of the task. - /// @param _taskOwner Owner of the task. - /// @param _refundableDeposit Refundable amount of deposit. - /// @param _lockedDeposit Total locked deposit. - function safeDepositRefund( - uint64 _taskIndex, - address _taskOwner, - uint128 _refundableDeposit, - uint128 _lockedDeposit - ) internal returns (bool) { - // Ensures that amount to unlock is not more than the total automation fees deposited. - bool result = safeUnlockLockedDeposit(_taskIndex, _lockedDeposit); - if (!result) { - return result; - } - - result = safeRefund(_taskIndex, _taskOwner, _refundableDeposit, DEPOSIT_CYCLE_FEE); - - if (result) { emit IRegistryFacet.TaskDepositFeeRefund(_taskIndex, _taskOwner, _refundableDeposit); } - return result; - } - - /// @notice Refunds the deposit fee and any autoamtion fees of the task. - function refundTaskFees( - uint64 _currentTime, - uint64 _refundDuration, - uint128 _automationFeePerSec, - TaskMetadata memory _task - ) internal { - AppStorage storage s = LibAppStorage.appStorage(); - - // Do not attempt fee refund if remaining duration is 0 - if (_task.taskState != LibUtils.TaskState.PENDING && _refundDuration != 0) { - uint128 _refundFee = calculateTaskFee( - _task.taskState, - _task.expiryTime, - _task.maxGasAmount, - _refundDuration, - _currentTime, - _automationFeePerSec - ); - ( , uint256 remainingCycleLockedFees) = safeFeeRefund( - _task.taskIndex, - _task.owner, - s.registryState.cycleLockedFees, - uint64(_refundFee) - ); - s.registryState.cycleLockedFees = remainingCycleLockedFees; - } - - safeDepositRefund( - _task.taskIndex, - _task.owner, - _task.depositFee, - _task.depositFee - ); - } - - /// @notice Calculates automation task fees for a single task at the time of new cycle. - /// This is supposed to be called only after removing expired task and must not be called for expired task. - function calculateAutomationFeeForInterval( - uint64 _duration, - uint128 _taskOccupancy, - uint128 _automationFeePerSec, - uint128 _registryMaxGasCap - ) private pure returns (uint128) { - uint256 taskOccupancyRatioByDuration = (uint256(_duration) * uint256(_taskOccupancy) * DECIMAL) / uint256(_registryMaxGasCap); - - uint256 automationFeeForInterval = _automationFeePerSec * taskOccupancyRatioByDuration; - - return uint128(automationFeeForInterval / DECIMAL); - } - - /// @notice Calculates automation task fees for a single task at the time of new cycle. - /// This is supposed to be called only after removing expired task and must not be called for expired task. - /// @param _state State of the task. - /// @param _expiryTime Task expiry time. - /// @param _maxGasAmount Task's max gas amount - /// @param _potentialFeeTimeframe Potential time frame to calculate task fees for. - /// @param _currentTime Current time - /// @param _automationFeePerSec Automation fee per sec - /// @return Calculated task fee for the interval the task will be active. - function calculateTaskFee( - LibUtils.TaskState _state, - uint64 _expiryTime, - uint128 _maxGasAmount, - uint64 _potentialFeeTimeframe, - uint64 _currentTime, - uint128 _automationFeePerSec - ) internal view returns (uint128) { - if (_automationFeePerSec == 0) { return 0; } - if (_expiryTime <= _currentTime) { return 0; } - - uint64 taskActiveTimeframe = _expiryTime - _currentTime; - - // If the task is a new task i.e. in Pending state, then it is charged always for - // the input _potentialFeeTimeframe(which is cycle-interval), - // For the new tasks which active-timeframe is less than cycle-interval - // it would mean it is their first and only cycle and we charge the fee for entire cycle. - // Note that although the new short tasks are charged for entire cycle, the refunding logic remains the same for - // them as for the long tasks. - // This way bad-actors will be discourged to submit small and short tasks with big occupancy by blocking other - // good-actors register tasks. - uint64 actualFeeTimeframe; - if (_state == LibUtils.TaskState.PENDING) { - actualFeeTimeframe = _potentialFeeTimeframe; - } else { - actualFeeTimeframe = taskActiveTimeframe < _potentialFeeTimeframe ? taskActiveTimeframe : _potentialFeeTimeframe; - } + // :::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::: PRIVATE FUNCTIONS :::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::: - AppStorage storage s = LibAppStorage.appStorage(); - return calculateAutomationFeeForInterval( - actualFeeTimeframe, - _maxGasAmount, - _automationFeePerSec, - s.activeConfig.registryMaxGasCap - ); - } - - /// @notice Unlocks the locked fee paid by the task for cycle. - /// Error event is emitted if the cycle locked fee amount is inconsistent with the requested unlock amount. - /// @param _cycleLockedFees Locked cycle fees - /// @param _refundableFee Refundable fees - /// @param _taskIndex Index of the task - /// @return Bool if _refundableFee can be unlocked safely. - /// @return Updated _cycleLockedFees after unlocking _refundableFee. - function safeUnlockLockedCycleFee( - uint256 _cycleLockedFees, - uint64 _refundableFee, - uint64 _taskIndex - ) private returns (bool, uint256) { - // This check makes sure that more than locked amount of the fees will be not be refunded. - // Any attempt means internal bug. - bool hasLockedFee = _cycleLockedFees >= _refundableFee; - if (hasLockedFee) { - // Unlock the refunded amount - _cycleLockedFees = _cycleLockedFees - _refundableFee; - } else { - emit IRegistryFacet.ErrorUnlockTaskCycleFee(_taskIndex, _cycleLockedFees, _refundableFee); - } - return (hasLockedFee, _cycleLockedFees); - } - - /// @notice Helper function to transfer refunds. - /// @param _erc20Supra Address of the ERC20Supra token. - /// @param _to Recipeint of the refund - /// @param _amount Amount to refund - /// @return Bool representing if refund was successful. - function _refund(address _erc20Supra, address _to, uint128 _amount) private returns (bool) { - bool sent = IERC20(_erc20Supra).transfer(_to, _amount); - if (!sent) { revert TransferFailed(); } - - return sent; - } - - /// @notice Refunds the specified amount to the task owner. - /// @dev Error event is emitted if the registry contract does not have sufficient balance. - /// @param _taskIndex Index of the task. - /// @param _taskOwner Owner of the task. - /// @param _refundableAmount Amount to refund. - /// @param _refundType Type of refund. - /// @return Bool representing if refund was successful. - function safeRefund( - uint64 _taskIndex, - address _taskOwner, - uint128 _refundableAmount, - uint8 _refundType - ) private returns (bool) { - AppStorage storage s = LibAppStorage.appStorage(); - - address erc20Supra = s.erc20Supra; - uint256 balance = IERC20(erc20Supra).balanceOf(address(this)); - if (balance < _refundableAmount) { - emit IRegistryFacet.ErrorInsufficientBalanceToRefund(_taskIndex, _taskOwner, _refundType, _refundableAmount); - return false; - } else { - return _refund(erc20Supra, _taskOwner, _refundableAmount); - } - } - - /// @notice Refunds fee paid by the task for the cycle to the task owner. - /// Note that here we do not unlock the fee, as on cycle change locked cycle-fees for the ended cycle are - /// automatically unlocked. - function safeFeeRefund( - uint64 _taskIndex, - address _taskOwner, - uint256 _cycleLockedFees, - uint64 _refundableFee - ) private returns (bool, uint256) { - bool result; - uint256 remainingLockedFees; - - (result, remainingLockedFees) = safeUnlockLockedCycleFee(_cycleLockedFees, _refundableFee, _taskIndex); - if (!result) { return (result, remainingLockedFees); } + /// @notice Helper function to validate the task duration. + function validateTaskDuration( + uint64 _regTime, + uint64 _expiryTime, + uint64 _taskDurationCap, + uint64 _cycleEndTime + ) private pure { + if (_expiryTime <= _regTime) { revert InvalidExpiryTime(); } - result = safeRefund( _taskIndex, _taskOwner, _refundableFee, CYCLE_FEE); - if (result) { emit IRegistryFacet.TaskFeeRefund(_taskIndex, _taskOwner, _refundableFee); } - return (result, remainingLockedFees); - } - - /// @notice Internally calls _refund, reverts if caller is not AutomationRegistry. - function refund(address _to, uint128 _amount) internal { - AppStorage storage s = LibAppStorage.appStorage(); + uint64 taskDuration = _expiryTime - _regTime; + if (taskDuration > _taskDurationCap) { revert InvalidTaskDuration(); } - address erc20Supra = s.erc20Supra; - uint256 balance = IERC20(erc20Supra).balanceOf(address(this)); - if (balance < _amount) { revert InsufficientBalanceForRefund(); } - _refund(erc20Supra, _to, _amount); + if ( _expiryTime <= _cycleEndTime) { revert TaskExpiresBeforeNextCycle(); } } - /// @notice Unlocks the deposit paid by the task from the total automation fees deposited. - /// @dev Error event is emitted if the total automation fees deposited is less than the requested unlock amount. - /// @param _taskIndex Index of the task. - /// @param _lockedDeposit Locked deposit amount to be unlocked. - /// @return Bool if _lockedDeposit can be unlocked safely. - function safeUnlockLockedDeposit( - uint64 _taskIndex, - uint128 _lockedDeposit - ) internal returns (bool) { - AppStorage storage s = LibAppStorage.appStorage(); - - uint256 totalDeposited = s.registryState.totalDepositedAutomationFees; + /// @notice Helper function to validate the inputs while registering a task. + function validateInputs(bytes memory _payloadTx, uint128 _maxGasAmount) private view { + ( , address payloadTarget, , ) = abi.decode(_payloadTx, (uint128, address, bytes, LibCommon.AccessListEntry[])); + payloadTarget.validateContractAddress(); - if (totalDeposited >= _lockedDeposit) { - s.registryState.totalDepositedAutomationFees = totalDeposited - _lockedDeposit; - return true; - } - - emit IRegistryFacet.ErrorUnlockTaskDepositFee(_taskIndex, totalDeposited, _lockedDeposit); - return false; - } - - /// @notice Calculates the automation fee multiplier for current cycle. - function calculateAutomationFeeMultiplierForCurrentCycle() internal view returns (uint128) { - AppStorage storage s = LibAppStorage.appStorage(); - - // Compute the automation fee multiplier for this cycle - return calculateAutomationFeeMultiplierForCycle( - s.registryState.gasCommittedForThisCycle, - s.activeConfig.registryMaxGasCap, - s.activeConfig.automationBaseFeeWeiPerSec - ); - } - - /// @notice Calculates the automation fee multiplier for cycle. - /// @param _totalCommittedGas Total committed gas. - /// @param _registryMaxGasCap Registry max gas cap. - /// @param _automationBaseFeeWeiPerSec Automation base fee in wei per sec. - function calculateAutomationFeeMultiplierForCycle( - uint128 _totalCommittedGas, - uint128 _registryMaxGasCap, - uint128 _automationBaseFeeWeiPerSec - ) private view returns (uint128) { - uint128 congesionFee = calculateAutomationCongestionFee(_totalCommittedGas, _registryMaxGasCap); - return (congesionFee + _automationBaseFeeWeiPerSec); + if (_maxGasAmount == 0) { revert InvalidMaxGasAmount(); } } - /// @notice Function to calculate the automation congestion fee. - /// @param _totalCommittedGas Total committed gas. - /// @param _registryMaxGasCap Registry max gas cap. - /// @return Returns the automation congestion fee. - function calculateAutomationCongestionFee( - uint128 _totalCommittedGas, - uint128 _registryMaxGasCap - ) internal view returns (uint128) { - AppStorage storage s = LibAppStorage.appStorage(); - if (s.activeConfig.congestionThresholdPercentage == 100 || s.activeConfig.congestionBaseFeeWeiPerSec == 0) { return 0; } - - // thresholdUsage = (totalCommittedGas / maxGasCap) * 100 - uint256 thresholdUsageScaled = (uint256(_totalCommittedGas) * DECIMAL * 100) / uint256(_registryMaxGasCap); - - uint256 thresholdPercentageScaled = uint256(s.activeConfig.congestionThresholdPercentage) * DECIMAL; - - // If usage is below threshold → no congestion fee - if (thresholdUsageScaled <= thresholdPercentageScaled) { - return 0; - } else { - // Calculate how much usage exceeds threshold - uint256 surplusScaled = (thresholdUsageScaled - thresholdPercentageScaled) / 100; - - - // Ensure threshold + threshold surplus does not exceed 1 (1 in scaled terms) - uint256 thresholdScaledAsFraction = thresholdPercentageScaled / 100; // DECIMAL-scaled fraction - uint256 surplusClipped = thresholdScaledAsFraction + surplusScaled > DECIMAL ? DECIMAL - thresholdScaledAsFraction : surplusScaled; - - uint256 baseScaled = DECIMAL + surplusClipped; // (1 + base) - uint256 resultScaled = DECIMAL; - for (uint8 i = 0; i < s.activeConfig.congestionExponent; i++) { - resultScaled = (resultScaled * baseScaled) / DECIMAL; - } - uint256 exponentResult = resultScaled - DECIMAL; // subtract 1 - + // :::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::: INTERNAL FUNCTIONS :::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::: - // Multiply base fee (wei/sec) with exponentResult and downscale by DECIMAL - uint256 acf = (uint256(s.activeConfig.congestionBaseFeeWeiPerSec) * exponentResult) / DECIMAL; - - return uint128(acf); - } + /// @notice Read tx hash via precompile. Reverts if precompile missing/fails. + function readTxHash() internal view returns (bytes32) { + (bool ok, bytes memory out) = TX_HASH_PRECOMPILE.staticcall(""); + require(ok, FailedToCallTxHashPrecompile()); + require(out.length == 32, TxnHashLengthShouldBe32(uint64(out.length))); + return abi.decode(out, (bytes32)); } /// @notice Function to update the cycle locked fees and gas committed. @@ -428,56 +84,20 @@ library LibRegistry { s.registryState.gasCommittedForThisCycle = _gasCommittedForNewCycle; } - /// @notice Calculates automation fee per second for the specified task occupancy - /// referencing the current automation registry fee parameters, specified total/committed occupancy and current registry - /// maximum allowed occupancy. - function calculateAutomationFeeMultiplierForCommittedOccupancy( - uint128 _totalCommittedMaxGas - ) internal view returns (uint128) { - AppStorage storage s = LibAppStorage.appStorage(); - - // Compute the automation fee multiplier for cycle - return calculateAutomationFeeMultiplierForCycle( - _totalCommittedMaxGas, - s.activeConfig.registryMaxGasCap, - s.activeConfig.automationBaseFeeWeiPerSec - ); - } - - /// @notice Helper function to charge fees from the user. - function chargeFees(address _from, uint256 _amount) internal { + function updateGasCommittedForNextCycle(LibCommon.TaskType _taskType, uint128 _maxGasAmount) internal { AppStorage storage s = LibAppStorage.appStorage(); - - bool sent = IERC20(s.erc20Supra).transferFrom(_from, address(this), _amount); - if (!sent) { revert TransferFailed(); } - } - /// @notice Refunds the deposit fee of the task and removes from the registry. - /// @param _taskIndex Index of the task. - /// @param _taskOwner Owner of the task. - /// @param _refundableDeposit Refundable amount of deposit. - /// @param _lockedDeposit Total locked deposit. - function refundDepositAndDrop( - uint64 _taskIndex, - address _taskOwner, - uint128 _refundableDeposit, - uint128 _lockedDeposit - ) internal { - AppStorage storage s = LibAppStorage.appStorage(); + bool isUST = _taskType == LibCommon.TaskType.UST; - // Check if task is UST - if (s.registryState.tasks[_taskIndex].taskType == LibUtils.TaskType.GST) { revert RegisteredTaskInvalidType(); } - - // Remove task from the registry state - removeTask(_taskIndex, _taskOwner,false); - - // Refund - safeDepositRefund( - _taskIndex, - _taskOwner, - _refundableDeposit, - _lockedDeposit - ); + uint128 gasCommittedForNextCycle = isUST ? s.registryState.gasCommittedForNextCycle : s.registryState.sysGasCommittedForNextCycle; + if (gasCommittedForNextCycle < _maxGasAmount) { revert GasCommittedValueUnderflow(); } + + // Adjust the gas committed for the next cycle by subtracting the gas amount of the cancelled/stopped task + if (isUST) { + s.registryState.gasCommittedForNextCycle = gasCommittedForNextCycle - _maxGasAmount; + } else { + s.registryState.sysGasCommittedForNextCycle = gasCommittedForNextCycle - _maxGasAmount; + } } /// @notice Helper function that performs validation and updates state for a valid task. @@ -485,7 +105,7 @@ library LibRegistry { uint256 _totalTasks, uint64 _regTime, uint64 _expiryTime, - LibUtils.TaskType _taskType, + LibCommon.TaskType _taskType, bytes memory _payloadTx, uint128 _maxGasAmount, uint128 _gasPriceCap, @@ -497,9 +117,9 @@ library LibRegistry { if (!s.automationEnabled) { revert AutomationNotEnabled(); } if (!s.registrationEnabled) { revert RegistrationDisabled(); } - if (!isCycleStarted()) { revert CycleTransitionInProgress(); } + if (!LibCommon.isCycleStarted()) { revert CycleTransitionInProgress(); } - bool isUST = _taskType == LibUtils.TaskType.UST; + bool isUST = _taskType == LibCommon.TaskType.UST; uint64 taskDurationCap; uint128 gasCommittedForNextCycle; @@ -509,7 +129,7 @@ library LibRegistry { if (_gasPriceCap == 0) { revert InvalidGasPriceCap(); } gasCommittedForNextCycle = s.registryState.gasCommittedForNextCycle; - uint128 estimatedAutomationFeeForCycle = estimateAutomationFeeWithCommittedOccupancyInternal(_maxGasAmount, gasCommittedForNextCycle); + uint128 estimatedAutomationFeeForCycle = LibAccounting.estimateAutomationFeeWithCommittedOccupancyInternal(_maxGasAmount, gasCommittedForNextCycle); if (_automationFeeCapForCycle < estimatedAutomationFeeForCycle) { revert InsufficientFeeCapForCycle(); } taskDurationCap = s.activeConfig.taskDurationCapSecs; @@ -534,142 +154,4 @@ library LibRegistry { s.registryState.sysGasCommittedForNextCycle = gasCommitted; } } - - /// @notice Helper function to validate the task duration. - function validateTaskDuration( - uint64 _regTime, - uint64 _expiryTime, - uint64 _taskDurationCap, - uint64 _cycleEndTime - ) private pure { - if (_expiryTime <= _regTime) { revert InvalidExpiryTime(); } - - uint64 taskDuration = _expiryTime - _regTime; - if (taskDuration > _taskDurationCap) { revert InvalidTaskDuration(); } - - if ( _expiryTime <= _cycleEndTime) { revert TaskExpiresBeforeNextCycle(); } - } - - /// @notice Helper function to validate the inputs while registering a task. - function validateInputs(bytes memory _payloadTx, uint128 _maxGasAmount) private view { - ( , address payloadTarget, , ) = abi.decode(_payloadTx, (uint128, address, bytes, LibUtils.AccessListEntry[])); - payloadTarget.validateContractAddress(); - - if (_maxGasAmount == 0) { revert InvalidMaxGasAmount(); } - } - - /// @notice Estimates automation fee the next cycle for specified task occupancy for the configured cycle interval - /// referencing the current automation registry fee parameters, specified total/committed occupancy and registry - /// maximum allowed occupancy for the next cycle. - /// Note it is expected that committed_occupancy does not include current task's occupancy. - function estimateAutomationFeeWithCommittedOccupancyInternal( - uint128 _taskOccupancy, - uint128 _committedOccupancy - ) internal view returns (uint128) { - AppStorage storage s = LibAppStorage.appStorage(); - - uint128 totalCommittedGas = _taskOccupancy + _committedOccupancy; - - uint128 automationFeePerSec = calculateAutomationFeeMultiplierForCycle( - totalCommittedGas, - s.registryState.nextCycleRegistryMaxGasCap, - s.activeConfig.automationBaseFeeWeiPerSec - ); - - if (automationFeePerSec == 0) return 0; - return calculateAutomationFeeForInterval(s.durationSecs, _taskOccupancy, automationFeePerSec, s.registryState.nextCycleRegistryMaxGasCap); - } - - function updateGasCommittedForNextCycle(LibUtils.TaskType _taskType, uint128 _maxGasAmount) external { - AppStorage storage s = LibAppStorage.appStorage(); - - bool isUST = _taskType == LibUtils.TaskType.UST; - - uint128 gasCommittedForNextCycle = isUST ? s.registryState.gasCommittedForNextCycle : s.registryState.sysGasCommittedForNextCycle; - if (gasCommittedForNextCycle < _maxGasAmount) { revert GasCommittedValueUnderflow(); } - - // Adjust the gas committed for the next cycle by subtracting the gas amount of the cancelled/stopped task - if (isUST) { - s.registryState.gasCommittedForNextCycle = gasCommittedForNextCycle - _maxGasAmount; - } else { - s.registryState.sysGasCommittedForNextCycle = gasCommittedForNextCycle - _maxGasAmount; - } - } - - /// @notice Helper function to unlock locked deposit and cycle fees when stopTasks is called. - function unlockDepositAndCycleFee( - uint64 _taskIndex, - LibUtils.TaskState _taskState, - uint64 _expiryTime, - uint128 _maxGasAmount, - uint64 _residualInterval, - uint64 _currentTime, - uint128 _depositFee - ) internal returns (uint128, uint128) { - AppStorage storage s = LibAppStorage.appStorage(); - - uint128 cycleFeeRefund; - uint128 depositRefund; - - if (_taskState != LibUtils.TaskState.PENDING) { - // Compute the automation fee multiplier for cycle - uint128 automationFeePerSec = calculateAutomationFeeMultiplierForCycle( - s.registryState.gasCommittedForThisCycle, - s.activeConfig.registryMaxGasCap, - s.activeConfig.automationBaseFeeWeiPerSec - ); - - uint128 taskFee = calculateTaskFee( - _taskState, - _expiryTime, - _maxGasAmount, - _residualInterval, - _currentTime, - automationFeePerSec - ); - - // Refund full deposit and the half of the remaining run-time fee when task is active or cancelled stage - cycleFeeRefund = taskFee / REFUND_FRACTION; - depositRefund = _depositFee; - } else { - cycleFeeRefund = 0; - depositRefund = _depositFee / REFUND_FRACTION; - } - - bool result = safeUnlockLockedDeposit(_taskIndex, _depositFee); - if (!result) { revert ErrorDepositRefund(); } - - (bool hasLockedFee, uint256 remainingCycleLockedFees ) = safeUnlockLockedCycleFee(s.registryState.cycleLockedFees, uint64(cycleFeeRefund), _taskIndex); - if (!hasLockedFee) { revert ErrorCycleFeeRefund(); } - - s.registryState.cycleLockedFees = remainingCycleLockedFees; - - return (cycleFeeRefund, depositRefund); - } - - // :::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::: HELPER FUNCTIONS :::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::: - - /// @notice Function to remove a task from the registry. - /// @param _taskIndex Index of the task to remove. - /// @param _owner Address of the task owner. - /// @param _removeFromSysReg Wheather to remove from system task registry. - function removeTask(uint64 _taskIndex, address _owner, bool _removeFromSysReg) internal { - AppStorage storage s = LibAppStorage.appStorage(); - - if (_removeFromSysReg) { - require(s.registryState.sysTaskIds.remove(_taskIndex), TaskIndexNotFound()); - } - - delete s.registryState.tasks[_taskIndex]; - require(s.registryState.taskIdList.remove(_taskIndex), TaskIndexNotFound()); - require(s.registryState.userTasks[_owner].remove(_taskIndex), TaskIndexNotFound()); - } - - /// @notice Read tx hash via precompile. Reverts if precompile missing/fails. - function readTxHash() internal view returns (bytes32) { - (bool ok, bytes memory out) = TX_HASH_PRECOMPILE.staticcall(""); - require(ok, FailedToCallTxHashPrecompile()); - require(out.length == 32, TxnHashLengthShouldBe32(uint64(out.length))); - return abi.decode(out, (bytes32)); - } } diff --git a/solidity/supra_contracts/src/libraries/LibUtils.sol b/solidity/supra_contracts/src/libraries/LibUtils.sol index ae256dc18d..bcf023e6db 100644 --- a/solidity/supra_contracts/src/libraries/LibUtils.sol +++ b/solidity/supra_contracts/src/libraries/LibUtils.sol @@ -7,70 +7,10 @@ library LibUtils { // Custom errors error AddressCannotBeEOA(); error AddressCannotBeZero(); - error InvalidTaskDuration(); - error InvalidRegistryMaxGasCap(); - error InvalidCongestionThreshold(); - error InvalidCongestionExponent(); - error InvalidTaskCapacity(); - error InvalidCycleDuration(); - error InvalidSysTaskDuration(); - error InvalidSysRegistryMaxGasCap(); - error InvalidSysTaskCapacity(); // Address of the VM Signer: SUP0 address constant VM_SIGNER = address(0x53555000); - /// @notice Enum describing state of the cycle. - enum CycleState { - READY, - STARTED, - FINISHED, - SUSPENDED - } - - /// @notice Enum describing state of a task. - enum TaskState { - PENDING, - ACTIVE, - CANCELLED - } - - /// @notice Enum describing task type. - enum TaskType { - UST, - GST - } - - /// @notice Represents intermediate state of the registry on cycle change. - struct IntermediateStateOfCycleChange { - uint256 cycleLockedFees; - uint128 gasCommittedForNextCycle; - uint128 sysGasCommittedForNextCycle; - uint64[] removedTasks; - } - - /// @notice Struct representing transition result. - struct TransitionResult { - uint128 fees; - uint128 gas; - uint128 sysGas; - bool isRemoved; - } - - /// @notice Struct representing a stopped task. - struct TaskStopped { - uint64 taskIndex; - uint128 depositRefund; - uint128 cycleFeeRefund; - bytes32 txHash; - } - - /// @notice Struct representing an entry in access list. - struct AccessListEntry { - address addr; - bytes32[] storageKeys; - } - /// @dev Returns a boolean indicating whether the given address is a contract or not. /// @param _addr The address to be checked. /// @return A boolean indicating whether the given address is a contract or not. @@ -102,61 +42,4 @@ library LibUtils { uint160 addr = uint160(_addr); return addr >= uint160(VM_SIGNER) && addr <= uint160(0x535550FF); } - - /// @notice Helper function to validate the registry configuration parameters. - function validateConfigParameters( - uint64 _taskDurationCapSecs, - uint128 _registryMaxGasCap, - uint8 _congestionThresholdPercentage, - uint8 _congestionExponent, - uint16 _taskCapacity, - uint64 _cycleDurationSecs, - uint64 _sysTaskDurationCapSecs, - uint128 _sysRegistryMaxGasCap, - uint16 _sysTaskCapacity - ) internal pure { - if (_taskDurationCapSecs <= _cycleDurationSecs) { revert InvalidTaskDuration(); } - if (_registryMaxGasCap == 0) { revert InvalidRegistryMaxGasCap(); } - if (_congestionThresholdPercentage > 100) { revert InvalidCongestionThreshold(); } - if (_congestionExponent == 0) { revert InvalidCongestionExponent(); } - if (_taskCapacity == 0) { revert InvalidTaskCapacity(); } - if (_cycleDurationSecs == 0) { revert InvalidCycleDuration(); } - if (_sysTaskDurationCapSecs <= _cycleDurationSecs) { revert InvalidSysTaskDuration(); } - if (_sysRegistryMaxGasCap == 0) { revert InvalidSysRegistryMaxGasCap(); } - if (_sysTaskCapacity == 0) { revert InvalidSysTaskCapacity(); } - } - - /// @notice Helper function to sort an array. - /// @param arr Input array to sort. - /// @return Returns the sorted array. - function sortUint64(uint64[] memory arr) internal pure returns (uint64[] memory) { - uint256 length = arr.length; - for (uint256 i = 0; i < length; i++) { - for (uint256 j = 0; j < length - 1; j++) { - if (arr[j] > arr[j + 1]) { - uint64 temp = arr[j]; - arr[j] = arr[j + 1]; - arr[j + 1] = temp; - } - } - } - return arr; - } - - /// @notice Helper function to sort an array. - /// @param arr Input array to sort. - /// @return Returns the sorted array. - function sortUint256(uint256[] memory arr) internal pure returns (uint256[] memory) { - uint256 length = arr.length; - for (uint256 i = 0; i < length; i++) { - for (uint256 j = 0; j < length - 1; j++) { - if (arr[j] > arr[j + 1]) { - uint256 temp = arr[j]; - arr[j] = arr[j + 1]; - arr[j + 1] = temp; - } - } - } - return arr; - } } diff --git a/solidity/supra_contracts/src/upgradeInitializers/DiamondInit.sol b/solidity/supra_contracts/src/upgradeInitializers/DiamondInit.sol index 2773317e83..6df69115ac 100644 --- a/solidity/supra_contracts/src/upgradeInitializers/DiamondInit.sol +++ b/solidity/supra_contracts/src/upgradeInitializers/DiamondInit.sol @@ -15,6 +15,7 @@ import { IERC173 } from "../interfaces/IERC173.sol"; import { IERC165 } from "../interfaces/IERC165.sol"; import { AppStorage, Config } from "../libraries/LibAppStorage.sol"; +import { LibCommon } from "../libraries/LibCommon.sol"; import { LibUtils } from "../libraries/LibUtils.sol"; /// @title DiamondInit @@ -81,7 +82,7 @@ contract DiamondInit { ds.supportedInterfaces[type(IERC173).interfaceId] = true; - LibUtils.validateConfigParameters( + LibCommon.validateConfigParameters( _taskDurationCapSecs, _registryMaxGasCap, _congestionThresholdPercentage, @@ -124,11 +125,11 @@ contract DiamondInit { // Cycle initialization // --------------------------------------------------------------------- ( - LibUtils.CycleState cycleState, + LibCommon.CycleState cycleState, uint64 cycleIndex ) = _automationEnabled - ? (LibUtils.CycleState.STARTED, 1) - : (LibUtils.CycleState.READY, 0); + ? (LibCommon.CycleState.STARTED, 1) + : (LibCommon.CycleState.READY, 0); s.index = cycleIndex; s.startTime = uint64(block.timestamp); diff --git a/solidity/supra_contracts/test/BaseDiamondTest.t.sol b/solidity/supra_contracts/test/BaseDiamondTest.t.sol index 22fa253bbb..5ed3ceac6a 100644 --- a/solidity/supra_contracts/test/BaseDiamondTest.t.sol +++ b/solidity/supra_contracts/test/BaseDiamondTest.t.sol @@ -3,15 +3,14 @@ pragma solidity 0.8.27; import {Test} from "forge-std/Test.sol"; import {ERC20Supra} from "../src/ERC20Supra.sol"; -import {Diamond} from "../src/Diamond.sol"; import {IConfigFacet} from "../src/interfaces/IConfigFacet.sol"; import {IRegistryFacet} from "../src/interfaces/IRegistryFacet.sol"; import {Deployment, InitParams, LibDiamondUtils} from "../src/libraries/LibDiamondUtils.sol"; +import {LibCommon} from "../src/libraries/LibCommon.sol"; import {LibUtils} from "../src/libraries/LibUtils.sol"; abstract contract BaseDiamondTest is Test { ERC20Supra erc20Supra; // ERC20Supra contract - Diamond diamond; // Diamond instance address diamondAddr; // Diamond address InitParams defaultParams; // Default initialization parameters @@ -36,8 +35,6 @@ abstract contract BaseDiamondTest is Test { defaultParams = LibDiamondUtils.defaultInitParams(); deployment = LibDiamondUtils.deploy(admin); LibDiamondUtils.executeCut(LibUtils.VM_SIGNER, address(erc20Supra), defaultParams, deployment); - - diamond = Diamond(payable(deployment.diamond)); diamondAddr = deployment.diamond; IConfigFacet(diamondAddr).grantAuthorization(bob); @@ -76,18 +73,18 @@ abstract contract BaseDiamondTest is Test { /// @param _value Value to be sent along with the transaction. /// @param _target Address of the destination smart contract. function createPayload(uint128 _value, address _target) internal pure returns (bytes memory) { - LibUtils.AccessListEntry[] memory accessList = new LibUtils.AccessListEntry[](2); + LibCommon.AccessListEntry[] memory accessList = new LibCommon.AccessListEntry[](2); bytes32[] memory keys = new bytes32[](2); keys[0] = bytes32(uint256(0)); keys[1] = bytes32(uint256(1)); - accessList[0] = LibUtils.AccessListEntry({ + accessList[0] = LibCommon.AccessListEntry({ addr: address(0x1111), storageKeys: keys }); - accessList[1] = LibUtils.AccessListEntry({ + accessList[1] = LibCommon.AccessListEntry({ addr: address(0x2222), storageKeys: keys }); diff --git a/solidity/supra_contracts/test/CoreFacet.t.sol b/solidity/supra_contracts/test/CoreFacet.t.sol index 788faca320..90fa195038 100644 --- a/solidity/supra_contracts/test/CoreFacet.t.sol +++ b/solidity/supra_contracts/test/CoreFacet.t.sol @@ -4,6 +4,7 @@ pragma solidity 0.8.27; import {BaseDiamondTest} from "./BaseDiamondTest.t.sol"; import {IRegistryFacet} from "../src/interfaces/IRegistryFacet.sol"; import {ICoreFacet} from "../src/interfaces/ICoreFacet.sol"; +import {LibCommon} from "../src/libraries/LibCommon.sol"; import {LibUtils} from "../src/libraries/LibUtils.sol"; import {LibCore} from "../src/libraries/LibCore.sol"; import {Deployment, InitParams, LibDiamondUtils} from "../src/libraries/LibDiamondUtils.sol"; @@ -20,12 +21,12 @@ contract CoreFacetTest is BaseDiamondTest { /// @dev Test to ensure 'monitorCycleEnd' does nothing before cycle expiry. function testMonitorCycleEndDoesNothingBeforeCycleExpiry() public { - (uint64 indexBefore, uint64 startBefore, uint64 durationBefore, LibUtils.CycleState stateBefore) = ICoreFacet(diamondAddr).getCycleInfo(); + (uint64 indexBefore, uint64 startBefore, uint64 durationBefore, LibCommon.CycleState stateBefore) = ICoreFacet(diamondAddr).getCycleInfo(); vm.prank(LibUtils.VM_SIGNER, LibUtils.VM_SIGNER); ICoreFacet(diamondAddr).monitorCycleEnd(); - (uint64 indexAfter, uint64 startAfter, uint64 durationAfter, LibUtils.CycleState stateAfter) = ICoreFacet(diamondAddr).getCycleInfo(); + (uint64 indexAfter, uint64 startAfter, uint64 durationAfter, LibCommon.CycleState stateAfter) = ICoreFacet(diamondAddr).getCycleInfo(); assertEq(indexAfter, indexBefore); assertEq(startAfter, startBefore); @@ -59,15 +60,15 @@ contract CoreFacetTest is BaseDiamondTest { address diamondAddr = deployment.diamond; vm.stopPrank(); - (uint64 indexBefore, uint64 startBefore, uint64 durationBefore, LibUtils.CycleState stateBefore) = ICoreFacet(diamondAddr).getCycleInfo(); - assertEq(uint8(stateBefore), uint8(LibUtils.CycleState.READY)); + (uint64 indexBefore, uint64 startBefore, uint64 durationBefore, LibCommon.CycleState stateBefore) = ICoreFacet(diamondAddr).getCycleInfo(); + assertEq(uint8(stateBefore), uint8(LibCommon.CycleState.READY)); vm.warp(startBefore + durationBefore); vm.prank(LibUtils.VM_SIGNER, LibUtils.VM_SIGNER); ICoreFacet(diamondAddr).monitorCycleEnd(); - (uint64 indexAfter, uint64 startAfter, uint64 durationAfter, LibUtils.CycleState stateAfter) = ICoreFacet(diamondAddr).getCycleInfo(); + (uint64 indexAfter, uint64 startAfter, uint64 durationAfter, LibCommon.CycleState stateAfter) = ICoreFacet(diamondAddr).getCycleInfo(); assertEq(indexAfter, indexBefore); assertEq(startAfter, startBefore); @@ -83,13 +84,13 @@ contract CoreFacetTest is BaseDiamondTest { assertFalse(ICoreFacet(diamondAddr).isAutomationEnabled()); - (uint64 indexBefore, uint64 startBefore, uint64 durationBefore, LibUtils.CycleState stateBefore) = ICoreFacet(diamondAddr).getCycleInfo(); + (uint64 indexBefore, uint64 startBefore, uint64 durationBefore, LibCommon.CycleState stateBefore) = ICoreFacet(diamondAddr).getCycleInfo(); vm.warp(startBefore + durationBefore); vm.expectEmit(true, true, false, true); emit ICoreFacet.AutomationCycleEvent( indexBefore, - LibUtils.CycleState.READY, + LibCommon.CycleState.READY, startBefore, durationBefore, stateBefore @@ -98,24 +99,24 @@ contract CoreFacetTest is BaseDiamondTest { vm.prank(LibUtils.VM_SIGNER, LibUtils.VM_SIGNER); ICoreFacet(diamondAddr).monitorCycleEnd(); - (uint64 indexAfter, uint64 startAfter, uint64 durationAfter, LibUtils.CycleState stateAfter) = ICoreFacet(diamondAddr).getCycleInfo(); + (uint64 indexAfter, uint64 startAfter, uint64 durationAfter, LibCommon.CycleState stateAfter) = ICoreFacet(diamondAddr).getCycleInfo(); assertEq(indexAfter, indexBefore); assertEq(startAfter, startBefore); assertEq(durationAfter, durationBefore); - assertEq(uint8(stateAfter), uint8(LibUtils.CycleState.READY)); + assertEq(uint8(stateAfter), uint8(LibCommon.CycleState.READY)); } /// @dev Test to ensure 'monitorCycleEnd' moves cycle state to STARTED if automation is enabled and no tasks exist. function testMonitorCycleEndWhenAutomationEnabledNoTasks() public { - (uint64 indexBefore, uint64 startBefore, uint64 durationBefore, LibUtils.CycleState stateBefore) = ICoreFacet(diamondAddr).getCycleInfo(); + (uint64 indexBefore, uint64 startBefore, uint64 durationBefore, LibCommon.CycleState stateBefore) = ICoreFacet(diamondAddr).getCycleInfo(); vm.warp(startBefore + durationBefore); vm.expectEmit(true, true, false, true); emit ICoreFacet.AutomationCycleEvent( indexBefore + 1, - LibUtils.CycleState.STARTED, + LibCommon.CycleState.STARTED, uint64(block.timestamp), durationBefore, stateBefore @@ -124,25 +125,25 @@ contract CoreFacetTest is BaseDiamondTest { vm.prank(LibUtils.VM_SIGNER, LibUtils.VM_SIGNER); ICoreFacet(diamondAddr).monitorCycleEnd(); - (uint64 indexAfter, uint64 startAfter, uint64 durationAfter, LibUtils.CycleState stateAfter) = ICoreFacet(diamondAddr).getCycleInfo(); + (uint64 indexAfter, uint64 startAfter, uint64 durationAfter, LibCommon.CycleState stateAfter) = ICoreFacet(diamondAddr).getCycleInfo(); assertEq(indexAfter, indexBefore + 1); assertEq(startAfter, block.timestamp); assertEq(durationAfter, durationBefore); - assertEq(uint8(stateAfter), uint8(LibUtils.CycleState.STARTED)); + assertEq(uint8(stateAfter), uint8(LibCommon.CycleState.STARTED)); } /// @dev Test to ensure 'monitorCycleEnd' moves cycle state to FINISHED if automation is enabled and tasks exist. function testMonitorCycleEndWhenAutomationEnabledAndTasksExist() public { registerUST(); - (uint64 indexBefore, uint64 startBefore, uint64 durationBefore, LibUtils.CycleState stateBefore) = ICoreFacet(diamondAddr).getCycleInfo(); + (uint64 indexBefore, uint64 startBefore, uint64 durationBefore, LibCommon.CycleState stateBefore) = ICoreFacet(diamondAddr).getCycleInfo(); vm.warp(startBefore + durationBefore); vm.expectEmit(true, true, false, true); emit ICoreFacet.AutomationCycleEvent( indexBefore, - LibUtils.CycleState.FINISHED, + LibCommon.CycleState.FINISHED, startBefore, durationBefore, stateBefore @@ -151,12 +152,12 @@ contract CoreFacetTest is BaseDiamondTest { vm.prank(LibUtils.VM_SIGNER, LibUtils.VM_SIGNER); ICoreFacet(diamondAddr).monitorCycleEnd(); - (uint64 indexAfter, uint64 startAfter, uint64 durationAfter, LibUtils.CycleState stateAfter) = ICoreFacet(diamondAddr).getCycleInfo(); + (uint64 indexAfter, uint64 startAfter, uint64 durationAfter, LibCommon.CycleState stateAfter) = ICoreFacet(diamondAddr).getCycleInfo(); assertEq(indexAfter, indexBefore); assertEq(startAfter, startBefore); assertEq(durationAfter, durationBefore); - assertEq(uint8(stateAfter), uint8(LibUtils.CycleState.FINISHED)); + assertEq(uint8(stateAfter), uint8(LibCommon.CycleState.FINISHED)); (uint64 refundDuration, uint128 automationFeePerSec) = ICoreFacet(diamondAddr).getTransitionInfo(); assertEq(refundDuration, 0); @@ -195,8 +196,8 @@ contract CoreFacetTest is BaseDiamondTest { vm.prank(LibUtils.VM_SIGNER, LibUtils.VM_SIGNER); ICoreFacet(diamondAddr).monitorCycleEnd(); - (uint64 index, , , LibUtils.CycleState state) = ICoreFacet(diamondAddr).getCycleInfo(); - assertEq(uint8(state), uint8(LibUtils.CycleState.FINISHED)); + (uint64 index, , , LibCommon.CycleState state) = ICoreFacet(diamondAddr).getCycleInfo(); + assertEq(uint8(state), uint8(LibCommon.CycleState.FINISHED)); uint64[] memory tasks = new uint64[](1); tasks[0] = 0; @@ -210,11 +211,11 @@ contract CoreFacetTest is BaseDiamondTest { vm.prank(LibUtils.VM_SIGNER, LibUtils.VM_SIGNER); ICoreFacet(diamondAddr).processTasks(index + 1, tasks); - (uint64 newIndex, uint64 newStart, uint64 newDuration, LibUtils.CycleState newState) = ICoreFacet(diamondAddr).getCycleInfo(); + (uint64 newIndex, uint64 newStart, uint64 newDuration, LibCommon.CycleState newState) = ICoreFacet(diamondAddr).getCycleInfo(); assertEq(newIndex, index + 1); assertEq(newStart, uint64(block.timestamp)); assertEq(newDuration, 2000); - assertEq(uint8(newState), uint8(LibUtils.CycleState.STARTED)); + assertEq(uint8(newState), uint8(LibCommon.CycleState.STARTED)); assertEq(IRegistryFacet(diamondAddr).getActiveTaskIds(), activeTasks); assertEq(IRegistryFacet(diamondAddr).getSystemGasCommittedForNextCycle(), 0); @@ -234,8 +235,8 @@ contract CoreFacetTest is BaseDiamondTest { vm.prank(LibUtils.VM_SIGNER, LibUtils.VM_SIGNER); ICoreFacet(diamondAddr).monitorCycleEnd(); - (uint64 index, , , LibUtils.CycleState state) = ICoreFacet(diamondAddr).getCycleInfo(); - assertEq(uint8(state), uint8(LibUtils.CycleState.FINISHED)); + (uint64 index, , , LibCommon.CycleState state) = ICoreFacet(diamondAddr).getCycleInfo(); + assertEq(uint8(state), uint8(LibCommon.CycleState.FINISHED)); uint64[] memory tasks = new uint64[](1); tasks[0] = 0; @@ -257,15 +258,15 @@ contract CoreFacetTest is BaseDiamondTest { vm.prank(LibUtils.VM_SIGNER, LibUtils.VM_SIGNER); ICoreFacet(diamondAddr).monitorCycleEnd(); - ( , , , LibUtils.CycleState stateBefore) = ICoreFacet(diamondAddr).getCycleInfo(); - assertEq(uint8(stateBefore), uint8(LibUtils.CycleState.FINISHED)); + ( , , , LibCommon.CycleState stateBefore) = ICoreFacet(diamondAddr).getCycleInfo(); + assertEq(uint8(stateBefore), uint8(LibCommon.CycleState.FINISHED)); // Disable automation → moves state to SUSPENDED vm.prank(admin); ICoreFacet(diamondAddr).disableAutomation(); - (uint64 indexAfter, , , LibUtils.CycleState stateAfter) = ICoreFacet(diamondAddr).getCycleInfo(); - assertEq(uint8(stateAfter), uint8(LibUtils.CycleState.SUSPENDED)); + (uint64 indexAfter, , , LibCommon.CycleState stateAfter) = ICoreFacet(diamondAddr).getCycleInfo(); + assertEq(uint8(stateAfter), uint8(LibCommon.CycleState.SUSPENDED)); uint64[] memory tasks = new uint64[](1); tasks[0] = 0; @@ -276,8 +277,8 @@ contract CoreFacetTest is BaseDiamondTest { vm.prank(LibUtils.VM_SIGNER, LibUtils.VM_SIGNER); ICoreFacet(diamondAddr).processTasks(indexAfter, tasks); - ( , , , LibUtils.CycleState newState) = ICoreFacet(diamondAddr).getCycleInfo(); - assertEq(uint8(newState), uint8(LibUtils.CycleState.READY)); + ( , , , LibCommon.CycleState newState) = ICoreFacet(diamondAddr).getCycleInfo(); + assertEq(uint8(newState), uint8(LibCommon.CycleState.READY)); assertFalse(IRegistryFacet(diamondAddr).ifTaskExists(tasks[0])); } @@ -292,15 +293,15 @@ contract CoreFacetTest is BaseDiamondTest { vm.prank(LibUtils.VM_SIGNER, LibUtils.VM_SIGNER); ICoreFacet(diamondAddr).monitorCycleEnd(); - ( , , , LibUtils.CycleState stateBefore) = ICoreFacet(diamondAddr).getCycleInfo(); - assertEq(uint8(stateBefore), uint8(LibUtils.CycleState.FINISHED)); + ( , , , LibCommon.CycleState stateBefore) = ICoreFacet(diamondAddr).getCycleInfo(); + assertEq(uint8(stateBefore), uint8(LibCommon.CycleState.FINISHED)); // Disable automation → moves state to SUSPENDED vm.prank(admin); ICoreFacet(diamondAddr).disableAutomation(); - (uint64 indexAfter, , , LibUtils.CycleState stateAfter) = ICoreFacet(diamondAddr).getCycleInfo(); - assertEq(uint8(stateAfter), uint8(LibUtils.CycleState.SUSPENDED)); + (uint64 indexAfter, , , LibCommon.CycleState stateAfter) = ICoreFacet(diamondAddr).getCycleInfo(); + assertEq(uint8(stateAfter), uint8(LibCommon.CycleState.SUSPENDED)); // Enable automation vm.prank(admin); @@ -315,11 +316,11 @@ contract CoreFacetTest is BaseDiamondTest { vm.prank(LibUtils.VM_SIGNER, LibUtils.VM_SIGNER); ICoreFacet(diamondAddr).processTasks(indexAfter, tasks); - (uint64 newIndex, uint64 newStart, uint64 newDuration, LibUtils.CycleState newState) = ICoreFacet(diamondAddr).getCycleInfo(); + (uint64 newIndex, uint64 newStart, uint64 newDuration, LibCommon.CycleState newState) = ICoreFacet(diamondAddr).getCycleInfo(); assertEq(newIndex, indexAfter + 1); assertEq(newStart, uint64(block.timestamp)); assertEq(newDuration, 2000); - assertEq(uint8(newState), uint8(LibUtils.CycleState.STARTED)); + assertEq(uint8(newState), uint8(LibCommon.CycleState.STARTED)); assertFalse(IRegistryFacet(diamondAddr).ifTaskExists(tasks[0])); } @@ -334,15 +335,15 @@ contract CoreFacetTest is BaseDiamondTest { vm.prank(LibUtils.VM_SIGNER, LibUtils.VM_SIGNER); ICoreFacet(diamondAddr).monitorCycleEnd(); - ( , , , LibUtils.CycleState stateBefore) = ICoreFacet(diamondAddr).getCycleInfo(); - assertEq(uint8(stateBefore), uint8(LibUtils.CycleState.FINISHED)); + ( , , , LibCommon.CycleState stateBefore) = ICoreFacet(diamondAddr).getCycleInfo(); + assertEq(uint8(stateBefore), uint8(LibCommon.CycleState.FINISHED)); // Disable automation → moves state to SUSPENDED vm.prank(admin); ICoreFacet(diamondAddr).disableAutomation(); - (uint64 indexAfter, , , LibUtils.CycleState stateAfter) = ICoreFacet(diamondAddr).getCycleInfo(); - assertEq(uint8(stateAfter), uint8(LibUtils.CycleState.SUSPENDED)); + (uint64 indexAfter, , , LibCommon.CycleState stateAfter) = ICoreFacet(diamondAddr).getCycleInfo(); + assertEq(uint8(stateAfter), uint8(LibCommon.CycleState.SUSPENDED)); uint64[] memory tasks = new uint64[](1); tasks[0] = 0; diff --git a/solidity/supra_contracts/test/DiamondInit.t.sol b/solidity/supra_contracts/test/DiamondInit.t.sol index fef76c5c3d..fba14af47c 100644 --- a/solidity/supra_contracts/test/DiamondInit.t.sol +++ b/solidity/supra_contracts/test/DiamondInit.t.sol @@ -3,12 +3,13 @@ pragma solidity 0.8.27; import {BaseDiamondTest} from "./BaseDiamondTest.t.sol"; import {OwnershipFacet} from "../src/facets/OwnershipFacet.sol"; -import {ConfigFacet} from "../src/facets/ConfigFacet.sol"; -import {RegistryFacet} from "../src/facets/RegistryFacet.sol"; -import {CoreFacet} from "../src/facets/CoreFacet.sol"; +import {LibCommon} from "../src/libraries/LibCommon.sol"; import {LibUtils} from "../src/libraries/LibUtils.sol"; import {Config} from "../src/libraries/LibAppStorage.sol"; import {Deployment, InitParams, LibDiamondUtils} from "../src/libraries/LibDiamondUtils.sol"; +import {IConfigFacet} from "../src/interfaces/IConfigFacet.sol"; +import {IRegistryFacet} from "../src/interfaces/IRegistryFacet.sol"; +import {ICoreFacet} from "../src/interfaces/ICoreFacet.sol"; import {IDiamondCut} from "../src/interfaces/IDiamondCut.sol"; import {IDiamondLoupe} from "../src/interfaces/IDiamondLoupe.sol"; import {IERC173} from "../src/interfaces/IERC173.sol"; @@ -21,20 +22,20 @@ contract DiamondInitTest is BaseDiamondTest { function testInitialize() public view { assertEq(OwnershipFacet(diamondAddr).owner(), admin); - (uint64 index, uint64 startTime, uint64 durationSecs, LibUtils.CycleState state) = CoreFacet(diamondAddr).getCycleInfo(); + (uint64 index, uint64 startTime, uint64 durationSecs, LibCommon.CycleState state) = ICoreFacet(diamondAddr).getCycleInfo(); assertEq(index, 1); assertEq(startTime, block.timestamp); assertEq(durationSecs, 2000); - assertEq(uint8(state), uint8(LibUtils.CycleState.STARTED)); + assertEq(uint8(state), uint8(LibCommon.CycleState.STARTED)); - assertEq(RegistryFacet(diamondAddr).getNextCycleRegistryMaxGasCap(), 10_000_000); - assertEq(RegistryFacet(diamondAddr).getNextCycleSysRegistryMaxGasCap(), 5_000_000); - assertTrue(ConfigFacet(diamondAddr).isRegistrationEnabled()); - assertTrue(CoreFacet(diamondAddr).isAutomationEnabled()); - assertEq(ConfigFacet(diamondAddr).getVmSigner(), LibUtils.VM_SIGNER); - assertEq(ConfigFacet(diamondAddr).erc20Supra(), address(erc20Supra)); + assertEq(IRegistryFacet(diamondAddr).getNextCycleRegistryMaxGasCap(), 10_000_000); + assertEq(IRegistryFacet(diamondAddr).getNextCycleSysRegistryMaxGasCap(), 5_000_000); + assertTrue(IConfigFacet(diamondAddr).isRegistrationEnabled()); + assertTrue(ICoreFacet(diamondAddr).isAutomationEnabled()); + assertEq(IConfigFacet(diamondAddr).getVmSigner(), LibUtils.VM_SIGNER); + assertEq(IConfigFacet(diamondAddr).erc20Supra(), address(erc20Supra)); - Config memory config = ConfigFacet(diamondAddr).getConfig(); + Config memory config = IConfigFacet(diamondAddr).getConfig(); assertEq(config.registryMaxGasCap, 10_000_000); assertEq(config.sysRegistryMaxGasCap, 5_000_000); @@ -125,12 +126,12 @@ contract DiamondInitTest is BaseDiamondTest { /// @dev Test to ensure 'facetAddress' points to correct facet for a selector. function testSelectorRouting() public view { assertEq( - IDiamondLoupe(diamondAddr).facetAddress(RegistryFacet.register.selector), + IDiamondLoupe(diamondAddr).facetAddress(IRegistryFacet.register.selector), deployment.registryFacet ); assertEq( - IDiamondLoupe(diamondAddr).facetAddress(CoreFacet.enableAutomation.selector), + IDiamondLoupe(diamondAddr).facetAddress(ICoreFacet.enableAutomation.selector), deployment.coreFacet ); @@ -159,8 +160,8 @@ contract DiamondInitTest is BaseDiamondTest { /// @dev Test to ensure 'diamondCut' reverts if caller is not owner. function testDiamondCutRevertsIfNotOwner() public { bytes4[] memory selectors = new bytes4[](2); - selectors[0] = RegistryFacet.register.selector; - selectors[1] = RegistryFacet.registerSystemTask.selector; + selectors[0] = IRegistryFacet.register.selector; + selectors[1] = IRegistryFacet.registerSystemTask.selector; IDiamondCut.FacetCut[] memory cut = new IDiamondCut.FacetCut[](1); cut[0] = IDiamondCut.FacetCut({ @@ -266,7 +267,7 @@ contract DiamondInitTest is BaseDiamondTest { uint256 numSelectorsBefore = IDiamondLoupe(diamondAddr).facetFunctionSelectors(deployment.registryFacet).length; bytes4[] memory selectors = new bytes4[](1); - selectors[0] = RegistryFacet.cancelTask.selector; + selectors[0] = IRegistryFacet.cancelTask.selector; IDiamondCut.FacetCut[] memory cut = new IDiamondCut.FacetCut[](1); cut[0] = IDiamondCut.FacetCut({ @@ -279,7 +280,7 @@ contract DiamondInitTest is BaseDiamondTest { IDiamondCut(diamondAddr).diamondCut(cut, address(0), ""); // Verify selector mapping cleared - address facet = IDiamondLoupe(diamondAddr).facetAddress(RegistryFacet.cancelTask.selector); + address facet = IDiamondLoupe(diamondAddr).facetAddress(IRegistryFacet.cancelTask.selector); assertEq(facet, address(0)); uint256 numSelectorsAfter = IDiamondLoupe(diamondAddr).facetFunctionSelectors(deployment.registryFacet).length; @@ -287,7 +288,7 @@ contract DiamondInitTest is BaseDiamondTest { // Verify call now reverts vm.expectRevert(bytes("Diamond: Function does not exist")); - RegistryFacet(diamondAddr).cancelTask(0); + IRegistryFacet(diamondAddr).cancelTask(0); } /// @dev Test to ensure 'diamondCut' reverts if tried to remove a selector that doesn't exist. @@ -314,7 +315,7 @@ contract DiamondInitTest is BaseDiamondTest { MockRegistryFacet mockRegistryFacet = new MockRegistryFacet(); bytes4[] memory selectors = new bytes4[](1); - selectors[0] = ConfigFacet.getVmSigner.selector; + selectors[0] = IConfigFacet.getVmSigner.selector; IDiamondCut.FacetCut[] memory cut = new IDiamondCut.FacetCut[](1); cut[0] = IDiamondCut.FacetCut({ @@ -327,17 +328,17 @@ contract DiamondInitTest is BaseDiamondTest { IDiamondCut(diamondAddr).diamondCut(cut, address(0), ""); // Verify selector now points to mockRegistryFacet - address facet = IDiamondLoupe(diamondAddr).facetAddress(ConfigFacet.getVmSigner.selector); + address facet = IDiamondLoupe(diamondAddr).facetAddress(IConfigFacet.getVmSigner.selector); assertEq(facet, address(mockRegistryFacet)); // Verify logic changed - assertEq(ConfigFacet(diamondAddr).getVmSigner(), address(0x999)); + assertEq(IConfigFacet(diamondAddr).getVmSigner(), address(0x999)); } /// @dev Test to ensure replacing a selector with same facet address reverts. function testReplaceWithSameFacetReverts() public { bytes4[] memory selectors = new bytes4[](1); - selectors[0] = ConfigFacet.getVmSigner.selector; + selectors[0] = IConfigFacet.getVmSigner.selector; IDiamondCut.FacetCut[] memory cut = new IDiamondCut.FacetCut[](1); cut[0] = IDiamondCut.FacetCut({ @@ -410,7 +411,7 @@ contract DiamondInitTest is BaseDiamondTest { automationEnabled: true }); - vm.expectRevert(LibUtils.InvalidTaskDuration.selector); + vm.expectRevert(LibCommon.InvalidTaskDuration.selector); LibDiamondUtils.executeCut(LibUtils.VM_SIGNER, address(erc20Supra), initParams, deployment); vm.stopPrank(); @@ -438,7 +439,7 @@ contract DiamondInitTest is BaseDiamondTest { automationEnabled: true }); - vm.expectRevert(LibUtils.InvalidRegistryMaxGasCap.selector); + vm.expectRevert(LibCommon.InvalidRegistryMaxGasCap.selector); LibDiamondUtils.executeCut(LibUtils.VM_SIGNER, address(erc20Supra), initParams, deployment); vm.stopPrank(); @@ -466,7 +467,7 @@ contract DiamondInitTest is BaseDiamondTest { automationEnabled: true }); - vm.expectRevert(LibUtils.InvalidCongestionThreshold.selector); + vm.expectRevert(LibCommon.InvalidCongestionThreshold.selector); LibDiamondUtils.executeCut(LibUtils.VM_SIGNER, address(erc20Supra), initParams, deployment); vm.stopPrank(); @@ -494,7 +495,7 @@ contract DiamondInitTest is BaseDiamondTest { automationEnabled: true }); - vm.expectRevert(LibUtils.InvalidCongestionExponent.selector); + vm.expectRevert(LibCommon.InvalidCongestionExponent.selector); LibDiamondUtils.executeCut(LibUtils.VM_SIGNER, address(erc20Supra), initParams, deployment); vm.stopPrank(); @@ -522,7 +523,7 @@ contract DiamondInitTest is BaseDiamondTest { automationEnabled: true }); - vm.expectRevert(LibUtils.InvalidTaskCapacity.selector); + vm.expectRevert(LibCommon.InvalidTaskCapacity.selector); LibDiamondUtils.executeCut(LibUtils.VM_SIGNER, address(erc20Supra), initParams, deployment); vm.stopPrank(); @@ -550,7 +551,7 @@ contract DiamondInitTest is BaseDiamondTest { automationEnabled: true }); - vm.expectRevert(LibUtils.InvalidCycleDuration.selector); + vm.expectRevert(LibCommon.InvalidCycleDuration.selector); LibDiamondUtils.executeCut(LibUtils.VM_SIGNER, address(erc20Supra), initParams, deployment); vm.stopPrank(); @@ -578,7 +579,7 @@ contract DiamondInitTest is BaseDiamondTest { automationEnabled: true }); - vm.expectRevert(LibUtils.InvalidSysTaskDuration.selector); + vm.expectRevert(LibCommon.InvalidSysTaskDuration.selector); LibDiamondUtils.executeCut(LibUtils.VM_SIGNER, address(erc20Supra), initParams, deployment); vm.stopPrank(); @@ -606,7 +607,7 @@ contract DiamondInitTest is BaseDiamondTest { automationEnabled: true }); - vm.expectRevert(LibUtils.InvalidSysRegistryMaxGasCap.selector); + vm.expectRevert(LibCommon.InvalidSysRegistryMaxGasCap.selector); LibDiamondUtils.executeCut(LibUtils.VM_SIGNER, address(erc20Supra), initParams, deployment); vm.stopPrank(); @@ -634,7 +635,7 @@ contract DiamondInitTest is BaseDiamondTest { automationEnabled: true }); - vm.expectRevert(LibUtils.InvalidSysTaskCapacity.selector); + vm.expectRevert(LibCommon.InvalidSysTaskCapacity.selector); LibDiamondUtils.executeCut(LibUtils.VM_SIGNER, address(erc20Supra), initParams, deployment); vm.stopPrank(); diff --git a/solidity/supra_contracts/test/RegistryFacet.t.sol b/solidity/supra_contracts/test/RegistryFacet.t.sol index 483e9a236a..c8cf76bb7d 100644 --- a/solidity/supra_contracts/test/RegistryFacet.t.sol +++ b/solidity/supra_contracts/test/RegistryFacet.t.sol @@ -5,6 +5,7 @@ import {BaseDiamondTest} from "./BaseDiamondTest.t.sol"; import {IConfigFacet} from "../src/interfaces/IConfigFacet.sol"; import {ICoreFacet} from "../src/interfaces/ICoreFacet.sol"; import {IRegistryFacet} from "../src/interfaces/IRegistryFacet.sol"; +import {LibCommon} from "../src/libraries/LibCommon.sol"; import {LibUtils} from "../src/libraries/LibUtils.sol"; import {LibRegistry} from "../src/libraries/LibRegistry.sol"; import {TaskMetadata} from "../src/libraries/LibAppStorage.sol"; @@ -300,8 +301,8 @@ contract RegistryFacetTest is BaseDiamondTest { expiryTime: uint64(block.timestamp + 2250), priority: 0, owner: alice, - taskType: LibUtils.TaskType.UST, - taskState: LibUtils.TaskState.PENDING, + taskType: LibCommon.TaskType.UST, + taskState: LibCommon.TaskState.PENDING, payloadTx: payload, auxData: auxData }); @@ -473,8 +474,8 @@ contract RegistryFacetTest is BaseDiamondTest { expiryTime: uint64(block.timestamp + 2250), priority: 2, owner: bob, - taskType: LibUtils.TaskType.GST, - taskState: LibUtils.TaskState.PENDING, + taskType: LibCommon.TaskType.GST, + taskState: LibCommon.TaskState.PENDING, payloadTx: payload, auxData: auxData }); @@ -727,8 +728,8 @@ contract RegistryFacetTest is BaseDiamondTest { ICoreFacet(diamondAddr).processTasks(2, taskIndexes); vm.stopPrank(); - LibUtils.TaskStopped[] memory stoppedTasks = new LibUtils.TaskStopped[](1); - stoppedTasks[0] = LibUtils.TaskStopped(0, 0.5 ether, 0.01245 ether, keccak256("txHash")); + LibCommon.TaskStopped[] memory stoppedTasks = new LibCommon.TaskStopped[](1); + stoppedTasks[0] = LibCommon.TaskStopped(0, 0.5 ether, 0.01245 ether, keccak256("txHash")); vm.expectEmit(true, true, false, false); emit IRegistryFacet.TasksStopped(stoppedTasks, alice); @@ -839,8 +840,8 @@ contract RegistryFacetTest is BaseDiamondTest { vm.prank(LibUtils.VM_SIGNER); ICoreFacet(diamondAddr).processTasks(2, taskIndexes); - LibUtils.TaskStopped[] memory stoppedTasks = new LibUtils.TaskStopped[](1); - stoppedTasks[0] = LibUtils.TaskStopped(0, 0, 0, keccak256("txHash")); + LibCommon.TaskStopped[] memory stoppedTasks = new LibCommon.TaskStopped[](1); + stoppedTasks[0] = LibCommon.TaskStopped(0, 0, 0, keccak256("txHash")); vm.expectEmit(true, true, false, false); emit IRegistryFacet.TasksStopped(stoppedTasks, bob); From cc734041696d4c868e888dd44cadbbd3060b8db7 Mon Sep 17 00:00:00 2001 From: Udit Yadav Date: Thu, 19 Feb 2026 15:35:25 +0530 Subject: [PATCH 6/8] fix to remove task if insufficient allowance while bookkeeping --- .../src/facets/RegistryFacet.sol | 5 ++++- .../src/interfaces/ICoreFacet.sol | 19 ++++++++++--------- .../src/interfaces/IRegistryFacet.sol | 1 + .../src/libraries/LibAccounting.sol | 8 -------- .../supra_contracts/src/libraries/LibCore.sol | 18 +++++++++++++----- 5 files changed, 28 insertions(+), 23 deletions(-) diff --git a/solidity/supra_contracts/src/facets/RegistryFacet.sol b/solidity/supra_contracts/src/facets/RegistryFacet.sol index b2582073b2..64f9165547 100644 --- a/solidity/supra_contracts/src/facets/RegistryFacet.sol +++ b/solidity/supra_contracts/src/facets/RegistryFacet.sol @@ -7,6 +7,7 @@ import {LibAccounting} from "../libraries/LibAccounting.sol"; import {LibCommon} from "../libraries/LibCommon.sol"; import {LibRegistry} from "../libraries/LibRegistry.sol"; import {IRegistryFacet} from "../interfaces/IRegistryFacet.sol"; +import {IERC20} from "../../lib/openzeppelin-contracts/contracts/token/ERC20/IERC20.sol"; contract RegistryFacet is IRegistryFacet { using EnumerableSet for *; @@ -73,7 +74,9 @@ contract RegistryFacet is IRegistryFacet { uint128 flatRegistrationFee = s.activeConfig.flatRegistrationFeeWei; uint128 fee = flatRegistrationFee + _automationFeeCapForCycle; - LibAccounting.chargeFees(msg.sender, fee); + + bool sent = IERC20(s.erc20Supra).transferFrom(msg.sender, address(this), fee); + if (!sent) { revert TransferFailed(); } emit TaskRegistered(taskIndex, msg.sender, flatRegistrationFee, _automationFeeCapForCycle, s.registryState.tasks[taskIndex]); } diff --git a/solidity/supra_contracts/src/interfaces/ICoreFacet.sol b/solidity/supra_contracts/src/interfaces/ICoreFacet.sol index f8c1214088..16791cd8f1 100644 --- a/solidity/supra_contracts/src/interfaces/ICoreFacet.sol +++ b/solidity/supra_contracts/src/interfaces/ICoreFacet.sol @@ -32,24 +32,25 @@ interface ICoreFacet { event TaskCycleFeeWithdraw( uint64 indexed taskIndex, address indexed owner, - uint128 fee + uint128 indexed fee ); /// @notice Emitted when a task is removed as fee exceeds task's automation fee cap for the cycle. event TaskCancelledCapacitySurpassed( uint64 indexed taskIndex, - address indexed owner, - uint128 fee, - uint128 automationFeeCapForCycle, + address owner, + uint128 indexed fee, + uint128 indexed automationFeeCapForCycle, bytes32 registrationHash ); - /// @notice Emitted when a task is removed due to insufficient balance. - event TaskCancelledInsufficentBalance( + /// @notice Emitted when a task is removed due to insufficient balance or allowance. + event TaskCancelledInsufficentBalanceAllowance( uint64 indexed taskIndex, - address indexed owner, - uint128 fee, - uint256 balance, + address owner, + uint128 indexed fee, + uint256 indexed balance, + uint256 allowance, bytes32 registrationHash ); diff --git a/solidity/supra_contracts/src/interfaces/IRegistryFacet.sol b/solidity/supra_contracts/src/interfaces/IRegistryFacet.sol index c39b68cbec..e3e8b66e46 100644 --- a/solidity/supra_contracts/src/interfaces/IRegistryFacet.sol +++ b/solidity/supra_contracts/src/interfaces/IRegistryFacet.sol @@ -86,6 +86,7 @@ interface IRegistryFacet { error TaskIndexesCannotBeEmpty(); error TaskIndexNotFound(); error TaskIndexNotUnique(); + error TransferFailed(); error UnauthorizedAccount(); error UnsupportedTaskOperation(); diff --git a/solidity/supra_contracts/src/libraries/LibAccounting.sol b/solidity/supra_contracts/src/libraries/LibAccounting.sol index 034489ee4a..6297eb5245 100644 --- a/solidity/supra_contracts/src/libraries/LibAccounting.sol +++ b/solidity/supra_contracts/src/libraries/LibAccounting.sol @@ -451,12 +451,4 @@ library LibAccounting { s.activeConfig.registryMaxGasCap ); } - - /// @notice Helper function to charge fees from the user. - function chargeFees(address _from, uint256 _amount) internal { - AppStorage storage s = LibAppStorage.appStorage(); - - bool sent = IERC20(s.erc20Supra).transferFrom(_from, address(this), _amount); - if (!sent) { revert TransferFailed(); } - } } \ No newline at end of file diff --git a/solidity/supra_contracts/src/libraries/LibCore.sol b/solidity/supra_contracts/src/libraries/LibCore.sol index e2e9f99d03..281352c041 100644 --- a/solidity/supra_contracts/src/libraries/LibCore.sol +++ b/solidity/supra_contracts/src/libraries/LibCore.sol @@ -21,6 +21,7 @@ library LibCore { error InvalidRegistryState(); error OutOfOrderTaskProcessingRequest(); error TaskIndexNotFound(); + error TransferFailed(); // ::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::: PRIVATE FUNCTIONS ::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::: @@ -378,26 +379,33 @@ library LibCore { _regHash ); } else { - uint256 userBalance = IERC20(s.erc20Supra).balanceOf(_owner); - if (userBalance < _fee) { - // If the user does not have enough balance, remove the task, DON'T refund the locked deposit, but simply unlock it and emit an event. + address erc20Supra = s.erc20Supra; + uint256 userBalance = IERC20(erc20Supra).balanceOf(_owner); + uint256 allowance = IERC20(erc20Supra).allowance(_owner, address(this)); + + if (userBalance < _fee || allowance < _fee) { + // If the user hasn't granted enough allowance or if they don't have enough balance, remove the task. + // DON'T refund the locked deposit, but simply unlock it and emit an event. LibAccounting.safeUnlockLockedDeposit(_taskIndex, _lockedFeeForNextCycle); LibCommon.removeTask(_taskIndex, _owner, false); isRemoved = true; - emit ICoreFacet.TaskCancelledInsufficentBalance( + emit ICoreFacet.TaskCancelledInsufficentBalanceAllowance( _taskIndex, _owner, _fee, userBalance, + allowance, _regHash ); } else { if (_fee != 0) { // Charge the fee - LibAccounting.chargeFees(_owner, _fee); + bool sent = IERC20(erc20Supra).transferFrom(_owner, address(this), _fee); + if (!sent) { revert TransferFailed(); } + fees = _fee; } From 7a844ddb4ff3650ce06f8589e9e1b27f359b8c62 Mon Sep 17 00:00:00 2001 From: Udit Yadav Date: Fri, 20 Feb 2026 11:53:45 +0530 Subject: [PATCH 7/8] updated unlockDepositAndCycleFee --- .../supra_contracts/src/libraries/LibAccounting.sol | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/solidity/supra_contracts/src/libraries/LibAccounting.sol b/solidity/supra_contracts/src/libraries/LibAccounting.sol index 6297eb5245..1abfb73082 100644 --- a/solidity/supra_contracts/src/libraries/LibAccounting.sol +++ b/solidity/supra_contracts/src/libraries/LibAccounting.sol @@ -27,6 +27,7 @@ library LibAccounting { error ErrorCycleFeeRefund(); error ErrorDepositRefund(); error InsufficientBalanceForRefund(); + error InvalidCycleRefundFee(); error RegisteredTaskInvalidType(); error TransferFailed(); @@ -345,6 +346,7 @@ library LibAccounting { ) internal returns (uint128, uint128) { AppStorage storage s = LibAppStorage.appStorage(); + uint128 cycleLockedFeeForTask; uint128 cycleFeeRefund; uint128 depositRefund; @@ -356,7 +358,8 @@ library LibAccounting { s.activeConfig.automationBaseFeeWeiPerSec ); - uint128 taskFee = calculateTaskFee( + uint128 taskFeeForFullCycle = calculateAutomationFeeForInterval(s.durationSecs, _maxGasAmount, automationFeePerSec, s.activeConfig.registryMaxGasCap); + uint128 taskFeeForResidualTime = calculateTaskFee( _taskState, _expiryTime, _maxGasAmount, @@ -366,9 +369,11 @@ library LibAccounting { ); // Refund full deposit and the half of the remaining run-time fee when task is active or cancelled stage - cycleFeeRefund = taskFee / REFUND_FRACTION; + cycleLockedFeeForTask = taskFeeForFullCycle; + cycleFeeRefund = taskFeeForResidualTime / REFUND_FRACTION; depositRefund = _depositFee; } else { + cycleLockedFeeForTask = 0; cycleFeeRefund = 0; depositRefund = _depositFee / REFUND_FRACTION; } @@ -376,7 +381,9 @@ library LibAccounting { bool result = safeUnlockLockedDeposit(_taskIndex, _depositFee); if (!result) { revert ErrorDepositRefund(); } - (bool hasLockedFee, uint256 remainingCycleLockedFees ) = safeUnlockLockedCycleFee(s.registryState.cycleLockedFees, uint64(cycleFeeRefund), _taskIndex); + if (cycleLockedFeeForTask < cycleFeeRefund) { revert InvalidCycleRefundFee(); } + + (bool hasLockedFee, uint256 remainingCycleLockedFees ) = safeUnlockLockedCycleFee(s.registryState.cycleLockedFees, uint64(cycleLockedFeeForTask), _taskIndex); if (!hasLockedFee) { revert ErrorCycleFeeRefund(); } s.registryState.cycleLockedFees = remainingCycleLockedFees; From 35ae8f6a16f6ed8a3bf9da1465931026c0dfb936 Mon Sep 17 00:00:00 2001 From: Udit Yadav Date: Fri, 20 Feb 2026 13:42:03 +0530 Subject: [PATCH 8/8] updated Diamond to not receive native tokens --- solidity/supra_contracts/src/Diamond.sol | 6 ++---- solidity/supra_contracts/test/DiamondInit.t.sol | 7 +++++++ 2 files changed, 9 insertions(+), 4 deletions(-) diff --git a/solidity/supra_contracts/src/Diamond.sol b/solidity/supra_contracts/src/Diamond.sol index cf37ea950e..59bdd96247 100644 --- a/solidity/supra_contracts/src/Diamond.sol +++ b/solidity/supra_contracts/src/Diamond.sol @@ -13,7 +13,7 @@ import { IDiamondCut } from "./interfaces/IDiamondCut.sol"; contract Diamond { - constructor(address _contractOwner, address _diamondCutFacet) payable { + constructor(address _contractOwner, address _diamondCutFacet) { LibDiamond.setContractOwner(_contractOwner); // Add the diamondCut external function from the diamondCutFacet @@ -30,7 +30,7 @@ contract Diamond { // Find facet for function that is called and execute the // function if a facet is found and return any value. - fallback() external payable { + fallback() external { LibDiamond.DiamondStorage storage ds; bytes32 position = LibDiamond.DIAMOND_STORAGE_POSITION; // get diamond storage @@ -58,6 +58,4 @@ contract Diamond { } } } - - receive() external payable {} } diff --git a/solidity/supra_contracts/test/DiamondInit.t.sol b/solidity/supra_contracts/test/DiamondInit.t.sol index fba14af47c..9ee2d41357 100644 --- a/solidity/supra_contracts/test/DiamondInit.t.sol +++ b/solidity/supra_contracts/test/DiamondInit.t.sol @@ -89,6 +89,13 @@ contract DiamondInitTest is BaseDiamondTest { true ); } + + /// @dev Test to ensure Diamond reverts if native token is sent to it. + function testDiamondTxFailsIfNativeTokenIsSent() public { + vm.prank(alice); + (bool success, ) = diamondAddr.call{value: 1 ether}(""); + assertFalse(success); + } /// @dev Test to ensure Diamond reverts if an unknown selector is called. function testUnknownSelectorReverts() public {