From 270f976fa7f11f9b22a4b164ce37768c5d9292bf Mon Sep 17 00:00:00 2001 From: Udit Yadav Date: Wed, 10 Dec 2025 11:19:15 +0530 Subject: [PATCH 01/31] added smart contracts, libraries for automation registry --- .gitignore | 5 + solidity/automation_registry/README.md | 66 + solidity/automation_registry/foundry.lock | 11 + solidity/automation_registry/foundry.toml | 7 + solidity/automation_registry/lib/forge-std | 1 + .../lib/openzeppelin-contracts | 1 + .../lib/openzeppelin-contracts-upgradeable | 1 + .../script/DeployScript.s.sol | 16 + .../src/AutomationController.sol | 799 ++++++++ .../src/AutomationRegistry.sol | 1602 +++++++++++++++++ .../automation_registry/src/CommonUtils.sol | 99 + .../src/IAutomationController.sol | 31 + .../src/IAutomationRegistry.sol | 117 ++ .../automation_registry/src/LibController.sol | 224 +++ .../automation_registry/src/LibRegistry.sol | 556 ++++++ 15 files changed, 3536 insertions(+) create mode 100644 solidity/automation_registry/README.md create mode 100644 solidity/automation_registry/foundry.lock create mode 100644 solidity/automation_registry/foundry.toml create mode 160000 solidity/automation_registry/lib/forge-std create mode 160000 solidity/automation_registry/lib/openzeppelin-contracts create mode 160000 solidity/automation_registry/lib/openzeppelin-contracts-upgradeable create mode 100644 solidity/automation_registry/script/DeployScript.s.sol create mode 100644 solidity/automation_registry/src/AutomationController.sol create mode 100644 solidity/automation_registry/src/AutomationRegistry.sol create mode 100644 solidity/automation_registry/src/CommonUtils.sol create mode 100644 solidity/automation_registry/src/IAutomationController.sol create mode 100644 solidity/automation_registry/src/IAutomationRegistry.sol create mode 100644 solidity/automation_registry/src/LibController.sol create mode 100644 solidity/automation_registry/src/LibRegistry.sol diff --git a/.gitignore b/.gitignore index 10e3bcae4d..c1863291f5 100644 --- a/.gitignore +++ b/.gitignore @@ -6,6 +6,11 @@ target .vscode .idea pkg/ +cache +out +broadcast +.env +all-chain.json bins/revme/temp_folder bins/revme/tests diff --git a/solidity/automation_registry/README.md b/solidity/automation_registry/README.md new file mode 100644 index 0000000000..bb49667c59 --- /dev/null +++ b/solidity/automation_registry/README.md @@ -0,0 +1,66 @@ +## Supra EVM Automation Registry + +**This repository includes Supra EVM Automation Registry contract and related contracts.** + +Foundry consists of: + +- **Forge**: Ethereum testing framework (like Truffle, Hardhat and DappTools). +- **Cast**: Swiss army knife for interacting with EVM smart contracts, sending transactions and getting chain data. +- **Anvil**: Local Ethereum node, akin to Ganache, Hardhat Network. +- **Chisel**: Fast, utilitarian, and verbose solidity REPL. + +## Documentation + +https://book.getfoundry.sh/ + +## Usage + +### Build + +```shell +$ forge build +``` + +### Test + +```shell +$ forge test +``` + +### Format + +```shell +$ forge fmt +``` + +### Gas Snapshots + +```shell +$ forge snapshot +``` + +### Anvil + +```shell +$ anvil +``` + +### Deploy + +```shell +$ forge script script/Counter.s.sol:CounterScript --rpc-url --private-key +``` + +### Cast + +```shell +$ cast +``` + +### Help + +```shell +$ forge --help +$ anvil --help +$ cast --help +``` diff --git a/solidity/automation_registry/foundry.lock b/solidity/automation_registry/foundry.lock new file mode 100644 index 0000000000..caec3b5f1f --- /dev/null +++ b/solidity/automation_registry/foundry.lock @@ -0,0 +1,11 @@ +{ + "lib/forge-std": { + "rev": "ebc60f500bc6870baaf321a0196fddc24d6edb03" + }, + "lib/openzeppelin-contracts": { + "rev": "77bc5642a53d9c8eac8aec5c8ea9809a21d466cb" + }, + "lib/openzeppelin-contracts-upgradeable": { + "rev": "db5a20c719fdf4e40c1cef546ac8b2ef7175d53f" + } +} \ No newline at end of file diff --git a/solidity/automation_registry/foundry.toml b/solidity/automation_registry/foundry.toml new file mode 100644 index 0000000000..83816a210a --- /dev/null +++ b/solidity/automation_registry/foundry.toml @@ -0,0 +1,7 @@ +[profile.default] +src = "src" +out = "out" +libs = ["lib"] +via_ir = true + +# See more config options https://github.com/foundry-rs/foundry/blob/master/crates/config/README.md#all-options diff --git a/solidity/automation_registry/lib/forge-std b/solidity/automation_registry/lib/forge-std new file mode 160000 index 0000000000..ebc60f500b --- /dev/null +++ b/solidity/automation_registry/lib/forge-std @@ -0,0 +1 @@ +Subproject commit ebc60f500bc6870baaf321a0196fddc24d6edb03 diff --git a/solidity/automation_registry/lib/openzeppelin-contracts b/solidity/automation_registry/lib/openzeppelin-contracts new file mode 160000 index 0000000000..77bc5642a5 --- /dev/null +++ b/solidity/automation_registry/lib/openzeppelin-contracts @@ -0,0 +1 @@ +Subproject commit 77bc5642a53d9c8eac8aec5c8ea9809a21d466cb diff --git a/solidity/automation_registry/lib/openzeppelin-contracts-upgradeable b/solidity/automation_registry/lib/openzeppelin-contracts-upgradeable new file mode 160000 index 0000000000..db5a20c719 --- /dev/null +++ b/solidity/automation_registry/lib/openzeppelin-contracts-upgradeable @@ -0,0 +1 @@ +Subproject commit db5a20c719fdf4e40c1cef546ac8b2ef7175d53f diff --git a/solidity/automation_registry/script/DeployScript.s.sol b/solidity/automation_registry/script/DeployScript.s.sol new file mode 100644 index 0000000000..977de856db --- /dev/null +++ b/solidity/automation_registry/script/DeployScript.s.sol @@ -0,0 +1,16 @@ +// SPDX-License-Identifier: UNLICENSED +pragma solidity ^0.8.13; + +import {Script} from "forge-std/Script.sol"; + +contract DeployScript is Script { + + function setUp() public {} + + function run() public { + vm.startBroadcast(); + + + vm.stopBroadcast(); + } +} diff --git a/solidity/automation_registry/src/AutomationController.sol b/solidity/automation_registry/src/AutomationController.sol new file mode 100644 index 0000000000..4557906111 --- /dev/null +++ b/solidity/automation_registry/src/AutomationController.sol @@ -0,0 +1,799 @@ +// 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 {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 Defines the cycle state, used to update the registry. + uint8 constant SUSPENDED = 0; + uint8 constant FINISHED = 1; + + /// @dev State variables + LibController.AutomationCycleInfo cycleInfo; + IAutomationRegistry public registry; + address public blockMeta; + + // ::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::: 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 registration_hash + ); + + /// @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 registry smart contract address is updated. + event RegistryUpdated(address indexed oldRegistryAddress, address indexed newRegistryAddress); + + /// @notice Emitted when the blockMeta smart contract address is updated. + event BlockMetaAddressUpdated(address indexed oldBlockMetaAddress, address indexed newBlockMetaAddress); + + // :::::::::::::::::::::::::::::::::::::::::::::::::::::::::: 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 _registry Address of the registry smart contract. + /// @param _blockMeta Address of the blockmeta smart contract. + function initialize(address _registry, address _blockMeta) public initializer { + if (_registry == address(0)) { revert AddressCannotBeZero(); } + if (!_registry.isContract()) { revert AddressCannotBeEOA(); } + + if (_blockMeta == address(0)) { revert AddressCannotBeZero(); } + if (!_blockMeta.isContract()) { revert AddressCannotBeEOA(); } + + registry = IAutomationRegistry(_registry); + blockMeta = _blockMeta; + + (CommonUtils.CycleState state, uint64 cycleId) = registry.isAutomationEnabled() ? (CommonUtils.CycleState.STARTED, 1) : (CommonUtils.CycleState.READY, 0); + + cycleInfo.initializeCycle( + cycleId, + uint64(block.timestamp), + registry.cycleDurationSecs(), + state + ); + + __Ownable2Step_init(); + __Ownable_init(msg.sender); + } + + /// @notice Called by the VM 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 + if (msg.sender != registry.getVM()) { revert CallerNotVM(); } + + 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 It will be triggered for automation registry caused by `supra_governance::reconfiguration` or DKG finalization + /// to update the automation registry state depending on SUPRA_NATIVE_AUTOMATION feature flag state. + /// If registry is not fully initialized nothing is done. + /// If native automation feature is disabled and automation cycle in STARTED state, + /// then automation lifecycle is suspended immediately. And detached managment will + /// initiate reprocessing of the available tasks which will end up in refund and cleanup actions. + /// Otherwise suspention is postponed until the end of the transition state. + /// Nothing will be done if automation cycle was already suspended, i.e. in READY state. + /// If native automation feature is enabled and automation lifecycle has been in READY state, then lifecycle is restarted. + function onNewCycle() public { + CommonUtils.CycleState state = cycleInfo.state(); + + // Check if automation is enabled + if (registry.isAutomationEnabled()) { + // If the lifecycle has been suspended and we are recovering from it, then we update config from buffer and + // then start a new cycle directly. + // Unless we are in READY state, the feature flag being enabled will not have any effect. + // All the other states mean that we are in the middle of previous transition, which should end + // before reenabling the feature. + if (state == CommonUtils.CycleState.READY) { + if(registry.totalTasks() > 0) { + emit ErrorInconsistentSuspendedState(); + } else { + updateConfigFromBuffer(); + moveToStartedState(); + } + } + + + // We do not update config here, as due to feature being disabled, cycle ends early so it is expected + // that the current fee-parameters will be used to calculate automation-fee for refund for a cycle + // that has been kept short. + // So the confing should remain intact. + } else if (state == CommonUtils.CycleState.STARTED) { + tryMoveToSuspendedState(); + } else if (state == CommonUtils.CycleState.FINISHED && cycleInfo.ifTransitionStateExists()) { + if (!isTransitionInProgress()) { + // Just entered cycle-end phase, and meanwhile also feature has been disabled so it is safe to move to suspended state. + tryMoveToSuspendedState(); + } + // Otherwise wait of the cycle transition to end and then feature flag value will be taken into account. + } + // If in already SUSPENDED state or in READY state then do nothing. + } + + /// @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 (msg.sender != blockMeta) { revert CallerNotBlockMeta(); } + if (!registry.isAutomationEnabled()) { + return; + } + + if(cycleInfo.state() != CommonUtils.CycleState.STARTED || cycleInfo.startTime() + cycleInfo.durationSecs() > 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) { + 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); + uint256 cycleLockedFees = registry.getCycleLockedFees(); + + // 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(registry.ifTaskExists(taskIndexes[i])) { + (bool removed, ) = address(registry).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(registry.checkTaskType(taskIndexes[i], CommonUtils.TaskType.UST)) { + (bool refunded, bytes memory data) = address(registry).call( + abi.encodeCall( + IAutomationRegistry.refundTaskFees, + (taskIndexes[i], currentTime, cycleLockedFees) + ) + ); + require(refunded, RefundFailed()); + cycleLockedFees = abi.decode(data, (uint256)); + } + } + } + + 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){ + if(registry.ifTaskExists(_taskIndex)) { + markTaskProcessed(_taskIndex); + + bool isUST = registry.checkTaskType(_taskIndex, CommonUtils.TaskType.UST); + CommonUtils.TaskDetails memory task = registry.getTaskDetails(_taskIndex); + + // Task is cancelled or expired + if(task.state == CommonUtils.TaskState.CANCELLED || _currentTime >= task.expiryTime) { + if(isUST) { + (bool sent, ) = address(registry).call( + abi.encodeCall( + IAutomationRegistry.refundDepositAndDrop, + (_taskIndex, task.owner, task.lockedFeeForNextCycle, task.lockedFeeForNextCycle) + ) + ); + require(sent, RefundDepositAndDropFailed()); + } else { + // Remove the task from registry and system registry + (bool removed, ) = address(registry).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, ) = address(registry).call(abi.encodeCall(IAutomationRegistry.updateTaskState, (_taskIndex, CommonUtils.TaskState.ACTIVE))); + require(updated, UpdateTaskStateFailed()); + } else { + // Active UST + + uint128 registryMaxGasCap = registry.getRegistryMaxGasCap(); + + uint128 fee = registry.calculateTaskFee( + task.state, + task.expiryTime, + task.maxGasAmount, + cycleInfo.newCycleDuration(), + _currentTime, + cycleInfo.automationFeePerSec(), + registryMaxGasCap + ); + + // 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, ) = address(registry).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.lockedFeeForNextCycle, + 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 _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) { + // 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 erc20Supra = registry.supraERC20(); + bool isRemoved; + uint128 gas; + uint128 fees; + if(_fee > _automationFeeCapForCycle) { + (bool sent, ) = address(registry).call( + abi.encodeCall( + IAutomationRegistry.refundDepositAndDrop, + (_taskIndex, _owner, _lockedFeeForNextCycle, _lockedFeeForNextCycle) + ) + ); + 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, ) = address(registry).call( + abi.encodeCall( + IAutomationRegistry.safeUnlockLockedDeposit, + (_taskIndex, _lockedFeeForNextCycle) + ) + ); + require(unlocked, UnlockLockedDepositFailed()); + + (bool removed, ) = address(registry).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 = IERC20(erc20Supra).transferFrom(_owner, address(registry), _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) { + + (bool updated, ) = address(registry).call( + abi.encodeCall( + IAutomationRegistry.updateRegistryState, + ( + cycleInfo.sysGasCommittedForNextCycle(), + cycleInfo.gasCommittedForNextCycle(), + cycleInfo.gasCommittedForNewCycle(), + cycleInfo.transitionState.lockedFees, + FINISHED + ) + ) + ); + require(updated, UpdateRegistryStateFailed()); + + // Set current timestamp as cycle start time + // Increment the cycle and update the state to STARTED + moveToStartedState(); + if(registry.getTotalActiveTasks() > 0 ) { + uint256[] memory activeTasks = registry.getAllActiveTaskIds(); + emit ActiveTasks(activeTasks); + } + + // Check if automation is disabled + if (!registry.isAutomationEnabled()) { + tryMoveToSuspendedState(); + } + } + } + + /// @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, )= address(registry).call(abi.encodeCall(IAutomationRegistry.updateRegistryState, (0, 0, 0, 0, SUSPENDED))); + require(updated, UpdateRegistryStateFailed()); + + // Check if automation is enabled + if (registry.isAutomationEnabled()) { + // 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 { + if(registry.totalTasks() == 0) { + // Registry is empty move to ready state directly + updateCycleStateTo(CommonUtils.CycleState.READY); + } else if (!cycleInfo.ifTransitionStateExists()) { + uint64 currentTime = uint64(block.timestamp); + + uint64 startTime = cycleInfo.startTime(); + uint64 cycleDuration = cycleInfo.durationSecs(); + uint64 cycleEndTime = startTime + cycleDuration; + + if(currentTime < startTime) { revert InvalidRegistryState(); } + if(currentTime >= cycleEndTime) { revert InvalidRegistryState(); } + if(cycleInfo.state() != CommonUtils.CycleState.STARTED) { revert InvalidRegistryState(); } + + uint256[] memory expected_tasks_to_be_processed = registry.getTaskIdList().sortUint256(); + + cycleInfo.setRefundDuration(cycleEndTime - currentTime); + cycleInfo.setNewCycleDuration(cycleDuration); + cycleInfo.setAutomationFeePerSec(registry.calculateAutomationFeeMultiplierForCurrentCycleInternal()); + cycleInfo.setGasCommittedForNewCycle(0); + cycleInfo.setGasCommittedForNextCycle(0); + cycleInfo.setSysGasCommittedForNextCycle(0); + cycleInfo.transitionState.lockedFees = 0; + cycleInfo.setNextTaskIndexPosition(0); + + updateExpectedTasks(expected_tasks_to_be_processed); + 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(registry.totalTasks() == 0) { + // Registry is empty update config buffer and move to STARTED state directly + updateConfigFromBuffer(); + moveToStartedState(); + } else { + uint256[] memory expected_tasks_to_be_processed = registry.getTaskIdList().sortUint256(); + + cycleInfo.setRefundDuration(0); + cycleInfo.setNewCycleDuration(cycleInfo.durationSecs()); + cycleInfo.setAutomationFeePerSec(0); + cycleInfo.setGasCommittedForNewCycle(registry.getGasCommittedForNextCycle()); + cycleInfo.setGasCommittedForNextCycle(0); + cycleInfo.setSysGasCommittedForNextCycle (0); + cycleInfo.transitionState.lockedFees = 0; + cycleInfo.setNextTaskIndexPosition(0); + + updateExpectedTasks(expected_tasks_to_be_processed); + 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(registry.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 { + if(registry.ifConfigBufferExists()) { + (bool sent, ) = address(registry).call(abi.encodeCall(IAutomationRegistry.applyPendingConfig, ())); + require(sent, ConfigUpdateFailed()); + + uint64 cycleDurationSecs = registry.getBufferCycleDurationSecs(); + // Check if transition state exists + if (cycleInfo.ifTransitionStateExists()) { + cycleInfo.setNewCycleDuration(cycleDurationSecs); + } else { + cycleInfo.setDurationSecs(cycleDurationSecs); + } + } + } + + /// @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(); + } + + /// @notice Checks if the cycle transition is in progress. + /// @return Bool representing if the cycle transition is in progress. + function isTransitionInProgress() private view returns (bool) { + return cycleInfo.nextTaskIndexPosition() != 0; + } + + // :::::::::::::::::::::::::::::::::::::::::::::::::::::::::::: VIEW FUNCTIONS :::::::::::::::::::::::::::::::::::::::::::::::::::::::::::: + + /// @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 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()); + } + + // ::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::: ADMIN FUNCTIONS ::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::: + + /// @notice Function to update the registry smart contract address. + /// @param _registry Address of the registry smart contract. + function setRegistry(address _registry) external onlyOwner { + if (_registry == address(0)) { revert AddressCannotBeZero(); } + if (!_registry.isContract()) { revert AddressCannotBeEOA(); } + + address oldRegistry = address(registry); + registry = IAutomationRegistry(_registry); + + emit RegistryUpdated(oldRegistry, _registry); + } + + /// @notice Function to update the blockMeta smart contract address. + /// @param _blockMeta Address of the blockMeta smart contract. + function setBlockMeta(address _blockMeta) external onlyOwner { + if (_blockMeta == address(0)) { revert AddressCannotBeZero(); } + if (!_blockMeta.isContract()) { revert AddressCannotBeEOA(); } + + address oldBlockMeta = blockMeta; + blockMeta = _blockMeta; + + emit BlockMetaAddressUpdated(oldBlockMeta, _blockMeta); + } + + // ::::::::::::::::::::::::::::::::::::::::::::::::::::::::: 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/automation_registry/src/AutomationRegistry.sol b/solidity/automation_registry/src/AutomationRegistry.sol new file mode 100644 index 0000000000..b4407713c7 --- /dev/null +++ b/solidity/automation_registry/src/AutomationRegistry.sol @@ -0,0 +1,1602 @@ +// 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 {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 Constant for 10^8 + uint256 constant DECIMAL = 100_000_000; + + /// @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; + + /// @dev Supported auxiliary data count + uint8 constant SUPPORTED_AUX_DATA_COUNT_MAX = 2; + /// @dev Index of the auxiliary data holding task type value + uint8 constant TYPE_AUX_DATA_INDEX = 0; + /// @dev Index of the auxiliary data holding task priority value + uint8 constant PRIORITY_AUX_DATA_INDEX = 1; + + /// @dev Defines the cycle state, used to update the registry. + uint8 constant SUSPENDED = 0; + uint8 constant FINISHED = 1; + + /// @dev Constants describing REFUND TYPE + uint8 constant DEPOSIT_CYCLE_FEE = 0; + uint8 constant CYCLE_FEE = 1; + + /// @dev State variables + LibRegistry.ConfigBuffer configBuffer; + LibRegistry.RegistryConfig regConfig; + LibRegistry.RegistryState regState; + LibRegistry.RegistryStateSystemTasks regSysState; + LibRegistry.Deposit deposit; + + // ::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::: 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 task registration is enabled. + event TaskRegistrationEnabled(bool indexed status); + + /// @notice Emitted when task registration is disabled. + event TaskRegistrationDisabled(bool indexed status); + + /// @notice Emitted when automation is enabled. + event AutomationEnabled(bool indexed status); + + /// @notice Emitted when automation is disabled. + event AutomationDisabled(bool indexed status); + + /// @notice Emitted when the VM address is updated. + event VmAddressUpdated(address indexed oldVmAddress, address indexed newVmAddress); + + /// @notice Emitted when the SupraERC20 address is updated. + event SupraERC20Updated(address indexed oldSupraERC20, address indexed newSupraERC20); + + /// @notice Emitted when a new config is added. + event ConfigBufferUpdated( + LibRegistry.ConfigDetails indexed pendingConfig + ); + + /// @notice Emitted when the cold wallet address is updated. + event ColdWalletUpdated(address indexed oldColdWallet, address indexed newColdWallet); + + /// @notice Emitted when the automation controller smart contract address is updated. + event AutomationControllerUpdated(address indexed oldController, address indexed newController); + + /// @notice Emitted when the registry fees is withdrawn by the admin. + event RegistryFeeWithdrawn(address indexed coldWallet, uint256 indexed feesWithdrawn); + + /// @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 + ); + + /// @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 totalDepositedAutomationFees, + uint128 lockedDeposit + ); + + /// @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 when a deposit fee is refunded for an automation task. + event TaskDepositFeeRefund(uint64 indexed taskIndex, address owner, uint128 amount); + + /// @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 _refundType, + 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 + ); + + // :::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::: MODIFIERS :::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::: + + /// @dev Modifier to assert that AutomationController contract is the caller. + modifier onlyController() { + if(msg.sender != regConfig.automationController()) { revert CallerNotController(); } + _; + } + + // :::::::::::::::::::::::::::::::::::::::::::::::::::::::::: 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 _vm Address for the VM. + /// @param _supraERC20 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 _vm, + address _supraERC20 + ) public initializer { + validateConfigParameters( + _taskDurationCapSecs, + _registryMaxGasCap, + _congestionThresholdPercentage, + _congestionExponent, + _taskCapacity, + _cycleDurationSecs, + _sysTaskDurationCapSecs, + _sysRegistryMaxGasCap, + _sysTaskCapacity + ); + if(_vm == address(0)) revert AddressCannotBeZero(); + + if(_supraERC20 == address(0)) revert AddressCannotBeZero(); + if(!_supraERC20.isContract()) revert AddressCannotBeEOA(); + + LibRegistry.Config memory config = LibRegistry.createConfig( + _registryMaxGasCap, + _sysRegistryMaxGasCap, + _automationBaseFeeWeiPerSec, + _flatRegistrationFeeWei, + _congestionBaseFeeWeiPerSec, + _taskDurationCapSecs, + _sysTaskDurationCapSecs, + _cycleDurationSecs, + _taskCapacity, + _sysTaskCapacity, + _congestionThresholdPercentage, + _congestionExponent + ); + + regConfig = LibRegistry.createRegistryConfig( + _registryMaxGasCap, + _sysRegistryMaxGasCap, + true, + true, + _vm, + _supraERC20, + config + ); + + __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 _txHash Transaction hash of the request transaction. + /// @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 _auxData Auxiliary data to be passed. + function register( + bytes memory _payloadTx, + uint64 _expiryTime, + bytes32 _txHash, + uint128 _maxGasAmount, + uint128 _gasPriceCap, + uint128 _automationFeeCapForCycle, + bytes[] memory _auxData + ) external { + // Check if automation is enabled + if (!regConfig.automationEnabled()) { revert AutomationNotEnabled(); } + if(!regConfig.registrationEnabled()) { revert RegistrationDisabled(); } + + ( , uint64 startTime, uint64 durationSecs, CommonUtils.CycleState state) = IAutomationController(regConfig.automationController()).getCycleInfo(); + if(state != CommonUtils.CycleState.STARTED) { revert CycleNotStarted(); } + if(totalTasks() >= regConfig.taskCapacity()) { revert TaskCapacityReached(); } + + bool hasNoPriority = checkAndValidateAuxData(_auxData, CommonUtils.TaskType.UST); + + uint64 regTime = uint64(block.timestamp); + validateTaskDuration(regTime, _expiryTime, CommonUtils.TaskType.UST, regConfig.taskDurationCapSecs(), regConfig.sysTaskDurationCapSecs(), startTime, durationSecs); + validateInputs(_payloadTx, _maxGasAmount, _txHash); + if(_gasPriceCap == 0) { revert InvalidGasPriceCap(); } + + uint128 gasCommitted = _maxGasAmount + regState.gasCommittedForNextCycle(); + if(gasCommitted > regConfig.nextCycleRegistryMaxGasCap()) { revert GasCommittedExceedsMaxGasCap(); } + + + uint128 estimatedAutomationFeeForCycle = estimateAutomationFeeWithCommittedOccupancyInternal(_maxGasAmount, regState.gasCommittedForNextCycle(), durationSecs); + if(_automationFeeCapForCycle < estimatedAutomationFeeForCycle) { revert InsufficientFeeCapForCycle(); } + + regState.setGasCommittedForNextCycle(gasCommitted); + uint64 taskIndex = regState.currentIndex; + + if(hasNoPriority) { _auxData[PRIORITY_AUX_DATA_INDEX] = abi.encode(taskIndex); } + LibRegistry.TaskMetadata memory taskMetadata = LibRegistry.createTaskMetadata( + _maxGasAmount, + _gasPriceCap, + _automationFeeCapForCycle, + _automationFeeCapForCycle , + _txHash, + taskIndex, + regTime, + _expiryTime, + msg.sender, + CommonUtils.TaskState.PENDING, + _payloadTx, + _auxData + ); + + regState.tasks[taskIndex] = taskMetadata; + require(regState.taskIdList.add(taskIndex), TaskIndexNotUnique()); + regState.currentIndex += 1; + + deposit.totalDepositedAutomationFees += _automationFeeCapForCycle; + + uint128 fee = regConfig.flatRegistrationFeeWei() + _automationFeeCapForCycle; + bool sent = IERC20(regConfig.supraERC20).transferFrom(msg.sender, address(this), fee); + if (!sent) { revert TransferFailed(); } + + emit TaskRegistered(taskIndex, msg.sender, regConfig.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 _txHash Transaction hash of the request transaction. + /// @param _maxGasAmount Maximum amount of gas for the automation task. + /// @param _auxData Auxiliary data to be passed. + function registerSystemTask( + bytes memory _payloadTx, + uint64 _expiryTime, + bytes32 _txHash, + uint128 _maxGasAmount, + bytes[] memory _auxData + ) external { + // Check if automation is enabled + if (!regConfig.automationEnabled()) { revert AutomationNotEnabled(); } + if(!regConfig.registrationEnabled()) { revert RegistrationDisabled(); } + + ( , uint64 startTime, uint64 durationSecs, CommonUtils.CycleState state) = IAutomationController(regConfig.automationController()).getCycleInfo(); + if(state != CommonUtils.CycleState.STARTED) { revert CycleNotStarted(); } + if(totalSystemTasks() >= regConfig.sysTaskCapacity()) { revert TaskCapacityReached(); } + if(!isAuthorizedSubmitter(msg.sender)) { revert UnauthorizedAccount(); } + + bool hasNoPriority = checkAndValidateAuxData(_auxData, CommonUtils.TaskType.GST); + + uint64 regTime = uint64(block.timestamp); + validateTaskDuration(regTime, _expiryTime, CommonUtils.TaskType.GST, regConfig.taskDurationCapSecs(), regConfig.sysTaskDurationCapSecs(), startTime, durationSecs); + validateInputs(_payloadTx, _maxGasAmount, _txHash); + + uint128 gasCommitted = _maxGasAmount + regSysState.gasCommittedForNextCycle(); + if(gasCommitted > regConfig.nextCycleSysRegistryMaxGasCap()) { revert GasCommittedExceedsMaxGasCap(); } + + regSysState.setGasCommittedForNextCycle(gasCommitted); + + uint64 taskIndex = regState.currentIndex; + + if(hasNoPriority) {_auxData[PRIORITY_AUX_DATA_INDEX] = abi.encode(taskIndex); } + LibRegistry.TaskMetadata memory taskMetadata = LibRegistry.createTaskMetadata( + _maxGasAmount, + 0, + 0, + 0, + _txHash, + taskIndex, + regTime, + _expiryTime, + msg.sender, + CommonUtils.TaskState.PENDING, + _payloadTx, + _auxData + ); + + regState.tasks[taskIndex] = taskMetadata; + require(regState.taskIdList.add(taskIndex), TaskIndexNotUnique()); + require(regSysState.taskIds.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 + if (!regConfig.automationEnabled()) { revert AutomationNotEnabled(); } + + ( , uint64 startTime, uint64 durationSecs, CommonUtils.CycleState state) = IAutomationController(regConfig.automationController()).getCycleInfo(); + + if(state != CommonUtils.CycleState.STARTED) { revert CycleTransitionInProgress(); } + if(!ifTaskExists(_taskIndex)) { revert TaskDoesNotExist(); } + + CommonUtils.TaskDetails memory task = regState.tasks[_taskIndex].getTaskDetails(); + + if(checkTaskType(_taskIndex, CommonUtils.TaskType.GST)) { revert UnsupportedTaskOperation(); } + if(task.owner != msg.sender) { revert UnauthorizedAccount(); } + if(task.state == CommonUtils.TaskState.CANCELLED) { revert AlreadyCancelled(); } + + 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 = safeDepositRefund( + _taskIndex, + task.owner, + task.lockedFeeForNextCycle / REFUND_FACTOR, + task.lockedFeeForNextCycle + ); + 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 > (startTime + durationSecs)) { + if(regState.gasCommittedForNextCycle() < task.maxGasAmount) { revert GasCommittedValueUnderflow(); } + + // Adjust the gas committed for the next cycle by subtracting the gas amount of the cancelled task + regState.setGasCommittedForNextCycle(regState.gasCommittedForNextCycle() - 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 (!regConfig.automationEnabled()) { revert AutomationNotEnabled(); } + + ( , uint64 startTime, uint64 durationSecs, CommonUtils.CycleState state) = IAutomationController(regConfig.automationController()).getCycleInfo(); + + if(state != CommonUtils.CycleState.STARTED) { revert CycleTransitionInProgress(); } + if(!ifTaskExists(_taskIndex)) { revert TaskDoesNotExist(); } + if(!ifSysTaskExists(_taskIndex)) { revert SystemTaskDoesNotExist(); } + + // Check if GST + if(checkTaskType(_taskIndex, CommonUtils.TaskType.UST)) { revert UnsupportedTaskOperation(); } + + CommonUtils.TaskDetails memory task = regState.tasks[_taskIndex].getTaskDetails(); + + 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 > startTime + durationSecs) { + if(regSysState.gasCommittedForNextCycle() < task.maxGasAmount) { revert GasCommittedValueUnderflow(); } + + // Adjust the gas committed for the next cycle by subtracting the gas amount of the cancelled task + regSysState.setGasCommittedForNextCycle(regSysState.gasCommittedForNextCycle() - 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 (!regConfig.automationEnabled()) { revert AutomationNotEnabled(); } + + ( , uint64 startTime, uint64 durationSecs, CommonUtils.CycleState state) = IAutomationController(regConfig.automationController()).getCycleInfo(); + if(state != CommonUtils.CycleState.STARTED) { revert CycleTransitionInProgress(); } + if(_taskIndexes.length == 0) { revert TaskIndexesCannotBeEmpty(); } + + // Compute the automation fee multiplier for cycle + uint128 registryMaxGasCap = getRegistryMaxGasCap(); + uint128 automationFeePerSec = calculateAutomationFeeMultiplierForCycle(regState.gasCommittedForThisCycle(), registryMaxGasCap, regConfig.automationBaseFeeWeiPerSec()); + + LibRegistry.TaskStopped[] memory stoppedTaskDetails = new LibRegistry.TaskStopped[](_taskIndexes.length); + uint256 counter = 0; + + uint128 totalRefundFee = 0; + uint256 cycleLockedFees = regState.cycleLockedFees; + + // Calculate refundable fee for this remaining time task in current cycle + uint256 currentTime = block.timestamp; + uint128 cycleEndTime = startTime + durationSecs; + uint64 residualInterval = cycleEndTime <= currentTime ? 0 : uint64(cycleEndTime - currentTime); + + // 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(checkTaskType(_taskIndexes[i], 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) { + // Prevent underflow in gas committed + if(regState.gasCommittedForNextCycle() < task.maxGasAmount) { revert GasCommittedValueUnderflow(); } + // Reduce committed gas by the stopped task's max gas + regState.setGasCommittedForNextCycle(regState.gasCommittedForNextCycle() - task.maxGasAmount); + } + + uint128 cycleFeeRefund; + uint128 depositRefund; + if(task.state != CommonUtils.TaskState.PENDING) { + uint128 taskFee = _calculateTaskFee( + task.state, + task.expiryTime, + task.maxGasAmount, + residualInterval, + uint64(currentTime), + automationFeePerSec, + registryMaxGasCap + ); + + // Refund full deposit and the half of the remaining run-time fee when task is active or cancelled stage + cycleFeeRefund = taskFee / REFUND_FRACTION; + depositRefund = task.lockedFeeForNextCycle; + } else { + cycleFeeRefund = 0; + depositRefund = task.lockedFeeForNextCycle / REFUND_FRACTION; + } + + bool result = _safeUnlockLockedDeposit(_taskIndexes[i], task.lockedFeeForNextCycle); + if(!result) { revert ErrorDepositRefund(); } + + (bool hasLockedFee, uint256 remainingCycleLockedFees ) = safeUnlockLockedCycleFee(cycleLockedFees, uint64(cycleFeeRefund), _taskIndexes[i]); + if(!hasLockedFee) { revert ErrorCycleFeeRefund(); } + cycleLockedFees = remainingCycleLockedFees; + 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) { + uint256 balance = IERC20(regConfig.supraERC20).balanceOf(address(this)); + + if(balance < totalRefundFee) { revert InsufficientBalanceForRefund(); } + 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 (!regConfig.automationEnabled()) { revert AutomationNotEnabled(); } + + ( , uint64 startTime, uint64 durationSecs, CommonUtils.CycleState state) = IAutomationController(regConfig.automationController()).getCycleInfo(); + if(state != CommonUtils.CycleState.STARTED) { 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; + + // Calculate refundable fee for this remaining time task in current cycle + uint128 cycleEndTime = startTime + durationSecs; + + // 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(checkTaskType(_taskIndexes[i], 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 > cycleEndTime) { + // Prevent underflow in gas committed + if(regSysState.gasCommittedForNextCycle() < task.maxGasAmount) { revert GasCommittedValueUnderflow(); } + regSysState.setGasCommittedForNextCycle(regSysState.gasCommittedForNextCycle() - 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 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, + CommonUtils.TaskType _type, + uint64 _taskDurationCapSecs, + uint64 _sysTaskDurationCapSecs, + uint64 _cycleStartTime, + uint64 _cycleDurationSecs + ) private pure { + if(_expiryTime <= _regTime) { revert InvalidExpiryTime(); } + + uint64 taskDuration = _expiryTime - _regTime; + if(_type == CommonUtils.TaskType.UST) { + if(taskDuration > _taskDurationCapSecs) { revert InvalidTaskDuration(); } + } else if(_type == CommonUtils.TaskType.GST) { + if ( taskDuration > _sysTaskDurationCapSecs) { revert InvalidTaskDuration(); } + } else { + revert InvalidTypeForTask(); + } + + if( _expiryTime <= _cycleStartTime + _cycleDurationSecs) { revert TaskExpiresBeforeNextCycle(); } + } + + /// @notice Helper function to validate the inputs while registering a task. + function validateInputs(bytes memory _payloadTx, uint128 _maxGasAmount, bytes32 _txHash) private view { + address payloadTarget; + (payloadTarget, ) = abi.decode(_payloadTx, (address, bytes)); + if(payloadTarget == address(0)) { revert AddressCannotBeZero(); } + if(!payloadTarget.isContract()) { revert AddressCannotBeEOA(); } + if(_maxGasAmount == 0) { revert InvalidMaxGasAmount(); } + if(_txHash == bytes32(0)) { revert InvalidTxHash(); } + } + + /// @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.supraERC20).transfer(_to, _amount); + if (!sent) { revert TransferFailed(); } + + return sent; + } + + /// @notice Helper function to update the active task indexes. + function updateActiveTaskIds() private { + uint256[] memory taskIds = regState.taskIdList.values(); + for (uint256 i = 0; i < taskIds.length; i++) { + regState.activeTaskIds.add(taskIds[i]); + } + } + + /// @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(regSysState.taskIds.remove(_taskIndex), TaskIndexNotFound()); + } + + delete regState.tasks[_taskIndex]; + require(regState.taskIdList.remove(_taskIndex), TaskIndexNotFound()); + } + + /// @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. + /// @param _automationBaseFeeWeiPerSec Automation base fee per second. + function calculateAutomationFeeMultiplierForCycle( + uint128 _totalCommittedGas, + uint128 _registryMaxGasCap, + uint128 _automationBaseFeeWeiPerSec + ) private view returns (uint128){ + uint128 congesionFee = calculateAutomationCongestionFee(_totalCommittedGas, _registryMaxGasCap); + return (congesionFee + _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 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, + uint64 _duration + ) private view returns (uint128) { + uint128 totalCommittedGas = _taskOccupancy + _committedOccupancy; + + uint128 automationFeePerSec = calculateAutomationFeeMultiplierForCycle(totalCommittedGas, regConfig.nextCycleRegistryMaxGasCap(), regConfig.automationBaseFeeWeiPerSec()); + + if(automationFeePerSec == 0) return 0; + + return calculateAutomationFeeForInterval(_duration, _taskOccupancy, automationFeePerSec, regConfig.nextCycleRegistryMaxGasCap()); + } + + /// @notice Function to check and validate the input auxiliary data. + /// @param _auxData Input auxiliary data. + /// @param _taskType Type of the task. + /// @return Bool representing if the task has priority. + function checkAndValidateAuxData(bytes[] memory _auxData, CommonUtils.TaskType _taskType) private pure returns (bool) { + if(_auxData.length != SUPPORTED_AUX_DATA_COUNT_MAX) { revert InvalidAuxDataLength(); } + + // Check task type + uint8 typeValue = abi.decode(_auxData[TYPE_AUX_DATA_INDEX], (uint8)); + if(typeValue != uint8(_taskType)) {revert InvalidTaskType(); } + + // Check if priority exists + bytes memory priorityBytes = _auxData[PRIORITY_AUX_DATA_INDEX]; + bool hasNoPriority = (priorityBytes.length == 0); + if (!hasNoPriority) { + uint64 _priority = abi.decode(priorityBytes, (uint64)); + } + + return hasNoPriority; + } + + /// @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 = deposit.totalDepositedAutomationFees; + + if(totalDeposited >= _lockedDeposit) { + deposit.totalDepositedAutomationFees = totalDeposited - _lockedDeposit; + return true; + } + + emit ErrorUnlockTaskDepositFee(_taskIndex, totalDeposited, _lockedDeposit); + return false; + } + + /// @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 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 + /// @param _registryMaxGasCap Registry max gas cap + /// @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, + uint128 _registryMaxGasCap + ) private pure 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, + _registryMaxGasCap + ); + } + + /// @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 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.supraERC20).balanceOf(address(this)); + if(balance < _refundableAmount) { + emit ErrorInsufficientBalanceToRefund(_taskIndex, _taskOwner, _refundType, _refundableAmount); + return false; + } else { + return refund(_taskOwner, _refundableAmount); + } + } + + /// 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); + } + + // ::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::: 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(regState.gasCommittedForNextCycle() > _registryMaxGasCap) { revert UnacceptableRegistryMaxGasCap(); } + if(regSysState.gasCommittedForNextCycle() > _sysRegistryMaxGasCap) { revert UnacceptableSysRegistryMaxGasCap(); } + + // Add new config to the buffer + LibRegistry.Config memory pendingConfig = LibRegistry.createConfig( + _registryMaxGasCap, + _sysRegistryMaxGasCap, + _automationBaseFeeWeiPerSec, + _flatRegistrationFeeWei, + _congestionBaseFeeWeiPerSec, + _taskDurationCapSecs, + _sysTaskDurationCapSecs, + _cycleDurationSecs, + _taskCapacity, + _sysTaskCapacity, + _congestionThresholdPercentage, + _congestionExponent + ); + configBuffer = LibRegistry.ConfigBuffer(pendingConfig, true); + + regConfig.setNextCycleRegistryMaxGasCap(_registryMaxGasCap); + regConfig.setNextCycleSysRegistryMaxGasCap(_sysRegistryMaxGasCap); + + emit ConfigBufferUpdated(pendingConfig.getConfig()); + } + + /// @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(regSysState.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(regSysState.authorizedAccounts.remove(_account), AddressDoesNotExist()); + emit AuthorizationRevoked(_account, block.timestamp); + } + + /// @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 enable the automation. + function enableAutomation() external onlyOwner { + if(regConfig.automationEnabled()) { revert AlreadyEnabled(); } + regConfig.setAutomationEnabled(true); + + emit AutomationEnabled(regConfig.automationEnabled()); + } + + /// @notice Function to disable the automation. + function disableAutomation() external onlyOwner { + if(!regConfig.automationEnabled()) { revert AlreadyDisabled(); } + regConfig.setAutomationEnabled(false); + + emit AutomationDisabled(regConfig.automationEnabled()); + } + + /// @notice Function to update the VM address. + /// @param _vm New address for VM. + function setVM(address _vm) external onlyOwner { + if(_vm == address(0)) { revert AddressCannotBeZero(); } + + address oldVM = regConfig.vm; + regConfig.vm = _vm; + + emit VmAddressUpdated(oldVM, _vm); + } + + /// @notice Function to update the SupraERC20 address. + /// @param _supraERC20 New address for SupraERC20. + function setSupraERC20(address _supraERC20) external onlyOwner { + if(_supraERC20 == address(0)) { revert AddressCannotBeZero(); } + if(!_supraERC20.isContract()) { revert AddressCannotBeEOA(); } + + address oldSupraERC20 = regConfig.supraERC20; + regConfig.supraERC20 = _supraERC20; + + emit SupraERC20Updated(oldSupraERC20, _supraERC20); + } + + /// @notice Function to withdraw the accumulated fees. + /// @param _amount Amount to withdraw. + function withdrawFees(uint256 _amount) external onlyOwner { + address coldWallet = deposit.coldWallet; + if(coldWallet == address(0)) { revert ColdWalletNotSet(); } + uint256 balance = IERC20(regConfig.supraERC20).balanceOf(address(this)); + + if(balance < _amount) { revert InsufficientBalance(); } + if(balance - _amount < regState.cycleLockedFees + deposit.totalDepositedAutomationFees) { revert RequestExceedsLockedBalance(); } + + bool sent = IERC20(regConfig.supraERC20).transfer(coldWallet, _amount); + if(!sent) { revert TransferFailed(); } + + emit RegistryFeeWithdrawn(coldWallet, _amount); + } + + /// @notice Function to update the cold wallet address. + /// @param _coldWallet Address for the new cold wallet. + function setColdWallet(address _coldWallet) external onlyOwner { + if(_coldWallet == address(0)) { revert AddressCannotBeZero(); } + + address oldColdWallet = deposit.coldWallet; + deposit.coldWallet = _coldWallet; + + emit ColdWalletUpdated(oldColdWallet, _coldWallet); + } + + /// @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 { + if (_controller == address(0)) { revert AddressCannotBeZero(); } + if(!_controller.isContract()) { revert AddressCannotBeEOA(); } + + address oldController = regConfig.automationController(); + regConfig.setAutomationController(_controller); + + emit AutomationControllerUpdated(oldController, _controller); + } + + // ::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::: 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 registry state. + /// @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 _lockedFees Updated cycle locked fees + /// @param _state Cycle transition state executing the update. + function updateRegistryState( + uint128 _sysGasCommittedForNextCycle, + uint128 _gasCommittedForNextCycle, + uint128 _gasCommittedForNewCycle, + uint256 _lockedFees, + uint8 _state + ) external onlyController { + regSysState.setGasCommittedForNextCycle(_sysGasCommittedForNextCycle); + regSysState.setGasCommittedForThisCycle(_sysGasCommittedForNextCycle); + regState.setGasCommittedForNextCycle(_gasCommittedForNextCycle); + regState.setGasCommittedForThisCycle(_gasCommittedForNewCycle); + regState.cycleLockedFees = _lockedFees; + + regState.activeTaskIds.clear(); + + if(_state == FINISHED) { + updateActiveTaskIds(); + } else { + regSysState.taskIds.clear(); + } + } + + /// @notice Function to update the registry configuration, reverts if caller is not AutomationController. + function applyPendingConfig() external onlyController { + regConfig.config = configBuffer.pendingConfig; + configBuffer.ifExists = false; + } + + function safeUnlockLockedDeposit( + uint64 _taskIndex, + uint128 _lockedDeposit + ) external onlyController returns (bool) { + return _safeUnlockLockedDeposit(_taskIndex, _lockedDeposit); + } + + /// @notice Internally calls _calculateTaskFee, reverts if caller is not AutomationController. + function calculateTaskFee( + CommonUtils.TaskState _state, + uint64 _expiryTime, + uint128 _maxGasAmount, + uint64 _potentialFeeTimeframe, + uint64 _currentTime, + uint128 _automationFeePerSec, + uint128 _registryMaxGasCap + ) external onlyController view returns (uint128) { + return _calculateTaskFee( + _state, + _expiryTime, + _maxGasAmount, + _potentialFeeTimeframe, + _currentTime, + _automationFeePerSec, + _registryMaxGasCap + ); + } + + /// @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(checkTaskType(_taskIndex, CommonUtils.TaskType.GST)) { revert RegisteredTaskInvalidType(); } + + // Remove task from the registry state + _removeTask(_taskIndex, false); + + // Refund + safeDepositRefund( + _taskIndex, + _taskOwner, + _refundableDeposit, + _lockedDeposit + ); + } + + /// @notice Refunds the deposit fee and any autoamtion fees of the task. + function refundTaskFees( + uint64 _taskIndex, + uint64 _currentTime, + uint256 _cycleLockedFees + ) external onlyController returns (uint256) { + if(checkTaskType(_taskIndex, CommonUtils.TaskType.GST)) { revert RegisteredTaskInvalidType(); } + + CommonUtils.TaskDetails memory task = regState.tasks[_taskIndex].getTaskDetails(); + + // Do not attempt fee refund if remaining duration is 0 + (uint64 refundDuration, uint128 automationFeePerSec) = IAutomationController(regConfig.automationController()).getTransitionInfo(); + + if (task.state != CommonUtils.TaskState.PENDING && refundDuration != 0) { + uint128 registryMaxGasCap = getRegistryMaxGasCap(); + uint128 _refund = _calculateTaskFee( + task.state, + task.expiryTime, + task.maxGasAmount, + refundDuration, + _currentTime, + automationFeePerSec, + registryMaxGasCap + ); + ( , uint256 remainingCycleLockedFees) = safeFeeRefund( + _taskIndex, + task.owner, + _cycleLockedFees, + uint64(_refund) + ); + _cycleLockedFees = remainingCycleLockedFees; + } + + safeDepositRefund( + _taskIndex, + task.owner, + task.lockedFeeForNextCycle, + task.lockedFeeForNextCycle + ); + + return _cycleLockedFees; + } + + function calculateAutomationFeeMultiplierForCurrentCycleInternal() external onlyController view returns (uint128) { + // Compute the automation fee multiplier for this cycle + return calculateAutomationFeeMultiplierForCycle( + regState.gasCommittedForThisCycle(), + regConfig.registryMaxGasCap(), + regConfig.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 + ) external onlyController view returns (uint128) { + // Compute the automation fee multiplier for cycle + return calculateAutomationFeeMultiplierForCycle( + _totalCommittedMaxGas, + regConfig.registryMaxGasCap(), + regConfig.automationBaseFeeWeiPerSec() + ); + } + + // :::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::: VIEW FUNCTIONS :::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::: + + /// @notice Returns the registry configuration. + function getConfig() external view returns (LibRegistry.ConfigDetails memory) { + return regConfig.config.getConfig(); + } + + /// @notice Returns the cold wallet address. + function getColdWallet() external view returns (address) { + return deposit.coldWallet; + } + + /// @notice Returns the VM address. + function getVM() external view returns (address) { + return regConfig.vm; + } + + /// @notice Returns the SupraERC20 address. + function supraERC20() external view returns (address) { + return regConfig.supraERC20; + } + + /// @notice Returns the address of AutomationController smart contract. + function getAutomationController() external view returns (address) { + return regConfig.automationController(); + } + + /// @notice Returns if automation is enabled. + function isAutomationEnabled() external view returns (bool) { + return regConfig.automationEnabled(); + } + + /// @notice Returns if task registration is enabled. + function isRegistrationEnabled() external view returns (bool) { + return regConfig.registrationEnabled(); + } + + /// @notice Returns the total amount locked which comprises of 'cycleLockedFees' and 'totalDepositedAutomationFees'. + function getTotalLockedBalance() external view returns (uint256) { + return regState.cycleLockedFees + deposit.totalDepositedAutomationFees; + } + + /// @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 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 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 regSysState.taskIds.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 regSysState.taskIds.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) public view returns (bool) { + uint8 taskType = abi.decode(regState.tasks[_taskIndex].auxData[TYPE_AUX_DATA_INDEX], (uint8)); + return taskType == uint8(_type); + } + + /// @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 Returns the gas committed for the next cycle. + function getGasCommittedForNextCycle() external view returns (uint128) { + return regState.gasCommittedForNextCycle(); + } + + /// @notice Returns the gas committed for the current cycle. + function getGasCommittedForCurrentCycle() external view returns (uint128) { + return regState.gasCommittedForThisCycle(); + } + + /// @notice Returns the system gas committed for the next cycle. + function getSystemGasCommittedForNextCycle() external view returns (uint128) { + return regSysState.gasCommittedForNextCycle(); + } + + /// @notice Returns the system gas committed for the current cycle. + function getSystemGasCommittedForCurrentCycle() external view returns (uint128) { + return regSysState.gasCommittedForThisCycle(); + } + + /// @notice Returns the total amount of automation fees deposited. + function getTotalDepositedAutomationFees() external view returns (uint256) { + return deposit.totalDepositedAutomationFees; + } + + /// @notice Returns the registry max gas cap configured. + function getRegistryMaxGasCap() public 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 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 regSysState.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 Returns the locked fees for the cycle. + function getCycleLockedFees() external view returns (uint256) { + return regState.cycleLockedFees; + } + + /// @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) { + return regState.tasks[_taskIndex].owner == _account && LibRegistry.state(regState.tasks[_taskIndex]) != CommonUtils.TaskState.PENDING && checkTaskType(_taskIndex, _type); + } + + /// @notice Checks if config buffer exists. + /// @return Bool representing if config buffer exists. + function ifConfigBufferExists() external view returns (bool) { + return configBuffer.ifExists; + } + + /// @notice Returns the pending configuration. + function getPendingConfig() external view returns (LibRegistry.ConfigDetails memory) { + return configBuffer.pendingConfig.getConfig(); + } + + /// @notice Returns the cycle duration of config buffer. + function getBufferCycleDurationSecs() external view returns (uint64) { + return configBuffer.pendingConfig.cycleDurationSecs(); + } + + /// @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 estimateAutomationFeeWithCommittedOccupancy(_taskOccupancy, regState.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 + ) public view returns (uint128) { + ( , , uint64 durationSecs, ) = IAutomationController(regConfig.automationController()).getCycleInfo(); + return estimateAutomationFeeWithCommittedOccupancyInternal( + _taskOccupancy, + _committedOccupancy, + durationSecs + ); + } + + // ::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::: 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/automation_registry/src/CommonUtils.sol b/solidity/automation_registry/src/CommonUtils.sol new file mode 100644 index 0000000000..6de9e510ff --- /dev/null +++ b/solidity/automation_registry/src/CommonUtils.sol @@ -0,0 +1,99 @@ +// SPDX-License-Identifier: MIT +pragma solidity 0.8.27; + +import {LibRegistry} from "./LibRegistry.sol"; + +// Helper library used by AutomationRegistry and AutomationController. +library CommonUtils { + + /// @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 lockedFeeForNextCycle; + bytes32 txHash; + uint64 taskIndex; + uint64 registrationTime; + uint64 expiryTime; + address owner; + CommonUtils.TaskState state; + 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_lockedFeeForNextCycle >> 128); + + // --- Decode lockedFeeForNextCycle (lower 128 bits) --- + details.lockedFeeForNextCycle = uint128(t.automationFeeCapForCycle_lockedFeeForNextCycle); + + // --- Direct values --- + details.txHash = t.txHash; + details.owner = t.owner; + details.payloadTx = t.payloadTx; + details.auxData = t.auxData; + + // --- Decode packed uint256: taskIndex | registrationTime | expiryTime | state --- + details.taskIndex = uint64(t.taskIndex_registrationTime_expiryTime_state >> 192); + details.registrationTime = uint64(t.taskIndex_registrationTime_expiryTime_state >> 128); + details.expiryTime = uint64(t.taskIndex_registrationTime_expiryTime_state >> 64); + details.state = CommonUtils.TaskState(uint8(t.taskIndex_registrationTime_expiryTime_state >> 56)); + } + + + /// @notice Deposit and fee related accounting. + struct Deposit { + uint256 totalDepositedAutomationFees; + address coldWallet; + // uint256 totalLockedFees; // TO_DO + // 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; + } +} diff --git a/solidity/automation_registry/src/IAutomationController.sol b/solidity/automation_registry/src/IAutomationController.sol new file mode 100644 index 0000000000..94a8915102 --- /dev/null +++ b/solidity/automation_registry/src/IAutomationController.sol @@ -0,0 +1,31 @@ +// SPDX-License-Identifier: MIT +pragma solidity 0.8.27; + +import {CommonUtils} from "./CommonUtils.sol"; + +interface IAutomationController { + // Custom errors + error AddressCannotBeEOA(); + error CallerNotBlockMeta(); + error CallerNotVM(); + error ConfigUpdateFailed(); + error InconsistentTransitionState(); + error AddressCannotBeZero(); + error InvalidInputCycleIndex(); + error InvalidRegistryState(); + error OutOfOrderTaskProcessingRequest(); + error RefundFailed(); + error RefundDepositAndDropFailed(); + error RemoveTaskFailed(); + error TransferFailed(); + error UnlockLockedDepositFailed(); + error UpdateRegistryStateFailed(); + error UpdateTaskStateFailed(); + + // View functions + function getCycleInfo() external view returns(uint64, uint64, uint64, CommonUtils.CycleState); + function getTransitionInfo() external view returns (uint64, uint128); + + // State updating functions + function monitorCycleEnd() external; +} diff --git a/solidity/automation_registry/src/IAutomationRegistry.sol b/solidity/automation_registry/src/IAutomationRegistry.sol new file mode 100644 index 0000000000..b16fbacfe8 --- /dev/null +++ b/solidity/automation_registry/src/IAutomationRegistry.sol @@ -0,0 +1,117 @@ +// SPDX-License-Identifier: MIT +pragma solidity 0.8.27; + +import {CommonUtils} from "./CommonUtils.sol"; + +interface IAutomationRegistry { + // Custom errors + error AddressCannotBeEOA(); + error AddressCannotBeZero(); + error AddressAlreadyExists(); + error AddressDoesNotExist(); + error CallerNotController(); + error CycleNotStarted(); + error GasCommittedExceedsMaxGasCap(); + error InsufficientFeeCapForCycle(); + error InsufficentValueSent(); + error InvalidAuxDataLength(); + error InvalidExpiryTime(); + error InvalidGasPriceCap(); + error InvalidMaxGasAmount(); + error InvalidTaskDuration(); + error InvalidTxHash(); + error InvalidTaskType(); + error InvalidTypeForTask(); + error RegistrationDisabled(); + error TaskCapacityReached(); + error TaskExpiresBeforeNextCycle(); + error TransferFailed(); + error UnauthorizedAccount(); + error AlreadyEnabled(); + error AlreadyDisabled(); + error InvalidCycleDuration(); + error InvalidCongestionThreshold(); + error InvalidCongestionExponent(); + error InvalidSysTaskDuration(); + error InvalidRegistryMaxGasCap(); + error InvalidSysRegistryMaxGasCap(); + error InvalidTaskCapacity(); + error InvalidSysTaskCapacity(); + error UnacceptableRegistryMaxGasCap(); + error UnacceptableSysRegistryMaxGasCap(); + error ColdWalletNotSet(); + error InsufficientBalance(); + error RequestExceedsLockedBalance(); + error CycleTransitionInProgress(); + error TaskDoesNotExist(); + error UnsupportedTaskOperation(); + error AlreadyCancelled(); + error ErrorDepositRefund(); + error GasCommittedValueUnderflow(); + error SystemTaskDoesNotExist(); + error TaskIndexesCannotBeEmpty(); + error ErrorCycleFeeRefund(); + error InsufficientBalanceForRefund(); + error UnauthorizedCaller(); + error RegisteredTaskInvalidType(); + error AutomationNotEnabled(); + error TaskIndexNotFound(); + error TaskIndexNotUnique(); + + // 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 getCycleLockedFees() external view returns (uint256); + function getGasCommittedForNextCycle() external view returns (uint128); + function getRegistryMaxGasCap() external view returns (uint128); + 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); + function ifConfigBufferExists() external view returns (bool); + function getBufferCycleDurationSecs() external view returns (uint64); + function getVM() external returns (address); + function supraERC20() external view returns (address); + function isAutomationEnabled() external view returns (bool); + function calculateAutomationFeeMultiplierForCurrentCycleInternal() external view returns (uint128); + function calculateAutomationFeeMultiplierForCommittedOccupancy(uint128 _totalCommittedMaxGas) external view returns (uint128); + function calculateTaskFee( + CommonUtils.TaskState _state, + uint64 _expiryTime, + uint128 _maxGasAmount, + uint64 _potentialFeeTimeframe, + uint64 _currentTime, + uint128 _automationFeePerSec, + uint128 _registryMaxGasCap + ) external view returns (uint128); + function cycleDurationSecs() external view returns (uint64); + + + // State updating functions + function removeTask(uint64 _taskIndex, bool _removeFromSysReg) external; + function updateTaskState(uint64 _taskIndex, CommonUtils.TaskState _taskState) external; + function updateRegistryState( + uint128 _sysGasCommittedForNextCycle, + uint128 _gasCommittedForNextCycle, + uint128 _gasCommittedForNewCycle, + uint256 _lockedFees, + uint8 _state + ) external; + function applyPendingConfig() external; + function safeUnlockLockedDeposit( + uint64 _taskIndex, + uint128 _lockedDeposit + ) external returns (bool); + function refundDepositAndDrop( + uint64 _taskIndex, + address _taskOwner, + uint128 _refundableDeposit, + uint128 _lockedDeposit + ) external; + function refundTaskFees( + uint64 _taskIndex, + uint64 _currentTime, + uint256 _cycleLockedFees + ) external returns (uint256); +} diff --git a/solidity/automation_registry/src/LibController.sol b/solidity/automation_registry/src/LibController.sol new file mode 100644 index 0000000000..d9a2d94df2 --- /dev/null +++ b/solidity/automation_registry/src/LibController.sol @@ -0,0 +1,224 @@ + +// 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 | bool + uint256 index_startTime_durationSecs_state_ifTransitionStateExists; + 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 + ) internal { + _cycleInfo.index_startTime_durationSecs_state_ifTransitionStateExists = + (uint256(_index) << 192) | + (uint256(_startTime) << 128) | + (uint256(_durationSecs) << 64) | + (uint256(_cycleState) << 56); + } + + // index (uint64) | startTime (uint64) | durationSecs (uint64) | state (CycleState/uint8) | ifTransitionStateExists (bool) [stored at bit 55] + function index(AutomationCycleInfo storage cycle) internal view returns (uint64) { + return uint64(cycle.index_startTime_durationSecs_state_ifTransitionStateExists >> 192); + } + + function startTime(AutomationCycleInfo storage cycle) internal view returns (uint64) { + return uint64(cycle.index_startTime_durationSecs_state_ifTransitionStateExists >> 128); + } + + function durationSecs(AutomationCycleInfo storage cycle) internal view returns (uint64) { + return uint64(cycle.index_startTime_durationSecs_state_ifTransitionStateExists >> 64); + } + + function state(AutomationCycleInfo storage cycle) internal view returns (CommonUtils.CycleState) { + return CommonUtils.CycleState(uint8(cycle.index_startTime_durationSecs_state_ifTransitionStateExists >> 56)); + } + + function ifTransitionStateExists(AutomationCycleInfo storage cycle) internal view returns (bool) { + return ((cycle.index_startTime_durationSecs_state_ifTransitionStateExists >> 55) & 1) != 0; + } + + function setIndex(AutomationCycleInfo storage cycle, uint64 _index) internal { + cycle.index_startTime_durationSecs_state_ifTransitionStateExists &= ~(MAX_UINT64 << 192); // Clear old bits + cycle.index_startTime_durationSecs_state_ifTransitionStateExists |= uint256(_index) << 192; // Set new value + } + + function setStartTime(AutomationCycleInfo storage cycle, uint64 _startTime) internal { + cycle.index_startTime_durationSecs_state_ifTransitionStateExists &= ~(MAX_UINT64 << 128); + cycle.index_startTime_durationSecs_state_ifTransitionStateExists |= uint256(_startTime) << 128; + } + + function setDurationSecs(AutomationCycleInfo storage cycle, uint64 _durationSecs) internal { + cycle.index_startTime_durationSecs_state_ifTransitionStateExists &= ~(MAX_UINT64 << 64); + cycle.index_startTime_durationSecs_state_ifTransitionStateExists |= uint256(_durationSecs) << 64; + } + + function setState(AutomationCycleInfo storage cycle, uint8 _state) internal { + cycle.index_startTime_durationSecs_state_ifTransitionStateExists &= ~(MAX_UINT8 << 56); + cycle.index_startTime_durationSecs_state_ifTransitionStateExists |= uint256(_state) << 56; + } + + function setTransitionStateExists(AutomationCycleInfo storage cycle, bool exists) internal { + cycle.index_startTime_durationSecs_state_ifTransitionStateExists &= ~(uint256(1) << 55); + cycle.index_startTime_durationSecs_state_ifTransitionStateExists |= exists ? (uint256(1) << 55) : 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/automation_registry/src/LibRegistry.sol b/solidity/automation_registry/src/LibRegistry.sol new file mode 100644 index 0000000000..add087416b --- /dev/null +++ b/solidity/automation_registry/src/LibRegistry.sol @@ -0,0 +1,556 @@ + +// 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_UINT64 = type(uint64).max; + uint256 private constant MAX_UINT16 = type(uint16).max; + uint256 private constant MAX_UINT8 = type(uint8).max; + + // ::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::: ConfigBuffer ::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::: + + /// @notice Struct representing configuration buffer. + struct ConfigBuffer { + Config pendingConfig; + bool ifExists; + } + + // ::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::: RegistryConfig ::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::: + + /// @notice Configuration of the automation registry. + struct RegistryConfig { + // uint128 | uint128 + uint256 nextCycleRegistryMaxGasCap_nextCycleSysRegistryMaxGasCap; + // address | bool | bool + uint256 controller_registrationEnabled_automationEnabled; + address vm; + address supraERC20; + Config config; + } + + function createRegistryConfig( + uint128 _nextCycleRegistryMaxGasCap, + uint128 _nextCycleSysRegistryMaxGasCap, + bool _registrationEnabled, + bool _automationEnabled, + address _vm, + address _supraERC20, + 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) | automationEnabled (bool at bit 94) + // Sets controller as address(0) + rcfg.controller_registrationEnabled_automationEnabled = + (_registrationEnabled ? (uint256(1) << 95) : 0) | + (_automationEnabled ? (uint256(1) << 94) : 0); + + rcfg.vm = _vm; + rcfg.supraERC20 = _supraERC20; + + // Assign inner Config + rcfg.config = _config; + } + + // 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) [stored at bit 95] | automationEnabled (bool) [stored at bit 94] + function automationController(RegistryConfig storage r) internal view returns (address) { + return address(uint160(r.controller_registrationEnabled_automationEnabled >> 96)); + } + + function registrationEnabled(RegistryConfig storage r) internal view returns (bool) { + return (r.controller_registrationEnabled_automationEnabled >> 95) & 1 != 0; + } + + function automationEnabled(RegistryConfig storage r) internal view returns (bool) { + return (r.controller_registrationEnabled_automationEnabled >> 94) & 1 != 0; + } + + function setAutomationController(RegistryConfig storage r, address _controller) internal { + // clear top 160 bits + r.controller_registrationEnabled_automationEnabled &= ~((uint256(type(uint160).max)) << 96); + + // insert 160-bit address + r.controller_registrationEnabled_automationEnabled |= uint256(uint160(_controller)) << 96; + } + + function setRegistrationEnabled(RegistryConfig storage r, bool enabled) internal { + // clear bit 95 + r.controller_registrationEnabled_automationEnabled &= ~(uint256(1) << 95); + + // set bit 95 if enabled + r.controller_registrationEnabled_automationEnabled |= enabled ? (uint256(1) << 95) : 0; + } + + function setAutomationEnabled(RegistryConfig storage r, bool enabled) internal { + // clear bit 94 + r.controller_registrationEnabled_automationEnabled &= ~(uint256(1) << 94); + + // set bit 94 if enabled + r.controller_registrationEnabled_automationEnabled |= enabled ? (uint256(1) << 94) : 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; + } + + // :::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::: TaskMetadata :::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::: + + /// @notice Task metadata for individual automation tasks. + struct TaskMetadata { + // uint128 | uint128 + uint256 maxGasAmount_gasPriceCap; + + // uint128 | uint128 + uint256 automationFeeCapForCycle_lockedFeeForNextCycle; + + bytes32 txHash; + + // uint64 | uint64 | uint64 | TaskState + uint256 taskIndex_registrationTime_expiryTime_state; + + address owner; + + bytes payloadTx; + bytes[] auxData; + } + + function createTaskMetadata( + uint128 _maxGasAmount, + uint128 _gasPriceCap, + uint128 _automationFeeCapForCycle, + uint128 _lockedFeeForNextCycle, + bytes32 _txHash, + uint64 _taskIndex, + uint64 _registrationTime, + uint64 _expiryTime, + address _owner, + 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_lockedFeeForNextCycle = (uint256(_automationFeeCapForCycle) << 128) | uint256(_lockedFeeForNextCycle); + + // Direct fields + t.txHash = _txHash; + t.owner = _owner; + t.payloadTx = _payloadTx; + t.auxData = _auxData; + + // Pack (uint64 | uint64 | uint64 | uint8) + // Layout: [taskIndex | registrationTime | expiryTime | state] + t.taskIndex_registrationTime_expiryTime_state = + (uint256(_taskIndex) << 192) | + (uint256(_registrationTime) << 128) | + (uint256(_expiryTime) << 64) | + (uint256(uint8(_state)) << 56); + } + + // 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) | lockedFeeForNextCycle (uint128) + function automationFeeCapForCycle(TaskMetadata storage t) internal view returns (uint128) { + return uint128(t.automationFeeCapForCycle_lockedFeeForNextCycle >> 128); + } + + function lockedFeeForNextCycle(TaskMetadata storage t) internal view returns (uint128) { + return uint128(t.automationFeeCapForCycle_lockedFeeForNextCycle); + } + + function setAutomationFeeCapForCycle(TaskMetadata storage t, uint128 _value) internal { + t.automationFeeCapForCycle_lockedFeeForNextCycle &= MAX_UINT128; + t.automationFeeCapForCycle_lockedFeeForNextCycle |= uint256(_value) << 128; + } + + function setLockedFeeForNextCycle(TaskMetadata storage t, uint128 _value) internal { + t.automationFeeCapForCycle_lockedFeeForNextCycle &= (MAX_UINT128 << 128); + t.automationFeeCapForCycle_lockedFeeForNextCycle |= uint256(_value); + } + + // taskIndex (uint64) | registrationTime (uint64) | expiryTime (uint64) | state (TaskState/uint8) + function taskIndex(TaskMetadata storage t) internal view returns (uint64) { + return uint64(t.taskIndex_registrationTime_expiryTime_state >> 192); + } + + function registrationTime(TaskMetadata storage t) internal view returns (uint64) { + return uint64(t.taskIndex_registrationTime_expiryTime_state >> 128); + } + + function expiryTime(TaskMetadata storage t) internal view returns (uint64) { + return uint64(t.taskIndex_registrationTime_expiryTime_state >> 64); + } + + function state(TaskMetadata storage t) internal view returns (CommonUtils.TaskState) { + return CommonUtils.TaskState(uint8(t.taskIndex_registrationTime_expiryTime_state >> 56)); + } + + function setTaskIndex(TaskMetadata storage t, uint64 _value) internal { + t.taskIndex_registrationTime_expiryTime_state &= ~(MAX_UINT64 << 192); + t.taskIndex_registrationTime_expiryTime_state |= uint256(_value) << 192; + } + + function setRegistrationTime(TaskMetadata storage t, uint64 _value) internal { + t.taskIndex_registrationTime_expiryTime_state &= ~(MAX_UINT64 << 128); + t.taskIndex_registrationTime_expiryTime_state |= uint256(_value) << 128; + } + + function setExpiryTime(TaskMetadata storage t, uint64 _value) internal { + t.taskIndex_registrationTime_expiryTime_state &= ~(MAX_UINT64 << 64); + t.taskIndex_registrationTime_expiryTime_state |= uint256(_value) << 64; + } + + function setState(TaskMetadata storage t, uint8 _value) internal { + t.taskIndex_registrationTime_expiryTime_state &= ~(MAX_UINT8 << 56); + t.taskIndex_registrationTime_expiryTime_state |= uint256(_value) << 56; + } + + // :::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::: RegistryState :::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::: + + /// @notice Tracks per-cycle automation state and task indexes. + struct RegistryState { + uint256 cycleLockedFees; + + // uint128 | uint128 + uint256 gasCommittedForNextCycle_gasCommittedForThisCycle; + + 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 + } + + // gasCommittedForNextCycle (uint128) | gasCommittedForThisCycle (uint128) + function gasCommittedForNextCycle(RegistryState storage r) internal view returns (uint128) { + return uint128(r.gasCommittedForNextCycle_gasCommittedForThisCycle >> 128); + } + + function gasCommittedForThisCycle(RegistryState storage r) internal view returns (uint128) { + return uint128(r.gasCommittedForNextCycle_gasCommittedForThisCycle); + } + + function setGasCommittedForNextCycle(RegistryState 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(RegistryState 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); + } + + // ::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::: RegistryStateSystemTasks ::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::: + + /// @notice Tracks per-cycle automation state and task indexes for system tasks. + struct RegistryStateSystemTasks { + // uint128 | uint128 + uint256 gasCommittedForNextCycle_gasCommittedForThisCycle; + + EnumerableSet.UintSet taskIds; + EnumerableSet.AddressSet authorizedAccounts; + } + + // gasCommittedForNextCycle (uint128) | gasCommittedForThisCycle (uint128) + function gasCommittedForNextCycle(RegistryStateSystemTasks storage s) internal view returns (uint128){ + return uint128(s.gasCommittedForNextCycle_gasCommittedForThisCycle >> 128); + } + + function gasCommittedForThisCycle(RegistryStateSystemTasks storage s) internal view returns (uint128){ + return uint128(s.gasCommittedForNextCycle_gasCommittedForThisCycle); + } + + function setGasCommittedForNextCycle(RegistryStateSystemTasks storage s, uint128 _value) internal { + // Clear upper 128 bits + s.gasCommittedForNextCycle_gasCommittedForThisCycle &= MAX_UINT128; // mask = lower 128 bits all 1s + + // Insert new upper 128 bits + s.gasCommittedForNextCycle_gasCommittedForThisCycle |= uint256(_value) << 128; + } + + function setGasCommittedForThisCycle(RegistryStateSystemTasks storage s, uint128 _value) internal { + // Clear lower 128 bits + s.gasCommittedForNextCycle_gasCommittedForThisCycle &= MAX_UINT128 << 128; // mask = upper 128 bits all 1s + + // Insert new lower 128 bits + s.gasCommittedForNextCycle_gasCommittedForThisCycle |= uint256(_value); + } + + /// @notice Deposit and fee related accounting. + struct Deposit { + uint256 totalDepositedAutomationFees; + address coldWallet; + } + + /// @notice Struct representing a stopped task. + struct TaskStopped { + uint64 taskIndex; + uint128 depositRefund; + uint128 cycleFeeRefund; + bytes32 txHash; + } + + /// @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); + } +} + From 7a6c635cbd160643e0b1a2e17c164a7399ecdeb9 Mon Sep 17 00:00:00 2001 From: Udit Yadav Date: Wed, 10 Dec 2025 11:33:46 +0530 Subject: [PATCH 02/31] added test cases for registry and controller smart contract --- .../test/AutomationController.t.sol | 276 +++ .../test/AutomationRegistry.t.sol | 1882 +++++++++++++++++ 2 files changed, 2158 insertions(+) create mode 100644 solidity/automation_registry/test/AutomationController.t.sol create mode 100644 solidity/automation_registry/test/AutomationRegistry.t.sol diff --git a/solidity/automation_registry/test/AutomationController.t.sol b/solidity/automation_registry/test/AutomationController.t.sol new file mode 100644 index 0000000000..2d546ae9bc --- /dev/null +++ b/solidity/automation_registry/test/AutomationController.t.sol @@ -0,0 +1,276 @@ +// 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 {AutomationController} from "../src/AutomationController.sol"; +import {IAutomationController} from "../src/IAutomationController.sol"; +import {ERC20Supra} from "../src/ERC20Supra.sol"; +import {BlockMeta} from "../src/BlockMeta.sol"; +import {CommonUtils} from "../src/CommonUtils.sol"; + +contract AutomationControllerTest is Test { + AutomationRegistry registry; // AutomationRegistry instance on proxy address + AutomationController controller; // AutomationController instance on proxy address + BlockMeta blockMeta; // BlockMeta instance on proxy address + ERC20Supra supraERC20; // ERC20Supra contract + + address admin = address(0xA11CE); + address vmAddress = address(0x99); + 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 ERC20Supra, BlockMeta, AutomationRegistry and AutomationController contracts. + function setUp() public { + vm.deal(alice, 100 ether); + + // Deploy ERC20Supra + vm.prank(admin); + supraERC20 = new ERC20Supra(msg.sender); + + // Deploy AutomationRegistry proxy + registry = AutomationRegistry(deployRegistry(address(supraERC20))); + + // Deploy BlockMeta proxy + blockMeta = BlockMeta(deployBlockMeta()); + + // Deploy AutomationController proxy + controller = AutomationController(deployController(address(registry), address(blockMeta))); + } + + /// @dev Helper function to deploy AutomationRegistry proxy + function deployRegistry(address _supraERC20) private returns (address) { + vm.startPrank(admin); + AutomationRegistry impl = new AutomationRegistry(); + + bytes memory initData = abi.encodeCall( + AutomationRegistry.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 + vmAddress, // vm address + address(_supraERC20) // supraERC20 address + ) + ); + + ERC1967Proxy proxy = new ERC1967Proxy(address(impl), initData); + vm.stopPrank(); + + return address(proxy); + } + + /// @dev Helper function to deploy BlockMeta proxy + function deployBlockMeta() private returns (address) { + vm.startPrank(admin); + BlockMeta impl = new BlockMeta(); + bytes memory initData = abi.encodeCall(BlockMeta.initialize, ()); + ERC1967Proxy proxy = new ERC1967Proxy(address(impl), initData); + vm.stopPrank(); + + return address(proxy); + } + + /// @dev Helper function to deploy AutomationController proxy + function deployController(address _registry, address _blockMeta) private returns (address) { + vm.startPrank(admin); + AutomationController impl = new AutomationController(); + bytes memory initData = abi.encodeCall(AutomationController.initialize,(_registry, _blockMeta)); + ERC1967Proxy proxy = new ERC1967Proxy(address(impl), initData); + vm.stopPrank(); + + return address(proxy); + } + + /// @dev Test to ensure all state variables are initialized correctly. + function testInitialize() public view { + assertEq(controller.owner(), admin); + assertEq(address(controller.registry()), address(registry)); + assertEq(controller.blockMeta(), address(blockMeta)); + + (uint64 index, uint64 startTime, uint64 durationSecs, CommonUtils.CycleState state) = controller.getCycleInfo(); + assertEq(index, 1); + assertEq(startTime, block.timestamp); + assertEq(durationSecs, registry.cycleDurationSecs()); + assertEq(uint8(state), uint8(CommonUtils.CycleState.STARTED)); + } + + /// @dev Test to ensure initialize reverts if reinitialized. + function testInitializeRevertsIfReinitialized() public { + vm.expectRevert(Initializable.InvalidInitialization.selector); + + vm.prank(admin); + controller.initialize(address(registry), address(blockMeta)); + } + + /// @dev Test to ensure initialize reverts if registry address is zero. + function testInitializeRevertsIfRegistryZero() public { + // Deploy BlockMeta proxy + address blockMetaProxy = deployBlockMeta(); + + // Deploy AutomationController proxy + AutomationController impl = new AutomationController(); + bytes memory initData = abi.encodeCall(AutomationController.initialize,(address(0), blockMetaProxy)); + + vm.expectRevert(IAutomationController.AddressCannotBeZero.selector); + new ERC1967Proxy(address(impl), initData); + } + + /// @dev Test to ensure initialize reverts if registry address is EOA. + function testInitializeRevertsIfRegistryEoa() public { + // Deploy BlockMeta proxy + address blockMetaProxy = deployBlockMeta(); + + // Deploy AutomationController proxy + AutomationController impl = new AutomationController(); + bytes memory initData = abi.encodeCall(AutomationController.initialize,(alice, blockMetaProxy)); + + vm.expectRevert(IAutomationController.AddressCannotBeEOA.selector); + new ERC1967Proxy(address(impl), initData); + } + + /// @dev Test to ensure initialize reverts if blockMeta address is zero. + function testInitializeRevertsIfBlockMetaZero() public { + // Deploy AutomationRegistry proxy + address registryProxy = deployRegistry(address(supraERC20)); + + // Deploy AutomationController proxy + AutomationController impl = new AutomationController(); + bytes memory initData = abi.encodeCall(AutomationController.initialize,(registryProxy, address(0))); + + vm.expectRevert(IAutomationController.AddressCannotBeZero.selector); + new ERC1967Proxy(address(impl), initData); + } + + /// @dev Test to ensure initialize reverts if blockMeta address is EOA. + function testInitializeRevertsIfBlockMetaEoa() public { + // Deploy AutomationRegistry proxy + address registryProxy = deployRegistry(address(supraERC20)); + + // Deploy AutomationController proxy + AutomationController impl = new AutomationController(); + bytes memory initData = abi.encodeCall(AutomationController.initialize,(registryProxy, alice)); + + vm.expectRevert(IAutomationController.AddressCannotBeEOA.selector); + new ERC1967Proxy(address(impl), initData); + } + + /// @dev Test to ensure 'setRegistry' reverts if caller is not owner. + function testSetRegistryRevertsIfNotOwner() public { + address newRegistry = deployRegistry(address(supraERC20)); + + vm.expectRevert(abi.encodeWithSelector(OwnableUpgradeable.OwnableUnauthorizedAccount.selector,alice)); + + vm.prank(alice); + controller.setRegistry(newRegistry); + } + + /// @dev Test to ensure 'setRegistry' reverts if address is zero. + function testSetRegistryRevertsIfAddressZero() public { + vm.expectRevert(IAutomationController.AddressCannotBeZero.selector); + + vm.prank(admin); + controller.setRegistry(address(0)); + } + + /// @dev Test to ensure 'setRegistry' reverts if address is EOA. + function testSetRegistryRevertsIfAddressEoa() public { + vm.expectRevert(IAutomationController.AddressCannotBeEOA.selector); + + vm.prank(admin); + controller.setRegistry(alice); + } + + /// @dev Test to ensure 'setRegistry' updates the registry address. + function testSetRegistry() public { + address newRegistry = deployRegistry(address(supraERC20)); + + vm.prank(admin); + controller.setRegistry(newRegistry); + + assertEq(address(controller.registry()), newRegistry); + } + + /// @dev Test to ensure 'setRegistry' emits event 'RegistryUpdated'. + function testSetRegistryEmitsEvent() public { + address newRegistry = deployRegistry(address(supraERC20)); + + vm.expectEmit(true, true, false, false); + emit AutomationController.RegistryUpdated(address(controller.registry()), newRegistry); + + vm.prank(admin); + controller.setRegistry(newRegistry); + } + + /// @dev Test to ensure 'setBlockMeta' reverts if caller is not owner. + function testSetBlockMetaRevertsIfNotOwner() public { + address newBlockMeta = deployBlockMeta(); + + vm.expectRevert(abi.encodeWithSelector(OwnableUpgradeable.OwnableUnauthorizedAccount.selector,alice)); + + vm.prank(alice); + controller.setBlockMeta(newBlockMeta); + } + + /// @dev Test to ensure 'setBlockMeta' reverts if address is zero. + function testSetBlockMetaRevertsIfAddressZero() public { + vm.expectRevert(IAutomationController.AddressCannotBeZero.selector); + + vm.prank(admin); + controller.setBlockMeta(address(0)); + } + + /// @dev Test to ensure 'setBlockMeta' reverts if address is EOA. + function testSetBlockMetaRevertsIfAddressEoa() public { + vm.expectRevert(IAutomationController.AddressCannotBeEOA.selector); + + vm.prank(admin); + controller.setBlockMeta(alice); + } + + /// @dev Test to ensure 'setBlockMeta' updates the blockMeta address. + function testSetBlockMeta() public { + address newBlockMeta = deployBlockMeta(); + + vm.prank(admin); + controller.setBlockMeta(newBlockMeta); + + assertEq(controller.blockMeta(), newBlockMeta); + } + + /// @dev Test to ensure 'setBlockMeta' emits event 'BlockMetaAddressUpdated'. + function testSetBlockMetaEmitsEvent() public { + address newBlockMeta = deployBlockMeta(); + + vm.expectEmit(true, true, false, false); + emit AutomationController.BlockMetaAddressUpdated(controller.blockMeta() , newBlockMeta); + + vm.prank(admin); + controller.setBlockMeta(newBlockMeta); + } + + /// @dev Test to ensure 'processTasks' reverts if caller is not VM address. + function testProcessTasksRevertsIfNotVm() public { + uint64[] memory tasks = new uint64[](1); + tasks[0] = 0; + + vm.expectRevert(IAutomationController.CallerNotVM.selector); + + vm.prank(admin); + controller.processTasks(1, tasks); + } +} \ No newline at end of file diff --git a/solidity/automation_registry/test/AutomationRegistry.t.sol b/solidity/automation_registry/test/AutomationRegistry.t.sol new file mode 100644 index 0000000000..d65e0bbbf0 --- /dev/null +++ b/solidity/automation_registry/test/AutomationRegistry.t.sol @@ -0,0 +1,1882 @@ +// 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 {AutomationController} from "../src/AutomationController.sol"; +import {IAutomationRegistry} from "../src/IAutomationRegistry.sol"; +import {ERC20Supra} from "../src/ERC20Supra.sol"; +import {BlockMeta} from "../src/BlockMeta.sol"; +import {LibRegistry} from "../src/LibRegistry.sol"; +import {CommonUtils} from "../src/CommonUtils.sol"; + +contract AutomationRegistryTest is Test { + AutomationRegistry impl; // implementation logic contract + AutomationRegistry registry; // registry instance on proxy address + ERC1967Proxy proxy; // proxy contract + ERC20Supra supraERC20; // ERC20Supra contract + + address admin = address(0xA11CE); + address vmAddress = address(0x99); + 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 ERC20Supra and AutomationRegistry contracts. + /// @dev Initializes AutomationRegistry with required parameters. + function setUp() public { + vm.deal(alice, 100 ether); + + vm.startPrank(admin); + supraERC20 = new ERC20Supra(msg.sender); + impl = new AutomationRegistry(); + + bytes memory initData = abi.encodeCall( + AutomationRegistry.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 + vmAddress, // vm address + address(supraERC20) // supraERC20 address + ) + ); + + proxy = new ERC1967Proxy(address(impl), initData); + registry = AutomationRegistry(address(proxy)); + vm.stopPrank(); + } + + /// @dev Test to ensure all state variables are initialized correctly. + function testInitialize() public view { + assertEq(registry.owner(), admin); + + assertEq(registry.getNextCycleRegistryMaxGasCap(), 10_000_000); + assertEq(registry.getNextCycleSysRegistryMaxGasCap(), 5_000_000); + assertEq(registry.getAutomationController(), address(0)); + assertTrue(registry.isRegistrationEnabled()); + assertTrue(registry.isAutomationEnabled()); + assertEq(registry.getVM(), vmAddress); + assertEq(registry.supraERC20(), address(supraERC20)); + + LibRegistry.ConfigDetails memory config = registry.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); + + assertEq(registry.owner(), admin); + } + + /// @dev Test to ensure reinitialization fails. + function testInitializeRevertsIfReinitialized() public { + vm.expectRevert(Initializable.InvalidInitialization.selector); + + vm.prank(admin); + registry.initialize( + 3600, 10_000_000, 0.001 ether, 0.002 ether, 50, 0.002 ether, 2, + 500, 2000, 3600, 5_000_000, 500, vmAddress, address(supraERC20) + ); + } + /// @dev Test to ensure initialization fails if zero address is passed as VM address. + function testInitializeRevertsIfVmAddressZero() public { + AutomationRegistry implementation = new AutomationRegistry(); + + bytes memory initData = abi.encodeCall( + AutomationRegistry.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 address as zero + address(supraERC20) + ) + ); + + vm.expectRevert(IAutomationRegistry.AddressCannotBeZero.selector); + new ERC1967Proxy(address(implementation), initData); + } + + /// @dev Test to ensure initialization fails if ERC20Supra address is zero. + function testInitializeRevertsIfSupraERC20IsZero() public { + AutomationRegistry implementation = new AutomationRegistry(); + + bytes memory initData = abi.encodeCall( + AutomationRegistry.initialize, + ( + 3600, 10_000_000, 0.001 ether, 0.002 ether, 50, 0.002 ether, + 2, 500, 2000, 3600, 5_000_000, 500, vmAddress, + address(0) // address(0) as ERC20Supra + ) + ); + + vm.expectRevert(IAutomationRegistry.AddressCannotBeZero.selector); + new ERC1967Proxy(address(implementation), initData); + } + + /// @dev Test to ensure initialization fails if EOA is passed as ERC20Supra address. + function testInitializeRevertsIfSupraERC20IsEoa() public { + AutomationRegistry implementation = new AutomationRegistry(); + + bytes memory initData = abi.encodeCall( + AutomationRegistry.initialize, + ( + 3600, 10_000_000, 0.001 ether, 0.002 ether, 50, 0.002 ether, + 2, 500, 2000, 3600, 5_000_000, 500, vmAddress, + admin // EOA address as ERC20Supra + ) + ); + + vm.expectRevert(IAutomationRegistry.AddressCannotBeEOA.selector); + new ERC1967Proxy(address(implementation), initData); + } + + /// @dev Test to ensure initialization fails if task duration is <= cycle duration. + function testInitializeRevertsIfInvalidTaskDuration() public { + AutomationRegistry implementation = new AutomationRegistry(); + + bytes memory initData = abi.encodeCall( + AutomationRegistry.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, vmAddress, address(supraERC20) + ) + ); + + vm.expectRevert(IAutomationRegistry.InvalidTaskDuration.selector); + new ERC1967Proxy(address(implementation), initData); + } + + /// @dev Test to ensure initialization fails if registry max gas cap is zero. + function testInitializeRevertsIfRegistryMaxGasCapZero() public { + AutomationRegistry implementation = new AutomationRegistry(); + + bytes memory initData = abi.encodeCall( + AutomationRegistry.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, vmAddress, address(supraERC20) + ) + ); + + vm.expectRevert(IAutomationRegistry.InvalidRegistryMaxGasCap.selector); + new ERC1967Proxy(address(implementation), initData); + } + + /// @dev Test to ensure initialization fails if congestion threshold percentage is > 100. + function testInitializeRevertsIfInvalidCongestionThreshold() public { + AutomationRegistry implementation = new AutomationRegistry(); + + bytes memory initData = abi.encodeCall( + AutomationRegistry.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, vmAddress, address(supraERC20) + ) + ); + + vm.expectRevert(IAutomationRegistry.InvalidCongestionThreshold.selector); + new ERC1967Proxy(address(implementation), initData); + } + + /// @dev Test to ensure initialization fails if congestion exponent is 0. + function testInitializeRevertsIfCongestionExponentZero() public { + AutomationRegistry implementation = new AutomationRegistry(); + + bytes memory initData = abi.encodeCall( + AutomationRegistry.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, vmAddress, address(supraERC20) + ) + ); + + vm.expectRevert(IAutomationRegistry.InvalidCongestionExponent.selector); + new ERC1967Proxy(address(implementation), initData); + } + + /// @dev Test to ensure initialization fails if task capacity is 0. + function testInitializeRevertsIfTaskCapacityZero() public { + AutomationRegistry implementation = new AutomationRegistry(); + + bytes memory initData = abi.encodeCall( + AutomationRegistry.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, vmAddress, address(supraERC20) + ) + ); + + vm.expectRevert(IAutomationRegistry.InvalidTaskCapacity.selector); + new ERC1967Proxy(address(implementation), initData); + } + + /// @dev Test to ensure initialization fails if cycle duration is 0. + function testInitializeRevertsIfCycleDurationZero() public { + AutomationRegistry implementation = new AutomationRegistry(); + + bytes memory initData = abi.encodeCall( + AutomationRegistry.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, vmAddress, address(supraERC20) + ) + ); + + vm.expectRevert(IAutomationRegistry.InvalidCycleDuration.selector); + new ERC1967Proxy(address(implementation), initData); + } + + /// @dev Test to ensure initialization fails if system task duration is <= cycle duration. + function testInitializeRevertsIfInvalidSysTaskDuration() public { + AutomationRegistry implementation = new AutomationRegistry(); + + bytes memory initData = abi.encodeCall( + AutomationRegistry.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, vmAddress, address(supraERC20) + ) + ); + + vm.expectRevert(IAutomationRegistry.InvalidSysTaskDuration.selector); + new ERC1967Proxy(address(implementation), initData); + } + + /// @dev Test to ensure initialization fails if system registry max gas cap is 0. + function testInitializeRevertsIfSysRegistryMaxGasCapZero() public { + AutomationRegistry implementation = new AutomationRegistry(); + + bytes memory initData = abi.encodeCall( + AutomationRegistry.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, vmAddress, address(supraERC20) + ) + ); + + vm.expectRevert(IAutomationRegistry.InvalidSysRegistryMaxGasCap.selector); + new ERC1967Proxy(address(implementation), initData); + } + + /// @dev Test to ensure initialization fails if system task capacity is 0. + function testInitializeRevertsIfSysTaskCapacityZero() public { + AutomationRegistry implementation = new AutomationRegistry(); + + bytes memory initData = abi.encodeCall( + AutomationRegistry.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 + vmAddress, address(supraERC20) + ) + ); + + vm.expectRevert(IAutomationRegistry.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); + registry.disableRegistration(); + + assertFalse(registry.isRegistrationEnabled()); + } + + /// @dev Test to ensure 'disableRegistration' emits event 'TaskRegistrationDisabled'. + function testDisableRegistrationEmitsEvent() public { + vm.expectEmit(true, false, false, false); + emit AutomationRegistry.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(IAutomationRegistry.AlreadyDisabled.selector); + + vm.prank(admin); + registry.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); + registry.disableRegistration(); + } + + // :::::::::::::::::::::::::::::::::::::::::::::::::::::: Tests related to 'enableRegistration' :::::::::::::::::::::::::::::::::::::::::::::::::::::: + + /// @dev Test to ensure 'enableRegistration' enables the registration. + function testEnableRegistration() public { + // Disable registration + testDisableRegistration(); + + // Enable registration + vm.prank(admin); + registry.enableRegistration(); + + assertTrue(registry.isRegistrationEnabled()); + } + + /// @dev Test to ensure 'enableRegistration' emits event 'TaskRegistrationEnabled'. + function testEnableRegistrationEmitsEvent() public { + // Disable registration + testDisableRegistration(); + + vm.expectEmit(true, false, false, false); + emit AutomationRegistry.TaskRegistrationEnabled(true); + + // Enable registration + vm.prank(admin); + registry.enableRegistration(); + } + + /// @dev Test to ensure 'enableRegistration' reverts if registration is already enabled. + function testEnableRegistrationRevertsIfAlreadyEnabled() public { + // Already enabled in initialize() + vm.expectRevert(IAutomationRegistry.AlreadyEnabled.selector); + + vm.prank(admin); + registry.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); + registry.enableRegistration(); + } + + // :::::::::::::::::::::::::::::::::::::::::::::::::::::: Tests related to 'disableAutomation' :::::::::::::::::::::::::::::::::::::::::::::::::::::: + + /// @dev Test to ensure 'disableAutomation' disables the automation. + function testDisableAutomation() public { + // Already enabled in initialize() + vm.prank(admin); + registry.disableAutomation(); + + assertFalse(registry.isAutomationEnabled()); + } + + /// @dev Test to ensure 'disableAutomation' emits event 'AutomationDisabled'. + function testDisableAutomationEmitsEvent() public { + vm.expectEmit(true, false, false, false); + emit AutomationRegistry.AutomationDisabled(false); + + testDisableAutomation(); + } + + /// @dev Test to ensure 'disableAutomation' reverts if automation is already disabled. + function testDisableAutomationRevertsIfAlreadyDisabled() public { + // Disable automation + testDisableAutomation(); + + // Disable again → revert + vm.expectRevert(IAutomationRegistry.AlreadyDisabled.selector); + + vm.prank(admin); + registry.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); + registry.disableAutomation(); + } + + // :::::::::::::::::::::::::::::::::::::::::::::::::::::: Tests related to 'enableAutomation' :::::::::::::::::::::::::::::::::::::::::::::::::::::: + + /// @dev Test to ensure 'enableAutomation' enables the automation. + function testEnableAutomation() public { + // Disable automation + testDisableAutomation(); + + // Enable automation + vm.prank(admin); + registry.enableAutomation(); + + assertTrue(registry.isAutomationEnabled()); + } + + /// @dev Test to ensure 'enableAutomation' emits event 'AutomationEnabled'. + function testEnableAutomationEmitsEvent() public { + // Disable automation + testDisableAutomation(); + + vm.expectEmit(true, false, false, false); + emit AutomationRegistry.AutomationEnabled(true); + + vm.prank(admin); + registry.enableAutomation(); + } + + /// @dev Test to ensure 'enableAutomation' reverts if automation is already enabled. + function testEnableAutomationRevertsIfAlreadyEnabled() public { + // Already enabled in initialize() + vm.expectRevert(IAutomationRegistry.AlreadyEnabled.selector); + + vm.prank(admin); + registry.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); + registry.enableAutomation(); + } + + // :::::::::::::::::::::::::::::::::::::::::::::::::::::: Tests related to 'setAutomationController' :::::::::::::::::::::::::::::::::::::::::::::::::::::: + + /// @dev Helper function that deploys AutomationController and returns its address. + function deployAutomationController() internal returns (address) { + // Deploy BlockMeta proxy + BlockMeta blockMetaImpl = new BlockMeta(); + bytes memory blockMetaInitData = abi.encodeCall(BlockMeta.initialize, ()); + ERC1967Proxy blockMetaProxy = new ERC1967Proxy(address(blockMetaImpl), blockMetaInitData); + + // Deploy AutomationController proxy + AutomationController controllerImpl = new AutomationController(); + bytes memory controllerInitData = abi.encodeCall(AutomationController.initialize,(address(registry), address(blockMetaProxy))); + 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); + registry.setAutomationController(controller); + + assertEq(registry.getAutomationController(), controller); + } + + /// @dev Test to ensure 'setAutomationController' emits event 'AutomationControllerUpdated'. + function testSetAutomationControllerEmitsEvent() public { + address oldController = registry.getAutomationController(); + address controller = deployAutomationController(); + + vm.expectEmit(true, true, false, false); + emit AutomationRegistry.AutomationControllerUpdated(oldController, controller); + + vm.prank(admin); + registry.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); + registry.setAutomationController(controller); + } + + /// @dev Test to ensure 'setAutomationController' reverts if zero address is passed. + function testSetAutomationControllerRevertsIfZeroAddress() public { + vm.expectRevert(IAutomationRegistry.AddressCannotBeZero.selector); + + vm.prank(admin); + registry.setAutomationController(address(0)); + } + + /// @dev Test to ensure 'setAutomationController' reverts if EOA is passed. + function testSetAutomationControllerRevertsIfEoa() public { + vm.expectRevert(IAutomationRegistry.AddressCannotBeEOA.selector); + + vm.prank(admin); + registry.setAutomationController(alice); + } + + // :::::::::::::::::::::::::::::::::::::::::::::::::::::: Tests related to 'setVM' :::::::::::::::::::::::::::::::::::::::::::::::::::::: + + /// @dev Test to ensure 'setVM' updates the VM address. + function testSetVm() public { + address newVM = address(0x100); + + vm.prank(admin); + registry.setVM(newVM); + + assertEq(registry.getVM(), newVM); + } + + /// @dev Test to ensure 'setVM' emits event 'VmAddressUpdated'. + function testSetVmEmitsEvent() public { + address oldVM = registry.getVM(); + address newVM = address(0x100); + + vm.expectEmit(true, true, false, false); + emit AutomationRegistry.VmAddressUpdated(oldVM, newVM); + + vm.prank(admin); + registry.setVM(newVM); + } + + /// @dev Test to ensure 'setVM' reverts if zero address is passed. + function testSetVmRevertsIfZeroAddress() public { + vm.expectRevert(IAutomationRegistry.AddressCannotBeZero.selector); + + vm.prank(admin); + registry.setVM(address(0)); + } + + /// @dev Test to ensure 'setVM' reverts if caller is not owner. + function testSetVmRevertsIfNotOwner() public { + vm.expectRevert(abi.encodeWithSelector(OwnableUpgradeable.OwnableUnauthorizedAccount.selector, alice)); + + vm.prank(alice); + registry.setVM(address(0x100)); + } + + // :::::::::::::::::::::::::::::::::::::::::::::::::::::: Tests related to 'setSupraERC20' :::::::::::::::::::::::::::::::::::::::::::::::::::::: + + /// @dev Test to ensure 'setSupraERC20' updates the ERC20Supra address. + function testSetSupraERC20() public { + ERC20Supra supra = new ERC20Supra(msg.sender); + + vm.prank(admin); + registry.setSupraERC20(address(supra)); + + assertEq(registry.supraERC20(), address(supra)); + } + + /// @dev Test to ensure 'setSupraERC20' emits event 'SupraERC20Updated'. + function testSetSupraERC20EmitsEvent() public { + address oldAddr = registry.supraERC20(); + ERC20Supra supra = new ERC20Supra(msg.sender); + + vm.expectEmit(true, true, false, false); + emit AutomationRegistry.SupraERC20Updated(oldAddr, address(supra)); + + vm.prank(admin); + registry.setSupraERC20(address(supra)); + } + + /// @dev Test to ensure 'setSupraERC20' reverts if zero address is passed. + function testSetSupraERC20RevertsIfZeroAddress() public { + vm.expectRevert(IAutomationRegistry.AddressCannotBeZero.selector); + + vm.prank(admin); + registry.setSupraERC20(address(0)); + } + + /// @dev Test to ensure 'setSupraERC20' reverts if EOA is passed. + function testSetSupraERC20RevertsIfEoa() public { + vm.expectRevert(IAutomationRegistry.AddressCannotBeEOA.selector); + + vm.prank(admin); + registry.setSupraERC20(alice); + } + + /// @dev Test to ensure 'setSupraERC20' reverts if caller is not owner. + function testSetSupraERC20RevertsIfNotOwner() public { + ERC20Supra supra = new ERC20Supra(msg.sender); + + vm.expectRevert(abi.encodeWithSelector(OwnableUpgradeable.OwnableUnauthorizedAccount.selector, alice)); + + vm.prank(alice); + registry.setSupraERC20(address(supra)); + } + + // :::::::::::::::::::::::::::::::::::::::::::::::::::::: Tests related to 'setColdWallet' :::::::::::::::::::::::::::::::::::::::::::::::::::::: + + /// @dev Test to ensure 'setColdWallet' updates the cold wallet address. + function testSetColdWallet() public { + vm.prank(admin); + registry.setColdWallet(address(0x1001)); + + assertEq(registry.getColdWallet(), address(0x1001)); + } + + /// @dev Test to ensure 'setColdWallet' emits event 'ColdWalletUpdated'. + function testSetColdWalletEmitsEvent() public { + address oldColdWallet = registry.getColdWallet(); + + vm.expectEmit(true, true, false, false); + emit AutomationRegistry.ColdWalletUpdated(oldColdWallet, address(0x1001)); + + vm.prank(admin); + registry.setColdWallet(address(0x1001)); + } + + /// @dev Test to ensure 'setColdWallet' reverts if zero address is passed. + function testSetColdWalletRevertsIfZeroAddress() public { + vm.expectRevert(IAutomationRegistry.AddressCannotBeZero.selector); + + vm.prank(admin); + registry.setColdWallet(address(0)); + } + + /// @dev Test to ensure 'setColdWallet' reverts if caller is not owner. + function testSetColdWalletRevertsIfNotOwner() public { + vm.expectRevert(abi.encodeWithSelector(OwnableUpgradeable.OwnableUnauthorizedAccount.selector, alice)); + + vm.prank(alice); + registry.setColdWallet(address(0x1001)); + } + + // :::::::::::::::::::::::::::::::::::::::::::::::::::::: 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 'updateConfigBuffer' :::::::::::::::::::::::::::::::::::::::::::::::::::::: + + /// @dev Helper function that returns a valid config. + function validConfig() internal pure returns (LibRegistry.ConfigDetails memory cfg) { + cfg = LibRegistry.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 { + LibRegistry.ConfigDetails memory cfg = validConfig(); + + vm.prank(admin); + registry.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 + LibRegistry.ConfigDetails memory pendingCfg = registry.getPendingConfig(); + assertTrue(registry.ifConfigBufferExists()); + 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 { + LibRegistry.ConfigDetails memory cfg = validConfig(); + + vm.expectEmit(true, false, false, false); + emit AutomationRegistry.ConfigBufferUpdated(cfg); + + vm.prank(admin); + registry.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 { + LibRegistry.ConfigDetails memory cfg = validConfig(); + + vm.expectRevert(abi.encodeWithSelector(OwnableUpgradeable.OwnableUnauthorizedAccount.selector,alice)); + + vm.prank(alice); + registry.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 cold wallet is not set. + function testWithdrawFeesRevertsIfColdWalletNotSet() public { + vm.prank(admin); + + vm.expectRevert(IAutomationRegistry.ColdWalletNotSet.selector); + registry.withdrawFees(1 ether); + } + + /// @dev Test to ensure 'withdrawFees' reverts if contract has insufficient balance. + function testWithdrawFeesRevertsIfInsufficientBalance() public { + // Set cold wallet + vm.prank(admin); + registry.setColdWallet(address(0x1001)); + + vm.expectRevert(IAutomationRegistry.InsufficientBalance.selector); + + vm.prank(admin); + registry.withdrawFees(1 ether); + } + + /// @dev Test to ensure 'withdrawFees' reverts if request amount exceeds the locked balance. + function testWithdrawFeesRevertsIfRequestExceedsLockedBalance() public { + testRegister(); + + // Set cold wallet + vm.prank(admin); + registry.setColdWallet(address(0x1001)); + + vm.expectRevert(IAutomationRegistry.RequestExceedsLockedBalance.selector); + + vm.prank(admin); + registry.withdrawFees(0.04 ether); + } + + /// @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); + registry.withdrawFees(1 ether); + } + + /// @dev Test to ensure 'withdrawFees' withdraws the requested amount and updates the balance. + function testWithdrawFees() public { + testRegister(); + + // Set cold wallet + address coldWallet = address(0x1001); + vm.prank(admin); + registry.setColdWallet(coldWallet); + + assertEq(supraERC20.balanceOf(coldWallet), 0); + assertEq(supraERC20.balanceOf(address(registry)), 0.502 ether); + + vm.prank(admin); + registry.withdrawFees(0.002 ether); + + assertEq(supraERC20.balanceOf(coldWallet), 0.002 ether); + assertEq(supraERC20.balanceOf(address(registry)), 0.5 ether); + } + + /// @dev Test to ensure 'withdrawFees' emits event 'RegistryFeeWithdrawn'. + function testWithdrawFeesEmitsEvent() public { + testRegister(); + + // Set cold wallet + address coldWallet = address(0x1001); + vm.prank(admin); + registry.setColdWallet(coldWallet); + + vm.expectEmit(true, true, false, false); + emit AutomationRegistry.RegistryFeeWithdrawn(coldWallet, 0.002 ether); + + vm.prank(admin); + registry.withdrawFees(0.002 ether); + } + + // :::::::::::::::::::::::::::::::::::::::::::::::::::::: Tests related to 'register' :::::::::::::::::::::::::::::::::::::::::::::::::::::: + + /// @dev Helper function to return payload and auxiliary data. + /// @param _type Type of the task. UST: 0, GST: 1 + function payloadAndAuxData(uint8 _type) internal returns (bytes memory, bytes[] memory) { + ERC20Supra target = new ERC20Supra(alice); + bytes memory callData = abi.encodeCall(ERC20Supra.withdraw, 100); + bytes memory payload = abi.encode(address(target), callData); + + bytes[] memory auxData = new bytes[](2); + auxData[0] = abi.encode(uint8(_type)); + auxData[1] = abi.encode(uint64(100)); + + return (payload, auxData); + } + + /// @dev Test to ensure 'register' reverts if automation is not enabled. + function testRegisterRevertsIfAutomationNotEnabled() public { + // Disable automation + vm.prank(admin); + registry.disableAutomation(); + + (bytes memory payload, bytes[] memory auxData) = payloadAndAuxData(0); + + vm.expectRevert(IAutomationRegistry.AutomationNotEnabled.selector); + + vm.prank(alice); + registry.register( + payload, // payload + uint64(block.timestamp + 2250), // expiryTime + keccak256("txHash"), // txHash + uint128(1_000_000), // maxGasAmount + uint128(10 gwei), // gasPriceCap + uint128(0.5 ether), // automationFeeCapForCycle + auxData // aux data + ); + } + + /// @dev Test to ensure 'register' reverts if registration is disabled. + function testRegisterRevertsIfRegistrationDisabled() public { + // Disable registration + vm.prank(admin); + registry.disableRegistration(); + + (bytes memory payload, bytes[] memory auxData) = payloadAndAuxData(0); + + vm.expectRevert(IAutomationRegistry.RegistrationDisabled.selector); + + vm.prank(alice); + registry.register( + payload, // payload + uint64(block.timestamp + 2250), // expiryTime + keccak256("txHash"), // txHash + uint128(1_000_000), // maxGasAmount + uint128(10 gwei), // gasPriceCap + uint128(0.5 ether), // automationFeeCapForCycle + auxData // aux data + ); + } + + /// @dev Test to ensure 'register' reverts if auxiliary data is of invalid length. + function testRegisterRevertsIfInvalidAuxDataLength() public { + testSetAutomationController(); + + ERC20Supra target = new ERC20Supra(alice); + bytes memory callData = abi.encodeCall(ERC20Supra.withdraw, 100); + bytes memory payload = abi.encode(address(target), callData); + + bytes[] memory auxData = new bytes[](3); // Invalid length + auxData[0] = abi.encode(uint8(0)); + + vm.expectRevert(IAutomationRegistry.InvalidAuxDataLength.selector); + + vm.prank(alice); + registry.register( + payload, + uint64(block.timestamp + 2250), + keccak256("txHash"), + uint128(1_000_000), + uint128(10 gwei), + uint128(0.5 ether), + auxData + ); + } + + /// @dev Test to ensure 'register' reverts if task type is not UST. + function testRegisterRevertsIfTaskTypeNotUST() public { + testSetAutomationController(); + + ERC20Supra target = new ERC20Supra(alice); + bytes memory callData = abi.encodeCall(ERC20Supra.withdraw, 100); + bytes memory payload = abi.encode(address(target), callData); + + bytes[] memory auxData = new bytes[](2); + auxData[0] = abi.encode(uint8(1)); // Invalid task type: expected 0, passing 1 + + vm.expectRevert(IAutomationRegistry.InvalidTaskType.selector); + + vm.prank(alice); + registry.register( + payload, + uint64(block.timestamp + 2250), + keccak256("txHash"), + uint128(1_000_000), + uint128(10 gwei), + uint128(0.5 ether), + auxData + ); + } + + /// @dev Test to ensure 'register' reverts if expiry time is equal to or less than registration time. + function testRegisterRevertsIfInvalidExpiryTime() public { + testSetAutomationController(); + (bytes memory payload, bytes[] memory auxData) = payloadAndAuxData(0); + + vm.expectRevert(IAutomationRegistry.InvalidExpiryTime.selector); + + vm.prank(alice); + registry.register( + payload, + uint64(block.timestamp), // invalid expiryTime + keccak256("txHash"), + uint128(1_000_000), + uint128(10 gwei), + uint128(0.5 ether), + auxData + ); + } + + /// @dev Test to ensure 'register' reverts if task duration is greater than the task duration cap. + function testRegisterRevertsIfInvalidTaskDuration() public { + testSetAutomationController(); + (bytes memory payload, bytes[] memory auxData) = payloadAndAuxData(0); + + vm.expectRevert(IAutomationRegistry.InvalidTaskDuration.selector); + + vm.prank(alice); + registry.register( + payload, + uint64(block.timestamp + 3601), // Invalid task duration + keccak256("txHash"), + uint128(1_000_000), + uint128(10 gwei), + uint128(0.5 ether), + auxData + ); + } + + /// @dev Test to ensure 'register' reverts if task expires before the next cycle. + function testRegisterRevertsIfTaskExpiresBeforeNextCycle() public { + testSetAutomationController(); + (bytes memory payload, bytes[] memory auxData) = payloadAndAuxData(0); + + vm.expectRevert(IAutomationRegistry.TaskExpiresBeforeNextCycle.selector); + + vm.prank(alice); + registry.register( + payload, + uint64(block.timestamp + 2000), // Task expires before next cycle + keccak256("txHash"), + uint128(1_000_000), + uint128(10 gwei), + uint128(0.5 ether), + auxData + ); + } + + /// @dev Test to ensure 'register' reverts if payload target address is zero. + function testRegisterRevertsIfPayloadTargetZero() public { + testSetAutomationController(); + + bytes memory callData = abi.encodeCall(ERC20Supra.withdraw, 100); + bytes memory payload = abi.encode(address(0), callData); // Invalid address: address(0) + + bytes[] memory auxData = new bytes[](2); + auxData[0] = abi.encode(uint8(0)); + + vm.expectRevert(IAutomationRegistry.AddressCannotBeZero.selector); + + vm.prank(alice); + registry.register( + payload, + uint64(block.timestamp + 2250), + keccak256("txHash"), + uint128(1_000_000), + uint128(10 gwei), + uint128(0.5 ether), + auxData + ); + } + + /// @dev Test to ensure 'register' reverts if payload target address is EOA. + function testRegisterRevertsIfPayloadTargetEoa() public { + testSetAutomationController(); + + bytes memory callData = abi.encodeCall(ERC20Supra.withdraw, 100); + bytes memory payload = abi.encode(alice, callData); // Invalid address: EOA address being passed + + bytes[] memory auxData = new bytes[](2); + auxData[0] = abi.encode(uint8(0)); + + vm.expectRevert(IAutomationRegistry.AddressCannotBeEOA.selector); + + vm.prank(alice); + registry.register( + payload, + uint64(block.timestamp + 2250), + keccak256("txHash"), + uint128(1_000_000), + uint128(10 gwei), + uint128(0.5 ether), + auxData + ); + } + + /// @dev Test to ensure 'register' reverts if 0 is passed as max gas amount. + function testRegisterRevertsIfMaxGasAmountZero() public { + testSetAutomationController(); + (bytes memory payload, bytes[] memory auxData) = payloadAndAuxData(0); + + vm.expectRevert(IAutomationRegistry.InvalidMaxGasAmount.selector); + + vm.prank(alice); + registry.register( + payload, + uint64(block.timestamp + 2250), + keccak256("txHash"), + uint128(0), // maxGasAmount + uint128(10 gwei), + uint128(0.5 ether), + auxData + ); + } + + /// @dev Test to ensure 'register' reverts if 0 is passed as gas price cap. + function testRegisterRevertsIfGasPriceCapZero() public { + testSetAutomationController(); + (bytes memory payload, bytes[] memory auxData) = payloadAndAuxData(0); + + vm.expectRevert(IAutomationRegistry.InvalidGasPriceCap.selector); + + vm.prank(alice); + registry.register( + payload, + uint64(block.timestamp + 2250), + keccak256("txHash"), + uint128(1_000_000), + uint128(0), // gasPriceCap + uint128(0.5 ether), + auxData + ); + } + + /// @dev Test to ensure 'register' reverts if transaction hash is bytes32(0). + function testRegisterRevertsIfInvalidTxHash() public { + testSetAutomationController(); + (bytes memory payload, bytes[] memory auxData) = payloadAndAuxData(0); + + vm.expectRevert(IAutomationRegistry.InvalidTxHash.selector); + + vm.prank(alice); + registry.register( + payload, + uint64(block.timestamp + 2250), + bytes32(0), // Invalid tx hash + uint128(1_000_000), + uint128(10 gwei), + uint128(0.5 ether), + auxData + ); + } + + /// @dev Test to ensure 'register' reverts if gas committed exceeds the registry max gas cap. + function testRegisterRevertsIfGasCommittedExceedsMaxGasCap() public { + testSetAutomationController(); + (bytes memory payload, bytes[] memory auxData) = payloadAndAuxData(0); + + vm.expectRevert(IAutomationRegistry.GasCommittedExceedsMaxGasCap.selector); + + vm.prank(alice); + registry.register( + payload, + uint64(block.timestamp + 2250), + keccak256("txHash"), + uint128(10_000_001), // Gas exceeds max gas cap + uint128(10 gwei), + uint128(0.5 ether), + auxData + ); + } + + /// @dev Test to ensure 'register' reverts if automation fee cap is less than the estimated automation fee. + function testRegisterRevertsIfAutomationFeeCapLessThanEstimated() public { + testSetAutomationController(); + (bytes memory payload, bytes[] memory auxData) = payloadAndAuxData(0); + + vm.expectRevert(IAutomationRegistry.InsufficientFeeCapForCycle.selector); + + vm.prank(alice); + registry.register( + payload, + uint64(block.timestamp + 2250), + keccak256("txHash"), + uint128(1_000_000), + uint128(10 gwei), + uint128(0), // automationFeeCapForCycle + auxData + ); + } + + /// @dev Test to ensure 'register' registers a UST. + function testRegister() public { + testSetAutomationController(); + (bytes memory payload, bytes[] memory auxData) = payloadAndAuxData(0); + + vm.startPrank(alice); + supraERC20.deposit{value: 5 ether}(); + supraERC20.approve(address(registry), type(uint256).max); + + registry.register( + payload, + uint64(block.timestamp + 2250), + keccak256("txHash"), + uint128(1_000_000), + uint128(10 gwei), + uint128(0.5 ether), + auxData + ); + vm.stopPrank(); + + CommonUtils.TaskDetails memory taskMetadata = registry.getTaskDetails(0); + assertTrue(registry.ifTaskExists(0)); + assertEq(registry.totalTasks(), 1); + assertEq(registry.getNextTaskIndex(), 1); + assertEq(registry.getGasCommittedForNextCycle(), 1_000_000); + assertEq(registry.getTotalDepositedAutomationFees(), 0.5 ether); + assertEq(supraERC20.balanceOf(address(registry)), 0.002 ether + 0.5 ether); + assertEq(supraERC20.balanceOf(alice), 4.5 ether - 0.002 ether); + + assertEq(taskMetadata.maxGasAmount, 1_000_000); + assertEq(taskMetadata.gasPriceCap, 10 gwei); + assertEq(taskMetadata.automationFeeCapForCycle, 0.5 ether); + assertEq(taskMetadata.lockedFeeForNextCycle, 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.owner, alice); + assertEq(uint8(taskMetadata.state), 0); + assertEq(taskMetadata.payloadTx, payload); + assertEq(taskMetadata.auxData, auxData); + } + + /// @dev Test to ensure 'register' emits event 'TaskRegistered'. + function testRegisterEmitsEvent() public { + testSetAutomationController(); + (bytes memory payload, bytes[] memory auxData) = payloadAndAuxData(0); + + vm.startPrank(alice); + supraERC20.deposit{value: 5 ether}(); + supraERC20.approve(address(registry), 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), + alice, + CommonUtils.TaskState.PENDING, + 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), + keccak256("txHash"), + uint128(1_000_000), + uint128(10 gwei), + uint128(0.5 ether), + auxData + ); + vm.stopPrank(); + } + + // ::::::::::::::::::::::::::::::::::::::::::::::::::::: Tests related to 'registerSystemTask' ::::::::::::::::::::::::::::::::::::::::::::::::::::: + + /// @dev Test to ensure 'registerSystemTask' reverts if automation is not enabled. + function testRegisterSystemTaskRevertsIfAutomationNotEnabled() public { + vm.prank(admin); + registry.disableAutomation(); + + (bytes memory payload, bytes[] memory auxData) = payloadAndAuxData(1); + + vm.expectRevert(IAutomationRegistry.AutomationNotEnabled.selector); + + vm.prank(alice); + registry.registerSystemTask( + payload, // payload + uint64(block.timestamp + 2250), // expiryTime + keccak256("txHash"), // txHash + uint128(1_000_000), // maxGasAmount + auxData // aux data + ); + } + + /// @dev Test to ensure 'registerSystemTask' reverts if registration is disabled. + function testRegisterSystemTaskRevertsIfRegistrationDisabled() public { + vm.prank(admin); + registry.disableRegistration(); + + (bytes memory payload, bytes[] memory auxData) = payloadAndAuxData(1); + + vm.expectRevert(IAutomationRegistry.RegistrationDisabled.selector); + + vm.prank(alice); + registry.registerSystemTask( + payload, // payload + uint64(block.timestamp + 2250), // expiryTime + keccak256("txHash"), // txHash + uint128(1_000_000), // maxGasAmount + auxData // aux data + ); + } + + /// @dev Test to ensure 'registerSystemTask' reverts if caller is not authorized. + function testRegisterSystemTaskRevertsIfUnauthorizedCaller() public { + testSetAutomationController(); + (bytes memory payload, bytes[] memory auxData) = payloadAndAuxData(1); + + vm.expectRevert(IAutomationRegistry.UnauthorizedAccount.selector); + + vm.prank(alice); + registry.registerSystemTask( + payload, // payload + uint64(block.timestamp + 2250), // expiryTime + keccak256("txHash"), // txHash + uint128(1_000_000), // maxGasAmount + auxData // aux data + ); + } + + /// @dev Test to ensure 'registerSystemTask' reverts if auxiliary data is of invalid length. + function testRegisterSystemTaskRevertsIfInvalidAuxDataLength() public { + testSetAutomationController(); + testGrantAuthorization(); + + ERC20Supra target = new ERC20Supra(alice); + bytes memory callData = abi.encodeCall(ERC20Supra.withdraw, 100); + bytes memory payload = abi.encode(address(target), callData); + + bytes[] memory auxData = new bytes[](3); + auxData[0] = abi.encode(uint8(1)); + auxData[1] = abi.encode(uint64(100)); + + vm.expectRevert(IAutomationRegistry.InvalidAuxDataLength.selector); + + vm.prank(bob); + registry.registerSystemTask( + payload, // payload + uint64(block.timestamp + 2250), // expiryTime + keccak256("txHash"), // txHash + uint128(1_000_000), // maxGasAmount + auxData // aux data + ); + } + + /// @dev Test to ensure 'registerSystemTask' reverts if task type is not GST. + function testRegisterSystemTaskRevertsIfTaskTypeNotGST() public { + testSetAutomationController(); + testGrantAuthorization(); + (bytes memory payload, bytes[] memory auxData) = payloadAndAuxData(0); + + vm.expectRevert(IAutomationRegistry.InvalidTaskType.selector); + + vm.prank(bob); + registry.registerSystemTask( + payload, // payload + uint64(block.timestamp + 2250), // expiryTime + keccak256("txHash"), // txHash + uint128(1_000_000), // maxGasAmount + auxData // aux data + ); + } + + /// @dev Test to ensure 'registerSystemTask' reverts if task duration is greater than system task duration cap. + function testRegisterSystemTaskRevertsIfInvalidTaskDuration() public { + testSetAutomationController(); + testGrantAuthorization(); + (bytes memory payload, bytes[] memory auxData) = payloadAndAuxData(1); + + vm.expectRevert(IAutomationRegistry.InvalidTaskDuration.selector); + + vm.prank(bob); + registry.registerSystemTask( + payload, // payload + uint64(block.timestamp + 3601), // expiryTime + keccak256("txHash"), // txHash + uint128(1_000_000), // maxGasAmount + auxData // aux data + ); + } + + /// @dev Test to ensure 'registerSystemTask' reverts if gas committed exceeds the system registry max gas cap. + function testRegisterSystemTaskRevertsIfGasCommittedExceedsMaxGasCap() public { + testSetAutomationController(); + testGrantAuthorization(); + (bytes memory payload, bytes[] memory auxData) = payloadAndAuxData(1); + + vm.expectRevert(IAutomationRegistry.GasCommittedExceedsMaxGasCap.selector); + + vm.prank(bob); + registry.registerSystemTask( + payload, + uint64(block.timestamp + 2250), + keccak256("txHash"), + uint128(5_000_001), // Gas exceeds max gas cap + auxData + ); + } + + /// @dev Test to ensure 'registerSystemTask' registers a GST. + function testRegisterSystemTask() public { + testSetAutomationController(); + testGrantAuthorization(); + (bytes memory payload, bytes[] memory auxData) = payloadAndAuxData(1); + + vm.prank(bob); + registry.registerSystemTask( + payload, // payload + uint64(block.timestamp + 2250), // expiryTime + keccak256("txHash"), // txHash + uint128(1_000_000), // maxGasAmount + 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(registry.getSystemGasCommittedForNextCycle(), 1_000_000); + + assertEq(taskMetadata.maxGasAmount, 1_000_000); + assertEq(taskMetadata.gasPriceCap, 0); + assertEq(taskMetadata.automationFeeCapForCycle, 0); + assertEq(taskMetadata.lockedFeeForNextCycle, 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.owner, bob); + assertEq(uint8(taskMetadata.state), 0); + assertEq(taskMetadata.payloadTx, payload); + assertEq(taskMetadata.auxData, auxData); + } + + /// @dev Test to ensure 'registerSystemTask' emits event 'SystemTaskRegistered'. + function testRegisterSystemTaskEmitsEvent() public { + testSetAutomationController(); + testGrantAuthorization(); + (bytes memory payload, bytes[] memory auxData) = payloadAndAuxData(1); + + CommonUtils.TaskDetails memory taskMetadata = CommonUtils.TaskDetails( + 1_000_000, + 0, + 0, + 0, + keccak256("txHash"), + 0, + uint64(block.timestamp), + uint64(block.timestamp + 2250), + bob, + CommonUtils.TaskState.PENDING, + 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 + keccak256("txHash"), // txHash + uint128(1_000_000), // maxGasAmount + auxData // aux data + ); + } + + // :::::::::::::::::::::::::::::::::::::::::::::::::::::: Tests related to 'cancelTask' :::::::::::::::::::::::::::::::::::::::::::::::::::::: + + /// @dev Test to ensure 'cancelTask' reverts if automation is not enabled. + function testCancelTaskRevertsIfAutomationNotEnabled() public { + vm.prank(admin); + registry.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 { + testSetAutomationController(); + + 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 { + testSetAutomationController(); + + 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 { + testSetAutomationController(); + + testRegister(); + vm.expectRevert(IAutomationRegistry.UnauthorizedAccount.selector); + + vm.prank(bob); + registry.cancelTask(0); + } + + /// @dev Test to ensure 'cancelTask' cancels a UST. + function testCancelTask() public { + testSetAutomationController(); + testRegister(); + + vm.prank(alice); + registry.cancelTask(0); + + assertFalse(registry.ifTaskExists(0)); + assertEq(registry.totalTasks(), 0); + assertEq(registry.getGasCommittedForNextCycle(), 0); + assertEq(registry.getTotalDepositedAutomationFees(), 0); + assertEq(supraERC20.balanceOf(address(registry)), 0.002 ether + 0.25 ether); + assertEq(supraERC20.balanceOf(alice), 4.75 ether - 0.002 ether); + } + + /// @dev Test to ensure 'cancelTask' emits event 'TaskCancelled'. + function testCancelTaskEmitsEvent() public { + testSetAutomationController(); + 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); + registry.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 { + testSetAutomationController(); + + 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 { + testSetAutomationController(); + + 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 { + testSetAutomationController(); + + testRegisterSystemTask(); + vm.expectRevert(IAutomationRegistry.UnauthorizedAccount.selector); + + vm.prank(alice); + registry.cancelSystemTask(0); + } + /// @dev Test to ensure 'cancelSystemTask' cancels a GST. + function testCancelSystemTask() public { + testSetAutomationController(); + testRegisterSystemTask(); + + vm.prank(bob); + registry.cancelSystemTask(0); + + assertFalse(registry.ifTaskExists(0)); + assertFalse(registry.ifSysTaskExists(0)); + assertEq(registry.totalTasks(), 0); + assertEq(registry.totalSystemTasks(), 0); + assertEq(registry.getSystemGasCommittedForNextCycle(), 0); + } + + /// @dev Test to ensure 'cancelSystemTask' emits event 'TaskCancelled'. + function testCancelSystemTaskEmitsEvent() public { + testSetAutomationController(); + 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); + registry.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 { + testSetAutomationController(); + + 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 { + testSetAutomationController(); + 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 { + testSetAutomationController(); + 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 { + testSetAutomationController(); + testRegister(); + + uint64[] memory taskIndexes = new uint64[](1); + taskIndexes[0] = 5; + + vm.prank(alice); + registry.stopTasks(taskIndexes); + + assertEq(registry.totalTasks(), 1); + assertEq(registry.getTotalDepositedAutomationFees(), 0.5 ether); + } + + /// @dev Test to ensure 'stopTasks' stops the input UST tasks. + function testStopTasks() public { + testSetAutomationController(); + testRegister(); + + uint64[] memory taskIndexes = new uint64[](1); + taskIndexes[0] = 0; + + vm.prank(alice); + registry.stopTasks(taskIndexes); + + assertFalse(registry.ifTaskExists(0)); + assertEq(registry.totalTasks(), 0); + assertEq(registry.getGasCommittedForNextCycle(), 0); + assertEq(registry.getTotalDepositedAutomationFees(), 0); + assertEq(supraERC20.balanceOf(address(registry)), 0.002 ether + 0.25 ether); + assertEq(supraERC20.balanceOf(alice), 4.75 ether - 0.002 ether); + } + + /// @dev Test to ensure 'stopTasks' emits event 'TasksStopped'. + function testStopTasksEmitsEvent() public { + testSetAutomationController(); + testRegister(); + + uint64[] memory taskIndexes = new uint64[](1); + taskIndexes[0] = 0; + + LibRegistry.TaskStopped[] memory stoppedTasks = new LibRegistry.TaskStopped[](1); + stoppedTasks[0] = LibRegistry.TaskStopped(0, 0.25 ether, 0, 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); + registry.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 { + testSetAutomationController(); + + 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 { + testSetAutomationController(); + 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 { + testSetAutomationController(); + 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 { + testSetAutomationController(); + 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 { + testSetAutomationController(); + testRegisterSystemTask(); + + uint64[] memory taskIndexes = new uint64[](1); + taskIndexes[0] = 0; + + vm.prank(bob); + registry.stopSystemTasks(taskIndexes); + + assertFalse(registry.ifTaskExists(0)); + assertFalse(registry.ifSysTaskExists(0)); + assertEq(registry.totalTasks(), 0); + assertEq(registry.totalSystemTasks(), 0); + assertEq(registry.getSystemGasCommittedForNextCycle(), 0); + } + + /// @dev Test to ensure 'stopSystemTasks' emits event 'TasksStopped'. + function testStopSystemTasksEmitsEvent() public { + testSetAutomationController(); + testRegisterSystemTask(); + + uint64[] memory taskIndexes = new uint64[](1); + taskIndexes[0] = 0; + + 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); + } +} \ No newline at end of file From d913adfd69bfc4c23541fe507807f7da91391503 Mon Sep 17 00:00:00 2001 From: Udit Yadav Date: Wed, 10 Dec 2025 12:42:23 +0530 Subject: [PATCH 03/31] fixed variable naming, replaced modifier with pvt function --- .../src/AutomationController.sol | 12 ++-- .../src/AutomationRegistry.sol | 55 +++++++++++-------- .../src/IAutomationRegistry.sol | 8 +-- .../test/AutomationRegistry.t.sol | 28 +++++----- 4 files changed, 55 insertions(+), 48 deletions(-) diff --git a/solidity/automation_registry/src/AutomationController.sol b/solidity/automation_registry/src/AutomationController.sol index 4557906111..67c1949596 100644 --- a/solidity/automation_registry/src/AutomationController.sol +++ b/solidity/automation_registry/src/AutomationController.sol @@ -42,7 +42,7 @@ contract AutomationController is IAutomationController, Ownable2StepUpgradeable, address indexed owner, uint128 fee, uint256 balance, - bytes32 registration_hash + bytes32 registrationHash ); /// @notice Emitted when an automation fee is charged for an automation task for the cycle. @@ -115,7 +115,7 @@ contract AutomationController is IAutomationController, Ownable2StepUpgradeable, /// @param _taskIndexes Array of task index to be processed. function processTasks(uint64 _cycleIndex, uint64[] memory _taskIndexes) external { // Check caller is VM - if (msg.sender != registry.getVM()) { revert CallerNotVM(); } + if (msg.sender != registry.getVm()) { revert CallerNotVM(); } CommonUtils.CycleState state = cycleInfo.state(); @@ -590,7 +590,7 @@ contract AutomationController is IAutomationController, Ownable2StepUpgradeable, if(currentTime >= cycleEndTime) { revert InvalidRegistryState(); } if(cycleInfo.state() != CommonUtils.CycleState.STARTED) { revert InvalidRegistryState(); } - uint256[] memory expected_tasks_to_be_processed = registry.getTaskIdList().sortUint256(); + uint256[] memory expectedTasksToBeProcessed = registry.getTaskIdList().sortUint256(); cycleInfo.setRefundDuration(cycleEndTime - currentTime); cycleInfo.setNewCycleDuration(cycleDuration); @@ -601,7 +601,7 @@ contract AutomationController is IAutomationController, Ownable2StepUpgradeable, cycleInfo.transitionState.lockedFees = 0; cycleInfo.setNextTaskIndexPosition(0); - updateExpectedTasks(expected_tasks_to_be_processed); + updateExpectedTasks(expectedTasksToBeProcessed); cycleInfo.setTransitionStateExists(true); updateCycleStateTo(CommonUtils.CycleState.SUSPENDED); @@ -696,7 +696,7 @@ contract AutomationController is IAutomationController, Ownable2StepUpgradeable, updateConfigFromBuffer(); moveToStartedState(); } else { - uint256[] memory expected_tasks_to_be_processed = registry.getTaskIdList().sortUint256(); + uint256[] memory expectedTasksToBeProcessed = registry.getTaskIdList().sortUint256(); cycleInfo.setRefundDuration(0); cycleInfo.setNewCycleDuration(cycleInfo.durationSecs()); @@ -707,7 +707,7 @@ contract AutomationController is IAutomationController, Ownable2StepUpgradeable, cycleInfo.transitionState.lockedFees = 0; cycleInfo.setNextTaskIndexPosition(0); - updateExpectedTasks(expected_tasks_to_be_processed); + 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. diff --git a/solidity/automation_registry/src/AutomationRegistry.sol b/solidity/automation_registry/src/AutomationRegistry.sol index b4407713c7..dd88d451d9 100644 --- a/solidity/automation_registry/src/AutomationRegistry.sol +++ b/solidity/automation_registry/src/AutomationRegistry.sol @@ -114,7 +114,7 @@ contract AutomationRegistry is IAutomationRegistry, Ownable2StepUpgradeable, UUP /// @notice Emitted when a task is stopped. event TasksStopped( - LibRegistry.TaskStopped[] indexed StoppedTasks, + LibRegistry.TaskStopped[] indexed stoppedTasks, address indexed owner ); @@ -153,14 +153,6 @@ contract AutomationRegistry is IAutomationRegistry, Ownable2StepUpgradeable, UUP uint64 indexed amount ); - // :::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::: MODIFIERS :::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::: - - /// @dev Modifier to assert that AutomationController contract is the caller. - modifier onlyController() { - if(msg.sender != regConfig.automationController()) { revert CallerNotController(); } - _; - } - // :::::::::::::::::::::::::::::::::::::::::::::::::::::::::: CONSTRUCTOR AND INITIALIZER :::::::::::::::::::::::::::::::::::::::::::::::::::::::::: /// @dev Disables the initialization for the implementation contract. @@ -988,7 +980,7 @@ contract AutomationRegistry is IAutomationRegistry, Ownable2StepUpgradeable, UUP } } - /// Refunds fee paid by the task for the cycle to the task owner. + /// @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( @@ -1008,6 +1000,11 @@ contract AutomationRegistry is IAutomationRegistry, Ownable2StepUpgradeable, UUP return (result, remainingLockedFees); } + /// @notice Function to ensure that AutomationController contract is the caller. + function onlyController() private view { + if(msg.sender != regConfig.automationController()) { revert CallerNotController(); } + } + // ::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::: ADMIN FUNCTIONS ::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::: /// @notice Function to update the registry configuration buffer. @@ -1111,13 +1108,13 @@ contract AutomationRegistry is IAutomationRegistry, Ownable2StepUpgradeable, UUP /// @notice Function to update the VM address. /// @param _vm New address for VM. - function setVM(address _vm) external onlyOwner { + function setVm(address _vm) external onlyOwner { if(_vm == address(0)) { revert AddressCannotBeZero(); } - address oldVM = regConfig.vm; + address oldVm = regConfig.vm; regConfig.vm = _vm; - emit VmAddressUpdated(oldVM, _vm); + emit VmAddressUpdated(oldVm, _vm); } /// @notice Function to update the SupraERC20 address. @@ -1174,14 +1171,16 @@ contract AutomationRegistry is IAutomationRegistry, Ownable2StepUpgradeable, UUP // ::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::: CONTROLLER FUNCTIONS ::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::: /// @notice Internally calls _removeTask, reverts if caller is not AutomationController. - function removeTask(uint64 _taskIndex, bool _removeFromSysReg) external onlyController { + 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 { + function updateTaskState(uint64 _taskIndex, CommonUtils.TaskState _taskState) external { + onlyController(); LibRegistry.setState(regState.tasks[_taskIndex], uint8(_taskState)); } @@ -1197,7 +1196,8 @@ contract AutomationRegistry is IAutomationRegistry, Ownable2StepUpgradeable, UUP uint128 _gasCommittedForNewCycle, uint256 _lockedFees, uint8 _state - ) external onlyController { + ) external { + onlyController(); regSysState.setGasCommittedForNextCycle(_sysGasCommittedForNextCycle); regSysState.setGasCommittedForThisCycle(_sysGasCommittedForNextCycle); regState.setGasCommittedForNextCycle(_gasCommittedForNextCycle); @@ -1214,7 +1214,8 @@ contract AutomationRegistry is IAutomationRegistry, Ownable2StepUpgradeable, UUP } /// @notice Function to update the registry configuration, reverts if caller is not AutomationController. - function applyPendingConfig() external onlyController { + function applyPendingConfig() external { + onlyController(); regConfig.config = configBuffer.pendingConfig; configBuffer.ifExists = false; } @@ -1222,7 +1223,8 @@ contract AutomationRegistry is IAutomationRegistry, Ownable2StepUpgradeable, UUP function safeUnlockLockedDeposit( uint64 _taskIndex, uint128 _lockedDeposit - ) external onlyController returns (bool) { + ) external returns (bool) { + onlyController(); return _safeUnlockLockedDeposit(_taskIndex, _lockedDeposit); } @@ -1235,7 +1237,8 @@ contract AutomationRegistry is IAutomationRegistry, Ownable2StepUpgradeable, UUP uint64 _currentTime, uint128 _automationFeePerSec, uint128 _registryMaxGasCap - ) external onlyController view returns (uint128) { + ) external returns (uint128) { + onlyController(); return _calculateTaskFee( _state, _expiryTime, @@ -1257,7 +1260,8 @@ contract AutomationRegistry is IAutomationRegistry, Ownable2StepUpgradeable, UUP address _taskOwner, uint128 _refundableDeposit, uint128 _lockedDeposit - ) external onlyController { + ) external { + onlyController(); // Check if task is UST if(checkTaskType(_taskIndex, CommonUtils.TaskType.GST)) { revert RegisteredTaskInvalidType(); } @@ -1278,7 +1282,8 @@ contract AutomationRegistry is IAutomationRegistry, Ownable2StepUpgradeable, UUP uint64 _taskIndex, uint64 _currentTime, uint256 _cycleLockedFees - ) external onlyController returns (uint256) { + ) external returns (uint256) { + onlyController(); if(checkTaskType(_taskIndex, CommonUtils.TaskType.GST)) { revert RegisteredTaskInvalidType(); } CommonUtils.TaskDetails memory task = regState.tasks[_taskIndex].getTaskDetails(); @@ -1316,7 +1321,8 @@ contract AutomationRegistry is IAutomationRegistry, Ownable2StepUpgradeable, UUP return _cycleLockedFees; } - function calculateAutomationFeeMultiplierForCurrentCycleInternal() external onlyController view returns (uint128) { + function calculateAutomationFeeMultiplierForCurrentCycleInternal() external returns (uint128) { + onlyController(); // Compute the automation fee multiplier for this cycle return calculateAutomationFeeMultiplierForCycle( regState.gasCommittedForThisCycle(), @@ -1330,7 +1336,8 @@ contract AutomationRegistry is IAutomationRegistry, Ownable2StepUpgradeable, UUP /// maximum allowed occupancy. function calculateAutomationFeeMultiplierForCommittedOccupancy( uint128 _totalCommittedMaxGas - ) external onlyController view returns (uint128) { + ) external returns (uint128) { + onlyController(); // Compute the automation fee multiplier for cycle return calculateAutomationFeeMultiplierForCycle( _totalCommittedMaxGas, @@ -1352,7 +1359,7 @@ contract AutomationRegistry is IAutomationRegistry, Ownable2StepUpgradeable, UUP } /// @notice Returns the VM address. - function getVM() external view returns (address) { + function getVm() external view returns (address) { return regConfig.vm; } diff --git a/solidity/automation_registry/src/IAutomationRegistry.sol b/solidity/automation_registry/src/IAutomationRegistry.sol index b16fbacfe8..a43160fa37 100644 --- a/solidity/automation_registry/src/IAutomationRegistry.sol +++ b/solidity/automation_registry/src/IAutomationRegistry.sol @@ -71,11 +71,11 @@ interface IAutomationRegistry { function totalTasks() external view returns (uint256); function ifConfigBufferExists() external view returns (bool); function getBufferCycleDurationSecs() external view returns (uint64); - function getVM() external returns (address); + function getVm() external returns (address); function supraERC20() external view returns (address); function isAutomationEnabled() external view returns (bool); - function calculateAutomationFeeMultiplierForCurrentCycleInternal() external view returns (uint128); - function calculateAutomationFeeMultiplierForCommittedOccupancy(uint128 _totalCommittedMaxGas) external view returns (uint128); + function calculateAutomationFeeMultiplierForCurrentCycleInternal() external returns (uint128); + function calculateAutomationFeeMultiplierForCommittedOccupancy(uint128 _totalCommittedMaxGas) external returns (uint128); function calculateTaskFee( CommonUtils.TaskState _state, uint64 _expiryTime, @@ -84,7 +84,7 @@ interface IAutomationRegistry { uint64 _currentTime, uint128 _automationFeePerSec, uint128 _registryMaxGasCap - ) external view returns (uint128); + ) external returns (uint128); function cycleDurationSecs() external view returns (uint64); diff --git a/solidity/automation_registry/test/AutomationRegistry.t.sol b/solidity/automation_registry/test/AutomationRegistry.t.sol index d65e0bbbf0..fa16d47879 100644 --- a/solidity/automation_registry/test/AutomationRegistry.t.sol +++ b/solidity/automation_registry/test/AutomationRegistry.t.sol @@ -69,7 +69,7 @@ contract AutomationRegistryTest is Test { assertEq(registry.getAutomationController(), address(0)); assertTrue(registry.isRegistrationEnabled()); assertTrue(registry.isAutomationEnabled()); - assertEq(registry.getVM(), vmAddress); + assertEq(registry.getVm(), vmAddress); assertEq(registry.supraERC20(), address(supraERC20)); LibRegistry.ConfigDetails memory config = registry.getConfig(); @@ -538,44 +538,44 @@ contract AutomationRegistryTest is Test { registry.setAutomationController(alice); } - // :::::::::::::::::::::::::::::::::::::::::::::::::::::: Tests related to 'setVM' :::::::::::::::::::::::::::::::::::::::::::::::::::::: + // :::::::::::::::::::::::::::::::::::::::::::::::::::::: Tests related to 'setVm' :::::::::::::::::::::::::::::::::::::::::::::::::::::: /// @dev Test to ensure 'setVM' updates the VM address. function testSetVm() public { - address newVM = address(0x100); + address newVm = address(0x100); vm.prank(admin); - registry.setVM(newVM); + registry.setVm(newVm); - assertEq(registry.getVM(), newVM); + assertEq(registry.getVm(), newVm); } - /// @dev Test to ensure 'setVM' emits event 'VmAddressUpdated'. + /// @dev Test to ensure 'setVm' emits event 'VmAddressUpdated'. function testSetVmEmitsEvent() public { - address oldVM = registry.getVM(); - address newVM = address(0x100); + address oldVm = registry.getVm(); + address newVm = address(0x100); vm.expectEmit(true, true, false, false); - emit AutomationRegistry.VmAddressUpdated(oldVM, newVM); + emit AutomationRegistry.VmAddressUpdated(oldVm, newVm); vm.prank(admin); - registry.setVM(newVM); + registry.setVm(newVm); } - /// @dev Test to ensure 'setVM' reverts if zero address is passed. + /// @dev Test to ensure 'setVm' reverts if zero address is passed. function testSetVmRevertsIfZeroAddress() public { vm.expectRevert(IAutomationRegistry.AddressCannotBeZero.selector); vm.prank(admin); - registry.setVM(address(0)); + registry.setVm(address(0)); } - /// @dev Test to ensure 'setVM' reverts if caller is not owner. + /// @dev Test to ensure 'setVm' reverts if caller is not owner. function testSetVmRevertsIfNotOwner() public { vm.expectRevert(abi.encodeWithSelector(OwnableUpgradeable.OwnableUnauthorizedAccount.selector, alice)); vm.prank(alice); - registry.setVM(address(0x100)); + registry.setVm(address(0x100)); } // :::::::::::::::::::::::::::::::::::::::::::::::::::::: Tests related to 'setSupraERC20' :::::::::::::::::::::::::::::::::::::::::::::::::::::: From b7981a86b3960367919c75ab1b69a3b16649d614 Mon Sep 17 00:00:00 2001 From: Udit Yadav Date: Thu, 11 Dec 2025 13:30:20 +0530 Subject: [PATCH 04/31] -created script to deploy automation registry contracts and initialise state -created bash script for deployment and interacting to registry contracts --- .gitignore | 2 + .../deploy_automation_registry.sh | 107 +++++++ solidity/automation_registry/foundry.toml | 1 + .../automation_registry/getTaskDetails.js | 37 +++ .../automation_registry/package-lock.json | 131 ++++++++ solidity/automation_registry/package.json | 7 + solidity/automation_registry/run.sh | 291 ++++++++++++++++++ .../script/DeployAutomationRegistry.s.sol | 119 +++++++ .../script/DeployScript.s.sol | 16 - .../src/AutomationRegistry.sol | 6 +- .../src/IAutomationRegistry.sol | 6 +- 11 files changed, 701 insertions(+), 22 deletions(-) create mode 100755 solidity/automation_registry/deploy_automation_registry.sh create mode 100755 solidity/automation_registry/getTaskDetails.js create mode 100644 solidity/automation_registry/package-lock.json create mode 100644 solidity/automation_registry/package.json create mode 100755 solidity/automation_registry/run.sh create mode 100644 solidity/automation_registry/script/DeployAutomationRegistry.s.sol delete mode 100644 solidity/automation_registry/script/DeployScript.s.sol diff --git a/.gitignore b/.gitignore index c1863291f5..855f2ab363 100644 --- a/.gitignore +++ b/.gitignore @@ -31,3 +31,5 @@ rustc-ice-* # Fixtures /test-fixtures + +node_modules \ No newline at end of file diff --git a/solidity/automation_registry/deploy_automation_registry.sh b/solidity/automation_registry/deploy_automation_registry.sh new file mode 100755 index 0000000000..38c1abbb75 --- /dev/null +++ b/solidity/automation_registry/deploy_automation_registry.sh @@ -0,0 +1,107 @@ +#!/bin/bash +set -e + +# -------------------------- +# CONFIGURATION +# -------------------------- +RPC_URL="http://127.0.0.1:8545" +DEPLOY_LOG="deploy.log" +ENV_FILE="deployed.env" +PRIVATE_KEY="" + +# Helper for cleaner + safer extraction +extract() { + local result + result=$(grep -m1 "$1" "$DEPLOY_LOG" | grep -o "0x[a-fA-F0-9]\{40\}") + echo "${result:-NOT_FOUND}" +} + +# ------------------------------------------------------------ +# 1. START ANVIL +# ------------------------------------------------------------ +echo "=== Starting Anvil with DEFAULT settings ===" + +echo "Checking if port 8545 is in use..." +EXISTING_PID=$(lsof -ti:8545 || true) + +if [ ! -z "$EXISTING_PID" ]; then + echo "Port is busy. Killing $EXISTING_PID ..." + kill -9 "$EXISTING_PID" + sleep 1 +fi + +echo "Starting Anvil..." +anvil > anvil.log 2>&1 & +ANVIL_PID=$! +sleep 2 + +echo "Anvil launched (PID $ANVIL_PID)" +echo "RPC URL: $RPC_URL" + +# ------------------------------------------------------------ +# 2. RUN DEPLOY SCRIPT +# ------------------------------------------------------------ +echo "" +echo "=== Deploying contracts ===" + +forge script script/DeployAutomationRegistry.s.sol:DeployAutomationRegistry \ + --rpc-url "$RPC_URL" \ + --private-key "$PRIVATE_KEY" \ + --broadcast \ + --skip-simulation \ + -vvvv > "$DEPLOY_LOG" 2>&1 + +echo "Deployment logs saved to $DEPLOY_LOG" + +# ------------------------------------------------------------ +# 3. PARSE DEPLOYED ADDRESSES +# ------------------------------------------------------------ +echo "" +echo "=== Extracting deployed addresses ===" + +ERC20_SUPRA=$(extract "ERC20Supra deployed at:") +AUTOMATION_REGISTRY_IMPL=$(extract "AutomationRegistry implementation deployed at:") +AUTOMATION_REGISTRY_PROXY=$(extract "AutomationRegistry proxy deployed at:") +BLOCKMETA_IMPL=$(extract "BlockMeta implementation deployed at:") +BLOCKMETA_PROXY=$(extract "BlockMeta proxy deployed at:") +AUTOMATION_CONTROLLER_IMPL=$(extract "AutomationController implementation deployed at:") +AUTOMATION_CONTROLLER_PROXY=$(extract "AutomationController proxy deployed at:") + +# ------------------------------------------------------------ +# 4. WRITE TO .env +# ------------------------------------------------------------ +echo "" +echo "=== Saving contract addresses to $ENV_FILE ===" +echo "" + +cat < "$ENV_FILE" +# Auto-generated deployment output + +ERC20_SUPRA=$ERC20_SUPRA + +AUTOMATION_REGISTRY_IMPL=$AUTOMATION_REGISTRY_IMPL +AUTOMATION_REGISTRY_PROXY=$AUTOMATION_REGISTRY_PROXY + +BLOCKMETA_IMPL=$BLOCKMETA_IMPL +BLOCKMETA_PROXY=$BLOCKMETA_PROXY + +AUTOMATION_CONTROLLER_IMPL=$AUTOMATION_CONTROLLER_IMPL +AUTOMATION_CONTROLLER_PROXY=$AUTOMATION_CONTROLLER_PROXY +EOF + +cat "$ENV_FILE" + +echo "" +echo "=== Deployment Complete ===" + +# ------------------------------------------------------------ +# 5. STOP ANVIL +# ------------------------------------------------------------ +# echo "Stopping Anvil (PID $ANVIL_PID)" + +# if kill -0 "$ANVIL_PID" 2>/dev/null; then +# kill "$ANVIL_PID" +# echo "Anvil stopped successfully." +# else +# echo "Anvil already exited." +# fi diff --git a/solidity/automation_registry/foundry.toml b/solidity/automation_registry/foundry.toml index 83816a210a..eb22be94ce 100644 --- a/solidity/automation_registry/foundry.toml +++ b/solidity/automation_registry/foundry.toml @@ -3,5 +3,6 @@ src = "src" out = "out" libs = ["lib"] via_ir = true +optimizer = true # See more config options https://github.com/foundry-rs/foundry/blob/master/crates/config/README.md#all-options diff --git a/solidity/automation_registry/getTaskDetails.js b/solidity/automation_registry/getTaskDetails.js new file mode 100755 index 0000000000..a305f0383a --- /dev/null +++ b/solidity/automation_registry/getTaskDetails.js @@ -0,0 +1,37 @@ +#!/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,address owner,uint8 state,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/automation_registry/package-lock.json b/solidity/automation_registry/package-lock.json new file mode 100644 index 0000000000..f345d193bd --- /dev/null +++ b/solidity/automation_registry/package-lock.json @@ -0,0 +1,131 @@ +{ + "name": "automation_registry", + "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/automation_registry/package.json b/solidity/automation_registry/package.json new file mode 100644 index 0000000000..db925d0be9 --- /dev/null +++ b/solidity/automation_registry/package.json @@ -0,0 +1,7 @@ +{ + "dependencies": { + "dotenv": "^17.2.3", + "ethers": "^6.16.0" + }, + "type": "module" +} diff --git a/solidity/automation_registry/run.sh b/solidity/automation_registry/run.sh new file mode 100755 index 0000000000..ade2c0bfe7 --- /dev/null +++ b/solidity/automation_registry/run.sh @@ -0,0 +1,291 @@ +#!/bin/bash +set -e + +# 1. Deploy everything +./deploy_automation_registry.sh + +echo "" +echo "=== Loading deployed contract addresses ===" +source deployed.env + +echo "" +echo "Contracts Loaded:" +echo "ERC20_SUPRA: $ERC20_SUPRA" +echo "AUTOMATION_REGISTRY: $AUTOMATION_REGISTRY_PROXY" + +echo "" +echo "=== Starting Automation CLI ===" + +# ------------------------------- +# Load deployed contract addresses +# ------------------------------- +if [ ! -f "deployed.env" ]; then + echo "ERROR: deployed.env not found. Run ./run.sh first." + exit 1 +fi + +source deployed.env + +REGISTRY="$AUTOMATION_REGISTRY_PROXY" +TOKEN="$ERC20_SUPRA" +RPC_URL="http://127.0.0.1:8545" + +# ------------------------------- +# Ask user for private key +# ------------------------------- +echo -n "Enter PRIVATE_KEY (0x...): " +read -r PRIVATE_KEY + +ADDRESS=$(cast wallet address --private-key "$PRIVATE_KEY") +echo "" +echo "Using RPC: $RPC_URL" +echo "Wallet: $ADDRESS" +echo "Registry proxy: $REGISTRY" +echo "ERC20 token: $TOKEN" +echo "" + +# ------------------------------- +# Helper - safe send +# ------------------------------- +send_tx() { + cast send \ + --rpc-url "$RPC_URL" \ + --private-key "$PRIVATE_KEY" \ + "$@" +} + +# ------------------------------- +# Balance + allowance helpers +# ------------------------------- +get_eth_balance() { + RAW=$(cast balance "$ADDRESS" --rpc-url "$RPC_URL" 2>/dev/null) + RAW=${RAW:-0} + ETH=$(cast --from-wei "$RAW") + echo "ETH Balance: $ETH ETH" +} + +get_token_balance() { + RAW=$(cast call "$TOKEN" "balanceOf(address)(uint256)" "$ADDRESS" --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") + echo "ERC20Supra Balance: $SUPRA SUPRA" +} + +get_allowance() { + RAW=$(cast call "$TOKEN" "allowance(address,address)(uint256)" "$ADDRESS" "$REGISTRY" --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") + echo "Allowance to Registry: $SUPRA SUPRA" +} + +# ------------------------------- +# Registry view functions +# ------------------------------- + +view_task_details() { + echo -n "Task index: " + read -r index + echo "" + echo "=== Task Details ===" + node getTaskDetails.js "$REGISTRY" "$index" "$RPC_URL" + echo "" +} + +view_registry_locked_balance() { + RAW=$(cast call "$REGISTRY" "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_token_balance() { + RAW=$(cast call "$TOKEN" "balanceOf(address)(uint256)" "$REGISTRY" --rpc-url "$RPC_URL") + + DEC=$(echo "$RAW" | awk '{print $1}') + SUPRA=$(cast --from-wei "$DEC") + + echo "Registry ERC20Supra Balance: $SUPRA SUPRA" +} + +view_task_list() { + RAW=$(cast call "$REGISTRY" "getTaskIdList()(uint256[])" --rpc-url "$RPC_URL") + echo "" + echo "=== Task IDs ===" + echo "$RAW" + echo "" +} + +view_total_tasks() { + RAW=$(cast call "$REGISTRY" "totalTasks()(uint256)" --rpc-url "$RPC_URL") + echo "Total Task Count: $RAW" +} + +# ------------------------------- +# Main menu +# ------------------------------- +while true; do + echo "" + echo "Automation CLI - extended" + echo "" + echo "Commands:" + echo " eth-balance Show ETH balance" + echo " supra-balance Show ERC20Supra balance" + echo " allowance Check ERC20 approval to registry" + echo " deposit Deposit ETH → mint ERC20Supra" + echo " approve Approve ERC20 token for fees" + echo " register Register a user task" + echo " register-system Register a system task" + echo " cancel Cancel a user task" + echo " cancel-system Cancel a system task" + echo " stop Stop user tasks" + echo " stop-system Stop system tasks" + echo " task-details View details of a task" + echo " registry-locked-balance View registry's locked balance" + echo " registry-balance View ERC20Supra balance of registry contract" + echo " task-list View all task IDs" + echo " total-tasks View number of tasks" + echo " exit Quit" + echo -n "Command> " + read -r CMD + echo "" + + case "$CMD" in + eth-balance) get_eth_balance ;; + supra-balance) get_token_balance ;; + allowance) get_allowance ;; + + deposit) + echo -n "Amount to deposit (ETH): " + read -r ethAmount + weiAmount=$(cast --to-wei "$ethAmount") + echo "Depositing $ethAmount ETH..." + send_tx "$TOKEN" "deposit()" --value "$weiAmount" + ;; + + approve) + echo -n "Amount to approve (ETH): " + read -r ethAmount + weiAmount=$(cast --to-wei "$ethAmount") + echo "Approving $ethAmount SUPRA..." + send_tx "$TOKEN" "approve(address,uint256)" "$REGISTRY" "$weiAmount" + ;; + + register) + echo "Register task (user task)" + echo -n "payloadTx (0x...): " + read -r payloadTx + + echo -n "Duration (seconds): " + read -r duration + now=$(cast block latest --rpc-url "$RPC_URL" | grep "timestamp" | awk '{print $2}') + expiryTime=$(("$now" + "$duration")) + echo "Computed expiryTime = $expiryTime" + + echo -n "txHash (0x...): " + read -r txHash + + echo -n "maxGasAmount: " + read -r maxGas + + echo -n "Gas price cap (GWEI): " + 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 + + + echo -n "auxData count: " + read -r auxCount + + auxArray=() + for ((i=0; i Date: Tue, 16 Dec 2025 23:51:41 +0400 Subject: [PATCH 05/31] Moved automation registry implementation to supra_contracts --- solidity/automation_registry/README.md | 66 ------------- solidity/automation_registry/foundry.lock | 11 --- solidity/automation_registry/foundry.toml | 8 -- solidity/automation_registry/lib/forge-std | 1 - .../lib/openzeppelin-contracts | 1 - .../lib/openzeppelin-contracts-upgradeable | 1 - .../automation_registry/src/CommonUtils.sol | 99 ------------------- .../deploy_automation_registry.sh | 0 .../getTaskDetails.js | 0 .../package-lock.json | 0 .../package.json | 0 .../run.sh | 0 .../script/DeployAutomationRegistry.s.sol | 0 .../src/AutomationController.sol | 0 .../src/AutomationRegistry.sol | 0 solidity/supra_contracts/src/CommonUtils.sol | 81 +++++++++++++++ .../src/IAutomationController.sol | 0 .../src/IAutomationRegistry.sol | 0 .../src/LibController.sol | 0 .../src/LibRegistry.sol | 0 .../test/AutomationController.t.sol | 0 .../test/AutomationRegistry.t.sol | 0 22 files changed, 81 insertions(+), 187 deletions(-) delete mode 100644 solidity/automation_registry/README.md delete mode 100644 solidity/automation_registry/foundry.lock delete mode 100644 solidity/automation_registry/foundry.toml delete mode 160000 solidity/automation_registry/lib/forge-std delete mode 160000 solidity/automation_registry/lib/openzeppelin-contracts delete mode 160000 solidity/automation_registry/lib/openzeppelin-contracts-upgradeable delete mode 100644 solidity/automation_registry/src/CommonUtils.sol rename solidity/{automation_registry => supra_contracts}/deploy_automation_registry.sh (100%) rename solidity/{automation_registry => supra_contracts}/getTaskDetails.js (100%) rename solidity/{automation_registry => supra_contracts}/package-lock.json (100%) rename solidity/{automation_registry => supra_contracts}/package.json (100%) rename solidity/{automation_registry => supra_contracts}/run.sh (100%) rename solidity/{automation_registry => supra_contracts}/script/DeployAutomationRegistry.s.sol (100%) rename solidity/{automation_registry => supra_contracts}/src/AutomationController.sol (100%) rename solidity/{automation_registry => supra_contracts}/src/AutomationRegistry.sol (100%) rename solidity/{automation_registry => supra_contracts}/src/IAutomationController.sol (100%) rename solidity/{automation_registry => supra_contracts}/src/IAutomationRegistry.sol (100%) rename solidity/{automation_registry => supra_contracts}/src/LibController.sol (100%) rename solidity/{automation_registry => supra_contracts}/src/LibRegistry.sol (100%) rename solidity/{automation_registry => supra_contracts}/test/AutomationController.t.sol (100%) rename solidity/{automation_registry => supra_contracts}/test/AutomationRegistry.t.sol (100%) diff --git a/solidity/automation_registry/README.md b/solidity/automation_registry/README.md deleted file mode 100644 index bb49667c59..0000000000 --- a/solidity/automation_registry/README.md +++ /dev/null @@ -1,66 +0,0 @@ -## Supra EVM Automation Registry - -**This repository includes Supra EVM Automation Registry contract and related contracts.** - -Foundry consists of: - -- **Forge**: Ethereum testing framework (like Truffle, Hardhat and DappTools). -- **Cast**: Swiss army knife for interacting with EVM smart contracts, sending transactions and getting chain data. -- **Anvil**: Local Ethereum node, akin to Ganache, Hardhat Network. -- **Chisel**: Fast, utilitarian, and verbose solidity REPL. - -## Documentation - -https://book.getfoundry.sh/ - -## Usage - -### Build - -```shell -$ forge build -``` - -### Test - -```shell -$ forge test -``` - -### Format - -```shell -$ forge fmt -``` - -### Gas Snapshots - -```shell -$ forge snapshot -``` - -### Anvil - -```shell -$ anvil -``` - -### Deploy - -```shell -$ forge script script/Counter.s.sol:CounterScript --rpc-url --private-key -``` - -### Cast - -```shell -$ cast -``` - -### Help - -```shell -$ forge --help -$ anvil --help -$ cast --help -``` diff --git a/solidity/automation_registry/foundry.lock b/solidity/automation_registry/foundry.lock deleted file mode 100644 index caec3b5f1f..0000000000 --- a/solidity/automation_registry/foundry.lock +++ /dev/null @@ -1,11 +0,0 @@ -{ - "lib/forge-std": { - "rev": "ebc60f500bc6870baaf321a0196fddc24d6edb03" - }, - "lib/openzeppelin-contracts": { - "rev": "77bc5642a53d9c8eac8aec5c8ea9809a21d466cb" - }, - "lib/openzeppelin-contracts-upgradeable": { - "rev": "db5a20c719fdf4e40c1cef546ac8b2ef7175d53f" - } -} \ No newline at end of file diff --git a/solidity/automation_registry/foundry.toml b/solidity/automation_registry/foundry.toml deleted file mode 100644 index eb22be94ce..0000000000 --- a/solidity/automation_registry/foundry.toml +++ /dev/null @@ -1,8 +0,0 @@ -[profile.default] -src = "src" -out = "out" -libs = ["lib"] -via_ir = true -optimizer = true - -# See more config options https://github.com/foundry-rs/foundry/blob/master/crates/config/README.md#all-options diff --git a/solidity/automation_registry/lib/forge-std b/solidity/automation_registry/lib/forge-std deleted file mode 160000 index ebc60f500b..0000000000 --- a/solidity/automation_registry/lib/forge-std +++ /dev/null @@ -1 +0,0 @@ -Subproject commit ebc60f500bc6870baaf321a0196fddc24d6edb03 diff --git a/solidity/automation_registry/lib/openzeppelin-contracts b/solidity/automation_registry/lib/openzeppelin-contracts deleted file mode 160000 index 77bc5642a5..0000000000 --- a/solidity/automation_registry/lib/openzeppelin-contracts +++ /dev/null @@ -1 +0,0 @@ -Subproject commit 77bc5642a53d9c8eac8aec5c8ea9809a21d466cb diff --git a/solidity/automation_registry/lib/openzeppelin-contracts-upgradeable b/solidity/automation_registry/lib/openzeppelin-contracts-upgradeable deleted file mode 160000 index db5a20c719..0000000000 --- a/solidity/automation_registry/lib/openzeppelin-contracts-upgradeable +++ /dev/null @@ -1 +0,0 @@ -Subproject commit db5a20c719fdf4e40c1cef546ac8b2ef7175d53f diff --git a/solidity/automation_registry/src/CommonUtils.sol b/solidity/automation_registry/src/CommonUtils.sol deleted file mode 100644 index 6de9e510ff..0000000000 --- a/solidity/automation_registry/src/CommonUtils.sol +++ /dev/null @@ -1,99 +0,0 @@ -// SPDX-License-Identifier: MIT -pragma solidity 0.8.27; - -import {LibRegistry} from "./LibRegistry.sol"; - -// Helper library used by AutomationRegistry and AutomationController. -library CommonUtils { - - /// @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 lockedFeeForNextCycle; - bytes32 txHash; - uint64 taskIndex; - uint64 registrationTime; - uint64 expiryTime; - address owner; - CommonUtils.TaskState state; - 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_lockedFeeForNextCycle >> 128); - - // --- Decode lockedFeeForNextCycle (lower 128 bits) --- - details.lockedFeeForNextCycle = uint128(t.automationFeeCapForCycle_lockedFeeForNextCycle); - - // --- Direct values --- - details.txHash = t.txHash; - details.owner = t.owner; - details.payloadTx = t.payloadTx; - details.auxData = t.auxData; - - // --- Decode packed uint256: taskIndex | registrationTime | expiryTime | state --- - details.taskIndex = uint64(t.taskIndex_registrationTime_expiryTime_state >> 192); - details.registrationTime = uint64(t.taskIndex_registrationTime_expiryTime_state >> 128); - details.expiryTime = uint64(t.taskIndex_registrationTime_expiryTime_state >> 64); - details.state = CommonUtils.TaskState(uint8(t.taskIndex_registrationTime_expiryTime_state >> 56)); - } - - - /// @notice Deposit and fee related accounting. - struct Deposit { - uint256 totalDepositedAutomationFees; - address coldWallet; - // uint256 totalLockedFees; // TO_DO - // 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; - } -} diff --git a/solidity/automation_registry/deploy_automation_registry.sh b/solidity/supra_contracts/deploy_automation_registry.sh similarity index 100% rename from solidity/automation_registry/deploy_automation_registry.sh rename to solidity/supra_contracts/deploy_automation_registry.sh diff --git a/solidity/automation_registry/getTaskDetails.js b/solidity/supra_contracts/getTaskDetails.js similarity index 100% rename from solidity/automation_registry/getTaskDetails.js rename to solidity/supra_contracts/getTaskDetails.js diff --git a/solidity/automation_registry/package-lock.json b/solidity/supra_contracts/package-lock.json similarity index 100% rename from solidity/automation_registry/package-lock.json rename to solidity/supra_contracts/package-lock.json diff --git a/solidity/automation_registry/package.json b/solidity/supra_contracts/package.json similarity index 100% rename from solidity/automation_registry/package.json rename to solidity/supra_contracts/package.json diff --git a/solidity/automation_registry/run.sh b/solidity/supra_contracts/run.sh similarity index 100% rename from solidity/automation_registry/run.sh rename to solidity/supra_contracts/run.sh diff --git a/solidity/automation_registry/script/DeployAutomationRegistry.s.sol b/solidity/supra_contracts/script/DeployAutomationRegistry.s.sol similarity index 100% rename from solidity/automation_registry/script/DeployAutomationRegistry.s.sol rename to solidity/supra_contracts/script/DeployAutomationRegistry.s.sol diff --git a/solidity/automation_registry/src/AutomationController.sol b/solidity/supra_contracts/src/AutomationController.sol similarity index 100% rename from solidity/automation_registry/src/AutomationController.sol rename to solidity/supra_contracts/src/AutomationController.sol diff --git a/solidity/automation_registry/src/AutomationRegistry.sol b/solidity/supra_contracts/src/AutomationRegistry.sol similarity index 100% rename from solidity/automation_registry/src/AutomationRegistry.sol rename to solidity/supra_contracts/src/AutomationRegistry.sol diff --git a/solidity/supra_contracts/src/CommonUtils.sol b/solidity/supra_contracts/src/CommonUtils.sol index 21c43f71f5..b207a4a946 100644 --- a/solidity/supra_contracts/src/CommonUtils.sol +++ b/solidity/supra_contracts/src/CommonUtils.sol @@ -1,10 +1,91 @@ // SPDX-License-Identifier: MIT pragma solidity 0.8.27; +import {LibRegistry} from "./LibRegistry.sol"; // Helper library used by supra contracts library CommonUtils { + /// @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 lockedFeeForNextCycle; + bytes32 txHash; + uint64 taskIndex; + uint64 registrationTime; + uint64 expiryTime; + address owner; + CommonUtils.TaskState state; + 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_lockedFeeForNextCycle >> 128); + + // --- Decode lockedFeeForNextCycle (lower 128 bits) --- + details.lockedFeeForNextCycle = uint128(t.automationFeeCapForCycle_lockedFeeForNextCycle); + + // --- Direct values --- + details.txHash = t.txHash; + details.owner = t.owner; + details.payloadTx = t.payloadTx; + details.auxData = t.auxData; + + // --- Decode packed uint256: taskIndex | registrationTime | expiryTime | state --- + details.taskIndex = uint64(t.taskIndex_registrationTime_expiryTime_state >> 192); + details.registrationTime = uint64(t.taskIndex_registrationTime_expiryTime_state >> 128); + details.expiryTime = uint64(t.taskIndex_registrationTime_expiryTime_state >> 64); + details.state = CommonUtils.TaskState(uint8(t.taskIndex_registrationTime_expiryTime_state >> 56)); + } + + + /// @notice Deposit and fee related accounting. + struct Deposit { + uint256 totalDepositedAutomationFees; + address coldWallet; + // uint256 totalLockedFees; // TO_DO + // 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. diff --git a/solidity/automation_registry/src/IAutomationController.sol b/solidity/supra_contracts/src/IAutomationController.sol similarity index 100% rename from solidity/automation_registry/src/IAutomationController.sol rename to solidity/supra_contracts/src/IAutomationController.sol diff --git a/solidity/automation_registry/src/IAutomationRegistry.sol b/solidity/supra_contracts/src/IAutomationRegistry.sol similarity index 100% rename from solidity/automation_registry/src/IAutomationRegistry.sol rename to solidity/supra_contracts/src/IAutomationRegistry.sol diff --git a/solidity/automation_registry/src/LibController.sol b/solidity/supra_contracts/src/LibController.sol similarity index 100% rename from solidity/automation_registry/src/LibController.sol rename to solidity/supra_contracts/src/LibController.sol diff --git a/solidity/automation_registry/src/LibRegistry.sol b/solidity/supra_contracts/src/LibRegistry.sol similarity index 100% rename from solidity/automation_registry/src/LibRegistry.sol rename to solidity/supra_contracts/src/LibRegistry.sol diff --git a/solidity/automation_registry/test/AutomationController.t.sol b/solidity/supra_contracts/test/AutomationController.t.sol similarity index 100% rename from solidity/automation_registry/test/AutomationController.t.sol rename to solidity/supra_contracts/test/AutomationController.t.sol diff --git a/solidity/automation_registry/test/AutomationRegistry.t.sol b/solidity/supra_contracts/test/AutomationRegistry.t.sol similarity index 100% rename from solidity/automation_registry/test/AutomationRegistry.t.sol rename to solidity/supra_contracts/test/AutomationRegistry.t.sol From f19c25f36bbc8e0caafa400272097eb015a23aa2 Mon Sep 17 00:00:00 2001 From: Udit Yadav Date: Wed, 17 Dec 2025 14:45:32 +0530 Subject: [PATCH 06/31] removed blockmeta address from controller variable name fixes --- .gitignore | 2 + .../script/DeployAutomationRegistry.s.sol | 25 +-- .../src/AutomationController.sol | 39 +---- .../src/AutomationRegistry.sol | 80 ++++----- .../src/IAutomationController.sol | 3 +- .../src/IAutomationRegistry.sol | 4 +- solidity/supra_contracts/src/LibRegistry.sol | 12 +- .../test/AutomationController.t.sol | 134 +++------------ .../test/AutomationRegistry.t.sol | 160 +++++++++--------- 9 files changed, 162 insertions(+), 297 deletions(-) diff --git a/.gitignore b/.gitignore index c1863291f5..855f2ab363 100644 --- a/.gitignore +++ b/.gitignore @@ -31,3 +31,5 @@ rustc-ice-* # Fixtures /test-fixtures + +node_modules \ No newline at end of file diff --git a/solidity/supra_contracts/script/DeployAutomationRegistry.s.sol b/solidity/supra_contracts/script/DeployAutomationRegistry.s.sol index ccd362a16c..cb2bb0fabf 100644 --- a/solidity/supra_contracts/script/DeployAutomationRegistry.s.sol +++ b/solidity/supra_contracts/script/DeployAutomationRegistry.s.sol @@ -4,7 +4,6 @@ pragma solidity 0.8.27; import {Script, console} from "forge-std/Script.sol"; import {AutomationController} from "../src/AutomationController.sol"; import {AutomationRegistry} from "../src/AutomationRegistry.sol"; -import {BlockMeta} from "../src/BlockMeta.sol"; import {ERC20Supra} from "../src/ERC20Supra.sol"; import {ERC1967Proxy} from "../lib/openzeppelin-contracts/contracts/proxy/ERC1967/ERC1967Proxy.sol"; @@ -43,15 +42,12 @@ contract DeployAutomationRegistry is Script { function run() public { vm.startBroadcast(); - ERC20Supra supraERC20; // ERC20Supra contract + ERC20Supra erc20Supra; // ERC20Supra contract AutomationRegistry registryImpl; // AutomationRegistry implementation contract ERC1967Proxy registryProxy; // AutomationRegistry proxy contract AutomationRegistry registry; // Instance of AutomationRegistry at proxy address - BlockMeta blockMetaImpl; // BlockMeta implementation contract - ERC1967Proxy blockMetaProxy; // BlockMeta proxy contract - AutomationController controllerImpl; // AutomationController implementation contract ERC1967Proxy controllerProxy; // AutomationController proxy contract AutomationController controller; // Instance of AutomationController at proxy address @@ -59,8 +55,8 @@ contract DeployAutomationRegistry is Script { // --------------------------------------------------------------------- // Deploy ERC20Supra // --------------------------------------------------------------------- - supraERC20 = new ERC20Supra(msg.sender); - console.log("ERC20Supra deployed at: ", address(supraERC20)); + erc20Supra = new ERC20Supra(msg.sender); + console.log("ERC20Supra deployed at: ", address(erc20Supra)); // --------------------------------------------------------------------- // Deploy AutomationRegistry @@ -82,29 +78,20 @@ contract DeployAutomationRegistry is Script { sysTaskDurationCapSecs, // sysTaskDurationCapSecs sysRegistryMaxGasCap, // sysRegistryMaxGasCap sysTaskCapacity, // sysTaskCapacity - vmSigner, // vm address - address(supraERC20) // supraERC20 address + vmSigner, // VM Signer address + address(erc20Supra) // ERC20Supra address ) ); registryProxy = new ERC1967Proxy(address(registryImpl), initData); console.log("AutomationRegistry proxy deployed at: ", address(registryProxy)); registry = AutomationRegistry(address(registryProxy)); - // --------------------------------------------------------------------- - // Deploy BlockMeta - // --------------------------------------------------------------------- - blockMetaImpl = new BlockMeta(); - console.log("BlockMeta implementation deployed at: ", address(blockMetaImpl)); - bytes memory blockMetaInitData = abi.encodeCall(BlockMeta.initialize, ()); - blockMetaProxy = new ERC1967Proxy(address(blockMetaImpl), blockMetaInitData); - console.log("BlockMeta proxy deployed at: ", address(blockMetaProxy)); - // --------------------------------------------------------------------- // Deploy AutomationController // --------------------------------------------------------------------- controllerImpl = new AutomationController(); console.log("AutomationController implementation deployed at: ", address(controllerImpl)); - bytes memory controllerInitData = abi.encodeCall(AutomationController.initialize,(address(registry), address(blockMetaProxy))); + bytes memory controllerInitData = abi.encodeCall(AutomationController.initialize,(address(registry))); controllerProxy = new ERC1967Proxy(address(controllerImpl), controllerInitData); console.log("AutomationController proxy deployed at: ", address(controllerProxy)); controller = AutomationController(address(controllerProxy)); diff --git a/solidity/supra_contracts/src/AutomationController.sol b/solidity/supra_contracts/src/AutomationController.sol index 67c1949596..80e92d513e 100644 --- a/solidity/supra_contracts/src/AutomationController.sol +++ b/solidity/supra_contracts/src/AutomationController.sol @@ -23,7 +23,6 @@ contract AutomationController is IAutomationController, Ownable2StepUpgradeable, /// @dev State variables LibController.AutomationCycleInfo cycleInfo; IAutomationRegistry public registry; - address public blockMeta; // ::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::: EVENTS ::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::: @@ -73,9 +72,6 @@ contract AutomationController is IAutomationController, Ownable2StepUpgradeable, /// @notice Emitted when the registry smart contract address is updated. event RegistryUpdated(address indexed oldRegistryAddress, address indexed newRegistryAddress); - - /// @notice Emitted when the blockMeta smart contract address is updated. - event BlockMetaAddressUpdated(address indexed oldBlockMetaAddress, address indexed newBlockMetaAddress); // :::::::::::::::::::::::::::::::::::::::::::::::::::::::::: CONSTRUCTOR AND INITIALIZER :::::::::::::::::::::::::::::::::::::::::::::::::::::::::: @@ -86,16 +82,11 @@ contract AutomationController is IAutomationController, Ownable2StepUpgradeable, /// @notice Initializes the configuration parameters of the contract, can only be called once. /// @param _registry Address of the registry smart contract. - /// @param _blockMeta Address of the blockmeta smart contract. - function initialize(address _registry, address _blockMeta) public initializer { + function initialize(address _registry) public initializer { if (_registry == address(0)) { revert AddressCannotBeZero(); } if (!_registry.isContract()) { revert AddressCannotBeEOA(); } - if (_blockMeta == address(0)) { revert AddressCannotBeZero(); } - if (!_blockMeta.isContract()) { revert AddressCannotBeEOA(); } - registry = IAutomationRegistry(_registry); - blockMeta = _blockMeta; (CommonUtils.CycleState state, uint64 cycleId) = registry.isAutomationEnabled() ? (CommonUtils.CycleState.STARTED, 1) : (CommonUtils.CycleState.READY, 0); @@ -110,12 +101,12 @@ contract AutomationController is IAutomationController, Ownable2StepUpgradeable, __Ownable_init(msg.sender); } - /// @notice Called by the VM on `AutomationBookkeepingAction::Process` action emitted by native layer ahead of the cycle transition. + /// @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 - if (msg.sender != registry.getVm()) { revert CallerNotVM(); } + // Check caller is VM Signer + if (msg.sender != registry.getVmSigner()) { revert CallerNotVmSigner(); } CommonUtils.CycleState state = cycleInfo.state(); @@ -174,7 +165,7 @@ contract AutomationController is IAutomationController, Ownable2StepUpgradeable, /// @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 (msg.sender != blockMeta) { revert CallerNotBlockMeta(); } + if (tx.origin != registry.getVmSigner()) { revert CallerNotVmSigner(); } if (!registry.isAutomationEnabled()) { return; } @@ -315,12 +306,12 @@ contract AutomationController is IAutomationController, Ownable2StepUpgradeable, if(registry.ifTaskExists(_taskIndex)) { markTaskProcessed(_taskIndex); - bool isUST = registry.checkTaskType(_taskIndex, CommonUtils.TaskType.UST); + bool isUst = registry.checkTaskType(_taskIndex, CommonUtils.TaskType.UST); CommonUtils.TaskDetails memory task = registry.getTaskDetails(_taskIndex); // Task is cancelled or expired if(task.state == CommonUtils.TaskState.CANCELLED || _currentTime >= task.expiryTime) { - if(isUST) { + if(isUst) { (bool sent, ) = address(registry).call( abi.encodeCall( IAutomationRegistry.refundDepositAndDrop, @@ -334,7 +325,7 @@ contract AutomationController is IAutomationController, Ownable2StepUpgradeable, require(removed, RemoveTaskFailed()); } result.isRemoved = true; - } else if(!isUST) { + } else if(!isUst) { // Active GST // Governance submitted tasks are not charged @@ -419,7 +410,7 @@ contract AutomationController is IAutomationController, Ownable2StepUpgradeable, // 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 erc20Supra = registry.supraERC20(); + address erc20Supra = registry.erc20Supra(); bool isRemoved; uint128 gas; uint128 fees; @@ -777,18 +768,6 @@ contract AutomationController is IAutomationController, Ownable2StepUpgradeable, emit RegistryUpdated(oldRegistry, _registry); } - /// @notice Function to update the blockMeta smart contract address. - /// @param _blockMeta Address of the blockMeta smart contract. - function setBlockMeta(address _blockMeta) external onlyOwner { - if (_blockMeta == address(0)) { revert AddressCannotBeZero(); } - if (!_blockMeta.isContract()) { revert AddressCannotBeEOA(); } - - address oldBlockMeta = blockMeta; - blockMeta = _blockMeta; - - emit BlockMetaAddressUpdated(oldBlockMeta, _blockMeta); - } - // ::::::::::::::::::::::::::::::::::::::::::::::::::::::::: UPGRADEABILITY FUNCTIONS ::::::::::::::::::::::::::::::::::::::::::::::::::::::::: /// @notice Helper function that reverts when 'msg.sender' is not authorized to upgrade the contract. diff --git a/solidity/supra_contracts/src/AutomationRegistry.sol b/solidity/supra_contracts/src/AutomationRegistry.sol index 01eb648eba..6cc2d8c743 100644 --- a/solidity/supra_contracts/src/AutomationRegistry.sol +++ b/solidity/supra_contracts/src/AutomationRegistry.sol @@ -85,11 +85,11 @@ contract AutomationRegistry is IAutomationRegistry, Ownable2StepUpgradeable, UUP /// @notice Emitted when automation is disabled. event AutomationDisabled(bool indexed status); - /// @notice Emitted when the VM address is updated. - event VmAddressUpdated(address indexed oldVmAddress, address indexed newVmAddress); + /// @notice Emitted when the VM Signer address is updated. + event VmSignerUpdated(address indexed oldVmSigner, address indexed newVmSigner); - /// @notice Emitted when the SupraERC20 address is updated. - event SupraERC20Updated(address indexed oldSupraERC20, address indexed newSupraERC20); + /// @notice Emitted when the ERC20Supra address is updated. + event Erc20SupraUpdated(address indexed oldErc20Supra, address indexed newErc20Supra); /// @notice Emitted when a new config is added. event ConfigBufferUpdated( @@ -174,8 +174,8 @@ contract AutomationRegistry is IAutomationRegistry, Ownable2StepUpgradeable, UUP /// @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 _vm Address for the VM. - /// @param _supraERC20 Address of the ERC20Supra contract. + /// @param _vmSigner Address for the VM Signer. + /// @param _erc20Supra Address of the ERC20Supra contract. function initialize( uint64 _taskDurationCapSecs, uint128 _registryMaxGasCap, @@ -189,8 +189,8 @@ contract AutomationRegistry is IAutomationRegistry, Ownable2StepUpgradeable, UUP uint64 _sysTaskDurationCapSecs, uint128 _sysRegistryMaxGasCap, uint16 _sysTaskCapacity, - address _vm, - address _supraERC20 + address _vmSigner, + address _erc20Supra ) public initializer { validateConfigParameters( _taskDurationCapSecs, @@ -203,10 +203,10 @@ contract AutomationRegistry is IAutomationRegistry, Ownable2StepUpgradeable, UUP _sysRegistryMaxGasCap, _sysTaskCapacity ); - if(_vm == address(0)) revert AddressCannotBeZero(); + if(_vmSigner == address(0)) revert AddressCannotBeZero(); - if(_supraERC20 == address(0)) revert AddressCannotBeZero(); - if(!_supraERC20.isContract()) revert AddressCannotBeEOA(); + if(_erc20Supra == address(0)) revert AddressCannotBeZero(); + if(!_erc20Supra.isContract()) revert AddressCannotBeEOA(); LibRegistry.Config memory config = LibRegistry.createConfig( _registryMaxGasCap, @@ -228,8 +228,8 @@ contract AutomationRegistry is IAutomationRegistry, Ownable2StepUpgradeable, UUP _sysRegistryMaxGasCap, true, true, - _vm, - _supraERC20, + _vmSigner, + _erc20Supra, config ); @@ -304,7 +304,7 @@ contract AutomationRegistry is IAutomationRegistry, Ownable2StepUpgradeable, UUP deposit.totalDepositedAutomationFees += _automationFeeCapForCycle; uint128 fee = regConfig.flatRegistrationFeeWei() + _automationFeeCapForCycle; - bool sent = IERC20(regConfig.supraERC20).transferFrom(msg.sender, address(this), fee); + bool sent = IERC20(regConfig.erc20Supra).transferFrom(msg.sender, address(this), fee); if (!sent) { revert TransferFailed(); } emit TaskRegistered(taskIndex, msg.sender, regConfig.flatRegistrationFeeWei(), _automationFeeCapForCycle, regState.tasks[taskIndex].getTaskDetails()); @@ -569,7 +569,7 @@ contract AutomationRegistry is IAutomationRegistry, Ownable2StepUpgradeable, UUP // Refund and emit event if any tasks were stopped if(stoppedTaskDetails.length > 0) { - uint256 balance = IERC20(regConfig.supraERC20).balanceOf(address(this)); + uint256 balance = IERC20(regConfig.erc20Supra).balanceOf(address(this)); if(balance < totalRefundFee) { revert InsufficientBalanceForRefund(); } refund(msg.sender, totalRefundFee); @@ -710,7 +710,7 @@ contract AutomationRegistry is IAutomationRegistry, Ownable2StepUpgradeable, UUP /// @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.supraERC20).transfer(_to, _amount); + bool sent = IERC20(regConfig.erc20Supra).transfer(_to, _amount); if (!sent) { revert TransferFailed(); } return sent; @@ -971,7 +971,7 @@ contract AutomationRegistry is IAutomationRegistry, Ownable2StepUpgradeable, UUP uint128 _refundableAmount, uint8 _refundType ) private returns (bool) { - uint256 balance = IERC20(regConfig.supraERC20).balanceOf(address(this)); + uint256 balance = IERC20(regConfig.erc20Supra).balanceOf(address(this)); if(balance < _refundableAmount) { emit ErrorInsufficientBalanceToRefund(_taskIndex, _taskOwner, _refundType, _refundableAmount); return false; @@ -1106,27 +1106,27 @@ contract AutomationRegistry is IAutomationRegistry, Ownable2StepUpgradeable, UUP emit AutomationDisabled(regConfig.automationEnabled()); } - /// @notice Function to update the VM address. - /// @param _vm New address for VM. - function setVm(address _vm) external onlyOwner { - if(_vm == address(0)) { revert AddressCannotBeZero(); } + /// @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 oldVm = regConfig.vm; - regConfig.vm = _vm; + address oldVmSigner = regConfig.vmSigner; + regConfig.vmSigner = _vmSigner; - emit VmAddressUpdated(oldVm, _vm); + emit VmSignerUpdated(oldVmSigner, _vmSigner); } - /// @notice Function to update the SupraERC20 address. - /// @param _supraERC20 New address for SupraERC20. - function setSupraERC20(address _supraERC20) external onlyOwner { - if(_supraERC20 == address(0)) { revert AddressCannotBeZero(); } - if(!_supraERC20.isContract()) { revert AddressCannotBeEOA(); } + /// @notice Function to update the ERC20Supra address. + /// @param _erc20Supra New address for ERC20Supra. + function setErc20Supra(address _erc20Supra) external onlyOwner { + if(_erc20Supra == address(0)) { revert AddressCannotBeZero(); } + if(!_erc20Supra.isContract()) { revert AddressCannotBeEOA(); } - address oldSupraERC20 = regConfig.supraERC20; - regConfig.supraERC20 = _supraERC20; + address oldErc20Supra = regConfig.erc20Supra; + regConfig.erc20Supra = _erc20Supra; - emit SupraERC20Updated(oldSupraERC20, _supraERC20); + emit Erc20SupraUpdated(oldErc20Supra, _erc20Supra); } /// @notice Function to withdraw the accumulated fees. @@ -1134,12 +1134,12 @@ contract AutomationRegistry is IAutomationRegistry, Ownable2StepUpgradeable, UUP function withdrawFees(uint256 _amount) external onlyOwner { address coldWallet = deposit.coldWallet; if(coldWallet == address(0)) { revert ColdWalletNotSet(); } - uint256 balance = IERC20(regConfig.supraERC20).balanceOf(address(this)); + uint256 balance = IERC20(regConfig.erc20Supra).balanceOf(address(this)); if(balance < _amount) { revert InsufficientBalance(); } if(balance - _amount < regState.cycleLockedFees + deposit.totalDepositedAutomationFees) { revert RequestExceedsLockedBalance(); } - bool sent = IERC20(regConfig.supraERC20).transfer(coldWallet, _amount); + bool sent = IERC20(regConfig.erc20Supra).transfer(coldWallet, _amount); if(!sent) { revert TransferFailed(); } emit RegistryFeeWithdrawn(coldWallet, _amount); @@ -1358,14 +1358,14 @@ contract AutomationRegistry is IAutomationRegistry, Ownable2StepUpgradeable, UUP return deposit.coldWallet; } - /// @notice Returns the VM address. - function getVm() external view returns (address) { - return regConfig.vm; + /// @notice Returns the VM Signer address. + function getVmSigner() external view returns (address) { + return regConfig.vmSigner; } - /// @notice Returns the SupraERC20 address. - function supraERC20() external view returns (address) { - return regConfig.supraERC20; + /// @notice Returns the ERC20Supra address. + function erc20Supra() external view returns (address) { + return regConfig.erc20Supra; } /// @notice Returns the address of AutomationController smart contract. diff --git a/solidity/supra_contracts/src/IAutomationController.sol b/solidity/supra_contracts/src/IAutomationController.sol index 94a8915102..12a1fa6967 100644 --- a/solidity/supra_contracts/src/IAutomationController.sol +++ b/solidity/supra_contracts/src/IAutomationController.sol @@ -6,8 +6,7 @@ import {CommonUtils} from "./CommonUtils.sol"; interface IAutomationController { // Custom errors error AddressCannotBeEOA(); - error CallerNotBlockMeta(); - error CallerNotVM(); + error CallerNotVmSigner(); error ConfigUpdateFailed(); error InconsistentTransitionState(); error AddressCannotBeZero(); diff --git a/solidity/supra_contracts/src/IAutomationRegistry.sol b/solidity/supra_contracts/src/IAutomationRegistry.sol index b7cafd857c..f5628b0f8e 100644 --- a/solidity/supra_contracts/src/IAutomationRegistry.sol +++ b/solidity/supra_contracts/src/IAutomationRegistry.sol @@ -71,8 +71,8 @@ interface IAutomationRegistry { function totalTasks() external view returns (uint256); function ifConfigBufferExists() external view returns (bool); function getBufferCycleDurationSecs() external view returns (uint64); - function getVm() external returns (address); - function supraERC20() external view returns (address); + function getVmSigner() external returns (address); + function erc20Supra() external view returns (address); function isAutomationEnabled() external view returns (bool); function calculateAutomationFeeMultiplierForCurrentCycleInternal() external view returns (uint128); function calculateAutomationFeeMultiplierForCommittedOccupancy(uint128 _totalCommittedMaxGas) external view returns (uint128); diff --git a/solidity/supra_contracts/src/LibRegistry.sol b/solidity/supra_contracts/src/LibRegistry.sol index add087416b..67cdc06b02 100644 --- a/solidity/supra_contracts/src/LibRegistry.sol +++ b/solidity/supra_contracts/src/LibRegistry.sol @@ -29,8 +29,8 @@ library LibRegistry { uint256 nextCycleRegistryMaxGasCap_nextCycleSysRegistryMaxGasCap; // address | bool | bool uint256 controller_registrationEnabled_automationEnabled; - address vm; - address supraERC20; + address vmSigner; + address erc20Supra; Config config; } @@ -39,8 +39,8 @@ library LibRegistry { uint128 _nextCycleSysRegistryMaxGasCap, bool _registrationEnabled, bool _automationEnabled, - address _vm, - address _supraERC20, + address _vmSigner, + address _erc20Supra, Config memory _config ) internal pure returns (RegistryConfig memory rcfg) { // Pack nextCycleRegistryMaxGasCap | nextCycleSysRegistryMaxGasCap @@ -54,8 +54,8 @@ library LibRegistry { (_registrationEnabled ? (uint256(1) << 95) : 0) | (_automationEnabled ? (uint256(1) << 94) : 0); - rcfg.vm = _vm; - rcfg.supraERC20 = _supraERC20; + rcfg.vmSigner = _vmSigner; + rcfg.erc20Supra = _erc20Supra; // Assign inner Config rcfg.config = _config; diff --git a/solidity/supra_contracts/test/AutomationController.t.sol b/solidity/supra_contracts/test/AutomationController.t.sol index 2d546ae9bc..a31615f5d0 100644 --- a/solidity/supra_contracts/test/AutomationController.t.sol +++ b/solidity/supra_contracts/test/AutomationController.t.sol @@ -9,42 +9,37 @@ import {AutomationRegistry} from "../src/AutomationRegistry.sol"; import {AutomationController} from "../src/AutomationController.sol"; import {IAutomationController} from "../src/IAutomationController.sol"; import {ERC20Supra} from "../src/ERC20Supra.sol"; -import {BlockMeta} from "../src/BlockMeta.sol"; import {CommonUtils} from "../src/CommonUtils.sol"; contract AutomationControllerTest is Test { AutomationRegistry registry; // AutomationRegistry instance on proxy address AutomationController controller; // AutomationController instance on proxy address - BlockMeta blockMeta; // BlockMeta instance on proxy address - ERC20Supra supraERC20; // ERC20Supra contract + ERC20Supra erc20Supra; // ERC20Supra contract address admin = address(0xA11CE); - address vmAddress = address(0x99); + address vmSigner = address(0x5355500000000000000000000000000000000000); 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 ERC20Supra, BlockMeta, AutomationRegistry and AutomationController contracts. + /// @dev Deploys and initializes ERC20Supra, AutomationRegistry and AutomationController contracts. function setUp() public { vm.deal(alice, 100 ether); // Deploy ERC20Supra vm.prank(admin); - supraERC20 = new ERC20Supra(msg.sender); + erc20Supra = new ERC20Supra(msg.sender); // Deploy AutomationRegistry proxy - registry = AutomationRegistry(deployRegistry(address(supraERC20))); - - // Deploy BlockMeta proxy - blockMeta = BlockMeta(deployBlockMeta()); + registry = AutomationRegistry(deployRegistry(address(erc20Supra))); // Deploy AutomationController proxy - controller = AutomationController(deployController(address(registry), address(blockMeta))); + controller = AutomationController(deployController(address(registry))); } /// @dev Helper function to deploy AutomationRegistry proxy - function deployRegistry(address _supraERC20) private returns (address) { + function deployRegistry(address _erc20Supra) private returns (address) { vm.startPrank(admin); AutomationRegistry impl = new AutomationRegistry(); @@ -63,8 +58,8 @@ contract AutomationControllerTest is Test { 3600, // sysTaskDurationCapSecs 5_000_000, // sysRegistryMaxGasCap 500, // sysTaskCapacity - vmAddress, // vm address - address(_supraERC20) // supraERC20 address + vmSigner, // VM Signer address + address(_erc20Supra) // ERC20Supra address ) ); @@ -74,22 +69,11 @@ contract AutomationControllerTest is Test { return address(proxy); } - /// @dev Helper function to deploy BlockMeta proxy - function deployBlockMeta() private returns (address) { - vm.startPrank(admin); - BlockMeta impl = new BlockMeta(); - bytes memory initData = abi.encodeCall(BlockMeta.initialize, ()); - ERC1967Proxy proxy = new ERC1967Proxy(address(impl), initData); - vm.stopPrank(); - - return address(proxy); - } - /// @dev Helper function to deploy AutomationController proxy - function deployController(address _registry, address _blockMeta) private returns (address) { + function deployController(address _registry) private returns (address) { vm.startPrank(admin); AutomationController impl = new AutomationController(); - bytes memory initData = abi.encodeCall(AutomationController.initialize,(_registry, _blockMeta)); + bytes memory initData = abi.encodeCall(AutomationController.initialize,(_registry)); ERC1967Proxy proxy = new ERC1967Proxy(address(impl), initData); vm.stopPrank(); @@ -100,7 +84,6 @@ contract AutomationControllerTest is Test { function testInitialize() public view { assertEq(controller.owner(), admin); assertEq(address(controller.registry()), address(registry)); - assertEq(controller.blockMeta(), address(blockMeta)); (uint64 index, uint64 startTime, uint64 durationSecs, CommonUtils.CycleState state) = controller.getCycleInfo(); assertEq(index, 1); @@ -114,17 +97,14 @@ contract AutomationControllerTest is Test { vm.expectRevert(Initializable.InvalidInitialization.selector); vm.prank(admin); - controller.initialize(address(registry), address(blockMeta)); + controller.initialize(address(registry)); } /// @dev Test to ensure initialize reverts if registry address is zero. function testInitializeRevertsIfRegistryZero() public { - // Deploy BlockMeta proxy - address blockMetaProxy = deployBlockMeta(); - // Deploy AutomationController proxy AutomationController impl = new AutomationController(); - bytes memory initData = abi.encodeCall(AutomationController.initialize,(address(0), blockMetaProxy)); + bytes memory initData = abi.encodeCall(AutomationController.initialize,(address(0))); vm.expectRevert(IAutomationController.AddressCannotBeZero.selector); new ERC1967Proxy(address(impl), initData); @@ -132,38 +112,9 @@ contract AutomationControllerTest is Test { /// @dev Test to ensure initialize reverts if registry address is EOA. function testInitializeRevertsIfRegistryEoa() public { - // Deploy BlockMeta proxy - address blockMetaProxy = deployBlockMeta(); - - // Deploy AutomationController proxy - AutomationController impl = new AutomationController(); - bytes memory initData = abi.encodeCall(AutomationController.initialize,(alice, blockMetaProxy)); - - vm.expectRevert(IAutomationController.AddressCannotBeEOA.selector); - new ERC1967Proxy(address(impl), initData); - } - - /// @dev Test to ensure initialize reverts if blockMeta address is zero. - function testInitializeRevertsIfBlockMetaZero() public { - // Deploy AutomationRegistry proxy - address registryProxy = deployRegistry(address(supraERC20)); - - // Deploy AutomationController proxy - AutomationController impl = new AutomationController(); - bytes memory initData = abi.encodeCall(AutomationController.initialize,(registryProxy, address(0))); - - vm.expectRevert(IAutomationController.AddressCannotBeZero.selector); - new ERC1967Proxy(address(impl), initData); - } - - /// @dev Test to ensure initialize reverts if blockMeta address is EOA. - function testInitializeRevertsIfBlockMetaEoa() public { - // Deploy AutomationRegistry proxy - address registryProxy = deployRegistry(address(supraERC20)); - // Deploy AutomationController proxy AutomationController impl = new AutomationController(); - bytes memory initData = abi.encodeCall(AutomationController.initialize,(registryProxy, alice)); + bytes memory initData = abi.encodeCall(AutomationController.initialize,(alice)); vm.expectRevert(IAutomationController.AddressCannotBeEOA.selector); new ERC1967Proxy(address(impl), initData); @@ -171,7 +122,7 @@ contract AutomationControllerTest is Test { /// @dev Test to ensure 'setRegistry' reverts if caller is not owner. function testSetRegistryRevertsIfNotOwner() public { - address newRegistry = deployRegistry(address(supraERC20)); + address newRegistry = deployRegistry(address(erc20Supra)); vm.expectRevert(abi.encodeWithSelector(OwnableUpgradeable.OwnableUnauthorizedAccount.selector,alice)); @@ -197,7 +148,7 @@ contract AutomationControllerTest is Test { /// @dev Test to ensure 'setRegistry' updates the registry address. function testSetRegistry() public { - address newRegistry = deployRegistry(address(supraERC20)); + address newRegistry = deployRegistry(address(erc20Supra)); vm.prank(admin); controller.setRegistry(newRegistry); @@ -207,7 +158,7 @@ contract AutomationControllerTest is Test { /// @dev Test to ensure 'setRegistry' emits event 'RegistryUpdated'. function testSetRegistryEmitsEvent() public { - address newRegistry = deployRegistry(address(supraERC20)); + address newRegistry = deployRegistry(address(erc20Supra)); vm.expectEmit(true, true, false, false); emit AutomationController.RegistryUpdated(address(controller.registry()), newRegistry); @@ -216,59 +167,12 @@ contract AutomationControllerTest is Test { controller.setRegistry(newRegistry); } - /// @dev Test to ensure 'setBlockMeta' reverts if caller is not owner. - function testSetBlockMetaRevertsIfNotOwner() public { - address newBlockMeta = deployBlockMeta(); - - vm.expectRevert(abi.encodeWithSelector(OwnableUpgradeable.OwnableUnauthorizedAccount.selector,alice)); - - vm.prank(alice); - controller.setBlockMeta(newBlockMeta); - } - - /// @dev Test to ensure 'setBlockMeta' reverts if address is zero. - function testSetBlockMetaRevertsIfAddressZero() public { - vm.expectRevert(IAutomationController.AddressCannotBeZero.selector); - - vm.prank(admin); - controller.setBlockMeta(address(0)); - } - - /// @dev Test to ensure 'setBlockMeta' reverts if address is EOA. - function testSetBlockMetaRevertsIfAddressEoa() public { - vm.expectRevert(IAutomationController.AddressCannotBeEOA.selector); - - vm.prank(admin); - controller.setBlockMeta(alice); - } - - /// @dev Test to ensure 'setBlockMeta' updates the blockMeta address. - function testSetBlockMeta() public { - address newBlockMeta = deployBlockMeta(); - - vm.prank(admin); - controller.setBlockMeta(newBlockMeta); - - assertEq(controller.blockMeta(), newBlockMeta); - } - - /// @dev Test to ensure 'setBlockMeta' emits event 'BlockMetaAddressUpdated'. - function testSetBlockMetaEmitsEvent() public { - address newBlockMeta = deployBlockMeta(); - - vm.expectEmit(true, true, false, false); - emit AutomationController.BlockMetaAddressUpdated(controller.blockMeta() , newBlockMeta); - - vm.prank(admin); - controller.setBlockMeta(newBlockMeta); - } - - /// @dev Test to ensure 'processTasks' reverts if caller is not VM address. + /// @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.CallerNotVM.selector); + vm.expectRevert(IAutomationController.CallerNotVmSigner.selector); vm.prank(admin); controller.processTasks(1, tasks); diff --git a/solidity/supra_contracts/test/AutomationRegistry.t.sol b/solidity/supra_contracts/test/AutomationRegistry.t.sol index fa16d47879..c8625866ec 100644 --- a/solidity/supra_contracts/test/AutomationRegistry.t.sol +++ b/solidity/supra_contracts/test/AutomationRegistry.t.sol @@ -9,7 +9,6 @@ import {AutomationRegistry} from "../src/AutomationRegistry.sol"; import {AutomationController} from "../src/AutomationController.sol"; import {IAutomationRegistry} from "../src/IAutomationRegistry.sol"; import {ERC20Supra} from "../src/ERC20Supra.sol"; -import {BlockMeta} from "../src/BlockMeta.sol"; import {LibRegistry} from "../src/LibRegistry.sol"; import {CommonUtils} from "../src/CommonUtils.sol"; @@ -17,10 +16,10 @@ contract AutomationRegistryTest is Test { AutomationRegistry impl; // implementation logic contract AutomationRegistry registry; // registry instance on proxy address ERC1967Proxy proxy; // proxy contract - ERC20Supra supraERC20; // ERC20Supra contract + ERC20Supra erc20Supra; // ERC20Supra contract address admin = address(0xA11CE); - address vmAddress = address(0x99); + address vmSigner = address(0x5355500000000000000000000000000000000000); address alice = address(0x123); address bob = address(0x456); @@ -32,7 +31,7 @@ contract AutomationRegistryTest is Test { vm.deal(alice, 100 ether); vm.startPrank(admin); - supraERC20 = new ERC20Supra(msg.sender); + erc20Supra = new ERC20Supra(msg.sender); impl = new AutomationRegistry(); bytes memory initData = abi.encodeCall( @@ -50,8 +49,8 @@ contract AutomationRegistryTest is Test { 3600, // sysTaskDurationCapSecs 5_000_000, // sysRegistryMaxGasCap 500, // sysTaskCapacity - vmAddress, // vm address - address(supraERC20) // supraERC20 address + vmSigner, // VM Signer address + address(erc20Supra) // ERC20Supra address ) ); @@ -69,8 +68,8 @@ contract AutomationRegistryTest is Test { assertEq(registry.getAutomationController(), address(0)); assertTrue(registry.isRegistrationEnabled()); assertTrue(registry.isAutomationEnabled()); - assertEq(registry.getVm(), vmAddress); - assertEq(registry.supraERC20(), address(supraERC20)); + assertEq(registry.getVmSigner(), vmSigner); + assertEq(registry.erc20Supra(), address(erc20Supra)); LibRegistry.ConfigDetails memory config = registry.getConfig(); @@ -97,11 +96,11 @@ contract AutomationRegistryTest is Test { vm.prank(admin); registry.initialize( 3600, 10_000_000, 0.001 ether, 0.002 ether, 50, 0.002 ether, 2, - 500, 2000, 3600, 5_000_000, 500, vmAddress, address(supraERC20) + 500, 2000, 3600, 5_000_000, 500, vmSigner, address(erc20Supra) ); } - /// @dev Test to ensure initialization fails if zero address is passed as VM address. - function testInitializeRevertsIfVmAddressZero() public { + /// @dev Test to ensure initialization fails if zero address is passed as VM Signer. + function testInitializeRevertsIfVmSignerZero() public { AutomationRegistry implementation = new AutomationRegistry(); bytes memory initData = abi.encodeCall( @@ -109,8 +108,8 @@ contract AutomationRegistryTest is Test { ( 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 address as zero - address(supraERC20) + address(0), // VM Signer as zero + address(erc20Supra) ) ); @@ -119,14 +118,14 @@ contract AutomationRegistryTest is Test { } /// @dev Test to ensure initialization fails if ERC20Supra address is zero. - function testInitializeRevertsIfSupraERC20IsZero() public { + function testInitializeRevertsIfErc20SupraIsZero() public { AutomationRegistry implementation = new AutomationRegistry(); bytes memory initData = abi.encodeCall( AutomationRegistry.initialize, ( 3600, 10_000_000, 0.001 ether, 0.002 ether, 50, 0.002 ether, - 2, 500, 2000, 3600, 5_000_000, 500, vmAddress, + 2, 500, 2000, 3600, 5_000_000, 500, vmSigner, address(0) // address(0) as ERC20Supra ) ); @@ -136,14 +135,14 @@ contract AutomationRegistryTest is Test { } /// @dev Test to ensure initialization fails if EOA is passed as ERC20Supra address. - function testInitializeRevertsIfSupraERC20IsEoa() public { + function testInitializeRevertsIfErc20SupraIsEoa() public { AutomationRegistry implementation = new AutomationRegistry(); bytes memory initData = abi.encodeCall( AutomationRegistry.initialize, ( 3600, 10_000_000, 0.001 ether, 0.002 ether, 50, 0.002 ether, - 2, 500, 2000, 3600, 5_000_000, 500, vmAddress, + 2, 500, 2000, 3600, 5_000_000, 500, vmSigner, admin // EOA address as ERC20Supra ) ); @@ -162,7 +161,7 @@ contract AutomationRegistryTest is Test { 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, vmAddress, address(supraERC20) + 3600, 5_000_000, 500, vmSigner, address(erc20Supra) ) ); @@ -180,7 +179,7 @@ contract AutomationRegistryTest is Test { 3600, 0, // registry max gas cap 0.001 ether, 0.002 ether, 50, 0.002 ether, 2, 500, - 2000, 3600, 5_000_000, 500, vmAddress, address(supraERC20) + 2000, 3600, 5_000_000, 500, vmSigner, address(erc20Supra) ) ); @@ -197,7 +196,7 @@ contract AutomationRegistryTest is Test { ( 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, vmAddress, address(supraERC20) + 0.002 ether, 2, 500, 2000, 3600, 5_000_000, 500, vmSigner, address(erc20Supra) ) ); @@ -214,7 +213,7 @@ contract AutomationRegistryTest is Test { ( 3600, 10_000_000, 0.001 ether, 0.002 ether, 50, 0.002 ether, 0, // congestion exponent - 500, 2000, 3600, 5_000_000, 500, vmAddress, address(supraERC20) + 500, 2000, 3600, 5_000_000, 500, vmSigner, address(erc20Supra) ) ); @@ -231,7 +230,7 @@ contract AutomationRegistryTest is Test { ( 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, vmAddress, address(supraERC20) + 2000, 3600, 5_000_000, 500, vmSigner, address(erc20Supra) ) ); @@ -248,7 +247,7 @@ contract AutomationRegistryTest is Test { ( 3600, 10_000_000, 0.001 ether, 0.002 ether, 50, 0.002 ether, 2, 500, 0, // cycle duration - 3600, 5_000_000, 500, vmAddress, address(supraERC20) + 3600, 5_000_000, 500, vmSigner, address(erc20Supra) ) ); @@ -266,7 +265,7 @@ contract AutomationRegistryTest is Test { 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, vmAddress, address(supraERC20) + 5_000_000, 500, vmSigner, address(erc20Supra) ) ); @@ -283,7 +282,7 @@ contract AutomationRegistryTest is Test { ( 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, vmAddress, address(supraERC20) + 500, vmSigner, address(erc20Supra) ) ); @@ -301,7 +300,7 @@ contract AutomationRegistryTest is Test { 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 - vmAddress, address(supraERC20) + vmSigner, address(erc20Supra) ) ); @@ -477,14 +476,9 @@ contract AutomationRegistryTest is Test { /// @dev Helper function that deploys AutomationController and returns its address. function deployAutomationController() internal returns (address) { - // Deploy BlockMeta proxy - BlockMeta blockMetaImpl = new BlockMeta(); - bytes memory blockMetaInitData = abi.encodeCall(BlockMeta.initialize, ()); - ERC1967Proxy blockMetaProxy = new ERC1967Proxy(address(blockMetaImpl), blockMetaInitData); - // Deploy AutomationController proxy AutomationController controllerImpl = new AutomationController(); - bytes memory controllerInitData = abi.encodeCall(AutomationController.initialize,(address(registry), address(blockMetaProxy))); + bytes memory controllerInitData = abi.encodeCall(AutomationController.initialize,(address(registry))); ERC1967Proxy controllerProxy = new ERC1967Proxy(address(controllerImpl), controllerInitData); return address(controllerProxy); @@ -538,94 +532,94 @@ contract AutomationRegistryTest is Test { registry.setAutomationController(alice); } - // :::::::::::::::::::::::::::::::::::::::::::::::::::::: Tests related to 'setVm' :::::::::::::::::::::::::::::::::::::::::::::::::::::: + // :::::::::::::::::::::::::::::::::::::::::::::::::::::: Tests related to 'setVmSigner' :::::::::::::::::::::::::::::::::::::::::::::::::::::: - /// @dev Test to ensure 'setVM' updates the VM address. - function testSetVm() public { - address newVm = address(0x100); + /// @dev Test to ensure 'setVmSigner' updates the VM Signer address. + function testSetVmSigner() public { + address newVmSigner = address(0x100); vm.prank(admin); - registry.setVm(newVm); + registry.setVmSigner(newVmSigner); - assertEq(registry.getVm(), newVm); + assertEq(registry.getVmSigner(), newVmSigner); } - /// @dev Test to ensure 'setVm' emits event 'VmAddressUpdated'. - function testSetVmEmitsEvent() public { - address oldVm = registry.getVm(); - address newVm = address(0x100); + /// @dev Test to ensure 'setVmSigner' emits event 'VmSignerUpdated'. + function testSetVmSignerEmitsEvent() public { + address oldVmSigner = registry.getVmSigner(); + address newVmSigner = address(0x100); vm.expectEmit(true, true, false, false); - emit AutomationRegistry.VmAddressUpdated(oldVm, newVm); + emit AutomationRegistry.VmSignerUpdated(oldVmSigner, newVmSigner); vm.prank(admin); - registry.setVm(newVm); + registry.setVmSigner(newVmSigner); } - /// @dev Test to ensure 'setVm' reverts if zero address is passed. - function testSetVmRevertsIfZeroAddress() public { + /// @dev Test to ensure 'setVmSigner' reverts if zero address is passed. + function testSetVmSignerRevertsIfZeroAddress() public { vm.expectRevert(IAutomationRegistry.AddressCannotBeZero.selector); vm.prank(admin); - registry.setVm(address(0)); + registry.setVmSigner(address(0)); } - /// @dev Test to ensure 'setVm' reverts if caller is not owner. - function testSetVmRevertsIfNotOwner() public { + /// @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); - registry.setVm(address(0x100)); + registry.setVmSigner(address(0x100)); } - // :::::::::::::::::::::::::::::::::::::::::::::::::::::: Tests related to 'setSupraERC20' :::::::::::::::::::::::::::::::::::::::::::::::::::::: + // :::::::::::::::::::::::::::::::::::::::::::::::::::::: Tests related to 'setErc20Supra' :::::::::::::::::::::::::::::::::::::::::::::::::::::: - /// @dev Test to ensure 'setSupraERC20' updates the ERC20Supra address. - function testSetSupraERC20() public { + /// @dev Test to ensure 'setErc20Supra' updates the ERC20Supra address. + function testSetErc20Supra() public { ERC20Supra supra = new ERC20Supra(msg.sender); vm.prank(admin); - registry.setSupraERC20(address(supra)); + registry.setErc20Supra(address(supra)); - assertEq(registry.supraERC20(), address(supra)); + assertEq(registry.erc20Supra(), address(supra)); } - /// @dev Test to ensure 'setSupraERC20' emits event 'SupraERC20Updated'. - function testSetSupraERC20EmitsEvent() public { - address oldAddr = registry.supraERC20(); + /// @dev Test to ensure 'setErc20Supra' emits event 'Erc20SupraUpdated'. + function testSetErc20SupraEmitsEvent() public { + address oldAddr = registry.erc20Supra(); ERC20Supra supra = new ERC20Supra(msg.sender); vm.expectEmit(true, true, false, false); - emit AutomationRegistry.SupraERC20Updated(oldAddr, address(supra)); + emit AutomationRegistry.Erc20SupraUpdated(oldAddr, address(supra)); vm.prank(admin); - registry.setSupraERC20(address(supra)); + registry.setErc20Supra(address(supra)); } - /// @dev Test to ensure 'setSupraERC20' reverts if zero address is passed. - function testSetSupraERC20RevertsIfZeroAddress() public { + /// @dev Test to ensure 'setErc20Supra' reverts if zero address is passed. + function testSetErc20SupraRevertsIfZeroAddress() public { vm.expectRevert(IAutomationRegistry.AddressCannotBeZero.selector); vm.prank(admin); - registry.setSupraERC20(address(0)); + registry.setErc20Supra(address(0)); } - /// @dev Test to ensure 'setSupraERC20' reverts if EOA is passed. - function testSetSupraERC20RevertsIfEoa() public { + /// @dev Test to ensure 'setErc20Supra' reverts if EOA is passed. + function testSetErc20SupraRevertsIfEoa() public { vm.expectRevert(IAutomationRegistry.AddressCannotBeEOA.selector); vm.prank(admin); - registry.setSupraERC20(alice); + registry.setErc20Supra(alice); } - /// @dev Test to ensure 'setSupraERC20' reverts if caller is not owner. - function testSetSupraERC20RevertsIfNotOwner() public { + /// @dev Test to ensure 'setErc20Supra' reverts if caller is not owner. + function testSetErc20SupraRevertsIfNotOwner() public { ERC20Supra supra = new ERC20Supra(msg.sender); vm.expectRevert(abi.encodeWithSelector(OwnableUpgradeable.OwnableUnauthorizedAccount.selector, alice)); vm.prank(alice); - registry.setSupraERC20(address(supra)); + registry.setErc20Supra(address(supra)); } // :::::::::::::::::::::::::::::::::::::::::::::::::::::: Tests related to 'setColdWallet' :::::::::::::::::::::::::::::::::::::::::::::::::::::: @@ -902,14 +896,14 @@ contract AutomationRegistryTest is Test { vm.prank(admin); registry.setColdWallet(coldWallet); - assertEq(supraERC20.balanceOf(coldWallet), 0); - assertEq(supraERC20.balanceOf(address(registry)), 0.502 ether); + assertEq(erc20Supra.balanceOf(coldWallet), 0); + assertEq(erc20Supra.balanceOf(address(registry)), 0.502 ether); vm.prank(admin); registry.withdrawFees(0.002 ether); - assertEq(supraERC20.balanceOf(coldWallet), 0.002 ether); - assertEq(supraERC20.balanceOf(address(registry)), 0.5 ether); + assertEq(erc20Supra.balanceOf(coldWallet), 0.002 ether); + assertEq(erc20Supra.balanceOf(address(registry)), 0.5 ether); } /// @dev Test to ensure 'withdrawFees' emits event 'RegistryFeeWithdrawn'. @@ -1244,8 +1238,8 @@ contract AutomationRegistryTest is Test { (bytes memory payload, bytes[] memory auxData) = payloadAndAuxData(0); vm.startPrank(alice); - supraERC20.deposit{value: 5 ether}(); - supraERC20.approve(address(registry), type(uint256).max); + erc20Supra.deposit{value: 5 ether}(); + erc20Supra.approve(address(registry), type(uint256).max); registry.register( payload, @@ -1264,8 +1258,8 @@ contract AutomationRegistryTest is Test { assertEq(registry.getNextTaskIndex(), 1); assertEq(registry.getGasCommittedForNextCycle(), 1_000_000); assertEq(registry.getTotalDepositedAutomationFees(), 0.5 ether); - assertEq(supraERC20.balanceOf(address(registry)), 0.002 ether + 0.5 ether); - assertEq(supraERC20.balanceOf(alice), 4.5 ether - 0.002 ether); + assertEq(erc20Supra.balanceOf(address(registry)), 0.002 ether + 0.5 ether); + assertEq(erc20Supra.balanceOf(alice), 4.5 ether - 0.002 ether); assertEq(taskMetadata.maxGasAmount, 1_000_000); assertEq(taskMetadata.gasPriceCap, 10 gwei); @@ -1287,8 +1281,8 @@ contract AutomationRegistryTest is Test { (bytes memory payload, bytes[] memory auxData) = payloadAndAuxData(0); vm.startPrank(alice); - supraERC20.deposit{value: 5 ether}(); - supraERC20.approve(address(registry), type(uint256).max); + erc20Supra.deposit{value: 5 ether}(); + erc20Supra.approve(address(registry), type(uint256).max); CommonUtils.TaskDetails memory taskMetadata = CommonUtils.TaskDetails( 1_000_000, @@ -1584,8 +1578,8 @@ contract AutomationRegistryTest is Test { assertEq(registry.totalTasks(), 0); assertEq(registry.getGasCommittedForNextCycle(), 0); assertEq(registry.getTotalDepositedAutomationFees(), 0); - assertEq(supraERC20.balanceOf(address(registry)), 0.002 ether + 0.25 ether); - assertEq(supraERC20.balanceOf(alice), 4.75 ether - 0.002 ether); + assertEq(erc20Supra.balanceOf(address(registry)), 0.002 ether + 0.25 ether); + assertEq(erc20Supra.balanceOf(alice), 4.75 ether - 0.002 ether); } /// @dev Test to ensure 'cancelTask' emits event 'TaskCancelled'. @@ -1754,8 +1748,8 @@ contract AutomationRegistryTest is Test { assertEq(registry.totalTasks(), 0); assertEq(registry.getGasCommittedForNextCycle(), 0); assertEq(registry.getTotalDepositedAutomationFees(), 0); - assertEq(supraERC20.balanceOf(address(registry)), 0.002 ether + 0.25 ether); - assertEq(supraERC20.balanceOf(alice), 4.75 ether - 0.002 ether); + assertEq(erc20Supra.balanceOf(address(registry)), 0.002 ether + 0.25 ether); + assertEq(erc20Supra.balanceOf(alice), 4.75 ether - 0.002 ether); } /// @dev Test to ensure 'stopTasks' emits event 'TasksStopped'. From 2cd7a8ded6a18e2ae8503ef4605686d0d91b6084 Mon Sep 17 00:00:00 2001 From: Udit Yadav Date: Thu, 18 Dec 2025 12:22:24 +0530 Subject: [PATCH 07/31] -added value in payload -added priority and task type in parameters -updated test cases --- .../src/AutomationController.sol | 66 ++--- .../src/AutomationRegistry.sol | 64 ++--- solidity/supra_contracts/src/CommonUtils.sol | 21 +- .../src/IAutomationRegistry.sol | 1 - solidity/supra_contracts/src/LibRegistry.sol | 84 ++++-- .../test/AutomationRegistry.t.sol | 243 +++++++++--------- 6 files changed, 256 insertions(+), 223 deletions(-) diff --git a/solidity/supra_contracts/src/AutomationController.sol b/solidity/supra_contracts/src/AutomationController.sol index 80e92d513e..1a0a7bc822 100644 --- a/solidity/supra_contracts/src/AutomationController.sol +++ b/solidity/supra_contracts/src/AutomationController.sol @@ -213,44 +213,46 @@ contract AutomationController is IAutomationController, Ownable2StepUpgradeable, /// @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) { - if(cycleInfo.state() != CommonUtils.CycleState.SUSPENDED) { revert InvalidRegistryState(); } - if(cycleInfo.index() != _cycleIndex) { revert InvalidInputCycleIndex(); } - // Check if transition state exists - if(!cycleInfo.ifTransitionStateExists()) { revert InvalidRegistryState(); } + if (_taskIndexes.length == 0) { + return; + } - uint64 currentTime = uint64(block.timestamp); - uint256 cycleLockedFees = registry.getCycleLockedFees(); + 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); + uint256 cycleLockedFees = registry.getCycleLockedFees(); - // 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(registry.ifTaskExists(taskIndexes[i])) { - (bool removed, ) = address(registry).call(abi.encodeCall(IAutomationRegistry.removeTask, (taskIndexes[i], false))); - require(removed, RemoveTaskFailed()); + // 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(registry.ifTaskExists(taskIndexes[i])) { + (bool removed, ) = address(registry).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(registry.checkTaskType(taskIndexes[i], CommonUtils.TaskType.UST)) { - (bool refunded, bytes memory data) = address(registry).call( - abi.encodeCall( - IAutomationRegistry.refundTaskFees, - (taskIndexes[i], currentTime, cycleLockedFees) - ) - ); - require(refunded, RefundFailed()); - cycleLockedFees = abi.decode(data, (uint256)); - } + removedTasks[removedCounter++] = taskIndexes[i]; + markTaskProcessed(taskIndexes[i]); + + // Nothing to refund for GST tasks + if(registry.checkTaskType(taskIndexes[i], CommonUtils.TaskType.UST)) { + (bool refunded, bytes memory data) = address(registry).call( + abi.encodeCall( + IAutomationRegistry.refundTaskFees, + (taskIndexes[i], currentTime, cycleLockedFees) + ) + ); + require(refunded, RefundFailed()); + cycleLockedFees = abi.decode(data, (uint256)); } } - - updateCycleTransitionStateFromSuspended(); - emit RemovedTasks(removedTasks); } + + updateCycleTransitionStateFromSuspended(); + emit RemovedTasks(removedTasks); } /// @notice Traverses all input task indexes and either drops or tries to charge automation fee if possible. diff --git a/solidity/supra_contracts/src/AutomationRegistry.sol b/solidity/supra_contracts/src/AutomationRegistry.sol index 6cc2d8c743..818d497c20 100644 --- a/solidity/supra_contracts/src/AutomationRegistry.sol +++ b/solidity/supra_contracts/src/AutomationRegistry.sol @@ -26,13 +26,6 @@ contract AutomationRegistry is IAutomationRegistry, Ownable2StepUpgradeable, UUP /// Factor of `2` suggests that `1/2` of the deposit will be refunded. uint8 constant REFUND_FACTOR = 2; - /// @dev Supported auxiliary data count - uint8 constant SUPPORTED_AUX_DATA_COUNT_MAX = 2; - /// @dev Index of the auxiliary data holding task type value - uint8 constant TYPE_AUX_DATA_INDEX = 0; - /// @dev Index of the auxiliary data holding task priority value - uint8 constant PRIORITY_AUX_DATA_INDEX = 1; - /// @dev Defines the cycle state, used to update the registry. uint8 constant SUSPENDED = 0; uint8 constant FINISHED = 1; @@ -246,6 +239,8 @@ contract AutomationRegistry is IAutomationRegistry, Ownable2StepUpgradeable, UUP /// @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 _type Type of task. /// @param _auxData Auxiliary data to be passed. function register( bytes memory _payloadTx, @@ -254,6 +249,8 @@ contract AutomationRegistry is IAutomationRegistry, Ownable2StepUpgradeable, UUP uint128 _maxGasAmount, uint128 _gasPriceCap, uint128 _automationFeeCapForCycle, + uint64 _priority, + uint8 _type, bytes[] memory _auxData ) external { // Check if automation is enabled @@ -263,8 +260,7 @@ contract AutomationRegistry is IAutomationRegistry, Ownable2StepUpgradeable, UUP ( , uint64 startTime, uint64 durationSecs, CommonUtils.CycleState state) = IAutomationController(regConfig.automationController()).getCycleInfo(); if(state != CommonUtils.CycleState.STARTED) { revert CycleNotStarted(); } if(totalTasks() >= regConfig.taskCapacity()) { revert TaskCapacityReached(); } - - bool hasNoPriority = checkAndValidateAuxData(_auxData, CommonUtils.TaskType.UST); + if(_type != uint8(CommonUtils.TaskType.UST)) { revert InvalidTaskType(); } uint64 regTime = uint64(block.timestamp); validateTaskDuration(regTime, _expiryTime, CommonUtils.TaskType.UST, regConfig.taskDurationCapSecs(), regConfig.sysTaskDurationCapSecs(), startTime, durationSecs); @@ -281,7 +277,6 @@ contract AutomationRegistry is IAutomationRegistry, Ownable2StepUpgradeable, UUP regState.setGasCommittedForNextCycle(gasCommitted); uint64 taskIndex = regState.currentIndex; - if(hasNoPriority) { _auxData[PRIORITY_AUX_DATA_INDEX] = abi.encode(taskIndex); } LibRegistry.TaskMetadata memory taskMetadata = LibRegistry.createTaskMetadata( _maxGasAmount, _gasPriceCap, @@ -291,7 +286,9 @@ contract AutomationRegistry is IAutomationRegistry, Ownable2StepUpgradeable, UUP taskIndex, regTime, _expiryTime, + taskIndex, // priority set to taskIndex msg.sender, + CommonUtils.TaskType.UST, CommonUtils.TaskState.PENDING, _payloadTx, _auxData @@ -315,12 +312,16 @@ contract AutomationRegistry is IAutomationRegistry, Ownable2StepUpgradeable, UUP /// @param _expiryTime Time after which the task gets expired. /// @param _txHash Transaction hash of the request transaction. /// @param _maxGasAmount Maximum amount of gas for the automation task. + /// @param _priority Priority for the task. 0 for default priority. + /// @param _type Type of task. /// @param _auxData Auxiliary data to be passed. function registerSystemTask( bytes memory _payloadTx, uint64 _expiryTime, bytes32 _txHash, uint128 _maxGasAmount, + uint64 _priority, + uint8 _type, bytes[] memory _auxData ) external { // Check if automation is enabled @@ -330,9 +331,8 @@ contract AutomationRegistry is IAutomationRegistry, Ownable2StepUpgradeable, UUP ( , uint64 startTime, uint64 durationSecs, CommonUtils.CycleState state) = IAutomationController(regConfig.automationController()).getCycleInfo(); if(state != CommonUtils.CycleState.STARTED) { revert CycleNotStarted(); } if(totalSystemTasks() >= regConfig.sysTaskCapacity()) { revert TaskCapacityReached(); } - if(!isAuthorizedSubmitter(msg.sender)) { revert UnauthorizedAccount(); } - - bool hasNoPriority = checkAndValidateAuxData(_auxData, CommonUtils.TaskType.GST); + if(!isAuthorizedSubmitter(msg.sender)) { revert UnauthorizedAccount(); } + if(_type != uint8(CommonUtils.TaskType.GST)) { revert InvalidTaskType(); } uint64 regTime = uint64(block.timestamp); validateTaskDuration(regTime, _expiryTime, CommonUtils.TaskType.GST, regConfig.taskDurationCapSecs(), regConfig.sysTaskDurationCapSecs(), startTime, durationSecs); @@ -345,7 +345,7 @@ contract AutomationRegistry is IAutomationRegistry, Ownable2StepUpgradeable, UUP uint64 taskIndex = regState.currentIndex; - if(hasNoPriority) {_auxData[PRIORITY_AUX_DATA_INDEX] = abi.encode(taskIndex); } + uint64 taskPriority = _priority == 0 ? taskIndex : _priority; // Defaults to taskIndex as priority if 0 is passed LibRegistry.TaskMetadata memory taskMetadata = LibRegistry.createTaskMetadata( _maxGasAmount, 0, @@ -355,7 +355,9 @@ contract AutomationRegistry is IAutomationRegistry, Ownable2StepUpgradeable, UUP taskIndex, regTime, _expiryTime, + taskPriority, msg.sender, + CommonUtils.TaskType.GST, CommonUtils.TaskState.PENDING, _payloadTx, _auxData @@ -697,10 +699,10 @@ contract AutomationRegistry is IAutomationRegistry, Ownable2StepUpgradeable, UUP /// @notice Helper function to validate the inputs while registering a task. function validateInputs(bytes memory _payloadTx, uint128 _maxGasAmount, bytes32 _txHash) private view { - address payloadTarget; - (payloadTarget, ) = abi.decode(_payloadTx, (address, bytes)); + ( , address payloadTarget, ) = abi.decode(_payloadTx, (uint128, address, bytes)); if(payloadTarget == address(0)) { revert AddressCannotBeZero(); } if(!payloadTarget.isContract()) { revert AddressCannotBeEOA(); } + if(_maxGasAmount == 0) { revert InvalidMaxGasAmount(); } if(_txHash == bytes32(0)) { revert InvalidTxHash(); } } @@ -824,27 +826,6 @@ contract AutomationRegistry is IAutomationRegistry, Ownable2StepUpgradeable, UUP return calculateAutomationFeeForInterval(_duration, _taskOccupancy, automationFeePerSec, regConfig.nextCycleRegistryMaxGasCap()); } - /// @notice Function to check and validate the input auxiliary data. - /// @param _auxData Input auxiliary data. - /// @param _taskType Type of the task. - /// @return Bool representing if the task has priority. - function checkAndValidateAuxData(bytes[] memory _auxData, CommonUtils.TaskType _taskType) private pure returns (bool) { - if(_auxData.length != SUPPORTED_AUX_DATA_COUNT_MAX) { revert InvalidAuxDataLength(); } - - // Check task type - uint8 typeValue = abi.decode(_auxData[TYPE_AUX_DATA_INDEX], (uint8)); - if(typeValue != uint8(_taskType)) {revert InvalidTaskType(); } - - // Check if priority exists - bytes memory priorityBytes = _auxData[PRIORITY_AUX_DATA_INDEX]; - bool hasNoPriority = (priorityBytes.length == 0); - if (!hasNoPriority) { - uint64 _priority = abi.decode(priorityBytes, (uint64)); - } - - return hasNoPriority; - } - /// @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. @@ -1450,7 +1431,7 @@ contract AutomationRegistry is IAutomationRegistry, Ownable2StepUpgradeable, UUP /// @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); + return regState.tasks[_taskIndex].owner() != address(0) && regState.taskIdList.contains(_taskIndex); } /// @notice Checks if a system task exist. @@ -1463,14 +1444,13 @@ contract AutomationRegistry is IAutomationRegistry, Ownable2StepUpgradeable, UUP /// @param _taskIndex Index of the task. /// @param _type Input task type. function checkTaskType(uint64 _taskIndex, CommonUtils.TaskType _type) public view returns (bool) { - uint8 taskType = abi.decode(regState.tasks[_taskIndex].auxData[TYPE_AUX_DATA_INDEX], (uint8)); - return taskType == uint8(_type); + 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; + return regState.tasks[_taskIndex].owner(); } /// @notice Returns the state of the task @@ -1558,7 +1538,7 @@ contract AutomationRegistry is IAutomationRegistry, Ownable2StepUpgradeable, UUP /// @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) { - return regState.tasks[_taskIndex].owner == _account && LibRegistry.state(regState.tasks[_taskIndex]) != CommonUtils.TaskState.PENDING && checkTaskType(_taskIndex, _type); + return regState.tasks[_taskIndex].owner() == _account && LibRegistry.state(regState.tasks[_taskIndex]) != CommonUtils.TaskState.PENDING && checkTaskType(_taskIndex, _type); } /// @notice Checks if config buffer exists. diff --git a/solidity/supra_contracts/src/CommonUtils.sol b/solidity/supra_contracts/src/CommonUtils.sol index b207a4a946..e4b7e9a38e 100644 --- a/solidity/supra_contracts/src/CommonUtils.sol +++ b/solidity/supra_contracts/src/CommonUtils.sol @@ -37,8 +37,10 @@ library CommonUtils { uint64 taskIndex; uint64 registrationTime; uint64 expiryTime; - address owner; + uint64 priority; + CommonUtils.TaskType taskType; CommonUtils.TaskState state; + address owner; bytes payloadTx; bytes[] auxData; } @@ -58,15 +60,19 @@ library CommonUtils { // --- Direct values --- details.txHash = t.txHash; - details.owner = t.owner; details.payloadTx = t.payloadTx; details.auxData = t.auxData; - // --- Decode packed uint256: taskIndex | registrationTime | expiryTime | state --- - details.taskIndex = uint64(t.taskIndex_registrationTime_expiryTime_state >> 192); - details.registrationTime = uint64(t.taskIndex_registrationTime_expiryTime_state >> 128); - details.expiryTime = uint64(t.taskIndex_registrationTime_expiryTime_state >> 64); - details.state = CommonUtils.TaskState(uint8(t.taskIndex_registrationTime_expiryTime_state >> 56)); + // --- 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 = CommonUtils.TaskType(uint8(t.owner_type_state >> 88)); + details.state = CommonUtils.TaskState(uint8(t.owner_type_state >> 80)); } @@ -74,7 +80,6 @@ library CommonUtils { struct Deposit { uint256 totalDepositedAutomationFees; address coldWallet; - // uint256 totalLockedFees; // TO_DO // mapping(uint64 => uint256) taskLockedFees; // TO_DO } diff --git a/solidity/supra_contracts/src/IAutomationRegistry.sol b/solidity/supra_contracts/src/IAutomationRegistry.sol index f5628b0f8e..d8c3eb0709 100644 --- a/solidity/supra_contracts/src/IAutomationRegistry.sol +++ b/solidity/supra_contracts/src/IAutomationRegistry.sol @@ -14,7 +14,6 @@ interface IAutomationRegistry { error GasCommittedExceedsMaxGasCap(); error InsufficientFeeCapForCycle(); error InsufficentValueSent(); - error InvalidAuxDataLength(); error InvalidExpiryTime(); error InvalidGasPriceCap(); error InvalidMaxGasAmount(); diff --git a/solidity/supra_contracts/src/LibRegistry.sol b/solidity/supra_contracts/src/LibRegistry.sol index 67cdc06b02..29ffb5b1f3 100644 --- a/solidity/supra_contracts/src/LibRegistry.sol +++ b/solidity/supra_contracts/src/LibRegistry.sol @@ -9,6 +9,7 @@ import {CommonUtils} from "./CommonUtils.sol"; 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_UINT16 = type(uint16).max; uint256 private constant MAX_UINT8 = type(uint8).max; @@ -97,7 +98,7 @@ library LibRegistry { function setAutomationController(RegistryConfig storage r, address _controller) internal { // clear top 160 bits - r.controller_registrationEnabled_automationEnabled &= ~((uint256(type(uint160).max)) << 96); + r.controller_registrationEnabled_automationEnabled &= ~(MAX_UINT160 << 96); // insert 160-bit address r.controller_registrationEnabled_automationEnabled |= uint256(uint160(_controller)) << 96; @@ -292,10 +293,11 @@ library LibRegistry { bytes32 txHash; - // uint64 | uint64 | uint64 | TaskState - uint256 taskIndex_registrationTime_expiryTime_state; + // uint64 | uint64 | uint64 | uint64 + uint256 taskIndex_registrationTime_expiryTime_priority; - address owner; + // address | TaskType (uint8) | TaskState (uint8) + uint256 owner_type_state; bytes payloadTx; bytes[] auxData; @@ -310,7 +312,9 @@ library LibRegistry { uint64 _taskIndex, uint64 _registrationTime, uint64 _expiryTime, + uint64 _priority, address _owner, + CommonUtils.TaskType _type, CommonUtils.TaskState _state, bytes memory _payloadTx, bytes[] memory _auxData @@ -323,17 +327,23 @@ library LibRegistry { // Direct fields t.txHash = _txHash; - t.owner = _owner; t.payloadTx = _payloadTx; t.auxData = _auxData; - // Pack (uint64 | uint64 | uint64 | uint8) - // Layout: [taskIndex | registrationTime | expiryTime | state] - t.taskIndex_registrationTime_expiryTime_state = + // 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(uint8(_state)) << 56); + 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) @@ -374,41 +384,69 @@ library LibRegistry { t.automationFeeCapForCycle_lockedFeeForNextCycle |= uint256(_value); } - // taskIndex (uint64) | registrationTime (uint64) | expiryTime (uint64) | state (TaskState/uint8) + // taskIndex (uint64) | registrationTime (uint64) | expiryTime (uint64) | priority (uint64) function taskIndex(TaskMetadata storage t) internal view returns (uint64) { - return uint64(t.taskIndex_registrationTime_expiryTime_state >> 192); + return uint64(t.taskIndex_registrationTime_expiryTime_priority >> 192); } function registrationTime(TaskMetadata storage t) internal view returns (uint64) { - return uint64(t.taskIndex_registrationTime_expiryTime_state >> 128); + return uint64(t.taskIndex_registrationTime_expiryTime_priority >> 128); } function expiryTime(TaskMetadata storage t) internal view returns (uint64) { - return uint64(t.taskIndex_registrationTime_expiryTime_state >> 64); + return uint64(t.taskIndex_registrationTime_expiryTime_priority >> 64); } - function state(TaskMetadata storage t) internal view returns (CommonUtils.TaskState) { - return CommonUtils.TaskState(uint8(t.taskIndex_registrationTime_expiryTime_state >> 56)); + 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_state &= ~(MAX_UINT64 << 192); - t.taskIndex_registrationTime_expiryTime_state |= uint256(_value) << 192; + 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_state &= ~(MAX_UINT64 << 128); - t.taskIndex_registrationTime_expiryTime_state |= uint256(_value) << 128; + 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_state &= ~(MAX_UINT64 << 64); - t.taskIndex_registrationTime_expiryTime_state |= uint256(_value) << 64; + 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.taskIndex_registrationTime_expiryTime_state &= ~(MAX_UINT8 << 56); - t.taskIndex_registrationTime_expiryTime_state |= uint256(_value) << 56; + t.owner_type_state &= ~(MAX_UINT8 << 80); + t.owner_type_state |= uint256(_value) << 80; } // :::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::: RegistryState :::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::: diff --git a/solidity/supra_contracts/test/AutomationRegistry.t.sol b/solidity/supra_contracts/test/AutomationRegistry.t.sol index c8625866ec..940860e0cb 100644 --- a/solidity/supra_contracts/test/AutomationRegistry.t.sol +++ b/solidity/supra_contracts/test/AutomationRegistry.t.sol @@ -924,18 +924,14 @@ contract AutomationRegistryTest is Test { // :::::::::::::::::::::::::::::::::::::::::::::::::::::: Tests related to 'register' :::::::::::::::::::::::::::::::::::::::::::::::::::::: - /// @dev Helper function to return payload and auxiliary data. - /// @param _type Type of the task. UST: 0, GST: 1 - function payloadAndAuxData(uint8 _type) internal returns (bytes memory, bytes[] memory) { - ERC20Supra target = new ERC20Supra(alice); + /// @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) { bytes memory callData = abi.encodeCall(ERC20Supra.withdraw, 100); - bytes memory payload = abi.encode(address(target), callData); + bytes memory payload = abi.encode(_value, _target, callData); - bytes[] memory auxData = new bytes[](2); - auxData[0] = abi.encode(uint8(_type)); - auxData[1] = abi.encode(uint64(100)); - - return (payload, auxData); + return payload; } /// @dev Test to ensure 'register' reverts if automation is not enabled. @@ -944,7 +940,8 @@ contract AutomationRegistryTest is Test { vm.prank(admin); registry.disableAutomation(); - (bytes memory payload, bytes[] memory auxData) = payloadAndAuxData(0); + bytes[] memory auxData; + bytes memory payload = createPayload(0, address(erc20Supra)); vm.expectRevert(IAutomationRegistry.AutomationNotEnabled.selector); @@ -956,6 +953,8 @@ contract AutomationRegistryTest is Test { uint128(1_000_000), // maxGasAmount uint128(10 gwei), // gasPriceCap uint128(0.5 ether), // automationFeeCapForCycle + 0, // priority + 0, // task type auxData // aux data ); } @@ -965,8 +964,9 @@ contract AutomationRegistryTest is Test { // Disable registration vm.prank(admin); registry.disableRegistration(); - - (bytes memory payload, bytes[] memory auxData) = payloadAndAuxData(0); + + bytes[] memory auxData; + bytes memory payload = createPayload(0, address(erc20Supra)); vm.expectRevert(IAutomationRegistry.RegistrationDisabled.selector); @@ -978,45 +978,17 @@ contract AutomationRegistryTest is Test { uint128(1_000_000), // maxGasAmount uint128(10 gwei), // gasPriceCap uint128(0.5 ether), // automationFeeCapForCycle + 0, // priority + 0, // task type auxData // aux data ); } - /// @dev Test to ensure 'register' reverts if auxiliary data is of invalid length. - function testRegisterRevertsIfInvalidAuxDataLength() public { - testSetAutomationController(); - - ERC20Supra target = new ERC20Supra(alice); - bytes memory callData = abi.encodeCall(ERC20Supra.withdraw, 100); - bytes memory payload = abi.encode(address(target), callData); - - bytes[] memory auxData = new bytes[](3); // Invalid length - auxData[0] = abi.encode(uint8(0)); - - vm.expectRevert(IAutomationRegistry.InvalidAuxDataLength.selector); - - vm.prank(alice); - registry.register( - payload, - uint64(block.timestamp + 2250), - keccak256("txHash"), - uint128(1_000_000), - uint128(10 gwei), - uint128(0.5 ether), - auxData - ); - } - /// @dev Test to ensure 'register' reverts if task type is not UST. function testRegisterRevertsIfTaskTypeNotUST() public { testSetAutomationController(); - - ERC20Supra target = new ERC20Supra(alice); - bytes memory callData = abi.encodeCall(ERC20Supra.withdraw, 100); - bytes memory payload = abi.encode(address(target), callData); - - bytes[] memory auxData = new bytes[](2); - auxData[0] = abi.encode(uint8(1)); // Invalid task type: expected 0, passing 1 + bytes[] memory auxData; + bytes memory payload = createPayload(0, address(erc20Supra)); vm.expectRevert(IAutomationRegistry.InvalidTaskType.selector); @@ -1028,6 +1000,8 @@ contract AutomationRegistryTest is Test { uint128(1_000_000), uint128(10 gwei), uint128(0.5 ether), + 0, + 1, // Task type not UST auxData ); } @@ -1035,18 +1009,21 @@ contract AutomationRegistryTest is Test { /// @dev Test to ensure 'register' reverts if expiry time is equal to or less than registration time. function testRegisterRevertsIfInvalidExpiryTime() public { testSetAutomationController(); - (bytes memory payload, bytes[] memory auxData) = payloadAndAuxData(0); + bytes[] memory auxData; + bytes memory payload = createPayload(0, address(erc20Supra)); vm.expectRevert(IAutomationRegistry.InvalidExpiryTime.selector); vm.prank(alice); registry.register( payload, - uint64(block.timestamp), // invalid expiryTime + uint64(block.timestamp), // Invalid expiryTime keccak256("txHash"), uint128(1_000_000), uint128(10 gwei), uint128(0.5 ether), + 0, + 0, auxData ); } @@ -1054,7 +1031,8 @@ contract AutomationRegistryTest is Test { /// @dev Test to ensure 'register' reverts if task duration is greater than the task duration cap. function testRegisterRevertsIfInvalidTaskDuration() public { testSetAutomationController(); - (bytes memory payload, bytes[] memory auxData) = payloadAndAuxData(0); + bytes[] memory auxData; + bytes memory payload = createPayload(0, address(erc20Supra)); vm.expectRevert(IAutomationRegistry.InvalidTaskDuration.selector); @@ -1066,6 +1044,8 @@ contract AutomationRegistryTest is Test { uint128(1_000_000), uint128(10 gwei), uint128(0.5 ether), + 0, + 0, auxData ); } @@ -1073,7 +1053,8 @@ contract AutomationRegistryTest is Test { /// @dev Test to ensure 'register' reverts if task expires before the next cycle. function testRegisterRevertsIfTaskExpiresBeforeNextCycle() public { testSetAutomationController(); - (bytes memory payload, bytes[] memory auxData) = payloadAndAuxData(0); + bytes[] memory auxData; + bytes memory payload = createPayload(0, address(erc20Supra)); vm.expectRevert(IAutomationRegistry.TaskExpiresBeforeNextCycle.selector); @@ -1085,6 +1066,8 @@ contract AutomationRegistryTest is Test { uint128(1_000_000), uint128(10 gwei), uint128(0.5 ether), + 0, + 0, auxData ); } @@ -1092,12 +1075,8 @@ contract AutomationRegistryTest is Test { /// @dev Test to ensure 'register' reverts if payload target address is zero. function testRegisterRevertsIfPayloadTargetZero() public { testSetAutomationController(); - - bytes memory callData = abi.encodeCall(ERC20Supra.withdraw, 100); - bytes memory payload = abi.encode(address(0), callData); // Invalid address: address(0) - - bytes[] memory auxData = new bytes[](2); - auxData[0] = abi.encode(uint8(0)); + bytes[] memory auxData; + bytes memory payload = createPayload(0, address(0)); // Invalid address: address(0) vm.expectRevert(IAutomationRegistry.AddressCannotBeZero.selector); @@ -1109,6 +1088,8 @@ contract AutomationRegistryTest is Test { uint128(1_000_000), uint128(10 gwei), uint128(0.5 ether), + 0, + 0, auxData ); } @@ -1116,12 +1097,8 @@ contract AutomationRegistryTest is Test { /// @dev Test to ensure 'register' reverts if payload target address is EOA. function testRegisterRevertsIfPayloadTargetEoa() public { testSetAutomationController(); - - bytes memory callData = abi.encodeCall(ERC20Supra.withdraw, 100); - bytes memory payload = abi.encode(alice, callData); // Invalid address: EOA address being passed - - bytes[] memory auxData = new bytes[](2); - auxData[0] = abi.encode(uint8(0)); + bytes[] memory auxData; + bytes memory payload = createPayload(0, alice); // Invalid address: EOA address being passed vm.expectRevert(IAutomationRegistry.AddressCannotBeEOA.selector); @@ -1133,6 +1110,8 @@ contract AutomationRegistryTest is Test { uint128(1_000_000), uint128(10 gwei), uint128(0.5 ether), + 0, + 0, auxData ); } @@ -1140,7 +1119,8 @@ contract AutomationRegistryTest is Test { /// @dev Test to ensure 'register' reverts if 0 is passed as max gas amount. function testRegisterRevertsIfMaxGasAmountZero() public { testSetAutomationController(); - (bytes memory payload, bytes[] memory auxData) = payloadAndAuxData(0); + bytes[] memory auxData; + bytes memory payload = createPayload(0, address(erc20Supra)); vm.expectRevert(IAutomationRegistry.InvalidMaxGasAmount.selector); @@ -1152,6 +1132,8 @@ contract AutomationRegistryTest is Test { uint128(0), // maxGasAmount uint128(10 gwei), uint128(0.5 ether), + 0, + 0, auxData ); } @@ -1159,7 +1141,8 @@ contract AutomationRegistryTest is Test { /// @dev Test to ensure 'register' reverts if 0 is passed as gas price cap. function testRegisterRevertsIfGasPriceCapZero() public { testSetAutomationController(); - (bytes memory payload, bytes[] memory auxData) = payloadAndAuxData(0); + bytes[] memory auxData; + bytes memory payload = createPayload(0, address(erc20Supra)); vm.expectRevert(IAutomationRegistry.InvalidGasPriceCap.selector); @@ -1171,6 +1154,8 @@ contract AutomationRegistryTest is Test { uint128(1_000_000), uint128(0), // gasPriceCap uint128(0.5 ether), + 0, + 0, auxData ); } @@ -1178,7 +1163,8 @@ contract AutomationRegistryTest is Test { /// @dev Test to ensure 'register' reverts if transaction hash is bytes32(0). function testRegisterRevertsIfInvalidTxHash() public { testSetAutomationController(); - (bytes memory payload, bytes[] memory auxData) = payloadAndAuxData(0); + bytes[] memory auxData; + bytes memory payload = createPayload(0, address(erc20Supra)); vm.expectRevert(IAutomationRegistry.InvalidTxHash.selector); @@ -1190,6 +1176,8 @@ contract AutomationRegistryTest is Test { uint128(1_000_000), uint128(10 gwei), uint128(0.5 ether), + 0, + 0, auxData ); } @@ -1197,7 +1185,8 @@ contract AutomationRegistryTest is Test { /// @dev Test to ensure 'register' reverts if gas committed exceeds the registry max gas cap. function testRegisterRevertsIfGasCommittedExceedsMaxGasCap() public { testSetAutomationController(); - (bytes memory payload, bytes[] memory auxData) = payloadAndAuxData(0); + bytes[] memory auxData; + bytes memory payload = createPayload(0, address(erc20Supra)); vm.expectRevert(IAutomationRegistry.GasCommittedExceedsMaxGasCap.selector); @@ -1209,6 +1198,8 @@ contract AutomationRegistryTest is Test { uint128(10_000_001), // Gas exceeds max gas cap uint128(10 gwei), uint128(0.5 ether), + 0, + 0, auxData ); } @@ -1216,7 +1207,8 @@ contract AutomationRegistryTest is Test { /// @dev Test to ensure 'register' reverts if automation fee cap is less than the estimated automation fee. function testRegisterRevertsIfAutomationFeeCapLessThanEstimated() public { testSetAutomationController(); - (bytes memory payload, bytes[] memory auxData) = payloadAndAuxData(0); + bytes[] memory auxData; + bytes memory payload = createPayload(0, address(erc20Supra)); vm.expectRevert(IAutomationRegistry.InsufficientFeeCapForCycle.selector); @@ -1228,6 +1220,8 @@ contract AutomationRegistryTest is Test { uint128(1_000_000), uint128(10 gwei), uint128(0), // automationFeeCapForCycle + 0, + 0, auxData ); } @@ -1235,7 +1229,8 @@ contract AutomationRegistryTest is Test { /// @dev Test to ensure 'register' registers a UST. function testRegister() public { testSetAutomationController(); - (bytes memory payload, bytes[] memory auxData) = payloadAndAuxData(0); + bytes[] memory auxData; + bytes memory payload = createPayload(0, address(erc20Supra)); vm.startPrank(alice); erc20Supra.deposit{value: 5 ether}(); @@ -1248,6 +1243,8 @@ contract AutomationRegistryTest is Test { uint128(1_000_000), uint128(10 gwei), uint128(0.5 ether), + 0, + 0, auxData ); vm.stopPrank(); @@ -1269,8 +1266,10 @@ contract AutomationRegistryTest is Test { assertEq(taskMetadata.taskIndex, 0); assertEq(taskMetadata.registrationTime, uint64(block.timestamp)); assertEq(taskMetadata.expiryTime, uint64(block.timestamp + 2250)); - assertEq(taskMetadata.owner, alice); + 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); } @@ -1278,7 +1277,8 @@ contract AutomationRegistryTest is Test { /// @dev Test to ensure 'register' emits event 'TaskRegistered'. function testRegisterEmitsEvent() public { testSetAutomationController(); - (bytes memory payload, bytes[] memory auxData) = payloadAndAuxData(0); + bytes[] memory auxData; + bytes memory payload = createPayload(0, address(erc20Supra)); vm.startPrank(alice); erc20Supra.deposit{value: 5 ether}(); @@ -1293,8 +1293,10 @@ contract AutomationRegistryTest is Test { 0, uint64(block.timestamp), uint64(block.timestamp + 2250), - alice, + 0, + CommonUtils.TaskType.UST, CommonUtils.TaskState.PENDING, + alice, payload, auxData ); @@ -1309,6 +1311,8 @@ contract AutomationRegistryTest is Test { uint128(1_000_000), uint128(10 gwei), uint128(0.5 ether), + 0, + 0, auxData ); vm.stopPrank(); @@ -1320,8 +1324,9 @@ contract AutomationRegistryTest is Test { function testRegisterSystemTaskRevertsIfAutomationNotEnabled() public { vm.prank(admin); registry.disableAutomation(); - - (bytes memory payload, bytes[] memory auxData) = payloadAndAuxData(1); + + bytes[] memory auxData; + bytes memory payload = createPayload(0, address(erc20Supra)); vm.expectRevert(IAutomationRegistry.AutomationNotEnabled.selector); @@ -1331,6 +1336,8 @@ contract AutomationRegistryTest is Test { uint64(block.timestamp + 2250), // expiryTime keccak256("txHash"), // txHash uint128(1_000_000), // maxGasAmount + 2, // priority + 1, // task type auxData // aux data ); } @@ -1339,8 +1346,9 @@ contract AutomationRegistryTest is Test { function testRegisterSystemTaskRevertsIfRegistrationDisabled() public { vm.prank(admin); registry.disableRegistration(); - - (bytes memory payload, bytes[] memory auxData) = payloadAndAuxData(1); + + bytes[] memory auxData; + bytes memory payload = createPayload(0, address(erc20Supra)); vm.expectRevert(IAutomationRegistry.RegistrationDisabled.selector); @@ -1350,6 +1358,8 @@ contract AutomationRegistryTest is Test { uint64(block.timestamp + 2250), // expiryTime keccak256("txHash"), // txHash uint128(1_000_000), // maxGasAmount + 2, // priority + 1, // task type auxData // aux data ); } @@ -1357,7 +1367,8 @@ contract AutomationRegistryTest is Test { /// @dev Test to ensure 'registerSystemTask' reverts if caller is not authorized. function testRegisterSystemTaskRevertsIfUnauthorizedCaller() public { testSetAutomationController(); - (bytes memory payload, bytes[] memory auxData) = payloadAndAuxData(1); + bytes[] memory auxData; + bytes memory payload = createPayload(0, address(erc20Supra)); vm.expectRevert(IAutomationRegistry.UnauthorizedAccount.selector); @@ -1367,31 +1378,8 @@ contract AutomationRegistryTest is Test { uint64(block.timestamp + 2250), // expiryTime keccak256("txHash"), // txHash uint128(1_000_000), // maxGasAmount - auxData // aux data - ); - } - - /// @dev Test to ensure 'registerSystemTask' reverts if auxiliary data is of invalid length. - function testRegisterSystemTaskRevertsIfInvalidAuxDataLength() public { - testSetAutomationController(); - testGrantAuthorization(); - - ERC20Supra target = new ERC20Supra(alice); - bytes memory callData = abi.encodeCall(ERC20Supra.withdraw, 100); - bytes memory payload = abi.encode(address(target), callData); - - bytes[] memory auxData = new bytes[](3); - auxData[0] = abi.encode(uint8(1)); - auxData[1] = abi.encode(uint64(100)); - - vm.expectRevert(IAutomationRegistry.InvalidAuxDataLength.selector); - - vm.prank(bob); - registry.registerSystemTask( - payload, // payload - uint64(block.timestamp + 2250), // expiryTime - keccak256("txHash"), // txHash - uint128(1_000_000), // maxGasAmount + 2, // priority + 1, // task type auxData // aux data ); } @@ -1400,17 +1388,21 @@ contract AutomationRegistryTest is Test { function testRegisterSystemTaskRevertsIfTaskTypeNotGST() public { testSetAutomationController(); testGrantAuthorization(); - (bytes memory payload, bytes[] memory auxData) = payloadAndAuxData(0); + + bytes[] memory auxData; + bytes memory payload = createPayload(0, address(erc20Supra)); vm.expectRevert(IAutomationRegistry.InvalidTaskType.selector); vm.prank(bob); registry.registerSystemTask( - payload, // payload - uint64(block.timestamp + 2250), // expiryTime - keccak256("txHash"), // txHash - uint128(1_000_000), // maxGasAmount - auxData // aux data + payload, + uint64(block.timestamp + 2250), + keccak256("txHash"), + uint128(1_000_000), + 2, + 0, // Task type not GST + auxData ); } @@ -1418,17 +1410,20 @@ contract AutomationRegistryTest is Test { function testRegisterSystemTaskRevertsIfInvalidTaskDuration() public { testSetAutomationController(); testGrantAuthorization(); - (bytes memory payload, bytes[] memory auxData) = payloadAndAuxData(1); + bytes[] memory auxData; + bytes memory payload = createPayload(0, address(erc20Supra)); vm.expectRevert(IAutomationRegistry.InvalidTaskDuration.selector); vm.prank(bob); registry.registerSystemTask( - payload, // payload - uint64(block.timestamp + 3601), // expiryTime - keccak256("txHash"), // txHash - uint128(1_000_000), // maxGasAmount - auxData // aux data + payload, + uint64(block.timestamp + 3601), // Invalid task duration + keccak256("txHash"), + uint128(1_000_000), + 2, + 1, + auxData ); } @@ -1436,7 +1431,8 @@ contract AutomationRegistryTest is Test { function testRegisterSystemTaskRevertsIfGasCommittedExceedsMaxGasCap() public { testSetAutomationController(); testGrantAuthorization(); - (bytes memory payload, bytes[] memory auxData) = payloadAndAuxData(1); + bytes[] memory auxData; + bytes memory payload = createPayload(0, address(erc20Supra)); vm.expectRevert(IAutomationRegistry.GasCommittedExceedsMaxGasCap.selector); @@ -1446,6 +1442,8 @@ contract AutomationRegistryTest is Test { uint64(block.timestamp + 2250), keccak256("txHash"), uint128(5_000_001), // Gas exceeds max gas cap + 2, + 1, auxData ); } @@ -1454,7 +1452,8 @@ contract AutomationRegistryTest is Test { function testRegisterSystemTask() public { testSetAutomationController(); testGrantAuthorization(); - (bytes memory payload, bytes[] memory auxData) = payloadAndAuxData(1); + bytes[] memory auxData; + bytes memory payload = createPayload(0, address(erc20Supra)); vm.prank(bob); registry.registerSystemTask( @@ -1462,6 +1461,8 @@ contract AutomationRegistryTest is Test { uint64(block.timestamp + 2250), // expiryTime keccak256("txHash"), // txHash uint128(1_000_000), // maxGasAmount + 2, // priority + 1, // task type auxData // aux data ); @@ -1481,8 +1482,10 @@ contract AutomationRegistryTest is Test { assertEq(taskMetadata.taskIndex, 0); assertEq(taskMetadata.registrationTime, uint64(block.timestamp)); assertEq(taskMetadata.expiryTime, uint64(block.timestamp + 2250)); - assertEq(taskMetadata.owner, bob); + 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); } @@ -1491,7 +1494,9 @@ contract AutomationRegistryTest is Test { function testRegisterSystemTaskEmitsEvent() public { testSetAutomationController(); testGrantAuthorization(); - (bytes memory payload, bytes[] memory auxData) = payloadAndAuxData(1); + + bytes[] memory auxData; + bytes memory payload = createPayload(0, address(erc20Supra)); CommonUtils.TaskDetails memory taskMetadata = CommonUtils.TaskDetails( 1_000_000, @@ -1502,8 +1507,10 @@ contract AutomationRegistryTest is Test { 0, uint64(block.timestamp), uint64(block.timestamp + 2250), - bob, + 2, + CommonUtils.TaskType.GST, CommonUtils.TaskState.PENDING, + bob, payload, auxData ); @@ -1517,6 +1524,8 @@ contract AutomationRegistryTest is Test { uint64(block.timestamp + 2250), // expiryTime keccak256("txHash"), // txHash uint128(1_000_000), // maxGasAmount + 2, // priority + 1, // task type auxData // aux data ); } From 16ce914018744d8922bf19cc7a6398c92d835158 Mon Sep 17 00:00:00 2001 From: Udit Yadav Date: Fri, 19 Dec 2025 12:08:07 +0530 Subject: [PATCH 08/31] added access list in payload --- .../src/AutomationRegistry.sol | 2 +- solidity/supra_contracts/src/LibRegistry.sol | 8 ++++++++ .../test/AutomationRegistry.t.sol | 20 +++++++++++++++++-- 3 files changed, 27 insertions(+), 3 deletions(-) diff --git a/solidity/supra_contracts/src/AutomationRegistry.sol b/solidity/supra_contracts/src/AutomationRegistry.sol index 818d497c20..29cad50fac 100644 --- a/solidity/supra_contracts/src/AutomationRegistry.sol +++ b/solidity/supra_contracts/src/AutomationRegistry.sol @@ -699,7 +699,7 @@ contract AutomationRegistry is IAutomationRegistry, Ownable2StepUpgradeable, UUP /// @notice Helper function to validate the inputs while registering a task. function validateInputs(bytes memory _payloadTx, uint128 _maxGasAmount, bytes32 _txHash) private view { - ( , address payloadTarget, ) = abi.decode(_payloadTx, (uint128, address, bytes)); + ( , address payloadTarget, , ) = abi.decode(_payloadTx, (uint128, address, bytes, LibRegistry.AccessListEntry[])); if(payloadTarget == address(0)) { revert AddressCannotBeZero(); } if(!payloadTarget.isContract()) { revert AddressCannotBeEOA(); } diff --git a/solidity/supra_contracts/src/LibRegistry.sol b/solidity/supra_contracts/src/LibRegistry.sol index 29ffb5b1f3..4b7f91098f 100644 --- a/solidity/supra_contracts/src/LibRegistry.sol +++ b/solidity/supra_contracts/src/LibRegistry.sol @@ -13,6 +13,14 @@ library LibRegistry { 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 ::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::: diff --git a/solidity/supra_contracts/test/AutomationRegistry.t.sol b/solidity/supra_contracts/test/AutomationRegistry.t.sol index 940860e0cb..81f8512055 100644 --- a/solidity/supra_contracts/test/AutomationRegistry.t.sol +++ b/solidity/supra_contracts/test/AutomationRegistry.t.sol @@ -928,8 +928,24 @@ contract AutomationRegistryTest is Test { /// @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) { + LibRegistry.AccessListEntry[] memory accessList = new LibRegistry.AccessListEntry[](2); + + bytes32[] memory keys = new bytes32[](2); + keys[0] = bytes32(uint256(0)); + keys[1] = bytes32(uint256(1)); + + accessList[0] = LibRegistry.AccessListEntry({ + addr: address(0x1111), + storageKeys: keys + }); + + accessList[1] = LibRegistry.AccessListEntry({ + addr: address(0x2222), + storageKeys: keys + }); + bytes memory callData = abi.encodeCall(ERC20Supra.withdraw, 100); - bytes memory payload = abi.encode(_value, _target, callData); + bytes memory payload = abi.encode(_value, _target, callData, accessList); return payload; } @@ -1243,7 +1259,7 @@ contract AutomationRegistryTest is Test { uint128(1_000_000), uint128(10 gwei), uint128(0.5 ether), - 0, + 4, 0, auxData ); From 261b22131765cde76fe5d8b41bfb05ac82c3d86f Mon Sep 17 00:00:00 2001 From: Udit Yadav Date: Tue, 23 Dec 2025 18:19:22 +0530 Subject: [PATCH 09/31] fixes for enabling/disbaling automation --- .../src/AutomationController.sol | 179 ++++++++---------- .../src/AutomationRegistry.sol | 15 ++ .../src/IAutomationController.sol | 7 +- 3 files changed, 95 insertions(+), 106 deletions(-) diff --git a/solidity/supra_contracts/src/AutomationController.sol b/solidity/supra_contracts/src/AutomationController.sol index 1a0a7bc822..8655eb7612 100644 --- a/solidity/supra_contracts/src/AutomationController.sol +++ b/solidity/supra_contracts/src/AutomationController.sol @@ -118,57 +118,9 @@ contract AutomationController is IAutomationController, Ownable2StepUpgradeable, } } - /// @notice It will be triggered for automation registry caused by `supra_governance::reconfiguration` or DKG finalization - /// to update the automation registry state depending on SUPRA_NATIVE_AUTOMATION feature flag state. - /// If registry is not fully initialized nothing is done. - /// If native automation feature is disabled and automation cycle in STARTED state, - /// then automation lifecycle is suspended immediately. And detached managment will - /// initiate reprocessing of the available tasks which will end up in refund and cleanup actions. - /// Otherwise suspention is postponed until the end of the transition state. - /// Nothing will be done if automation cycle was already suspended, i.e. in READY state. - /// If native automation feature is enabled and automation lifecycle has been in READY state, then lifecycle is restarted. - function onNewCycle() public { - CommonUtils.CycleState state = cycleInfo.state(); - - // Check if automation is enabled - if (registry.isAutomationEnabled()) { - // If the lifecycle has been suspended and we are recovering from it, then we update config from buffer and - // then start a new cycle directly. - // Unless we are in READY state, the feature flag being enabled will not have any effect. - // All the other states mean that we are in the middle of previous transition, which should end - // before reenabling the feature. - if (state == CommonUtils.CycleState.READY) { - if(registry.totalTasks() > 0) { - emit ErrorInconsistentSuspendedState(); - } else { - updateConfigFromBuffer(); - moveToStartedState(); - } - } - - - // We do not update config here, as due to feature being disabled, cycle ends early so it is expected - // that the current fee-parameters will be used to calculate automation-fee for refund for a cycle - // that has been kept short. - // So the confing should remain intact. - } else if (state == CommonUtils.CycleState.STARTED) { - tryMoveToSuspendedState(); - } else if (state == CommonUtils.CycleState.FINISHED && cycleInfo.ifTransitionStateExists()) { - if (!isTransitionInProgress()) { - // Just entered cycle-end phase, and meanwhile also feature has been disabled so it is safe to move to suspended state. - tryMoveToSuspendedState(); - } - // Otherwise wait of the cycle transition to end and then feature flag value will be taken into account. - } - // If in already SUSPENDED state or in READY state then do nothing. - } - /// @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 != registry.getVmSigner()) { revert CallerNotVmSigner(); } - if (!registry.isAutomationEnabled()) { - return; - } if(cycleInfo.state() != CommonUtils.CycleState.STARTED || cycleInfo.startTime() + cycleInfo.durationSecs() > block.timestamp) { return; @@ -493,32 +445,30 @@ contract AutomationController is IAutomationController, Ownable2StepUpgradeable, bool transitionFinalized = isTransitionFinalized(); if (transitionFinalized) { - - (bool updated, ) = address(registry).call( - abi.encodeCall( - IAutomationRegistry.updateRegistryState, - ( - cycleInfo.sysGasCommittedForNextCycle(), - cycleInfo.gasCommittedForNextCycle(), - cycleInfo.gasCommittedForNewCycle(), - cycleInfo.transitionState.lockedFees, - FINISHED + if (!registry.isAutomationEnabled() && cycleInfo.state() == CommonUtils.CycleState.FINISHED) { + _tryMoveToSuspendedState(); + } else { + (bool updated, ) = address(registry).call( + abi.encodeCall( + IAutomationRegistry.updateRegistryState, + ( + cycleInfo.sysGasCommittedForNextCycle(), + cycleInfo.gasCommittedForNextCycle(), + cycleInfo.gasCommittedForNewCycle(), + cycleInfo.transitionState.lockedFees, + FINISHED + ) ) - ) - ); - require(updated, UpdateRegistryStateFailed()); - - // Set current timestamp as cycle start time - // Increment the cycle and update the state to STARTED - moveToStartedState(); - if(registry.getTotalActiveTasks() > 0 ) { - uint256[] memory activeTasks = registry.getAllActiveTaskIds(); - emit ActiveTasks(activeTasks); - } - - // Check if automation is disabled - if (!registry.isAutomationEnabled()) { - tryMoveToSuspendedState(); + ); + require(updated, UpdateRegistryStateFailed()); + + // Set current timestamp as cycle start time + // Increment the cycle and update the state to STARTED + _moveToStartedState(); + if(registry.getTotalActiveTasks() > 0 ) { + uint256[] memory activeTasks = registry.getAllActiveTaskIds(); + emit ActiveTasks(activeTasks); + } } } } @@ -547,8 +497,8 @@ contract AutomationController is IAutomationController, Ownable2StepUpgradeable, if (registry.isAutomationEnabled()) { // 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(); + _updateConfigFromBuffer(); + _moveToStartedState(); } else { moveToReadyState(); } @@ -568,7 +518,7 @@ contract AutomationController is IAutomationController, Ownable2StepUpgradeable, /// 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 { + function _tryMoveToSuspendedState() private { if(registry.totalTasks() == 0) { // Registry is empty move to ready state directly updateCycleStateTo(CommonUtils.CycleState.READY); @@ -612,6 +562,11 @@ contract AutomationController is IAutomationController, Ownable2StepUpgradeable, } } + function tryMoveToSuspendedState() external { + if (msg.sender != address(registry)) { revert CallerNotRegistry(); } + _tryMoveToSuspendedState(); + } + /// @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 @@ -645,7 +600,7 @@ contract AutomationController is IAutomationController, Ownable2StepUpgradeable, } /// @notice Transitions cycle state to the STARTED state. - function moveToStartedState() private { + function _moveToStartedState() private { cycleInfo.setIndex(cycleInfo.index() + 1); cycleInfo.setStartTime(uint64(block.timestamp)); @@ -658,6 +613,11 @@ contract AutomationController is IAutomationController, Ownable2StepUpgradeable, updateCycleStateTo(CommonUtils.CycleState.STARTED); } + function moveToStartedState() external { + if (msg.sender != address(registry)) { revert CallerNotRegistry(); } + _moveToStartedState(); + } + /// @notice Updates the state of the cycle. /// @param _state Input state to update cycle state with. function updateCycleStateTo(CommonUtils.CycleState _state) private { @@ -684,38 +644,42 @@ contract AutomationController is IAutomationController, Ownable2StepUpgradeable, /// @notice Helper function called when cycle end is identified. function onCycleEndInternal() private { - if(registry.totalTasks() == 0) { - // Registry is empty update config buffer and move to STARTED state directly - updateConfigFromBuffer(); - moveToStartedState(); - } else { - uint256[] memory expectedTasksToBeProcessed = registry.getTaskIdList().sortUint256(); + if (!registry.isAutomationEnabled()) { + _tryMoveToSuspendedState(); + } else{ + if(registry.totalTasks() == 0) { + // Registry is empty update config buffer and move to STARTED state directly + _updateConfigFromBuffer(); + _moveToStartedState(); + } else { + uint256[] memory expectedTasksToBeProcessed = registry.getTaskIdList().sortUint256(); - cycleInfo.setRefundDuration(0); - cycleInfo.setNewCycleDuration(cycleInfo.durationSecs()); - cycleInfo.setAutomationFeePerSec(0); - cycleInfo.setGasCommittedForNewCycle(registry.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(registry.calculateAutomationFeeMultiplierForCommittedOccupancy(cycleInfo.gasCommittedForNewCycle())); - updateCycleStateTo(CommonUtils.CycleState.FINISHED); + cycleInfo.setRefundDuration(0); + cycleInfo.setNewCycleDuration(cycleInfo.durationSecs()); + cycleInfo.setAutomationFeePerSec(0); + cycleInfo.setGasCommittedForNewCycle(registry.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(registry.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 { + function _updateConfigFromBuffer() private { if(registry.ifConfigBufferExists()) { (bool sent, ) = address(registry).call(abi.encodeCall(IAutomationRegistry.applyPendingConfig, ())); require(sent, ConfigUpdateFailed()); @@ -730,6 +694,11 @@ contract AutomationController is IAutomationController, Ownable2StepUpgradeable, } } + function updateConfigFromBuffer() external { + if (msg.sender != address(registry)) { revert CallerNotRegistry(); } + _updateConfigFromBuffer(); + } + /// @notice Checks if the cycle transition is finalized. /// @return Bool representing if the cycle transition is finalized. function isTransitionFinalized() private view returns (bool) { @@ -738,7 +707,7 @@ contract AutomationController is IAutomationController, Ownable2StepUpgradeable, /// @notice Checks if the cycle transition is in progress. /// @return Bool representing if the cycle transition is in progress. - function isTransitionInProgress() private view returns (bool) { + function isTransitionInProgress() public view returns (bool) { return cycleInfo.nextTaskIndexPosition() != 0; } diff --git a/solidity/supra_contracts/src/AutomationRegistry.sol b/solidity/supra_contracts/src/AutomationRegistry.sol index 29cad50fac..ef75bde010 100644 --- a/solidity/supra_contracts/src/AutomationRegistry.sol +++ b/solidity/supra_contracts/src/AutomationRegistry.sol @@ -1074,7 +1074,15 @@ contract AutomationRegistry is IAutomationRegistry, Ownable2StepUpgradeable, UUP /// @notice Function to enable the automation. function enableAutomation() external onlyOwner { if(regConfig.automationEnabled()) { revert AlreadyEnabled(); } + + IAutomationController controller = IAutomationController(regConfig.automationController()); + ( , , , CommonUtils.CycleState state) = controller.getCycleInfo(); regConfig.setAutomationEnabled(true); + + if (state == CommonUtils.CycleState.READY) { + controller.moveToStartedState(); + controller.updateConfigFromBuffer(); + } emit AutomationEnabled(regConfig.automationEnabled()); } @@ -1082,7 +1090,14 @@ contract AutomationRegistry is IAutomationRegistry, Ownable2StepUpgradeable, UUP /// @notice Function to disable the automation. function disableAutomation() external onlyOwner { if(!regConfig.automationEnabled()) { revert AlreadyDisabled(); } + + IAutomationController controller = IAutomationController(regConfig.automationController()); + ( , , , CommonUtils.CycleState state) = controller.getCycleInfo(); + regConfig.setAutomationEnabled(false); + if (state == CommonUtils.CycleState.FINISHED && !controller.isTransitionInProgress()) { + controller.tryMoveToSuspendedState(); + } emit AutomationDisabled(regConfig.automationEnabled()); } diff --git a/solidity/supra_contracts/src/IAutomationController.sol b/solidity/supra_contracts/src/IAutomationController.sol index 12a1fa6967..b8a209d79c 100644 --- a/solidity/supra_contracts/src/IAutomationController.sol +++ b/solidity/supra_contracts/src/IAutomationController.sol @@ -6,6 +6,7 @@ import {CommonUtils} from "./CommonUtils.sol"; interface IAutomationController { // Custom errors error AddressCannotBeEOA(); + error CallerNotRegistry(); error CallerNotVmSigner(); error ConfigUpdateFailed(); error InconsistentTransitionState(); @@ -22,9 +23,13 @@ interface IAutomationController { error UpdateTaskStateFailed(); // View functions - function getCycleInfo() external view returns(uint64, uint64, uint64, CommonUtils.CycleState); + function getCycleInfo() external view returns (uint64, uint64, uint64, CommonUtils.CycleState); function getTransitionInfo() external view returns (uint64, uint128); + function isTransitionInProgress() external view returns (bool); // State updating functions function monitorCycleEnd() external; + function moveToStartedState() external; + function tryMoveToSuspendedState() external; + function updateConfigFromBuffer() external; } From 6bc8d4340d7c323de221cb07a68bc90dd9c5aaa8 Mon Sep 17 00:00:00 2001 From: Udit Yadav Date: Wed, 31 Dec 2025 14:44:49 +0530 Subject: [PATCH 10/31] fixed test cases involving automation disable --- .../test/AutomationRegistry.t.sol | 19 ++++++++++++++++++- 1 file changed, 18 insertions(+), 1 deletion(-) diff --git a/solidity/supra_contracts/test/AutomationRegistry.t.sol b/solidity/supra_contracts/test/AutomationRegistry.t.sol index 81f8512055..d5f65832e1 100644 --- a/solidity/supra_contracts/test/AutomationRegistry.t.sol +++ b/solidity/supra_contracts/test/AutomationRegistry.t.sol @@ -394,6 +394,8 @@ contract AutomationRegistryTest is Test { /// @dev Test to ensure 'disableAutomation' disables the automation. function testDisableAutomation() public { + testSetAutomationController(); + // Already enabled in initialize() vm.prank(admin); registry.disableAutomation(); @@ -403,10 +405,13 @@ contract AutomationRegistryTest is Test { /// @dev Test to ensure 'disableAutomation' emits event 'AutomationDisabled'. function testDisableAutomationEmitsEvent() public { + testSetAutomationController(); + vm.expectEmit(true, false, false, false); emit AutomationRegistry.AutomationDisabled(false); - testDisableAutomation(); + vm.prank(admin); + registry.disableAutomation(); } /// @dev Test to ensure 'disableAutomation' reverts if automation is already disabled. @@ -952,6 +957,8 @@ contract AutomationRegistryTest is Test { /// @dev Test to ensure 'register' reverts if automation is not enabled. function testRegisterRevertsIfAutomationNotEnabled() public { + testSetAutomationController(); + // Disable automation vm.prank(admin); registry.disableAutomation(); @@ -1338,6 +1345,8 @@ contract AutomationRegistryTest is Test { /// @dev Test to ensure 'registerSystemTask' reverts if automation is not enabled. function testRegisterSystemTaskRevertsIfAutomationNotEnabled() public { + testSetAutomationController(); + vm.prank(admin); registry.disableAutomation(); @@ -1550,6 +1559,8 @@ contract AutomationRegistryTest is Test { /// @dev Test to ensure 'cancelTask' reverts if automation is not enabled. function testCancelTaskRevertsIfAutomationNotEnabled() public { + testSetAutomationController(); + vm.prank(admin); registry.disableAutomation(); @@ -1623,6 +1634,8 @@ contract AutomationRegistryTest is Test { /// @dev Test to ensure 'cancelSystemTask' reverts if automation is not enabled. function testCancelSystemTaskRevertsIfAutomationNotEnabled() public { + testSetAutomationController(); + vm.prank(admin); registry.disableAutomation(); @@ -1694,6 +1707,8 @@ contract AutomationRegistryTest is Test { /// @dev Test to ensure 'stopTasks' reverts if automation is not enabled. function testStopTasksRevertsIfAutomationNotEnabled() public { + testSetAutomationController(); + vm.prank(admin); registry.disableAutomation(); @@ -1799,6 +1814,8 @@ contract AutomationRegistryTest is Test { /// @dev Test to ensure 'stopSystemTasks' reverts if automation is not enabled. function testStopSystemTasksRevertsIfAutomationNotEnabled() public { + testSetAutomationController(); + vm.prank(admin); registry.disableAutomation(); From a5b09a0aae2f3663e904d1311a385486e408d31f Mon Sep 17 00:00:00 2001 From: Udit Yadav Date: Wed, 31 Dec 2025 15:06:50 +0530 Subject: [PATCH 11/31] renamed CommonUtils to LibCommonUtils --- .../src/AutomationController.sol | 58 ++++---- .../src/AutomationRegistry.sol | 132 +++++++++--------- solidity/supra_contracts/src/BlockMeta.sol | 4 +- .../src/IAutomationController.sol | 4 +- .../src/IAutomationRegistry.sol | 10 +- .../{CommonUtils.sol => LibCommonUtils.sol} | 10 +- .../supra_contracts/src/LibController.sol | 8 +- solidity/supra_contracts/src/LibRegistry.sol | 14 +- .../test/AutomationController.t.sol | 6 +- .../test/AutomationRegistry.t.sol | 18 +-- 10 files changed, 132 insertions(+), 132 deletions(-) rename solidity/supra_contracts/src/{CommonUtils.sol => LibCommonUtils.sol} (92%) diff --git a/solidity/supra_contracts/src/AutomationController.sol b/solidity/supra_contracts/src/AutomationController.sol index 8655eb7612..cb19e2aee8 100644 --- a/solidity/supra_contracts/src/AutomationController.sol +++ b/solidity/supra_contracts/src/AutomationController.sol @@ -2,7 +2,7 @@ pragma solidity 0.8.27; import {EnumerableSet} from "../lib/openzeppelin-contracts/contracts/utils/structs/EnumerableSet.sol"; -import {CommonUtils} from "./CommonUtils.sol"; +import {LibCommonUtils} from "./LibCommonUtils.sol"; import {LibController} from "./LibController.sol"; import {IAutomationController} from "./IAutomationController.sol"; @@ -13,7 +13,7 @@ import {UUPSUpgradeable} from "../lib/openzeppelin-contracts/contracts/proxy/uti contract AutomationController is IAutomationController, Ownable2StepUpgradeable, UUPSUpgradeable { using EnumerableSet for EnumerableSet.UintSet; - using CommonUtils for *; + using LibCommonUtils for *; using LibController for *; /// @dev Defines the cycle state, used to update the registry. @@ -54,10 +54,10 @@ contract AutomationController is IAutomationController, Ownable2StepUpgradeable, /// @notice Emitted when the cycle state transitions. event AutomationCycleEvent( uint64 indexed index, - CommonUtils.CycleState indexed state, + LibCommonUtils.CycleState indexed state, uint64 startTime, uint64 durationSecs, - CommonUtils.CycleState indexed oldState + LibCommonUtils.CycleState indexed oldState ); /// @notice Event emitted on cycle transition containing active task indexes for the new cycle. @@ -88,7 +88,7 @@ contract AutomationController is IAutomationController, Ownable2StepUpgradeable, registry = IAutomationRegistry(_registry); - (CommonUtils.CycleState state, uint64 cycleId) = registry.isAutomationEnabled() ? (CommonUtils.CycleState.STARTED, 1) : (CommonUtils.CycleState.READY, 0); + (LibCommonUtils.CycleState state, uint64 cycleId) = registry.isAutomationEnabled() ? (LibCommonUtils.CycleState.STARTED, 1) : (LibCommonUtils.CycleState.READY, 0); cycleInfo.initializeCycle( cycleId, @@ -108,12 +108,12 @@ contract AutomationController is IAutomationController, Ownable2StepUpgradeable, // Check caller is VM Signer if (msg.sender != registry.getVmSigner()) { revert CallerNotVmSigner(); } - CommonUtils.CycleState state = cycleInfo.state(); + LibCommonUtils.CycleState state = cycleInfo.state(); - if(state == CommonUtils.CycleState.FINISHED) { + if(state == LibCommonUtils.CycleState.FINISHED) { onCycleTransition(_cycleIndex, _taskIndexes); } else { - if(state != CommonUtils.CycleState.SUSPENDED) { revert InvalidRegistryState(); } + if(state != LibCommonUtils.CycleState.SUSPENDED) { revert InvalidRegistryState(); } onCycleSuspend(_cycleIndex, _taskIndexes); } } @@ -122,7 +122,7 @@ contract AutomationController is IAutomationController, Ownable2StepUpgradeable, function monitorCycleEnd() external { if (tx.origin != registry.getVmSigner()) { revert CallerNotVmSigner(); } - if(cycleInfo.state() != CommonUtils.CycleState.STARTED || cycleInfo.startTime() + cycleInfo.durationSecs() > block.timestamp) { + if(cycleInfo.state() != LibCommonUtils.CycleState.STARTED || cycleInfo.startTime() + cycleInfo.durationSecs() > block.timestamp) { return; } @@ -140,7 +140,7 @@ contract AutomationController is IAutomationController, Ownable2StepUpgradeable, /// @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(); } + if(cycleInfo.state() != LibCommonUtils.CycleState.FINISHED) { revert InvalidRegistryState(); } // Check if transition state exists if(!cycleInfo.ifTransitionStateExists()) { revert InvalidRegistryState(); } @@ -169,7 +169,7 @@ contract AutomationController is IAutomationController, Ownable2StepUpgradeable, return; } - if(cycleInfo.state() != CommonUtils.CycleState.SUSPENDED) { revert InvalidRegistryState(); } + if(cycleInfo.state() != LibCommonUtils.CycleState.SUSPENDED) { revert InvalidRegistryState(); } if(cycleInfo.index() != _cycleIndex) { revert InvalidInputCycleIndex(); } // Check if transition state exists if(!cycleInfo.ifTransitionStateExists()) { revert InvalidRegistryState(); } @@ -190,7 +190,7 @@ contract AutomationController is IAutomationController, Ownable2StepUpgradeable, markTaskProcessed(taskIndexes[i]); // Nothing to refund for GST tasks - if(registry.checkTaskType(taskIndexes[i], CommonUtils.TaskType.UST)) { + if(registry.checkTaskType(taskIndexes[i], LibCommonUtils.TaskType.UST)) { (bool refunded, bytes memory data) = address(registry).call( abi.encodeCall( IAutomationRegistry.refundTaskFees, @@ -260,11 +260,11 @@ contract AutomationController is IAutomationController, Ownable2StepUpgradeable, if(registry.ifTaskExists(_taskIndex)) { markTaskProcessed(_taskIndex); - bool isUst = registry.checkTaskType(_taskIndex, CommonUtils.TaskType.UST); - CommonUtils.TaskDetails memory task = registry.getTaskDetails(_taskIndex); + bool isUst = registry.checkTaskType(_taskIndex, LibCommonUtils.TaskType.UST); + LibCommonUtils.TaskDetails memory task = registry.getTaskDetails(_taskIndex); // Task is cancelled or expired - if(task.state == CommonUtils.TaskState.CANCELLED || _currentTime >= task.expiryTime) { + if(task.state == LibCommonUtils.TaskState.CANCELLED || _currentTime >= task.expiryTime) { if(isUst) { (bool sent, ) = address(registry).call( abi.encodeCall( @@ -284,7 +284,7 @@ contract AutomationController is IAutomationController, Ownable2StepUpgradeable, // Governance submitted tasks are not charged result.sysGas = task.maxGasAmount; - (bool updated, ) = address(registry).call(abi.encodeCall(IAutomationRegistry.updateTaskState, (_taskIndex, CommonUtils.TaskState.ACTIVE))); + (bool updated, ) = address(registry).call(abi.encodeCall(IAutomationRegistry.updateTaskState, (_taskIndex, LibCommonUtils.TaskState.ACTIVE))); require(updated, UpdateTaskStateFailed()); } else { // Active UST @@ -306,7 +306,7 @@ contract AutomationController is IAutomationController, Ownable2StepUpgradeable, // 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, ) = address(registry).call(abi.encodeCall(IAutomationRegistry.updateTaskState, (_taskIndex, CommonUtils.TaskState.ACTIVE))); + (bool updated, ) = address(registry).call(abi.encodeCall(IAutomationRegistry.updateTaskState, (_taskIndex, LibCommonUtils.TaskState.ACTIVE))); require(updated, UpdateTaskStateFailed()); (result.isRemoved, result.gas, result.fees) = tryWithdrawTaskAutomationFee( @@ -445,7 +445,7 @@ contract AutomationController is IAutomationController, Ownable2StepUpgradeable, bool transitionFinalized = isTransitionFinalized(); if (transitionFinalized) { - if (!registry.isAutomationEnabled() && cycleInfo.state() == CommonUtils.CycleState.FINISHED) { + if (!registry.isAutomationEnabled() && cycleInfo.state() == LibCommonUtils.CycleState.FINISHED) { _tryMoveToSuspendedState(); } else { (bool updated, ) = address(registry).call( @@ -521,7 +521,7 @@ contract AutomationController is IAutomationController, Ownable2StepUpgradeable, function _tryMoveToSuspendedState() private { if(registry.totalTasks() == 0) { // Registry is empty move to ready state directly - updateCycleStateTo(CommonUtils.CycleState.READY); + updateCycleStateTo(LibCommonUtils.CycleState.READY); } else if (!cycleInfo.ifTransitionStateExists()) { uint64 currentTime = uint64(block.timestamp); @@ -531,7 +531,7 @@ contract AutomationController is IAutomationController, Ownable2StepUpgradeable, if(currentTime < startTime) { revert InvalidRegistryState(); } if(currentTime >= cycleEndTime) { revert InvalidRegistryState(); } - if(cycleInfo.state() != CommonUtils.CycleState.STARTED) { revert InvalidRegistryState(); } + if(cycleInfo.state() != LibCommonUtils.CycleState.STARTED) { revert InvalidRegistryState(); } uint256[] memory expectedTasksToBeProcessed = registry.getTaskIdList().sortUint256(); @@ -547,9 +547,9 @@ contract AutomationController is IAutomationController, Ownable2StepUpgradeable, updateExpectedTasks(expectedTasksToBeProcessed); cycleInfo.setTransitionStateExists(true); - updateCycleStateTo(CommonUtils.CycleState.SUSPENDED); + updateCycleStateTo(LibCommonUtils.CycleState.SUSPENDED); } else { - if(cycleInfo.state() != CommonUtils.CycleState.FINISHED) { revert InvalidRegistryState(); } + if(cycleInfo.state() != LibCommonUtils.CycleState.FINISHED) { revert InvalidRegistryState(); } if(isTransitionInProgress()) { revert InvalidRegistryState(); } // Did not manage to charge cycle fee, so automationFeePerSec will be 0 along with remaining duration @@ -558,7 +558,7 @@ contract AutomationController is IAutomationController, Ownable2StepUpgradeable, cycleInfo.setAutomationFeePerSec(0); cycleInfo.setGasCommittedForNewCycle(0); - updateCycleStateTo(CommonUtils.CycleState.SUSPENDED); + updateCycleStateTo(LibCommonUtils.CycleState.SUSPENDED); } } @@ -596,7 +596,7 @@ contract AutomationController is IAutomationController, Ownable2StepUpgradeable, cycleInfo.transitionState.expectedTasksToBeProcessed.clear(); } } - updateCycleStateTo(CommonUtils.CycleState.READY); + updateCycleStateTo(LibCommonUtils.CycleState.READY); } /// @notice Transitions cycle state to the STARTED state. @@ -610,7 +610,7 @@ contract AutomationController is IAutomationController, Ownable2StepUpgradeable, cycleInfo.setDurationSecs(cycleInfo.newCycleDuration()); } - updateCycleStateTo(CommonUtils.CycleState.STARTED); + updateCycleStateTo(LibCommonUtils.CycleState.STARTED); } function moveToStartedState() external { @@ -620,8 +620,8 @@ contract AutomationController is IAutomationController, Ownable2StepUpgradeable, /// @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(); + function updateCycleStateTo(LibCommonUtils.CycleState _state) private { + LibCommonUtils.CycleState oldState = cycleInfo.state(); cycleInfo.setState(uint8(_state)); emit AutomationCycleEvent ( @@ -673,7 +673,7 @@ contract AutomationController is IAutomationController, Ownable2StepUpgradeable, // 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(registry.calculateAutomationFeeMultiplierForCommittedOccupancy(cycleInfo.gasCommittedForNewCycle())); - updateCycleStateTo(CommonUtils.CycleState.FINISHED); + updateCycleStateTo(LibCommonUtils.CycleState.FINISHED); } } } @@ -714,7 +714,7 @@ contract AutomationController is IAutomationController, Ownable2StepUpgradeable, // :::::::::::::::::::::::::::::::::::::::::::::::::::::::::::: VIEW FUNCTIONS :::::::::::::::::::::::::::::::::::::::::::::::::::::::::::: /// @notice Returns the index, start time, duration and state of the current cycle. - function getCycleInfo() external view returns (uint64, uint64, uint64, CommonUtils.CycleState) { + function getCycleInfo() external view returns (uint64, uint64, uint64, LibCommonUtils.CycleState) { return (cycleInfo.index(), cycleInfo.startTime(), cycleInfo.durationSecs(), cycleInfo.state()); } diff --git a/solidity/supra_contracts/src/AutomationRegistry.sol b/solidity/supra_contracts/src/AutomationRegistry.sol index ef75bde010..d5cb851252 100644 --- a/solidity/supra_contracts/src/AutomationRegistry.sol +++ b/solidity/supra_contracts/src/AutomationRegistry.sol @@ -2,7 +2,7 @@ pragma solidity 0.8.27; import {EnumerableSet} from "../lib/openzeppelin-contracts/contracts/utils/structs/EnumerableSet.sol"; -import {CommonUtils} from "./CommonUtils.sol"; +import {LibCommonUtils} from "./LibCommonUtils.sol"; import {LibRegistry} from "./LibRegistry.sol"; import {IAutomationController} from "./IAutomationController.sol"; @@ -13,7 +13,7 @@ import {UUPSUpgradeable} from "../lib/openzeppelin-contracts/contracts/proxy/uti contract AutomationRegistry is IAutomationRegistry, Ownable2StepUpgradeable, UUPSUpgradeable { using EnumerableSet for *; - using CommonUtils for *; + using LibCommonUtils for *; using LibRegistry for *; /// @dev Constant for 10^8 @@ -49,7 +49,7 @@ contract AutomationRegistry is IAutomationRegistry, Ownable2StepUpgradeable, UUP address indexed owner, uint128 registrationFee, uint128 lockedDepositFee, - CommonUtils.TaskDetails taskMetadata + LibCommonUtils.TaskDetails taskMetadata ); /// @notice Emitted when a system task is registered. @@ -57,7 +57,7 @@ contract AutomationRegistry is IAutomationRegistry, Ownable2StepUpgradeable, UUP uint64 indexed taskIndex, address indexed owner, uint256 timestamp, - CommonUtils.TaskDetails taskMetadata + LibCommonUtils.TaskDetails taskMetadata ); /// @notice Emitted when an account is authorized as submitter for system tasks. @@ -257,13 +257,13 @@ contract AutomationRegistry is IAutomationRegistry, Ownable2StepUpgradeable, UUP if (!regConfig.automationEnabled()) { revert AutomationNotEnabled(); } if(!regConfig.registrationEnabled()) { revert RegistrationDisabled(); } - ( , uint64 startTime, uint64 durationSecs, CommonUtils.CycleState state) = IAutomationController(regConfig.automationController()).getCycleInfo(); - if(state != CommonUtils.CycleState.STARTED) { revert CycleNotStarted(); } + ( , uint64 startTime, uint64 durationSecs, LibCommonUtils.CycleState state) = IAutomationController(regConfig.automationController()).getCycleInfo(); + if(state != LibCommonUtils.CycleState.STARTED) { revert CycleNotStarted(); } if(totalTasks() >= regConfig.taskCapacity()) { revert TaskCapacityReached(); } - if(_type != uint8(CommonUtils.TaskType.UST)) { revert InvalidTaskType(); } + if(_type != uint8(LibCommonUtils.TaskType.UST)) { revert InvalidTaskType(); } uint64 regTime = uint64(block.timestamp); - validateTaskDuration(regTime, _expiryTime, CommonUtils.TaskType.UST, regConfig.taskDurationCapSecs(), regConfig.sysTaskDurationCapSecs(), startTime, durationSecs); + validateTaskDuration(regTime, _expiryTime, LibCommonUtils.TaskType.UST, regConfig.taskDurationCapSecs(), regConfig.sysTaskDurationCapSecs(), startTime, durationSecs); validateInputs(_payloadTx, _maxGasAmount, _txHash); if(_gasPriceCap == 0) { revert InvalidGasPriceCap(); } @@ -288,8 +288,8 @@ contract AutomationRegistry is IAutomationRegistry, Ownable2StepUpgradeable, UUP _expiryTime, taskIndex, // priority set to taskIndex msg.sender, - CommonUtils.TaskType.UST, - CommonUtils.TaskState.PENDING, + LibCommonUtils.TaskType.UST, + LibCommonUtils.TaskState.PENDING, _payloadTx, _auxData ); @@ -328,14 +328,14 @@ contract AutomationRegistry is IAutomationRegistry, Ownable2StepUpgradeable, UUP if (!regConfig.automationEnabled()) { revert AutomationNotEnabled(); } if(!regConfig.registrationEnabled()) { revert RegistrationDisabled(); } - ( , uint64 startTime, uint64 durationSecs, CommonUtils.CycleState state) = IAutomationController(regConfig.automationController()).getCycleInfo(); - if(state != CommonUtils.CycleState.STARTED) { revert CycleNotStarted(); } + ( , uint64 startTime, uint64 durationSecs, LibCommonUtils.CycleState state) = IAutomationController(regConfig.automationController()).getCycleInfo(); + if(state != LibCommonUtils.CycleState.STARTED) { revert CycleNotStarted(); } if(totalSystemTasks() >= regConfig.sysTaskCapacity()) { revert TaskCapacityReached(); } if(!isAuthorizedSubmitter(msg.sender)) { revert UnauthorizedAccount(); } - if(_type != uint8(CommonUtils.TaskType.GST)) { revert InvalidTaskType(); } + if(_type != uint8(LibCommonUtils.TaskType.GST)) { revert InvalidTaskType(); } uint64 regTime = uint64(block.timestamp); - validateTaskDuration(regTime, _expiryTime, CommonUtils.TaskType.GST, regConfig.taskDurationCapSecs(), regConfig.sysTaskDurationCapSecs(), startTime, durationSecs); + validateTaskDuration(regTime, _expiryTime, LibCommonUtils.TaskType.GST, regConfig.taskDurationCapSecs(), regConfig.sysTaskDurationCapSecs(), startTime, durationSecs); validateInputs(_payloadTx, _maxGasAmount, _txHash); uint128 gasCommitted = _maxGasAmount + regSysState.gasCommittedForNextCycle(); @@ -357,8 +357,8 @@ contract AutomationRegistry is IAutomationRegistry, Ownable2StepUpgradeable, UUP _expiryTime, taskPriority, msg.sender, - CommonUtils.TaskType.GST, - CommonUtils.TaskState.PENDING, + LibCommonUtils.TaskType.GST, + LibCommonUtils.TaskState.PENDING, _payloadTx, _auxData ); @@ -385,18 +385,18 @@ contract AutomationRegistry is IAutomationRegistry, Ownable2StepUpgradeable, UUP // Check if automation is enabled if (!regConfig.automationEnabled()) { revert AutomationNotEnabled(); } - ( , uint64 startTime, uint64 durationSecs, CommonUtils.CycleState state) = IAutomationController(regConfig.automationController()).getCycleInfo(); + ( , uint64 startTime, uint64 durationSecs, LibCommonUtils.CycleState state) = IAutomationController(regConfig.automationController()).getCycleInfo(); - if(state != CommonUtils.CycleState.STARTED) { revert CycleTransitionInProgress(); } + if(state != LibCommonUtils.CycleState.STARTED) { revert CycleTransitionInProgress(); } if(!ifTaskExists(_taskIndex)) { revert TaskDoesNotExist(); } - CommonUtils.TaskDetails memory task = regState.tasks[_taskIndex].getTaskDetails(); + LibCommonUtils.TaskDetails memory task = regState.tasks[_taskIndex].getTaskDetails(); - if(checkTaskType(_taskIndex, CommonUtils.TaskType.GST)) { revert UnsupportedTaskOperation(); } + if(checkTaskType(_taskIndex, LibCommonUtils.TaskType.GST)) { revert UnsupportedTaskOperation(); } if(task.owner != msg.sender) { revert UnauthorizedAccount(); } - if(task.state == CommonUtils.TaskState.CANCELLED) { revert AlreadyCancelled(); } + if(task.state == LibCommonUtils.TaskState.CANCELLED) { revert AlreadyCancelled(); } - if (task.state == CommonUtils.TaskState.PENDING) { + if (task.state == LibCommonUtils.TaskState.PENDING) { // When Pending tasks are cancelled, refund of the deposit fee is done with penalty _removeTask(_taskIndex, false); bool result = safeDepositRefund( @@ -409,7 +409,7 @@ contract AutomationRegistry is IAutomationRegistry, Ownable2StepUpgradeable, UUP } 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)); + LibRegistry.setState(regState.tasks[_taskIndex], uint8(LibCommonUtils.TaskState.CANCELLED)); } // This check means the task was expected to be executed in the next cycle, but it has been cancelled. @@ -438,24 +438,24 @@ contract AutomationRegistry is IAutomationRegistry, Ownable2StepUpgradeable, UUP // Check if automation is enabled if (!regConfig.automationEnabled()) { revert AutomationNotEnabled(); } - ( , uint64 startTime, uint64 durationSecs, CommonUtils.CycleState state) = IAutomationController(regConfig.automationController()).getCycleInfo(); + ( , uint64 startTime, uint64 durationSecs, LibCommonUtils.CycleState state) = IAutomationController(regConfig.automationController()).getCycleInfo(); - if(state != CommonUtils.CycleState.STARTED) { revert CycleTransitionInProgress(); } + if(state != LibCommonUtils.CycleState.STARTED) { revert CycleTransitionInProgress(); } if(!ifTaskExists(_taskIndex)) { revert TaskDoesNotExist(); } if(!ifSysTaskExists(_taskIndex)) { revert SystemTaskDoesNotExist(); } // Check if GST - if(checkTaskType(_taskIndex, CommonUtils.TaskType.UST)) { revert UnsupportedTaskOperation(); } + if(checkTaskType(_taskIndex, LibCommonUtils.TaskType.UST)) { revert UnsupportedTaskOperation(); } - CommonUtils.TaskDetails memory task = regState.tasks[_taskIndex].getTaskDetails(); + LibCommonUtils.TaskDetails memory task = regState.tasks[_taskIndex].getTaskDetails(); if(task.owner != msg.sender) { revert UnauthorizedAccount(); } - if(task.state == CommonUtils.TaskState.CANCELLED) { revert AlreadyCancelled(); } + if(task.state == LibCommonUtils.TaskState.CANCELLED) { revert AlreadyCancelled(); } - if(task.state == CommonUtils.TaskState.PENDING) { + if(task.state == LibCommonUtils.TaskState.PENDING) { _removeTask(_taskIndex, true); } else { - LibRegistry.setState(regState.tasks[_taskIndex], uint8(CommonUtils.TaskState.CANCELLED)); + LibRegistry.setState(regState.tasks[_taskIndex], uint8(LibCommonUtils.TaskState.CANCELLED)); } // This check means the task was expected to be executed in the next cycle, but it has been cancelled. @@ -482,8 +482,8 @@ contract AutomationRegistry is IAutomationRegistry, Ownable2StepUpgradeable, UUP // Check if automation is enabled if (!regConfig.automationEnabled()) { revert AutomationNotEnabled(); } - ( , uint64 startTime, uint64 durationSecs, CommonUtils.CycleState state) = IAutomationController(regConfig.automationController()).getCycleInfo(); - if(state != CommonUtils.CycleState.STARTED) { revert CycleTransitionInProgress(); } + ( , uint64 startTime, uint64 durationSecs, LibCommonUtils.CycleState state) = IAutomationController(regConfig.automationController()).getCycleInfo(); + if(state != LibCommonUtils.CycleState.STARTED) { revert CycleTransitionInProgress(); } if(_taskIndexes.length == 0) { revert TaskIndexesCannotBeEmpty(); } // Compute the automation fee multiplier for cycle @@ -504,13 +504,13 @@ contract AutomationRegistry is IAutomationRegistry, Ownable2StepUpgradeable, UUP // 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(); + LibCommonUtils.TaskDetails memory task = regState.tasks[_taskIndexes[i]].getTaskDetails(); // Check if authorised if(msg.sender != task.owner) { revert UnauthorizedAccount(); } // Check if UST - if(checkTaskType(_taskIndexes[i], CommonUtils.TaskType.GST)) { revert UnsupportedTaskOperation(); } + if(checkTaskType(_taskIndexes[i], LibCommonUtils.TaskType.GST)) { revert UnsupportedTaskOperation(); } // Remove task from the registry _removeTask(_taskIndexes[i], false); @@ -520,7 +520,7 @@ contract AutomationRegistry is IAutomationRegistry, Ownable2StepUpgradeable, UUP // 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) { + if(task.state != LibCommonUtils.TaskState.CANCELLED && task.expiryTime > cycleEndTime) { // Prevent underflow in gas committed if(regState.gasCommittedForNextCycle() < task.maxGasAmount) { revert GasCommittedValueUnderflow(); } // Reduce committed gas by the stopped task's max gas @@ -529,7 +529,7 @@ contract AutomationRegistry is IAutomationRegistry, Ownable2StepUpgradeable, UUP uint128 cycleFeeRefund; uint128 depositRefund; - if(task.state != CommonUtils.TaskState.PENDING) { + if(task.state != LibCommonUtils.TaskState.PENDING) { uint128 taskFee = _calculateTaskFee( task.state, task.expiryTime, @@ -596,8 +596,8 @@ contract AutomationRegistry is IAutomationRegistry, Ownable2StepUpgradeable, UUP // Check if automation is enabled if (!regConfig.automationEnabled()) { revert AutomationNotEnabled(); } - ( , uint64 startTime, uint64 durationSecs, CommonUtils.CycleState state) = IAutomationController(regConfig.automationController()).getCycleInfo(); - if(state != CommonUtils.CycleState.STARTED) { revert CycleTransitionInProgress(); } + ( , uint64 startTime, uint64 durationSecs, LibCommonUtils.CycleState state) = IAutomationController(regConfig.automationController()).getCycleInfo(); + if(state != LibCommonUtils.CycleState.STARTED) { revert CycleTransitionInProgress(); } // Ensure that task indexes are provided if(_taskIndexes.length == 0) { revert TaskIndexesCannotBeEmpty(); } @@ -611,17 +611,17 @@ contract AutomationRegistry is IAutomationRegistry, Ownable2StepUpgradeable, UUP // 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(); + LibCommonUtils.TaskDetails memory task = regState.tasks[_taskIndexes[i]].getTaskDetails(); if(task.owner != msg.sender) { revert UnauthorizedAccount(); } // Check if GST - if(checkTaskType(_taskIndexes[i], CommonUtils.TaskType.UST)) { revert UnsupportedTaskOperation(); } + if(checkTaskType(_taskIndexes[i], LibCommonUtils.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 > cycleEndTime) { + if(task.state != LibCommonUtils.TaskState.CANCELLED && task.expiryTime > cycleEndTime) { // Prevent underflow in gas committed if(regSysState.gasCommittedForNextCycle() < task.maxGasAmount) { revert GasCommittedValueUnderflow(); } regSysState.setGasCommittedForNextCycle(regSysState.gasCommittedForNextCycle() - task.maxGasAmount); @@ -677,7 +677,7 @@ contract AutomationRegistry is IAutomationRegistry, Ownable2StepUpgradeable, UUP function validateTaskDuration( uint64 _regTime, uint64 _expiryTime, - CommonUtils.TaskType _type, + LibCommonUtils.TaskType _type, uint64 _taskDurationCapSecs, uint64 _sysTaskDurationCapSecs, uint64 _cycleStartTime, @@ -686,9 +686,9 @@ contract AutomationRegistry is IAutomationRegistry, Ownable2StepUpgradeable, UUP if(_expiryTime <= _regTime) { revert InvalidExpiryTime(); } uint64 taskDuration = _expiryTime - _regTime; - if(_type == CommonUtils.TaskType.UST) { + if(_type == LibCommonUtils.TaskType.UST) { if(taskDuration > _taskDurationCapSecs) { revert InvalidTaskDuration(); } - } else if(_type == CommonUtils.TaskType.GST) { + } else if(_type == LibCommonUtils.TaskType.GST) { if ( taskDuration > _sysTaskDurationCapSecs) { revert InvalidTaskDuration(); } } else { revert InvalidTypeForTask(); @@ -881,7 +881,7 @@ contract AutomationRegistry is IAutomationRegistry, Ownable2StepUpgradeable, UUP /// @param _registryMaxGasCap Registry max gas cap /// @return Calculated task fee for the interval the task will be active. function _calculateTaskFee( - CommonUtils.TaskState _state, + LibCommonUtils.TaskState _state, uint64 _expiryTime, uint128 _maxGasAmount, uint64 _potentialFeeTimeframe, @@ -903,7 +903,7 @@ contract AutomationRegistry is IAutomationRegistry, Ownable2StepUpgradeable, UUP // 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) { + if(_state == LibCommonUtils.TaskState.PENDING) { actualFeeTimeframe = _potentialFeeTimeframe; } else { actualFeeTimeframe = taskActiveTimeframe < _potentialFeeTimeframe ? taskActiveTimeframe : _potentialFeeTimeframe; @@ -1076,10 +1076,10 @@ contract AutomationRegistry is IAutomationRegistry, Ownable2StepUpgradeable, UUP if(regConfig.automationEnabled()) { revert AlreadyEnabled(); } IAutomationController controller = IAutomationController(regConfig.automationController()); - ( , , , CommonUtils.CycleState state) = controller.getCycleInfo(); + ( , , , LibCommonUtils.CycleState state) = controller.getCycleInfo(); regConfig.setAutomationEnabled(true); - if (state == CommonUtils.CycleState.READY) { + if (state == LibCommonUtils.CycleState.READY) { controller.moveToStartedState(); controller.updateConfigFromBuffer(); } @@ -1092,10 +1092,10 @@ contract AutomationRegistry is IAutomationRegistry, Ownable2StepUpgradeable, UUP if(!regConfig.automationEnabled()) { revert AlreadyDisabled(); } IAutomationController controller = IAutomationController(regConfig.automationController()); - ( , , , CommonUtils.CycleState state) = controller.getCycleInfo(); + ( , , , LibCommonUtils.CycleState state) = controller.getCycleInfo(); regConfig.setAutomationEnabled(false); - if (state == CommonUtils.CycleState.FINISHED && !controller.isTransitionInProgress()) { + if (state == LibCommonUtils.CycleState.FINISHED && !controller.isTransitionInProgress()) { controller.tryMoveToSuspendedState(); } @@ -1175,7 +1175,7 @@ contract AutomationRegistry is IAutomationRegistry, Ownable2StepUpgradeable, UUP /// @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 { + function updateTaskState(uint64 _taskIndex, LibCommonUtils.TaskState _taskState) external { onlyController(); LibRegistry.setState(regState.tasks[_taskIndex], uint8(_taskState)); } @@ -1226,7 +1226,7 @@ contract AutomationRegistry is IAutomationRegistry, Ownable2StepUpgradeable, UUP /// @notice Internally calls _calculateTaskFee, reverts if caller is not AutomationController. function calculateTaskFee( - CommonUtils.TaskState _state, + LibCommonUtils.TaskState _state, uint64 _expiryTime, uint128 _maxGasAmount, uint64 _potentialFeeTimeframe, @@ -1259,7 +1259,7 @@ contract AutomationRegistry is IAutomationRegistry, Ownable2StepUpgradeable, UUP ) external { onlyController(); // Check if task is UST - if(checkTaskType(_taskIndex, CommonUtils.TaskType.GST)) { revert RegisteredTaskInvalidType(); } + if(checkTaskType(_taskIndex, LibCommonUtils.TaskType.GST)) { revert RegisteredTaskInvalidType(); } // Remove task from the registry state _removeTask(_taskIndex, false); @@ -1280,14 +1280,14 @@ contract AutomationRegistry is IAutomationRegistry, Ownable2StepUpgradeable, UUP uint256 _cycleLockedFees ) external returns (uint256) { onlyController(); - if(checkTaskType(_taskIndex, CommonUtils.TaskType.GST)) { revert RegisteredTaskInvalidType(); } + if(checkTaskType(_taskIndex, LibCommonUtils.TaskType.GST)) { revert RegisteredTaskInvalidType(); } - CommonUtils.TaskDetails memory task = regState.tasks[_taskIndex].getTaskDetails(); + LibCommonUtils.TaskDetails memory task = regState.tasks[_taskIndex].getTaskDetails(); // Do not attempt fee refund if remaining duration is 0 (uint64 refundDuration, uint128 automationFeePerSec) = IAutomationController(regConfig.automationController()).getTransitionInfo(); - if (task.state != CommonUtils.TaskState.PENDING && refundDuration != 0) { + if (task.state != LibCommonUtils.TaskState.PENDING && refundDuration != 0) { uint128 registryMaxGasCap = getRegistryMaxGasCap(); uint128 _refund = _calculateTaskFee( task.state, @@ -1387,9 +1387,9 @@ contract AutomationRegistry is IAutomationRegistry, Ownable2StepUpgradeable, UUP /// @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) { + function getTaskDetailsBulk(uint64[] memory _taskIndexes) external view returns (LibCommonUtils.TaskDetails[] memory) { uint256 count = _taskIndexes.length; - CommonUtils.TaskDetails[] memory temp = new CommonUtils.TaskDetails[](count); + LibCommonUtils.TaskDetails[] memory temp = new LibCommonUtils.TaskDetails[](count); uint256 exists; for (uint256 i = 0; i < count; i++) { @@ -1399,7 +1399,7 @@ contract AutomationRegistry is IAutomationRegistry, Ownable2StepUpgradeable, UUP } } - CommonUtils.TaskDetails[] memory taskDetails = new CommonUtils.TaskDetails[](exists); + LibCommonUtils.TaskDetails[] memory taskDetails = new LibCommonUtils.TaskDetails[](exists); for (uint256 i = 0; i < exists; i++) { taskDetails[i] = temp[i]; } @@ -1438,7 +1438,7 @@ contract AutomationRegistry is IAutomationRegistry, Ownable2StepUpgradeable, UUP /// @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) { + function getTaskDetails(uint64 _taskIndex) external view returns (LibCommonUtils.TaskDetails memory) { if(!ifTaskExists(_taskIndex)) { revert TaskDoesNotExist(); } return regState.tasks[_taskIndex].getTaskDetails(); } @@ -1458,7 +1458,7 @@ contract AutomationRegistry is IAutomationRegistry, Ownable2StepUpgradeable, UUP /// @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) public view returns (bool) { + function checkTaskType(uint64 _taskIndex, LibCommonUtils.TaskType _type) public view returns (bool) { return _type == regState.tasks[_taskIndex].taskType(); } @@ -1470,7 +1470,7 @@ contract AutomationRegistry is IAutomationRegistry, Ownable2StepUpgradeable, UUP /// @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) { + function getTaskState(uint64 _taskIndex) external view returns (LibCommonUtils.TaskState) { return LibRegistry.state(regState.tasks[_taskIndex]); } @@ -1542,18 +1542,18 @@ contract AutomationRegistry is IAutomationRegistry, Ownable2StepUpgradeable, UUP /// @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); + return hasActiveTaskOfType(_account, _taskIndex, LibCommonUtils.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); + return hasActiveTaskOfType(_account, _taskIndex, LibCommonUtils.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) { - return regState.tasks[_taskIndex].owner() == _account && LibRegistry.state(regState.tasks[_taskIndex]) != CommonUtils.TaskState.PENDING && checkTaskType(_taskIndex, _type); + function hasActiveTaskOfType(address _account, uint64 _taskIndex, LibCommonUtils.TaskType _type) public view returns (bool) { + return regState.tasks[_taskIndex].owner() == _account && LibRegistry.state(regState.tasks[_taskIndex]) != LibCommonUtils.TaskState.PENDING && checkTaskType(_taskIndex, _type); } /// @notice Checks if config buffer exists. diff --git a/solidity/supra_contracts/src/BlockMeta.sol b/solidity/supra_contracts/src/BlockMeta.sol index aa661968fc..e9d3c74463 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 {Ownable2StepUpgradeable} from "../lib/openzeppelin-contracts-upgradeable/contracts/access/Ownable2StepUpgradeable.sol"; import {UUPSUpgradeable} from "../lib/openzeppelin-contracts/contracts/proxy/utils/UUPSUpgradeable.sol"; -import {CommonUtils} from "./CommonUtils.sol"; +import {LibCommonUtils} from "./LibCommonUtils.sol"; contract BlockMeta is Ownable2StepUpgradeable, UUPSUpgradeable { - using CommonUtils for address; + using LibCommonUtils for address; /*////////////////////////////////////////////////////////////// STORAGE diff --git a/solidity/supra_contracts/src/IAutomationController.sol b/solidity/supra_contracts/src/IAutomationController.sol index b8a209d79c..a6c43f6137 100644 --- a/solidity/supra_contracts/src/IAutomationController.sol +++ b/solidity/supra_contracts/src/IAutomationController.sol @@ -1,7 +1,7 @@ // SPDX-License-Identifier: MIT pragma solidity 0.8.27; -import {CommonUtils} from "./CommonUtils.sol"; +import {LibCommonUtils} from "./LibCommonUtils.sol"; interface IAutomationController { // Custom errors @@ -23,7 +23,7 @@ interface IAutomationController { error UpdateTaskStateFailed(); // View functions - function getCycleInfo() external view returns (uint64, uint64, uint64, CommonUtils.CycleState); + function getCycleInfo() external view returns (uint64, uint64, uint64, LibCommonUtils.CycleState); function getTransitionInfo() external view returns (uint64, uint128); function isTransitionInProgress() external view returns (bool); diff --git a/solidity/supra_contracts/src/IAutomationRegistry.sol b/solidity/supra_contracts/src/IAutomationRegistry.sol index d8c3eb0709..bb67b62c04 100644 --- a/solidity/supra_contracts/src/IAutomationRegistry.sol +++ b/solidity/supra_contracts/src/IAutomationRegistry.sol @@ -1,7 +1,7 @@ // SPDX-License-Identifier: MIT pragma solidity 0.8.27; -import {CommonUtils} from "./CommonUtils.sol"; +import {LibCommonUtils} from "./LibCommonUtils.sol"; interface IAutomationRegistry { // Custom errors @@ -59,12 +59,12 @@ interface IAutomationRegistry { // View functions function ifTaskExists(uint64 _taskIndex) external view returns (bool); - function checkTaskType(uint64 _taskIndex, CommonUtils.TaskType _type) external view returns (bool); + function checkTaskType(uint64 _taskIndex, LibCommonUtils.TaskType _type) external view returns (bool); function getAllActiveTaskIds() external view returns (uint256[] memory); function getCycleLockedFees() external view returns (uint256); function getGasCommittedForNextCycle() external view returns (uint128); function getRegistryMaxGasCap() external view returns (uint128); - function getTaskDetails(uint64 _taskIndex) external view returns (CommonUtils.TaskDetails memory); + function getTaskDetails(uint64 _taskIndex) external view returns (LibCommonUtils.TaskDetails memory); function getTaskIdList() external view returns (uint256[] memory); function getTotalActiveTasks() external view returns (uint256); function totalTasks() external view returns (uint256); @@ -76,7 +76,7 @@ interface IAutomationRegistry { function calculateAutomationFeeMultiplierForCurrentCycleInternal() external view returns (uint128); function calculateAutomationFeeMultiplierForCommittedOccupancy(uint128 _totalCommittedMaxGas) external view returns (uint128); function calculateTaskFee( - CommonUtils.TaskState _state, + LibCommonUtils.TaskState _state, uint64 _expiryTime, uint128 _maxGasAmount, uint64 _potentialFeeTimeframe, @@ -89,7 +89,7 @@ interface IAutomationRegistry { // State updating functions function removeTask(uint64 _taskIndex, bool _removeFromSysReg) external; - function updateTaskState(uint64 _taskIndex, CommonUtils.TaskState _taskState) external; + function updateTaskState(uint64 _taskIndex, LibCommonUtils.TaskState _taskState) external; function updateRegistryState( uint128 _sysGasCommittedForNextCycle, uint128 _gasCommittedForNextCycle, diff --git a/solidity/supra_contracts/src/CommonUtils.sol b/solidity/supra_contracts/src/LibCommonUtils.sol similarity index 92% rename from solidity/supra_contracts/src/CommonUtils.sol rename to solidity/supra_contracts/src/LibCommonUtils.sol index e4b7e9a38e..bb96e9fb22 100644 --- a/solidity/supra_contracts/src/CommonUtils.sol +++ b/solidity/supra_contracts/src/LibCommonUtils.sol @@ -4,7 +4,7 @@ pragma solidity 0.8.27; import {LibRegistry} from "./LibRegistry.sol"; // Helper library used by supra contracts -library CommonUtils { +library LibCommonUtils { /// @notice Enum describing state of the cycle. enum CycleState { @@ -38,8 +38,8 @@ library CommonUtils { uint64 registrationTime; uint64 expiryTime; uint64 priority; - CommonUtils.TaskType taskType; - CommonUtils.TaskState state; + LibCommonUtils.TaskType taskType; + LibCommonUtils.TaskState state; address owner; bytes payloadTx; bytes[] auxData; @@ -71,8 +71,8 @@ library CommonUtils { // --- Decode packed uint256: owner | taskType | taskState --- details.owner = address(uint160(t.owner_type_state >> 96)); - details.taskType = CommonUtils.TaskType(uint8(t.owner_type_state >> 88)); - details.state = CommonUtils.TaskState(uint8(t.owner_type_state >> 80)); + details.taskType = LibCommonUtils.TaskType(uint8(t.owner_type_state >> 88)); + details.state = LibCommonUtils.TaskState(uint8(t.owner_type_state >> 80)); } diff --git a/solidity/supra_contracts/src/LibController.sol b/solidity/supra_contracts/src/LibController.sol index d9a2d94df2..74c5531c8a 100644 --- a/solidity/supra_contracts/src/LibController.sol +++ b/solidity/supra_contracts/src/LibController.sol @@ -3,7 +3,7 @@ pragma solidity 0.8.27; import {EnumerableSet} from "../lib/openzeppelin-contracts/contracts/utils/structs/EnumerableSet.sol"; -import {CommonUtils} from "./CommonUtils.sol"; +import {LibCommonUtils} from "./LibCommonUtils.sol"; // Helper library used by AutomationController. library LibController { @@ -38,7 +38,7 @@ library LibController { uint64 _index, uint64 _startTime, uint64 _durationSecs, - CommonUtils.CycleState _cycleState + LibCommonUtils.CycleState _cycleState ) internal { _cycleInfo.index_startTime_durationSecs_state_ifTransitionStateExists = (uint256(_index) << 192) | @@ -60,8 +60,8 @@ library LibController { return uint64(cycle.index_startTime_durationSecs_state_ifTransitionStateExists >> 64); } - function state(AutomationCycleInfo storage cycle) internal view returns (CommonUtils.CycleState) { - return CommonUtils.CycleState(uint8(cycle.index_startTime_durationSecs_state_ifTransitionStateExists >> 56)); + function state(AutomationCycleInfo storage cycle) internal view returns (LibCommonUtils.CycleState) { + return LibCommonUtils.CycleState(uint8(cycle.index_startTime_durationSecs_state_ifTransitionStateExists >> 56)); } function ifTransitionStateExists(AutomationCycleInfo storage cycle) internal view returns (bool) { diff --git a/solidity/supra_contracts/src/LibRegistry.sol b/solidity/supra_contracts/src/LibRegistry.sol index 4b7f91098f..658dd8aa7c 100644 --- a/solidity/supra_contracts/src/LibRegistry.sol +++ b/solidity/supra_contracts/src/LibRegistry.sol @@ -3,7 +3,7 @@ pragma solidity 0.8.27; import {EnumerableSet} from "../lib/openzeppelin-contracts/contracts/utils/structs/EnumerableSet.sol"; -import {CommonUtils} from "./CommonUtils.sol"; +import {LibCommonUtils} from "./LibCommonUtils.sol"; // Helper library used by AutomationRegistry. library LibRegistry { @@ -322,8 +322,8 @@ library LibRegistry { uint64 _expiryTime, uint64 _priority, address _owner, - CommonUtils.TaskType _type, - CommonUtils.TaskState _state, + LibCommonUtils.TaskType _type, + LibCommonUtils.TaskState _state, bytes memory _payloadTx, bytes[] memory _auxData ) internal pure returns (TaskMetadata memory t) { @@ -434,12 +434,12 @@ library LibRegistry { 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 taskType(TaskMetadata storage t) internal view returns (LibCommonUtils.TaskType) { + return LibCommonUtils.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 state(TaskMetadata storage t) internal view returns (LibCommonUtils.TaskState) { + return LibCommonUtils.TaskState(uint8(t.owner_type_state >> 80)); } function setOwner(TaskMetadata storage t, address _value) internal { diff --git a/solidity/supra_contracts/test/AutomationController.t.sol b/solidity/supra_contracts/test/AutomationController.t.sol index a31615f5d0..550fbd69eb 100644 --- a/solidity/supra_contracts/test/AutomationController.t.sol +++ b/solidity/supra_contracts/test/AutomationController.t.sol @@ -9,7 +9,7 @@ import {AutomationRegistry} from "../src/AutomationRegistry.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 {LibCommonUtils} from "../src/LibCommonUtils.sol"; contract AutomationControllerTest is Test { AutomationRegistry registry; // AutomationRegistry instance on proxy address @@ -85,11 +85,11 @@ contract AutomationControllerTest is Test { assertEq(controller.owner(), admin); assertEq(address(controller.registry()), address(registry)); - (uint64 index, uint64 startTime, uint64 durationSecs, CommonUtils.CycleState state) = controller.getCycleInfo(); + (uint64 index, uint64 startTime, uint64 durationSecs, LibCommonUtils.CycleState state) = controller.getCycleInfo(); assertEq(index, 1); assertEq(startTime, block.timestamp); assertEq(durationSecs, registry.cycleDurationSecs()); - assertEq(uint8(state), uint8(CommonUtils.CycleState.STARTED)); + assertEq(uint8(state), uint8(LibCommonUtils.CycleState.STARTED)); } /// @dev Test to ensure initialize reverts if reinitialized. diff --git a/solidity/supra_contracts/test/AutomationRegistry.t.sol b/solidity/supra_contracts/test/AutomationRegistry.t.sol index d5f65832e1..507c83d786 100644 --- a/solidity/supra_contracts/test/AutomationRegistry.t.sol +++ b/solidity/supra_contracts/test/AutomationRegistry.t.sol @@ -10,7 +10,7 @@ import {AutomationController} from "../src/AutomationController.sol"; import {IAutomationRegistry} from "../src/IAutomationRegistry.sol"; import {ERC20Supra} from "../src/ERC20Supra.sol"; import {LibRegistry} from "../src/LibRegistry.sol"; -import {CommonUtils} from "../src/CommonUtils.sol"; +import {LibCommonUtils} from "../src/LibCommonUtils.sol"; contract AutomationRegistryTest is Test { AutomationRegistry impl; // implementation logic contract @@ -1272,7 +1272,7 @@ contract AutomationRegistryTest is Test { ); vm.stopPrank(); - CommonUtils.TaskDetails memory taskMetadata = registry.getTaskDetails(0); + LibCommonUtils.TaskDetails memory taskMetadata = registry.getTaskDetails(0); assertTrue(registry.ifTaskExists(0)); assertEq(registry.totalTasks(), 1); assertEq(registry.getNextTaskIndex(), 1); @@ -1307,7 +1307,7 @@ contract AutomationRegistryTest is Test { erc20Supra.deposit{value: 5 ether}(); erc20Supra.approve(address(registry), type(uint256).max); - CommonUtils.TaskDetails memory taskMetadata = CommonUtils.TaskDetails( + LibCommonUtils.TaskDetails memory taskMetadata = LibCommonUtils.TaskDetails( 1_000_000, 10 gwei, 0.5 ether, @@ -1317,8 +1317,8 @@ contract AutomationRegistryTest is Test { uint64(block.timestamp), uint64(block.timestamp + 2250), 0, - CommonUtils.TaskType.UST, - CommonUtils.TaskState.PENDING, + LibCommonUtils.TaskType.UST, + LibCommonUtils.TaskState.PENDING, alice, payload, auxData @@ -1491,7 +1491,7 @@ contract AutomationRegistryTest is Test { auxData // aux data ); - CommonUtils.TaskDetails memory taskMetadata = registry.getTaskDetails(0); + LibCommonUtils.TaskDetails memory taskMetadata = registry.getTaskDetails(0); assertTrue(registry.ifTaskExists(0)); assertTrue(registry.ifSysTaskExists(0)); assertEq(registry.totalTasks(), 1); @@ -1523,7 +1523,7 @@ contract AutomationRegistryTest is Test { bytes[] memory auxData; bytes memory payload = createPayload(0, address(erc20Supra)); - CommonUtils.TaskDetails memory taskMetadata = CommonUtils.TaskDetails( + LibCommonUtils.TaskDetails memory taskMetadata = LibCommonUtils.TaskDetails( 1_000_000, 0, 0, @@ -1533,8 +1533,8 @@ contract AutomationRegistryTest is Test { uint64(block.timestamp), uint64(block.timestamp + 2250), 2, - CommonUtils.TaskType.GST, - CommonUtils.TaskState.PENDING, + LibCommonUtils.TaskType.GST, + LibCommonUtils.TaskState.PENDING, bob, payload, auxData From f61f9237835fbfde18e3ecb7e568f6f86d886a14 Mon Sep 17 00:00:00 2001 From: Udit Yadav Date: Wed, 31 Dec 2025 15:33:02 +0530 Subject: [PATCH 12/31] renamed LibCommonUtils to CommonUtils --- .../src/AutomationController.sol | 58 ++++---- .../src/AutomationRegistry.sol | 132 +++++++++--------- solidity/supra_contracts/src/BlockMeta.sol | 6 +- .../{LibCommonUtils.sol => CommonUtils.sol} | 10 +- .../src/IAutomationController.sol | 4 +- .../src/IAutomationRegistry.sol | 10 +- .../supra_contracts/src/LibController.sol | 8 +- solidity/supra_contracts/src/LibRegistry.sol | 14 +- .../test/AutomationController.t.sol | 8 +- .../test/AutomationRegistry.t.sol | 20 +-- solidity/supra_contracts/test/BlockMeta.t.sol | 2 +- 11 files changed, 136 insertions(+), 136 deletions(-) rename solidity/supra_contracts/src/{LibCommonUtils.sol => CommonUtils.sol} (92%) diff --git a/solidity/supra_contracts/src/AutomationController.sol b/solidity/supra_contracts/src/AutomationController.sol index cb19e2aee8..8655eb7612 100644 --- a/solidity/supra_contracts/src/AutomationController.sol +++ b/solidity/supra_contracts/src/AutomationController.sol @@ -2,7 +2,7 @@ pragma solidity 0.8.27; import {EnumerableSet} from "../lib/openzeppelin-contracts/contracts/utils/structs/EnumerableSet.sol"; -import {LibCommonUtils} from "./LibCommonUtils.sol"; +import {CommonUtils} from "./CommonUtils.sol"; import {LibController} from "./LibController.sol"; import {IAutomationController} from "./IAutomationController.sol"; @@ -13,7 +13,7 @@ import {UUPSUpgradeable} from "../lib/openzeppelin-contracts/contracts/proxy/uti contract AutomationController is IAutomationController, Ownable2StepUpgradeable, UUPSUpgradeable { using EnumerableSet for EnumerableSet.UintSet; - using LibCommonUtils for *; + using CommonUtils for *; using LibController for *; /// @dev Defines the cycle state, used to update the registry. @@ -54,10 +54,10 @@ contract AutomationController is IAutomationController, Ownable2StepUpgradeable, /// @notice Emitted when the cycle state transitions. event AutomationCycleEvent( uint64 indexed index, - LibCommonUtils.CycleState indexed state, + CommonUtils.CycleState indexed state, uint64 startTime, uint64 durationSecs, - LibCommonUtils.CycleState indexed oldState + CommonUtils.CycleState indexed oldState ); /// @notice Event emitted on cycle transition containing active task indexes for the new cycle. @@ -88,7 +88,7 @@ contract AutomationController is IAutomationController, Ownable2StepUpgradeable, registry = IAutomationRegistry(_registry); - (LibCommonUtils.CycleState state, uint64 cycleId) = registry.isAutomationEnabled() ? (LibCommonUtils.CycleState.STARTED, 1) : (LibCommonUtils.CycleState.READY, 0); + (CommonUtils.CycleState state, uint64 cycleId) = registry.isAutomationEnabled() ? (CommonUtils.CycleState.STARTED, 1) : (CommonUtils.CycleState.READY, 0); cycleInfo.initializeCycle( cycleId, @@ -108,12 +108,12 @@ contract AutomationController is IAutomationController, Ownable2StepUpgradeable, // Check caller is VM Signer if (msg.sender != registry.getVmSigner()) { revert CallerNotVmSigner(); } - LibCommonUtils.CycleState state = cycleInfo.state(); + CommonUtils.CycleState state = cycleInfo.state(); - if(state == LibCommonUtils.CycleState.FINISHED) { + if(state == CommonUtils.CycleState.FINISHED) { onCycleTransition(_cycleIndex, _taskIndexes); } else { - if(state != LibCommonUtils.CycleState.SUSPENDED) { revert InvalidRegistryState(); } + if(state != CommonUtils.CycleState.SUSPENDED) { revert InvalidRegistryState(); } onCycleSuspend(_cycleIndex, _taskIndexes); } } @@ -122,7 +122,7 @@ contract AutomationController is IAutomationController, Ownable2StepUpgradeable, function monitorCycleEnd() external { if (tx.origin != registry.getVmSigner()) { revert CallerNotVmSigner(); } - if(cycleInfo.state() != LibCommonUtils.CycleState.STARTED || cycleInfo.startTime() + cycleInfo.durationSecs() > block.timestamp) { + if(cycleInfo.state() != CommonUtils.CycleState.STARTED || cycleInfo.startTime() + cycleInfo.durationSecs() > block.timestamp) { return; } @@ -140,7 +140,7 @@ contract AutomationController is IAutomationController, Ownable2StepUpgradeable, /// @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() != LibCommonUtils.CycleState.FINISHED) { revert InvalidRegistryState(); } + if(cycleInfo.state() != CommonUtils.CycleState.FINISHED) { revert InvalidRegistryState(); } // Check if transition state exists if(!cycleInfo.ifTransitionStateExists()) { revert InvalidRegistryState(); } @@ -169,7 +169,7 @@ contract AutomationController is IAutomationController, Ownable2StepUpgradeable, return; } - if(cycleInfo.state() != LibCommonUtils.CycleState.SUSPENDED) { revert InvalidRegistryState(); } + if(cycleInfo.state() != CommonUtils.CycleState.SUSPENDED) { revert InvalidRegistryState(); } if(cycleInfo.index() != _cycleIndex) { revert InvalidInputCycleIndex(); } // Check if transition state exists if(!cycleInfo.ifTransitionStateExists()) { revert InvalidRegistryState(); } @@ -190,7 +190,7 @@ contract AutomationController is IAutomationController, Ownable2StepUpgradeable, markTaskProcessed(taskIndexes[i]); // Nothing to refund for GST tasks - if(registry.checkTaskType(taskIndexes[i], LibCommonUtils.TaskType.UST)) { + if(registry.checkTaskType(taskIndexes[i], CommonUtils.TaskType.UST)) { (bool refunded, bytes memory data) = address(registry).call( abi.encodeCall( IAutomationRegistry.refundTaskFees, @@ -260,11 +260,11 @@ contract AutomationController is IAutomationController, Ownable2StepUpgradeable, if(registry.ifTaskExists(_taskIndex)) { markTaskProcessed(_taskIndex); - bool isUst = registry.checkTaskType(_taskIndex, LibCommonUtils.TaskType.UST); - LibCommonUtils.TaskDetails memory task = registry.getTaskDetails(_taskIndex); + bool isUst = registry.checkTaskType(_taskIndex, CommonUtils.TaskType.UST); + CommonUtils.TaskDetails memory task = registry.getTaskDetails(_taskIndex); // Task is cancelled or expired - if(task.state == LibCommonUtils.TaskState.CANCELLED || _currentTime >= task.expiryTime) { + if(task.state == CommonUtils.TaskState.CANCELLED || _currentTime >= task.expiryTime) { if(isUst) { (bool sent, ) = address(registry).call( abi.encodeCall( @@ -284,7 +284,7 @@ contract AutomationController is IAutomationController, Ownable2StepUpgradeable, // Governance submitted tasks are not charged result.sysGas = task.maxGasAmount; - (bool updated, ) = address(registry).call(abi.encodeCall(IAutomationRegistry.updateTaskState, (_taskIndex, LibCommonUtils.TaskState.ACTIVE))); + (bool updated, ) = address(registry).call(abi.encodeCall(IAutomationRegistry.updateTaskState, (_taskIndex, CommonUtils.TaskState.ACTIVE))); require(updated, UpdateTaskStateFailed()); } else { // Active UST @@ -306,7 +306,7 @@ contract AutomationController is IAutomationController, Ownable2StepUpgradeable, // 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, ) = address(registry).call(abi.encodeCall(IAutomationRegistry.updateTaskState, (_taskIndex, LibCommonUtils.TaskState.ACTIVE))); + (bool updated, ) = address(registry).call(abi.encodeCall(IAutomationRegistry.updateTaskState, (_taskIndex, CommonUtils.TaskState.ACTIVE))); require(updated, UpdateTaskStateFailed()); (result.isRemoved, result.gas, result.fees) = tryWithdrawTaskAutomationFee( @@ -445,7 +445,7 @@ contract AutomationController is IAutomationController, Ownable2StepUpgradeable, bool transitionFinalized = isTransitionFinalized(); if (transitionFinalized) { - if (!registry.isAutomationEnabled() && cycleInfo.state() == LibCommonUtils.CycleState.FINISHED) { + if (!registry.isAutomationEnabled() && cycleInfo.state() == CommonUtils.CycleState.FINISHED) { _tryMoveToSuspendedState(); } else { (bool updated, ) = address(registry).call( @@ -521,7 +521,7 @@ contract AutomationController is IAutomationController, Ownable2StepUpgradeable, function _tryMoveToSuspendedState() private { if(registry.totalTasks() == 0) { // Registry is empty move to ready state directly - updateCycleStateTo(LibCommonUtils.CycleState.READY); + updateCycleStateTo(CommonUtils.CycleState.READY); } else if (!cycleInfo.ifTransitionStateExists()) { uint64 currentTime = uint64(block.timestamp); @@ -531,7 +531,7 @@ contract AutomationController is IAutomationController, Ownable2StepUpgradeable, if(currentTime < startTime) { revert InvalidRegistryState(); } if(currentTime >= cycleEndTime) { revert InvalidRegistryState(); } - if(cycleInfo.state() != LibCommonUtils.CycleState.STARTED) { revert InvalidRegistryState(); } + if(cycleInfo.state() != CommonUtils.CycleState.STARTED) { revert InvalidRegistryState(); } uint256[] memory expectedTasksToBeProcessed = registry.getTaskIdList().sortUint256(); @@ -547,9 +547,9 @@ contract AutomationController is IAutomationController, Ownable2StepUpgradeable, updateExpectedTasks(expectedTasksToBeProcessed); cycleInfo.setTransitionStateExists(true); - updateCycleStateTo(LibCommonUtils.CycleState.SUSPENDED); + updateCycleStateTo(CommonUtils.CycleState.SUSPENDED); } else { - if(cycleInfo.state() != LibCommonUtils.CycleState.FINISHED) { revert InvalidRegistryState(); } + 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 @@ -558,7 +558,7 @@ contract AutomationController is IAutomationController, Ownable2StepUpgradeable, cycleInfo.setAutomationFeePerSec(0); cycleInfo.setGasCommittedForNewCycle(0); - updateCycleStateTo(LibCommonUtils.CycleState.SUSPENDED); + updateCycleStateTo(CommonUtils.CycleState.SUSPENDED); } } @@ -596,7 +596,7 @@ contract AutomationController is IAutomationController, Ownable2StepUpgradeable, cycleInfo.transitionState.expectedTasksToBeProcessed.clear(); } } - updateCycleStateTo(LibCommonUtils.CycleState.READY); + updateCycleStateTo(CommonUtils.CycleState.READY); } /// @notice Transitions cycle state to the STARTED state. @@ -610,7 +610,7 @@ contract AutomationController is IAutomationController, Ownable2StepUpgradeable, cycleInfo.setDurationSecs(cycleInfo.newCycleDuration()); } - updateCycleStateTo(LibCommonUtils.CycleState.STARTED); + updateCycleStateTo(CommonUtils.CycleState.STARTED); } function moveToStartedState() external { @@ -620,8 +620,8 @@ contract AutomationController is IAutomationController, Ownable2StepUpgradeable, /// @notice Updates the state of the cycle. /// @param _state Input state to update cycle state with. - function updateCycleStateTo(LibCommonUtils.CycleState _state) private { - LibCommonUtils.CycleState oldState = cycleInfo.state(); + function updateCycleStateTo(CommonUtils.CycleState _state) private { + CommonUtils.CycleState oldState = cycleInfo.state(); cycleInfo.setState(uint8(_state)); emit AutomationCycleEvent ( @@ -673,7 +673,7 @@ contract AutomationController is IAutomationController, Ownable2StepUpgradeable, // 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(registry.calculateAutomationFeeMultiplierForCommittedOccupancy(cycleInfo.gasCommittedForNewCycle())); - updateCycleStateTo(LibCommonUtils.CycleState.FINISHED); + updateCycleStateTo(CommonUtils.CycleState.FINISHED); } } } @@ -714,7 +714,7 @@ contract AutomationController is IAutomationController, Ownable2StepUpgradeable, // :::::::::::::::::::::::::::::::::::::::::::::::::::::::::::: VIEW FUNCTIONS :::::::::::::::::::::::::::::::::::::::::::::::::::::::::::: /// @notice Returns the index, start time, duration and state of the current cycle. - function getCycleInfo() external view returns (uint64, uint64, uint64, LibCommonUtils.CycleState) { + function getCycleInfo() external view returns (uint64, uint64, uint64, CommonUtils.CycleState) { return (cycleInfo.index(), cycleInfo.startTime(), cycleInfo.durationSecs(), cycleInfo.state()); } diff --git a/solidity/supra_contracts/src/AutomationRegistry.sol b/solidity/supra_contracts/src/AutomationRegistry.sol index d5cb851252..ef75bde010 100644 --- a/solidity/supra_contracts/src/AutomationRegistry.sol +++ b/solidity/supra_contracts/src/AutomationRegistry.sol @@ -2,7 +2,7 @@ pragma solidity 0.8.27; import {EnumerableSet} from "../lib/openzeppelin-contracts/contracts/utils/structs/EnumerableSet.sol"; -import {LibCommonUtils} from "./LibCommonUtils.sol"; +import {CommonUtils} from "./CommonUtils.sol"; import {LibRegistry} from "./LibRegistry.sol"; import {IAutomationController} from "./IAutomationController.sol"; @@ -13,7 +13,7 @@ import {UUPSUpgradeable} from "../lib/openzeppelin-contracts/contracts/proxy/uti contract AutomationRegistry is IAutomationRegistry, Ownable2StepUpgradeable, UUPSUpgradeable { using EnumerableSet for *; - using LibCommonUtils for *; + using CommonUtils for *; using LibRegistry for *; /// @dev Constant for 10^8 @@ -49,7 +49,7 @@ contract AutomationRegistry is IAutomationRegistry, Ownable2StepUpgradeable, UUP address indexed owner, uint128 registrationFee, uint128 lockedDepositFee, - LibCommonUtils.TaskDetails taskMetadata + CommonUtils.TaskDetails taskMetadata ); /// @notice Emitted when a system task is registered. @@ -57,7 +57,7 @@ contract AutomationRegistry is IAutomationRegistry, Ownable2StepUpgradeable, UUP uint64 indexed taskIndex, address indexed owner, uint256 timestamp, - LibCommonUtils.TaskDetails taskMetadata + CommonUtils.TaskDetails taskMetadata ); /// @notice Emitted when an account is authorized as submitter for system tasks. @@ -257,13 +257,13 @@ contract AutomationRegistry is IAutomationRegistry, Ownable2StepUpgradeable, UUP if (!regConfig.automationEnabled()) { revert AutomationNotEnabled(); } if(!regConfig.registrationEnabled()) { revert RegistrationDisabled(); } - ( , uint64 startTime, uint64 durationSecs, LibCommonUtils.CycleState state) = IAutomationController(regConfig.automationController()).getCycleInfo(); - if(state != LibCommonUtils.CycleState.STARTED) { revert CycleNotStarted(); } + ( , uint64 startTime, uint64 durationSecs, CommonUtils.CycleState state) = IAutomationController(regConfig.automationController()).getCycleInfo(); + if(state != CommonUtils.CycleState.STARTED) { revert CycleNotStarted(); } if(totalTasks() >= regConfig.taskCapacity()) { revert TaskCapacityReached(); } - if(_type != uint8(LibCommonUtils.TaskType.UST)) { revert InvalidTaskType(); } + if(_type != uint8(CommonUtils.TaskType.UST)) { revert InvalidTaskType(); } uint64 regTime = uint64(block.timestamp); - validateTaskDuration(regTime, _expiryTime, LibCommonUtils.TaskType.UST, regConfig.taskDurationCapSecs(), regConfig.sysTaskDurationCapSecs(), startTime, durationSecs); + validateTaskDuration(regTime, _expiryTime, CommonUtils.TaskType.UST, regConfig.taskDurationCapSecs(), regConfig.sysTaskDurationCapSecs(), startTime, durationSecs); validateInputs(_payloadTx, _maxGasAmount, _txHash); if(_gasPriceCap == 0) { revert InvalidGasPriceCap(); } @@ -288,8 +288,8 @@ contract AutomationRegistry is IAutomationRegistry, Ownable2StepUpgradeable, UUP _expiryTime, taskIndex, // priority set to taskIndex msg.sender, - LibCommonUtils.TaskType.UST, - LibCommonUtils.TaskState.PENDING, + CommonUtils.TaskType.UST, + CommonUtils.TaskState.PENDING, _payloadTx, _auxData ); @@ -328,14 +328,14 @@ contract AutomationRegistry is IAutomationRegistry, Ownable2StepUpgradeable, UUP if (!regConfig.automationEnabled()) { revert AutomationNotEnabled(); } if(!regConfig.registrationEnabled()) { revert RegistrationDisabled(); } - ( , uint64 startTime, uint64 durationSecs, LibCommonUtils.CycleState state) = IAutomationController(regConfig.automationController()).getCycleInfo(); - if(state != LibCommonUtils.CycleState.STARTED) { revert CycleNotStarted(); } + ( , uint64 startTime, uint64 durationSecs, CommonUtils.CycleState state) = IAutomationController(regConfig.automationController()).getCycleInfo(); + if(state != CommonUtils.CycleState.STARTED) { revert CycleNotStarted(); } if(totalSystemTasks() >= regConfig.sysTaskCapacity()) { revert TaskCapacityReached(); } if(!isAuthorizedSubmitter(msg.sender)) { revert UnauthorizedAccount(); } - if(_type != uint8(LibCommonUtils.TaskType.GST)) { revert InvalidTaskType(); } + if(_type != uint8(CommonUtils.TaskType.GST)) { revert InvalidTaskType(); } uint64 regTime = uint64(block.timestamp); - validateTaskDuration(regTime, _expiryTime, LibCommonUtils.TaskType.GST, regConfig.taskDurationCapSecs(), regConfig.sysTaskDurationCapSecs(), startTime, durationSecs); + validateTaskDuration(regTime, _expiryTime, CommonUtils.TaskType.GST, regConfig.taskDurationCapSecs(), regConfig.sysTaskDurationCapSecs(), startTime, durationSecs); validateInputs(_payloadTx, _maxGasAmount, _txHash); uint128 gasCommitted = _maxGasAmount + regSysState.gasCommittedForNextCycle(); @@ -357,8 +357,8 @@ contract AutomationRegistry is IAutomationRegistry, Ownable2StepUpgradeable, UUP _expiryTime, taskPriority, msg.sender, - LibCommonUtils.TaskType.GST, - LibCommonUtils.TaskState.PENDING, + CommonUtils.TaskType.GST, + CommonUtils.TaskState.PENDING, _payloadTx, _auxData ); @@ -385,18 +385,18 @@ contract AutomationRegistry is IAutomationRegistry, Ownable2StepUpgradeable, UUP // Check if automation is enabled if (!regConfig.automationEnabled()) { revert AutomationNotEnabled(); } - ( , uint64 startTime, uint64 durationSecs, LibCommonUtils.CycleState state) = IAutomationController(regConfig.automationController()).getCycleInfo(); + ( , uint64 startTime, uint64 durationSecs, CommonUtils.CycleState state) = IAutomationController(regConfig.automationController()).getCycleInfo(); - if(state != LibCommonUtils.CycleState.STARTED) { revert CycleTransitionInProgress(); } + if(state != CommonUtils.CycleState.STARTED) { revert CycleTransitionInProgress(); } if(!ifTaskExists(_taskIndex)) { revert TaskDoesNotExist(); } - LibCommonUtils.TaskDetails memory task = regState.tasks[_taskIndex].getTaskDetails(); + CommonUtils.TaskDetails memory task = regState.tasks[_taskIndex].getTaskDetails(); - if(checkTaskType(_taskIndex, LibCommonUtils.TaskType.GST)) { revert UnsupportedTaskOperation(); } + if(checkTaskType(_taskIndex, CommonUtils.TaskType.GST)) { revert UnsupportedTaskOperation(); } if(task.owner != msg.sender) { revert UnauthorizedAccount(); } - if(task.state == LibCommonUtils.TaskState.CANCELLED) { revert AlreadyCancelled(); } + if(task.state == CommonUtils.TaskState.CANCELLED) { revert AlreadyCancelled(); } - if (task.state == LibCommonUtils.TaskState.PENDING) { + 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 = safeDepositRefund( @@ -409,7 +409,7 @@ contract AutomationRegistry is IAutomationRegistry, Ownable2StepUpgradeable, UUP } 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(LibCommonUtils.TaskState.CANCELLED)); + 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. @@ -438,24 +438,24 @@ contract AutomationRegistry is IAutomationRegistry, Ownable2StepUpgradeable, UUP // Check if automation is enabled if (!regConfig.automationEnabled()) { revert AutomationNotEnabled(); } - ( , uint64 startTime, uint64 durationSecs, LibCommonUtils.CycleState state) = IAutomationController(regConfig.automationController()).getCycleInfo(); + ( , uint64 startTime, uint64 durationSecs, CommonUtils.CycleState state) = IAutomationController(regConfig.automationController()).getCycleInfo(); - if(state != LibCommonUtils.CycleState.STARTED) { revert CycleTransitionInProgress(); } + if(state != CommonUtils.CycleState.STARTED) { revert CycleTransitionInProgress(); } if(!ifTaskExists(_taskIndex)) { revert TaskDoesNotExist(); } if(!ifSysTaskExists(_taskIndex)) { revert SystemTaskDoesNotExist(); } // Check if GST - if(checkTaskType(_taskIndex, LibCommonUtils.TaskType.UST)) { revert UnsupportedTaskOperation(); } + if(checkTaskType(_taskIndex, CommonUtils.TaskType.UST)) { revert UnsupportedTaskOperation(); } - LibCommonUtils.TaskDetails memory task = regState.tasks[_taskIndex].getTaskDetails(); + CommonUtils.TaskDetails memory task = regState.tasks[_taskIndex].getTaskDetails(); if(task.owner != msg.sender) { revert UnauthorizedAccount(); } - if(task.state == LibCommonUtils.TaskState.CANCELLED) { revert AlreadyCancelled(); } + if(task.state == CommonUtils.TaskState.CANCELLED) { revert AlreadyCancelled(); } - if(task.state == LibCommonUtils.TaskState.PENDING) { + if(task.state == CommonUtils.TaskState.PENDING) { _removeTask(_taskIndex, true); } else { - LibRegistry.setState(regState.tasks[_taskIndex], uint8(LibCommonUtils.TaskState.CANCELLED)); + 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. @@ -482,8 +482,8 @@ contract AutomationRegistry is IAutomationRegistry, Ownable2StepUpgradeable, UUP // Check if automation is enabled if (!regConfig.automationEnabled()) { revert AutomationNotEnabled(); } - ( , uint64 startTime, uint64 durationSecs, LibCommonUtils.CycleState state) = IAutomationController(regConfig.automationController()).getCycleInfo(); - if(state != LibCommonUtils.CycleState.STARTED) { revert CycleTransitionInProgress(); } + ( , uint64 startTime, uint64 durationSecs, CommonUtils.CycleState state) = IAutomationController(regConfig.automationController()).getCycleInfo(); + if(state != CommonUtils.CycleState.STARTED) { revert CycleTransitionInProgress(); } if(_taskIndexes.length == 0) { revert TaskIndexesCannotBeEmpty(); } // Compute the automation fee multiplier for cycle @@ -504,13 +504,13 @@ contract AutomationRegistry is IAutomationRegistry, Ownable2StepUpgradeable, UUP // Loop through each task index to validate and stop the task for (uint256 i = 0; i < _taskIndexes.length; i++) { if(ifTaskExists(_taskIndexes[i])) { - LibCommonUtils.TaskDetails memory task = regState.tasks[_taskIndexes[i]].getTaskDetails(); + CommonUtils.TaskDetails memory task = regState.tasks[_taskIndexes[i]].getTaskDetails(); // Check if authorised if(msg.sender != task.owner) { revert UnauthorizedAccount(); } // Check if UST - if(checkTaskType(_taskIndexes[i], LibCommonUtils.TaskType.GST)) { revert UnsupportedTaskOperation(); } + if(checkTaskType(_taskIndexes[i], CommonUtils.TaskType.GST)) { revert UnsupportedTaskOperation(); } // Remove task from the registry _removeTask(_taskIndexes[i], false); @@ -520,7 +520,7 @@ contract AutomationRegistry is IAutomationRegistry, Ownable2StepUpgradeable, UUP // 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 != LibCommonUtils.TaskState.CANCELLED && task.expiryTime > cycleEndTime) { + if(task.state != CommonUtils.TaskState.CANCELLED && task.expiryTime > cycleEndTime) { // Prevent underflow in gas committed if(regState.gasCommittedForNextCycle() < task.maxGasAmount) { revert GasCommittedValueUnderflow(); } // Reduce committed gas by the stopped task's max gas @@ -529,7 +529,7 @@ contract AutomationRegistry is IAutomationRegistry, Ownable2StepUpgradeable, UUP uint128 cycleFeeRefund; uint128 depositRefund; - if(task.state != LibCommonUtils.TaskState.PENDING) { + if(task.state != CommonUtils.TaskState.PENDING) { uint128 taskFee = _calculateTaskFee( task.state, task.expiryTime, @@ -596,8 +596,8 @@ contract AutomationRegistry is IAutomationRegistry, Ownable2StepUpgradeable, UUP // Check if automation is enabled if (!regConfig.automationEnabled()) { revert AutomationNotEnabled(); } - ( , uint64 startTime, uint64 durationSecs, LibCommonUtils.CycleState state) = IAutomationController(regConfig.automationController()).getCycleInfo(); - if(state != LibCommonUtils.CycleState.STARTED) { revert CycleTransitionInProgress(); } + ( , uint64 startTime, uint64 durationSecs, CommonUtils.CycleState state) = IAutomationController(regConfig.automationController()).getCycleInfo(); + if(state != CommonUtils.CycleState.STARTED) { revert CycleTransitionInProgress(); } // Ensure that task indexes are provided if(_taskIndexes.length == 0) { revert TaskIndexesCannotBeEmpty(); } @@ -611,17 +611,17 @@ contract AutomationRegistry is IAutomationRegistry, Ownable2StepUpgradeable, UUP // Loop through each task index to validate and stop the task for (uint256 i = 0; i < _taskIndexes.length; i++) { if(ifTaskExists(_taskIndexes[i])) { - LibCommonUtils.TaskDetails memory task = regState.tasks[_taskIndexes[i]].getTaskDetails(); + CommonUtils.TaskDetails memory task = regState.tasks[_taskIndexes[i]].getTaskDetails(); if(task.owner != msg.sender) { revert UnauthorizedAccount(); } // Check if GST - if(checkTaskType(_taskIndexes[i], LibCommonUtils.TaskType.UST)) { revert UnsupportedTaskOperation(); } + if(checkTaskType(_taskIndexes[i], CommonUtils.TaskType.UST)) { revert UnsupportedTaskOperation(); } _removeTask(_taskIndexes[i], true); // Remove from active tasks require(regState.activeTaskIds.remove(_taskIndexes[i]), TaskIndexNotFound()); - if(task.state != LibCommonUtils.TaskState.CANCELLED && task.expiryTime > cycleEndTime) { + if(task.state != CommonUtils.TaskState.CANCELLED && task.expiryTime > cycleEndTime) { // Prevent underflow in gas committed if(regSysState.gasCommittedForNextCycle() < task.maxGasAmount) { revert GasCommittedValueUnderflow(); } regSysState.setGasCommittedForNextCycle(regSysState.gasCommittedForNextCycle() - task.maxGasAmount); @@ -677,7 +677,7 @@ contract AutomationRegistry is IAutomationRegistry, Ownable2StepUpgradeable, UUP function validateTaskDuration( uint64 _regTime, uint64 _expiryTime, - LibCommonUtils.TaskType _type, + CommonUtils.TaskType _type, uint64 _taskDurationCapSecs, uint64 _sysTaskDurationCapSecs, uint64 _cycleStartTime, @@ -686,9 +686,9 @@ contract AutomationRegistry is IAutomationRegistry, Ownable2StepUpgradeable, UUP if(_expiryTime <= _regTime) { revert InvalidExpiryTime(); } uint64 taskDuration = _expiryTime - _regTime; - if(_type == LibCommonUtils.TaskType.UST) { + if(_type == CommonUtils.TaskType.UST) { if(taskDuration > _taskDurationCapSecs) { revert InvalidTaskDuration(); } - } else if(_type == LibCommonUtils.TaskType.GST) { + } else if(_type == CommonUtils.TaskType.GST) { if ( taskDuration > _sysTaskDurationCapSecs) { revert InvalidTaskDuration(); } } else { revert InvalidTypeForTask(); @@ -881,7 +881,7 @@ contract AutomationRegistry is IAutomationRegistry, Ownable2StepUpgradeable, UUP /// @param _registryMaxGasCap Registry max gas cap /// @return Calculated task fee for the interval the task will be active. function _calculateTaskFee( - LibCommonUtils.TaskState _state, + CommonUtils.TaskState _state, uint64 _expiryTime, uint128 _maxGasAmount, uint64 _potentialFeeTimeframe, @@ -903,7 +903,7 @@ contract AutomationRegistry is IAutomationRegistry, Ownable2StepUpgradeable, UUP // 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 == LibCommonUtils.TaskState.PENDING) { + if(_state == CommonUtils.TaskState.PENDING) { actualFeeTimeframe = _potentialFeeTimeframe; } else { actualFeeTimeframe = taskActiveTimeframe < _potentialFeeTimeframe ? taskActiveTimeframe : _potentialFeeTimeframe; @@ -1076,10 +1076,10 @@ contract AutomationRegistry is IAutomationRegistry, Ownable2StepUpgradeable, UUP if(regConfig.automationEnabled()) { revert AlreadyEnabled(); } IAutomationController controller = IAutomationController(regConfig.automationController()); - ( , , , LibCommonUtils.CycleState state) = controller.getCycleInfo(); + ( , , , CommonUtils.CycleState state) = controller.getCycleInfo(); regConfig.setAutomationEnabled(true); - if (state == LibCommonUtils.CycleState.READY) { + if (state == CommonUtils.CycleState.READY) { controller.moveToStartedState(); controller.updateConfigFromBuffer(); } @@ -1092,10 +1092,10 @@ contract AutomationRegistry is IAutomationRegistry, Ownable2StepUpgradeable, UUP if(!regConfig.automationEnabled()) { revert AlreadyDisabled(); } IAutomationController controller = IAutomationController(regConfig.automationController()); - ( , , , LibCommonUtils.CycleState state) = controller.getCycleInfo(); + ( , , , CommonUtils.CycleState state) = controller.getCycleInfo(); regConfig.setAutomationEnabled(false); - if (state == LibCommonUtils.CycleState.FINISHED && !controller.isTransitionInProgress()) { + if (state == CommonUtils.CycleState.FINISHED && !controller.isTransitionInProgress()) { controller.tryMoveToSuspendedState(); } @@ -1175,7 +1175,7 @@ contract AutomationRegistry is IAutomationRegistry, Ownable2StepUpgradeable, UUP /// @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, LibCommonUtils.TaskState _taskState) external { + function updateTaskState(uint64 _taskIndex, CommonUtils.TaskState _taskState) external { onlyController(); LibRegistry.setState(regState.tasks[_taskIndex], uint8(_taskState)); } @@ -1226,7 +1226,7 @@ contract AutomationRegistry is IAutomationRegistry, Ownable2StepUpgradeable, UUP /// @notice Internally calls _calculateTaskFee, reverts if caller is not AutomationController. function calculateTaskFee( - LibCommonUtils.TaskState _state, + CommonUtils.TaskState _state, uint64 _expiryTime, uint128 _maxGasAmount, uint64 _potentialFeeTimeframe, @@ -1259,7 +1259,7 @@ contract AutomationRegistry is IAutomationRegistry, Ownable2StepUpgradeable, UUP ) external { onlyController(); // Check if task is UST - if(checkTaskType(_taskIndex, LibCommonUtils.TaskType.GST)) { revert RegisteredTaskInvalidType(); } + if(checkTaskType(_taskIndex, CommonUtils.TaskType.GST)) { revert RegisteredTaskInvalidType(); } // Remove task from the registry state _removeTask(_taskIndex, false); @@ -1280,14 +1280,14 @@ contract AutomationRegistry is IAutomationRegistry, Ownable2StepUpgradeable, UUP uint256 _cycleLockedFees ) external returns (uint256) { onlyController(); - if(checkTaskType(_taskIndex, LibCommonUtils.TaskType.GST)) { revert RegisteredTaskInvalidType(); } + if(checkTaskType(_taskIndex, CommonUtils.TaskType.GST)) { revert RegisteredTaskInvalidType(); } - LibCommonUtils.TaskDetails memory task = regState.tasks[_taskIndex].getTaskDetails(); + CommonUtils.TaskDetails memory task = regState.tasks[_taskIndex].getTaskDetails(); // Do not attempt fee refund if remaining duration is 0 (uint64 refundDuration, uint128 automationFeePerSec) = IAutomationController(regConfig.automationController()).getTransitionInfo(); - if (task.state != LibCommonUtils.TaskState.PENDING && refundDuration != 0) { + if (task.state != CommonUtils.TaskState.PENDING && refundDuration != 0) { uint128 registryMaxGasCap = getRegistryMaxGasCap(); uint128 _refund = _calculateTaskFee( task.state, @@ -1387,9 +1387,9 @@ contract AutomationRegistry is IAutomationRegistry, Ownable2StepUpgradeable, UUP /// @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 (LibCommonUtils.TaskDetails[] memory) { + function getTaskDetailsBulk(uint64[] memory _taskIndexes) external view returns (CommonUtils.TaskDetails[] memory) { uint256 count = _taskIndexes.length; - LibCommonUtils.TaskDetails[] memory temp = new LibCommonUtils.TaskDetails[](count); + CommonUtils.TaskDetails[] memory temp = new CommonUtils.TaskDetails[](count); uint256 exists; for (uint256 i = 0; i < count; i++) { @@ -1399,7 +1399,7 @@ contract AutomationRegistry is IAutomationRegistry, Ownable2StepUpgradeable, UUP } } - LibCommonUtils.TaskDetails[] memory taskDetails = new LibCommonUtils.TaskDetails[](exists); + CommonUtils.TaskDetails[] memory taskDetails = new CommonUtils.TaskDetails[](exists); for (uint256 i = 0; i < exists; i++) { taskDetails[i] = temp[i]; } @@ -1438,7 +1438,7 @@ contract AutomationRegistry is IAutomationRegistry, Ownable2StepUpgradeable, UUP /// @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 (LibCommonUtils.TaskDetails memory) { + function getTaskDetails(uint64 _taskIndex) external view returns (CommonUtils.TaskDetails memory) { if(!ifTaskExists(_taskIndex)) { revert TaskDoesNotExist(); } return regState.tasks[_taskIndex].getTaskDetails(); } @@ -1458,7 +1458,7 @@ contract AutomationRegistry is IAutomationRegistry, Ownable2StepUpgradeable, UUP /// @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, LibCommonUtils.TaskType _type) public view returns (bool) { + function checkTaskType(uint64 _taskIndex, CommonUtils.TaskType _type) public view returns (bool) { return _type == regState.tasks[_taskIndex].taskType(); } @@ -1470,7 +1470,7 @@ contract AutomationRegistry is IAutomationRegistry, Ownable2StepUpgradeable, UUP /// @notice Returns the state of the task /// @param _taskIndex Task index of the task to query. - function getTaskState(uint64 _taskIndex) external view returns (LibCommonUtils.TaskState) { + function getTaskState(uint64 _taskIndex) external view returns (CommonUtils.TaskState) { return LibRegistry.state(regState.tasks[_taskIndex]); } @@ -1542,18 +1542,18 @@ contract AutomationRegistry is IAutomationRegistry, Ownable2StepUpgradeable, UUP /// @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, LibCommonUtils.TaskType.UST); + 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, LibCommonUtils.TaskType.GST); + 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, LibCommonUtils.TaskType _type) public view returns (bool) { - return regState.tasks[_taskIndex].owner() == _account && LibRegistry.state(regState.tasks[_taskIndex]) != LibCommonUtils.TaskState.PENDING && checkTaskType(_taskIndex, _type); + function hasActiveTaskOfType(address _account, uint64 _taskIndex, CommonUtils.TaskType _type) public view returns (bool) { + return regState.tasks[_taskIndex].owner() == _account && LibRegistry.state(regState.tasks[_taskIndex]) != CommonUtils.TaskState.PENDING && checkTaskType(_taskIndex, _type); } /// @notice Checks if config buffer exists. diff --git a/solidity/supra_contracts/src/BlockMeta.sol b/solidity/supra_contracts/src/BlockMeta.sol index e9d3c74463..28c3503c59 100644 --- a/solidity/supra_contracts/src/BlockMeta.sol +++ b/solidity/supra_contracts/src/BlockMeta.sol @@ -3,17 +3,17 @@ pragma solidity 0.8.27; import {Ownable2StepUpgradeable} from "../lib/openzeppelin-contracts-upgradeable/contracts/access/Ownable2StepUpgradeable.sol"; import {UUPSUpgradeable} from "../lib/openzeppelin-contracts/contracts/proxy/utils/UUPSUpgradeable.sol"; -import {LibCommonUtils} from "./LibCommonUtils.sol"; +import {CommonUtils} from "./CommonUtils.sol"; contract BlockMeta is Ownable2StepUpgradeable, UUPSUpgradeable { - using LibCommonUtils for address; + using CommonUtils for address; /*////////////////////////////////////////////////////////////// STORAGE //////////////////////////////////////////////////////////////*/ // Address of VM Signer: SUP0 - address constant VM_SIGNER = address(0x5355500000000000000000000000000000000000); + address constant VM_SIGNER = address(0x53555000); struct Entry { bytes4[] selectors; diff --git a/solidity/supra_contracts/src/LibCommonUtils.sol b/solidity/supra_contracts/src/CommonUtils.sol similarity index 92% rename from solidity/supra_contracts/src/LibCommonUtils.sol rename to solidity/supra_contracts/src/CommonUtils.sol index bb96e9fb22..acdafdb21d 100644 --- a/solidity/supra_contracts/src/LibCommonUtils.sol +++ b/solidity/supra_contracts/src/CommonUtils.sol @@ -4,7 +4,7 @@ pragma solidity 0.8.27; import {LibRegistry} from "./LibRegistry.sol"; // Helper library used by supra contracts -library LibCommonUtils { +library CommonUtils { /// @notice Enum describing state of the cycle. enum CycleState { @@ -38,8 +38,8 @@ library LibCommonUtils { uint64 registrationTime; uint64 expiryTime; uint64 priority; - LibCommonUtils.TaskType taskType; - LibCommonUtils.TaskState state; + TaskType taskType; + TaskState state; address owner; bytes payloadTx; bytes[] auxData; @@ -71,8 +71,8 @@ library LibCommonUtils { // --- Decode packed uint256: owner | taskType | taskState --- details.owner = address(uint160(t.owner_type_state >> 96)); - details.taskType = LibCommonUtils.TaskType(uint8(t.owner_type_state >> 88)); - details.state = LibCommonUtils.TaskState(uint8(t.owner_type_state >> 80)); + details.taskType = TaskType(uint8(t.owner_type_state >> 88)); + details.state = TaskState(uint8(t.owner_type_state >> 80)); } diff --git a/solidity/supra_contracts/src/IAutomationController.sol b/solidity/supra_contracts/src/IAutomationController.sol index a6c43f6137..b8a209d79c 100644 --- a/solidity/supra_contracts/src/IAutomationController.sol +++ b/solidity/supra_contracts/src/IAutomationController.sol @@ -1,7 +1,7 @@ // SPDX-License-Identifier: MIT pragma solidity 0.8.27; -import {LibCommonUtils} from "./LibCommonUtils.sol"; +import {CommonUtils} from "./CommonUtils.sol"; interface IAutomationController { // Custom errors @@ -23,7 +23,7 @@ interface IAutomationController { error UpdateTaskStateFailed(); // View functions - function getCycleInfo() external view returns (uint64, uint64, uint64, LibCommonUtils.CycleState); + function getCycleInfo() external view returns (uint64, uint64, uint64, CommonUtils.CycleState); function getTransitionInfo() external view returns (uint64, uint128); function isTransitionInProgress() external view returns (bool); diff --git a/solidity/supra_contracts/src/IAutomationRegistry.sol b/solidity/supra_contracts/src/IAutomationRegistry.sol index bb67b62c04..d8c3eb0709 100644 --- a/solidity/supra_contracts/src/IAutomationRegistry.sol +++ b/solidity/supra_contracts/src/IAutomationRegistry.sol @@ -1,7 +1,7 @@ // SPDX-License-Identifier: MIT pragma solidity 0.8.27; -import {LibCommonUtils} from "./LibCommonUtils.sol"; +import {CommonUtils} from "./CommonUtils.sol"; interface IAutomationRegistry { // Custom errors @@ -59,12 +59,12 @@ interface IAutomationRegistry { // View functions function ifTaskExists(uint64 _taskIndex) external view returns (bool); - function checkTaskType(uint64 _taskIndex, LibCommonUtils.TaskType _type) external view returns (bool); + function checkTaskType(uint64 _taskIndex, CommonUtils.TaskType _type) external view returns (bool); function getAllActiveTaskIds() external view returns (uint256[] memory); function getCycleLockedFees() external view returns (uint256); function getGasCommittedForNextCycle() external view returns (uint128); function getRegistryMaxGasCap() external view returns (uint128); - function getTaskDetails(uint64 _taskIndex) external view returns (LibCommonUtils.TaskDetails 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); @@ -76,7 +76,7 @@ interface IAutomationRegistry { function calculateAutomationFeeMultiplierForCurrentCycleInternal() external view returns (uint128); function calculateAutomationFeeMultiplierForCommittedOccupancy(uint128 _totalCommittedMaxGas) external view returns (uint128); function calculateTaskFee( - LibCommonUtils.TaskState _state, + CommonUtils.TaskState _state, uint64 _expiryTime, uint128 _maxGasAmount, uint64 _potentialFeeTimeframe, @@ -89,7 +89,7 @@ interface IAutomationRegistry { // State updating functions function removeTask(uint64 _taskIndex, bool _removeFromSysReg) external; - function updateTaskState(uint64 _taskIndex, LibCommonUtils.TaskState _taskState) external; + function updateTaskState(uint64 _taskIndex, CommonUtils.TaskState _taskState) external; function updateRegistryState( uint128 _sysGasCommittedForNextCycle, uint128 _gasCommittedForNextCycle, diff --git a/solidity/supra_contracts/src/LibController.sol b/solidity/supra_contracts/src/LibController.sol index 74c5531c8a..d9a2d94df2 100644 --- a/solidity/supra_contracts/src/LibController.sol +++ b/solidity/supra_contracts/src/LibController.sol @@ -3,7 +3,7 @@ pragma solidity 0.8.27; import {EnumerableSet} from "../lib/openzeppelin-contracts/contracts/utils/structs/EnumerableSet.sol"; -import {LibCommonUtils} from "./LibCommonUtils.sol"; +import {CommonUtils} from "./CommonUtils.sol"; // Helper library used by AutomationController. library LibController { @@ -38,7 +38,7 @@ library LibController { uint64 _index, uint64 _startTime, uint64 _durationSecs, - LibCommonUtils.CycleState _cycleState + CommonUtils.CycleState _cycleState ) internal { _cycleInfo.index_startTime_durationSecs_state_ifTransitionStateExists = (uint256(_index) << 192) | @@ -60,8 +60,8 @@ library LibController { return uint64(cycle.index_startTime_durationSecs_state_ifTransitionStateExists >> 64); } - function state(AutomationCycleInfo storage cycle) internal view returns (LibCommonUtils.CycleState) { - return LibCommonUtils.CycleState(uint8(cycle.index_startTime_durationSecs_state_ifTransitionStateExists >> 56)); + function state(AutomationCycleInfo storage cycle) internal view returns (CommonUtils.CycleState) { + return CommonUtils.CycleState(uint8(cycle.index_startTime_durationSecs_state_ifTransitionStateExists >> 56)); } function ifTransitionStateExists(AutomationCycleInfo storage cycle) internal view returns (bool) { diff --git a/solidity/supra_contracts/src/LibRegistry.sol b/solidity/supra_contracts/src/LibRegistry.sol index 658dd8aa7c..4b7f91098f 100644 --- a/solidity/supra_contracts/src/LibRegistry.sol +++ b/solidity/supra_contracts/src/LibRegistry.sol @@ -3,7 +3,7 @@ pragma solidity 0.8.27; import {EnumerableSet} from "../lib/openzeppelin-contracts/contracts/utils/structs/EnumerableSet.sol"; -import {LibCommonUtils} from "./LibCommonUtils.sol"; +import {CommonUtils} from "./CommonUtils.sol"; // Helper library used by AutomationRegistry. library LibRegistry { @@ -322,8 +322,8 @@ library LibRegistry { uint64 _expiryTime, uint64 _priority, address _owner, - LibCommonUtils.TaskType _type, - LibCommonUtils.TaskState _state, + CommonUtils.TaskType _type, + CommonUtils.TaskState _state, bytes memory _payloadTx, bytes[] memory _auxData ) internal pure returns (TaskMetadata memory t) { @@ -434,12 +434,12 @@ library LibRegistry { return address(uint160(t.owner_type_state >> 96)); } - function taskType(TaskMetadata storage t) internal view returns (LibCommonUtils.TaskType) { - return LibCommonUtils.TaskType(uint8(t.owner_type_state >> 88)); + 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 (LibCommonUtils.TaskState) { - return LibCommonUtils.TaskState(uint8(t.owner_type_state >> 80)); + 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 { diff --git a/solidity/supra_contracts/test/AutomationController.t.sol b/solidity/supra_contracts/test/AutomationController.t.sol index 550fbd69eb..15fa100b30 100644 --- a/solidity/supra_contracts/test/AutomationController.t.sol +++ b/solidity/supra_contracts/test/AutomationController.t.sol @@ -9,7 +9,7 @@ import {AutomationRegistry} from "../src/AutomationRegistry.sol"; import {AutomationController} from "../src/AutomationController.sol"; import {IAutomationController} from "../src/IAutomationController.sol"; import {ERC20Supra} from "../src/ERC20Supra.sol"; -import {LibCommonUtils} from "../src/LibCommonUtils.sol"; +import {CommonUtils} from "../src/CommonUtils.sol"; contract AutomationControllerTest is Test { AutomationRegistry registry; // AutomationRegistry instance on proxy address @@ -17,7 +17,7 @@ contract AutomationControllerTest is Test { ERC20Supra erc20Supra; // ERC20Supra contract address admin = address(0xA11CE); - address vmSigner = address(0x5355500000000000000000000000000000000000); + address vmSigner = address(0x53555000); address alice = address(0x123); address bob = address(0x456); @@ -85,11 +85,11 @@ contract AutomationControllerTest is Test { assertEq(controller.owner(), admin); assertEq(address(controller.registry()), address(registry)); - (uint64 index, uint64 startTime, uint64 durationSecs, LibCommonUtils.CycleState state) = controller.getCycleInfo(); + (uint64 index, uint64 startTime, uint64 durationSecs, CommonUtils.CycleState state) = controller.getCycleInfo(); assertEq(index, 1); assertEq(startTime, block.timestamp); assertEq(durationSecs, registry.cycleDurationSecs()); - assertEq(uint8(state), uint8(LibCommonUtils.CycleState.STARTED)); + assertEq(uint8(state), uint8(CommonUtils.CycleState.STARTED)); } /// @dev Test to ensure initialize reverts if reinitialized. diff --git a/solidity/supra_contracts/test/AutomationRegistry.t.sol b/solidity/supra_contracts/test/AutomationRegistry.t.sol index 507c83d786..67185f1983 100644 --- a/solidity/supra_contracts/test/AutomationRegistry.t.sol +++ b/solidity/supra_contracts/test/AutomationRegistry.t.sol @@ -10,7 +10,7 @@ import {AutomationController} from "../src/AutomationController.sol"; import {IAutomationRegistry} from "../src/IAutomationRegistry.sol"; import {ERC20Supra} from "../src/ERC20Supra.sol"; import {LibRegistry} from "../src/LibRegistry.sol"; -import {LibCommonUtils} from "../src/LibCommonUtils.sol"; +import {CommonUtils} from "../src/CommonUtils.sol"; contract AutomationRegistryTest is Test { AutomationRegistry impl; // implementation logic contract @@ -19,7 +19,7 @@ contract AutomationRegistryTest is Test { ERC20Supra erc20Supra; // ERC20Supra contract address admin = address(0xA11CE); - address vmSigner = address(0x5355500000000000000000000000000000000000); + address vmSigner = address(0x53555000); address alice = address(0x123); address bob = address(0x456); @@ -1272,7 +1272,7 @@ contract AutomationRegistryTest is Test { ); vm.stopPrank(); - LibCommonUtils.TaskDetails memory taskMetadata = registry.getTaskDetails(0); + CommonUtils.TaskDetails memory taskMetadata = registry.getTaskDetails(0); assertTrue(registry.ifTaskExists(0)); assertEq(registry.totalTasks(), 1); assertEq(registry.getNextTaskIndex(), 1); @@ -1307,7 +1307,7 @@ contract AutomationRegistryTest is Test { erc20Supra.deposit{value: 5 ether}(); erc20Supra.approve(address(registry), type(uint256).max); - LibCommonUtils.TaskDetails memory taskMetadata = LibCommonUtils.TaskDetails( + CommonUtils.TaskDetails memory taskMetadata = CommonUtils.TaskDetails( 1_000_000, 10 gwei, 0.5 ether, @@ -1317,8 +1317,8 @@ contract AutomationRegistryTest is Test { uint64(block.timestamp), uint64(block.timestamp + 2250), 0, - LibCommonUtils.TaskType.UST, - LibCommonUtils.TaskState.PENDING, + CommonUtils.TaskType.UST, + CommonUtils.TaskState.PENDING, alice, payload, auxData @@ -1491,7 +1491,7 @@ contract AutomationRegistryTest is Test { auxData // aux data ); - LibCommonUtils.TaskDetails memory taskMetadata = registry.getTaskDetails(0); + CommonUtils.TaskDetails memory taskMetadata = registry.getTaskDetails(0); assertTrue(registry.ifTaskExists(0)); assertTrue(registry.ifSysTaskExists(0)); assertEq(registry.totalTasks(), 1); @@ -1523,7 +1523,7 @@ contract AutomationRegistryTest is Test { bytes[] memory auxData; bytes memory payload = createPayload(0, address(erc20Supra)); - LibCommonUtils.TaskDetails memory taskMetadata = LibCommonUtils.TaskDetails( + CommonUtils.TaskDetails memory taskMetadata = CommonUtils.TaskDetails( 1_000_000, 0, 0, @@ -1533,8 +1533,8 @@ contract AutomationRegistryTest is Test { uint64(block.timestamp), uint64(block.timestamp + 2250), 2, - LibCommonUtils.TaskType.GST, - LibCommonUtils.TaskState.PENDING, + CommonUtils.TaskType.GST, + CommonUtils.TaskState.PENDING, bob, payload, auxData diff --git a/solidity/supra_contracts/test/BlockMeta.t.sol b/solidity/supra_contracts/test/BlockMeta.t.sol index adf45145b1..fed9ca3b7b 100644 --- a/solidity/supra_contracts/test/BlockMeta.t.sol +++ b/solidity/supra_contracts/test/BlockMeta.t.sol @@ -86,7 +86,7 @@ contract BlockMetaTest is Test { function testBlockPrologue() public { testEntryRegistration(); - vm.prank(address(0x5355500000000000000000000000000000000000)); + vm.prank(address(0x53555000)); blockMeta.blockPrologue(); assertEq(counter.counter(), 1); } From e0a69f23d0391b710f775638c42e06a0279af6f5 Mon Sep 17 00:00:00 2001 From: Udit Yadav Date: Fri, 2 Jan 2026 17:35:31 +0530 Subject: [PATCH 13/31] fixed test cases --- .../src/AutomationController.sol | 2 +- .../src/AutomationRegistry.sol | 15 +++++- .../src/IAutomationRegistry.sol | 1 + .../test/AutomationRegistry.t.sol | 53 ++++++++++++++----- 4 files changed, 56 insertions(+), 15 deletions(-) diff --git a/solidity/supra_contracts/src/AutomationController.sol b/solidity/supra_contracts/src/AutomationController.sol index 8655eb7612..f82e1f10d0 100644 --- a/solidity/supra_contracts/src/AutomationController.sol +++ b/solidity/supra_contracts/src/AutomationController.sol @@ -413,7 +413,7 @@ contract AutomationController is IAutomationController, Ownable2StepUpgradeable, } else { if(_fee != 0) { // Charge the fee - bool sent = IERC20(erc20Supra).transferFrom(_owner, address(registry), _fee); + (bool sent, ) = address(registry).call(abi.encodeCall(IAutomationRegistry.chargeFees, (_owner, _fee))); if (!sent) { revert TransferFailed(); } fees = _fee; diff --git a/solidity/supra_contracts/src/AutomationRegistry.sol b/solidity/supra_contracts/src/AutomationRegistry.sol index ef75bde010..01484ed10a 100644 --- a/solidity/supra_contracts/src/AutomationRegistry.sol +++ b/solidity/supra_contracts/src/AutomationRegistry.sol @@ -301,8 +301,7 @@ contract AutomationRegistry is IAutomationRegistry, Ownable2StepUpgradeable, UUP deposit.totalDepositedAutomationFees += _automationFeeCapForCycle; uint128 fee = regConfig.flatRegistrationFeeWei() + _automationFeeCapForCycle; - bool sent = IERC20(regConfig.erc20Supra).transferFrom(msg.sender, address(this), fee); - if (!sent) { revert TransferFailed(); } + _chargeFees(msg.sender, fee); emit TaskRegistered(taskIndex, msg.sender, regConfig.flatRegistrationFeeWei(), _automationFeeCapForCycle, regState.tasks[taskIndex].getTaskDetails()); } @@ -986,6 +985,12 @@ contract AutomationRegistry is IAutomationRegistry, Ownable2StepUpgradeable, UUP if(msg.sender != regConfig.automationController()) { revert CallerNotController(); } } + /// @notice Helper function to charge fees from the user. + function _chargeFees(address _from, uint256 _amount) private { + bool sent = IERC20(regConfig.erc20Supra).transferFrom(_from, address(this), _amount); + if(!sent) { revert TransferFailed(); } + } + // ::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::: ADMIN FUNCTIONS ::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::: /// @notice Function to update the registry configuration buffer. @@ -1165,6 +1170,12 @@ contract AutomationRegistry is IAutomationRegistry, Ownable2StepUpgradeable, UUP } // ::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::: CONTROLLER FUNCTIONS ::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::: + + /// @notice Helper function to charge fees from the user. + function chargeFees(address _from, uint256 _amount) external { + onlyController(); + _chargeFees(_from, _amount); + } /// @notice Internally calls _removeTask, reverts if caller is not AutomationController. function removeTask(uint64 _taskIndex, bool _removeFromSysReg) external { diff --git a/solidity/supra_contracts/src/IAutomationRegistry.sol b/solidity/supra_contracts/src/IAutomationRegistry.sol index d8c3eb0709..a84f3b6e93 100644 --- a/solidity/supra_contracts/src/IAutomationRegistry.sol +++ b/solidity/supra_contracts/src/IAutomationRegistry.sol @@ -113,4 +113,5 @@ interface IAutomationRegistry { uint64 _currentTime, uint256 _cycleLockedFees ) external returns (uint256); + function chargeFees(address _from, uint256 _amount) external; } diff --git a/solidity/supra_contracts/test/AutomationRegistry.t.sol b/solidity/supra_contracts/test/AutomationRegistry.t.sol index 9e57b55264..c7ad389595 100644 --- a/solidity/supra_contracts/test/AutomationRegistry.t.sol +++ b/solidity/supra_contracts/test/AutomationRegistry.t.sol @@ -1278,8 +1278,8 @@ contract AutomationRegistryTest is Test { assertEq(registry.getNextTaskIndex(), 1); assertEq(registry.getGasCommittedForNextCycle(), 1_000_000); assertEq(registry.getTotalDepositedAutomationFees(), 0.5 ether); - assertEq(erc20Supra.balanceOf(address(registry)), 0.002 ether + 0.5 ether); - assertEq(erc20Supra.balanceOf(alice), 4.5 ether - 0.002 ether); + assertEq(erc20Supra.balanceOf(address(registry)), 0.502 ether); + assertEq(erc20Supra.balanceOf(alice), 4.498 ether); assertEq(taskMetadata.maxGasAmount, 1_000_000); assertEq(taskMetadata.gasPriceCap, 10 gwei); @@ -1614,8 +1614,8 @@ contract AutomationRegistryTest is Test { assertEq(registry.totalTasks(), 0); assertEq(registry.getGasCommittedForNextCycle(), 0); assertEq(registry.getTotalDepositedAutomationFees(), 0); - assertEq(erc20Supra.balanceOf(address(registry)), 0.002 ether + 0.25 ether); - assertEq(erc20Supra.balanceOf(alice), 4.75 ether - 0.002 ether); + assertEq(erc20Supra.balanceOf(address(registry)), 0.252 ether); + assertEq(erc20Supra.balanceOf(alice), 4.748 ether); } /// @dev Test to ensure 'cancelTask' emits event 'TaskCancelled'. @@ -1775,12 +1775,21 @@ contract AutomationRegistryTest is Test { /// @dev Test to ensure 'stopTasks' stops the input UST tasks. function testStopTasks() public { - testSetAutomationController(); testRegister(); + address controller = registry.getAutomationController(); uint64[] memory taskIndexes = new uint64[](1); taskIndexes[0] = 0; + vm.warp(2002); + vm.startPrank(vmSigner, vmSigner); + AutomationController(controller).monitorCycleEnd(); + AutomationController(controller).processTasks(2, taskIndexes); + vm.stopPrank(); + + assertEq(erc20Supra.balanceOf(address(registry)), 0.702 ether); + assertEq(erc20Supra.balanceOf(alice), 4.298 ether); + vm.prank(alice); registry.stopTasks(taskIndexes); @@ -1788,20 +1797,26 @@ contract AutomationRegistryTest is Test { assertEq(registry.totalTasks(), 0); assertEq(registry.getGasCommittedForNextCycle(), 0); assertEq(registry.getTotalDepositedAutomationFees(), 0); - assertEq(erc20Supra.balanceOf(address(registry)), 0.002 ether + 0.25 ether); - assertEq(erc20Supra.balanceOf(alice), 4.75 ether - 0.002 ether); + assertEq(erc20Supra.balanceOf(address(registry)), 0.18955 ether); + assertEq(erc20Supra.balanceOf(alice), 4.81045 ether); } /// @dev Test to ensure 'stopTasks' emits event 'TasksStopped'. function testStopTasksEmitsEvent() public { - testSetAutomationController(); testRegister(); + address controller = registry.getAutomationController(); uint64[] memory taskIndexes = new uint64[](1); taskIndexes[0] = 0; + vm.warp(2002); + vm.startPrank(vmSigner, vmSigner); + AutomationController(controller).monitorCycleEnd(); + AutomationController(controller).processTasks(2, taskIndexes); + vm.stopPrank(); + LibRegistry.TaskStopped[] memory stoppedTasks = new LibRegistry.TaskStopped[](1); - stoppedTasks[0] = LibRegistry.TaskStopped(0, 0.25 ether, 0, keccak256("txHash")); + stoppedTasks[0] = LibRegistry.TaskStopped(0, 0.5 ether, 0.01245 ether, keccak256("txHash")); vm.expectEmit(true, true, false, false); emit AutomationRegistry.TasksStopped(stoppedTasks, alice); @@ -1882,12 +1897,19 @@ contract AutomationRegistryTest is Test { /// @dev Test to ensure 'stopSystemTasks' stops the input GST tasks. function testStopSystemTasks() public { - testSetAutomationController(); testRegisterSystemTask(); + address controller = registry.getAutomationController(); uint64[] memory taskIndexes = new uint64[](1); taskIndexes[0] = 0; + vm.warp(2002); + vm.prank(vmSigner, vmSigner); + AutomationController(controller).monitorCycleEnd(); + + vm.prank(vmSigner); + AutomationController(controller).processTasks(2, taskIndexes); + vm.prank(bob); registry.stopSystemTasks(taskIndexes); @@ -1895,17 +1917,24 @@ contract AutomationRegistryTest is Test { assertFalse(registry.ifSysTaskExists(0)); assertEq(registry.totalTasks(), 0); assertEq(registry.totalSystemTasks(), 0); - assertEq(registry.getSystemGasCommittedForNextCycle(), 0); + assertEq(registry.getSystemGasCommittedForNextCycle(), 1000000); } /// @dev Test to ensure 'stopSystemTasks' emits event 'TasksStopped'. function testStopSystemTasksEmitsEvent() public { - testSetAutomationController(); testRegisterSystemTask(); + address controller = registry.getAutomationController(); uint64[] memory taskIndexes = new uint64[](1); taskIndexes[0] = 0; + vm.warp(2002); + vm.prank(vmSigner, vmSigner); + AutomationController(controller).monitorCycleEnd(); + + vm.prank(vmSigner); + AutomationController(controller).processTasks(2, taskIndexes); + LibRegistry.TaskStopped[] memory stoppedTasks = new LibRegistry.TaskStopped[](1); stoppedTasks[0] = LibRegistry.TaskStopped(0, 0, 0, keccak256("txHash")); From d6a9259f3ef6b47f0d51f9e84a0edfabf7745430 Mon Sep 17 00:00:00 2001 From: Udit Yadav Date: Thu, 8 Jan 2026 12:14:20 +0530 Subject: [PATCH 14/31] separated the config and deposit logic from AutomationRegistry to AutomationCore updated scripts --- .../script/DeployAutomationRegistry.s.sol | 47 +- .../src/AutomationController.sol | 100 +- .../supra_contracts/src/AutomationCore.sol | 1013 ++++++++++++++++ .../src/AutomationRegistry.sol | 1031 ++--------------- solidity/supra_contracts/src/BlockMeta.sol | 5 +- solidity/supra_contracts/src/CommonUtils.sol | 10 + .../src/IAutomationController.sol | 5 +- .../supra_contracts/src/IAutomationCore.sol | 114 ++ .../src/IAutomationRegistry.sol | 66 +- solidity/supra_contracts/src/LibConfig.sol | 347 ++++++ solidity/supra_contracts/src/LibRegistry.sol | 333 ------ 11 files changed, 1696 insertions(+), 1375 deletions(-) create mode 100644 solidity/supra_contracts/src/AutomationCore.sol create mode 100644 solidity/supra_contracts/src/IAutomationCore.sol create mode 100644 solidity/supra_contracts/src/LibConfig.sol diff --git a/solidity/supra_contracts/script/DeployAutomationRegistry.s.sol b/solidity/supra_contracts/script/DeployAutomationRegistry.s.sol index cb2bb0fabf..32bc952e18 100644 --- a/solidity/supra_contracts/script/DeployAutomationRegistry.s.sol +++ b/solidity/supra_contracts/script/DeployAutomationRegistry.s.sol @@ -2,6 +2,7 @@ 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 {ERC20Supra} from "../src/ERC20Supra.sol"; @@ -44,6 +45,10 @@ contract DeployAutomationRegistry is Script { ERC20Supra erc20Supra; // ERC20Supra contract + 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 @@ -59,12 +64,12 @@ contract DeployAutomationRegistry is Script { console.log("ERC20Supra deployed at: ", address(erc20Supra)); // --------------------------------------------------------------------- - // Deploy AutomationRegistry + // Deploy AutomationCore // --------------------------------------------------------------------- - registryImpl = new AutomationRegistry(); - console.log("AutomationRegistry implementation deployed at: ", address(registryImpl)); - bytes memory initData = abi.encodeCall( - AutomationRegistry.initialize, + coreImpl = new AutomationCore(); + console.log("AutomationCore implementation deployed at: ", address(coreImpl)); + bytes memory coreInitData = abi.encodeCall( + AutomationCore.initialize, ( taskDurationCapSecs, // taskDurationCapSecs registryMaxGasCap, // registryMaxGasCap @@ -82,7 +87,18 @@ contract DeployAutomationRegistry is Script { address(erc20Supra) // ERC20Supra address ) ); - registryProxy = new ERC1967Proxy(address(registryImpl), initData); + 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)); @@ -91,14 +107,27 @@ contract DeployAutomationRegistry is Script { // --------------------------------------------------------------------- controllerImpl = new AutomationController(); console.log("AutomationController implementation deployed at: ", address(controllerImpl)); - bytes memory controllerInitData = abi.encodeCall(AutomationController.initialize,(address(registry))); + + bytes memory controllerInitData = abi.encodeCall( + AutomationController.initialize, + ( + address(automationCore), + address(registry) + ) + ); 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/src/AutomationController.sol b/solidity/supra_contracts/src/AutomationController.sol index f82e1f10d0..fb678745e4 100644 --- a/solidity/supra_contracts/src/AutomationController.sol +++ b/solidity/supra_contracts/src/AutomationController.sol @@ -6,6 +6,7 @@ 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"; @@ -23,6 +24,7 @@ contract AutomationController is IAutomationController, Ownable2StepUpgradeable, /// @dev State variables LibController.AutomationCycleInfo cycleInfo; IAutomationRegistry public registry; + IAutomationCore public automationCore; // ::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::: EVENTS ::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::: @@ -72,6 +74,10 @@ contract AutomationController is IAutomationController, Ownable2StepUpgradeable, /// @notice Emitted when the registry smart contract address is updated. event RegistryUpdated(address indexed oldRegistryAddress, address indexed newRegistryAddress); + + /// @notice Emitted when the AutomationCore contract address is updated. + event AutomationCoreUpdated(address indexed oldAutomationCore, address indexed newAutomationCore); + // :::::::::::::::::::::::::::::::::::::::::::::::::::::::::: CONSTRUCTOR AND INITIALIZER :::::::::::::::::::::::::::::::::::::::::::::::::::::::::: @@ -81,19 +87,21 @@ contract AutomationController is IAutomationController, Ownable2StepUpgradeable, } /// @notice Initializes the configuration parameters of the contract, can only be called once. - /// @param _registry Address of the registry smart contract. - function initialize(address _registry) public initializer { - if (_registry == address(0)) { revert AddressCannotBeZero(); } - if (!_registry.isContract()) { revert AddressCannotBeEOA(); } + /// @param _automationCore Address of the AutomationCore smart contract. + /// @param _registry Address of the AutomationRegistry smart contract. + function initialize(address _automationCore, address _registry) public initializer { + _automationCore.validateContractAddress(); + _registry.validateContractAddress(); + automationCore = IAutomationCore(_automationCore); registry = IAutomationRegistry(_registry); - (CommonUtils.CycleState state, uint64 cycleId) = registry.isAutomationEnabled() ? (CommonUtils.CycleState.STARTED, 1) : (CommonUtils.CycleState.READY, 0); + (CommonUtils.CycleState state, uint64 cycleId) = automationCore.isAutomationEnabled() ? (CommonUtils.CycleState.STARTED, 1) : (CommonUtils.CycleState.READY, 0); cycleInfo.initializeCycle( cycleId, uint64(block.timestamp), - registry.cycleDurationSecs(), + automationCore.cycleDurationSecs(), state ); @@ -106,7 +114,7 @@ contract AutomationController is IAutomationController, Ownable2StepUpgradeable, /// @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 != registry.getVmSigner()) { revert CallerNotVmSigner(); } + if (msg.sender != automationCore.getVmSigner()) { revert CallerNotVmSigner(); } CommonUtils.CycleState state = cycleInfo.state(); @@ -120,7 +128,7 @@ contract AutomationController is IAutomationController, Ownable2StepUpgradeable, /// @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 != registry.getVmSigner()) { revert CallerNotVmSigner(); } + if (tx.origin != automationCore.getVmSigner()) { revert CallerNotVmSigner(); } if(cycleInfo.state() != CommonUtils.CycleState.STARTED || cycleInfo.startTime() + cycleInfo.durationSecs() > block.timestamp) { return; @@ -191,10 +199,10 @@ contract AutomationController is IAutomationController, Ownable2StepUpgradeable, // Nothing to refund for GST tasks if(registry.checkTaskType(taskIndexes[i], CommonUtils.TaskType.UST)) { - (bool refunded, bytes memory data) = address(registry).call( + (bool refunded, bytes memory data) = address(automationCore).call( abi.encodeCall( - IAutomationRegistry.refundTaskFees, - (taskIndexes[i], currentTime, cycleLockedFees) + IAutomationCore.refundTaskFees, + (taskIndexes[i], currentTime, cycleLockedFees, cycleInfo.refundDuration(), cycleInfo.automationFeePerSec()) ) ); require(refunded, RefundFailed()); @@ -288,17 +296,13 @@ contract AutomationController is IAutomationController, Ownable2StepUpgradeable, require(updated, UpdateTaskStateFailed()); } else { // Active UST - - uint128 registryMaxGasCap = registry.getRegistryMaxGasCap(); - - uint128 fee = registry.calculateTaskFee( + uint128 fee = automationCore.calculateTaskFee( task.state, task.expiryTime, task.maxGasAmount, cycleInfo.newCycleDuration(), _currentTime, - cycleInfo.automationFeePerSec(), - registryMaxGasCap + cycleInfo.automationFeePerSec() ); // If the task reached this phase that means it is a valid active task for the new cycle. @@ -364,7 +368,7 @@ contract AutomationController is IAutomationController, Ownable2StepUpgradeable, // 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 erc20Supra = registry.erc20Supra(); + address erc20Supra = automationCore.erc20Supra(); bool isRemoved; uint128 gas; uint128 fees; @@ -391,9 +395,9 @@ contract AutomationController is IAutomationController, Ownable2StepUpgradeable, 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, ) = address(registry).call( + (bool unlocked, ) = address(automationCore).call( abi.encodeCall( - IAutomationRegistry.safeUnlockLockedDeposit, + IAutomationCore.safeUnlockLockedDeposit, (_taskIndex, _lockedFeeForNextCycle) ) ); @@ -413,7 +417,7 @@ contract AutomationController is IAutomationController, Ownable2StepUpgradeable, } else { if(_fee != 0) { // Charge the fee - (bool sent, ) = address(registry).call(abi.encodeCall(IAutomationRegistry.chargeFees, (_owner, _fee))); + (bool sent, ) = address(automationCore).call(abi.encodeCall(IAutomationCore.chargeFees, (_owner, _fee))); if (!sent) { revert TransferFailed(); } fees = _fee; @@ -445,7 +449,7 @@ contract AutomationController is IAutomationController, Ownable2StepUpgradeable, bool transitionFinalized = isTransitionFinalized(); if (transitionFinalized) { - if (!registry.isAutomationEnabled() && cycleInfo.state() == CommonUtils.CycleState.FINISHED) { + if (!automationCore.isAutomationEnabled() && cycleInfo.state() == CommonUtils.CycleState.FINISHED) { _tryMoveToSuspendedState(); } else { (bool updated, ) = address(registry).call( @@ -494,7 +498,7 @@ contract AutomationController is IAutomationController, Ownable2StepUpgradeable, require(updated, UpdateRegistryStateFailed()); // Check if automation is enabled - if (registry.isAutomationEnabled()) { + if (automationCore.isAutomationEnabled()) { // 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(); @@ -537,7 +541,7 @@ contract AutomationController is IAutomationController, Ownable2StepUpgradeable, cycleInfo.setRefundDuration(cycleEndTime - currentTime); cycleInfo.setNewCycleDuration(cycleDuration); - cycleInfo.setAutomationFeePerSec(registry.calculateAutomationFeeMultiplierForCurrentCycleInternal()); + cycleInfo.setAutomationFeePerSec(automationCore.calculateAutomationFeeMultiplierForCurrentCycleInternal()); cycleInfo.setGasCommittedForNewCycle(0); cycleInfo.setGasCommittedForNextCycle(0); cycleInfo.setSysGasCommittedForNextCycle(0); @@ -644,7 +648,7 @@ contract AutomationController is IAutomationController, Ownable2StepUpgradeable, /// @notice Helper function called when cycle end is identified. function onCycleEndInternal() private { - if (!registry.isAutomationEnabled()) { + if (!automationCore.isAutomationEnabled()) { _tryMoveToSuspendedState(); } else{ if(registry.totalTasks() == 0) { @@ -672,7 +676,7 @@ contract AutomationController is IAutomationController, Ownable2StepUpgradeable, // 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(registry.calculateAutomationFeeMultiplierForCommittedOccupancy(cycleInfo.gasCommittedForNewCycle())); + cycleInfo.setAutomationFeePerSec(automationCore.calculateAutomationFeeMultiplierForCommittedOccupancy(cycleInfo.gasCommittedForNewCycle())); updateCycleStateTo(CommonUtils.CycleState.FINISHED); } } @@ -680,23 +684,20 @@ contract AutomationController is IAutomationController, Ownable2StepUpgradeable, /// @notice Function to update the registry config structure with values extracted from the buffer, if the buffer exists. function _updateConfigFromBuffer() private { - if(registry.ifConfigBufferExists()) { - (bool sent, ) = address(registry).call(abi.encodeCall(IAutomationRegistry.applyPendingConfig, ())); - require(sent, ConfigUpdateFailed()); - - uint64 cycleDurationSecs = registry.getBufferCycleDurationSecs(); - // Check if transition state exists - if (cycleInfo.ifTransitionStateExists()) { - cycleInfo.setNewCycleDuration(cycleDurationSecs); - } else { - cycleInfo.setDurationSecs(cycleDurationSecs); - } - } + (bool sent, ) = address(automationCore).call(abi.encodeCall(IAutomationCore.applyPendingConfig, ())); + require(sent, ConfigUpdateFailed()); } - function updateConfigFromBuffer() external { - if (msg.sender != address(registry)) { revert CallerNotRegistry(); } - _updateConfigFromBuffer(); + /// @notice Helper function to update cycle duration. + function updateCyleDuration(uint64 _cycleDurationSecs) external { + if (msg.sender != address(automationCore)) { revert CallerNotAutomationCore(); } + + // Check if transition state exists + if (cycleInfo.ifTransitionStateExists()) { + cycleInfo.setNewCycleDuration(_cycleDurationSecs); + } else { + cycleInfo.setDurationSecs(_cycleDurationSecs); + } } /// @notice Checks if the cycle transition is finalized. @@ -730,15 +731,26 @@ contract AutomationController is IAutomationController, Ownable2StepUpgradeable, /// @notice Function to update the registry smart contract address. /// @param _registry Address of the registry smart contract. function setRegistry(address _registry) external onlyOwner { - if (_registry == address(0)) { revert AddressCannotBeZero(); } - if (!_registry.isContract()) { revert AddressCannotBeEOA(); } - + _registry.validateContractAddress(); + address oldRegistry = address(registry); registry = IAutomationRegistry(_registry); emit RegistryUpdated(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 = address(automationCore); + automationCore = IAutomationCore(_automationCore); + + emit AutomationCoreUpdated(oldAutomationCore, _automationCore); + } + + // ::::::::::::::::::::::::::::::::::::::::::::::::::::::::: UPGRADEABILITY FUNCTIONS ::::::::::::::::::::::::::::::::::::::::::::::::::::::::: /// @notice Helper function that reverts when 'msg.sender' is not authorized to upgrade the contract. diff --git a/solidity/supra_contracts/src/AutomationCore.sol b/solidity/supra_contracts/src/AutomationCore.sol new file mode 100644 index 0000000000..f750e28fec --- /dev/null +++ b/solidity/supra_contracts/src/AutomationCore.sol @@ -0,0 +1,1013 @@ +// 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; + LibConfig.Deposit deposit; + + // ::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::: 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 automation is enabled. + event AutomationEnabled(bool indexed status); + + /// @notice Emitted when automation is disabled. + event AutomationDisabled(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 cold wallet address is updated. + event ColdWalletUpdated(address indexed oldColdWallet, address indexed newColdWallet); + + /// @notice Emitted when the registry fees is withdrawn by the admin. + event RegistryFeeWithdrawn(address indexed coldWallet, 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, + 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, bytes32 _txHash) private view { + ( , address payloadTarget, , ) = abi.decode(_payloadTx, (uint128, address, bytes, LibConfig.AccessListEntry[])); + payloadTarget.validateContractAddress(); + + if(_maxGasAmount == 0) { revert InvalidMaxGasAmount(); } + if(_txHash == bytes32(0)) { revert InvalidTxHash(); } + } + + /// @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 + ) public view returns (uint128) { + ( , , uint64 durationSecs, ) = IAutomationController(regConfig.automationController()).getCycleInfo(); + + uint128 totalCommittedGas = _taskOccupancy + _committedOccupancy; + + uint128 automationFeePerSec = calculateAutomationFeeMultiplierForCycle(totalCommittedGas, regConfig.nextCycleRegistryMaxGasCap()); + + if(automationFeePerSec == 0) return 0; + + 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 = deposit.totalDepositedAutomationFees; + + if(totalDeposited >= _lockedDeposit) { + deposit.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); + } + + /// @notice Helper function to update the registry configuration. + function _applyPendingConfig() private { + if (configBuffer.ifExists) { + regConfig.config = configBuffer.pendingConfig; + configBuffer.ifExists = false; + + IAutomationController(regConfig.automationController()).updateCyleDuration(configBuffer.pendingConfig.cycleDurationSecs()); + } + } + + // ::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::: CONTROLLER FUNCTIONS ::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::: + + /// @notice Function to update the registry configuration, reverts if caller is not AutomationController. + function applyPendingConfig() external { + onlyController(); + _applyPendingConfig(); + } + + /// @notice Internally calls _calculateTaskFee, reverts if caller is not AutomationController. + function calculateTaskFee( + CommonUtils.TaskState _state, + uint64 _expiryTime, + uint128 _maxGasAmount, + uint64 _potentialFeeTimeframe, + uint64 _currentTime, + uint128 _automationFeePerSec + ) external view returns (uint128) { + onlyController(); + + 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 _taskIndex, + uint64 _currentTime, + uint256 _cycleLockedFees, + uint64 _refundDuration, + uint128 _automationFeePerSec + ) external returns (uint256) { + onlyController(); + if(IAutomationRegistry(regConfig.registry).checkTaskType(_taskIndex, CommonUtils.TaskType.GST)) { revert RegisteredTaskInvalidType(); } + + CommonUtils.TaskDetails memory task = IAutomationRegistry(regConfig.registry).getTaskDetails(_taskIndex); + + // 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( + _taskIndex, + task.owner, + _cycleLockedFees, + uint64(_refundFee) + ); + _cycleLockedFees = remainingCycleLockedFees; + } + + _safeDepositRefund( + _taskIndex, + task.owner, + task.lockedFeeForNextCycle, + task.lockedFeeForNextCycle + ); + + return _cycleLockedFees; + } + + function calculateAutomationFeeMultiplierForCurrentCycleInternal() external view returns (uint128) { + onlyController(); + // Compute the automation fee multiplier for this cycle + uint128 gasCommittedForThisCycle = IAutomationRegistry(regConfig.registry).getGasCommittedForCurrentCycle(); + return calculateAutomationFeeMultiplierForCycle( + 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) { + onlyController(); + // Compute the automation fee multiplier for cycle + return calculateAutomationFeeMultiplierForCycle( + _totalCommittedMaxGas, + regConfig.registryMaxGasCap() + ); + } + + // :::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::: REGISTRY FUNCTIONS ::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::: + + /// @notice Helper function to perform validation while registering a task. + function validateRegistration( + uint256 _totalTasks, + uint8 _inputType, + uint64 _regTime, + uint64 _expiryTime, + CommonUtils.TaskType _taskType, + bytes memory _payloadTx, + uint128 _maxGasAmount, + bytes32 _txHash, + uint128 _gasCommittedForNextCycle, + uint128 _gasPriceCap, + uint128 _automationFeeCapForCycle + ) external view { + onlyRegistry(); + + // Check if automation and registration is enabled + if (!regConfig.automationEnabled()) { revert AutomationNotEnabled(); } + if (!regConfig.registrationEnabled()) { revert RegistrationDisabled(); } + + if(_inputType != uint8(_taskType)) { revert InvalidTaskType(); } + + ( , uint64 startTime, uint64 durationSecs, CommonUtils.CycleState state) = IAutomationController(regConfig.automationController()).getCycleInfo(); + if(state != CommonUtils.CycleState.STARTED) { revert CycleTransitionInProgress(); } + + uint128 nextCycleRegistryMaxGasCap; + uint64 taskDurationCap; + if (_taskType == CommonUtils.TaskType.UST) { + if(_totalTasks >= regConfig.taskCapacity()) { revert TaskCapacityReached(); } + if(_gasPriceCap == 0) { revert InvalidGasPriceCap(); } + + uint128 estimatedAutomationFeeForCycle = estimateAutomationFeeWithCommittedOccupancyInternal(_maxGasAmount, _gasCommittedForNextCycle); + if(_automationFeeCapForCycle < estimatedAutomationFeeForCycle) { revert InsufficientFeeCapForCycle(); } + + taskDurationCap = regConfig.taskDurationCapSecs(); + nextCycleRegistryMaxGasCap = regConfig.nextCycleRegistryMaxGasCap(); + } else { + if(_totalTasks >= regConfig.sysTaskCapacity()) { revert TaskCapacityReached(); } + + taskDurationCap = regConfig.sysTaskDurationCapSecs(); + nextCycleRegistryMaxGasCap = regConfig.nextCycleSysRegistryMaxGasCap(); + } + + validateTaskDuration(_regTime, _expiryTime, taskDurationCap, startTime + durationSecs); + validateInputs(_payloadTx, _maxGasAmount, _txHash); + + uint128 gasCommitted = _maxGasAmount + _gasCommittedForNextCycle; + if(gasCommitted > nextCycleRegistryMaxGasCap) { revert GasCommittedExceedsMaxGasCap(); } + } + + /// @notice Helper function to increment the total deposited automation fees. + function incTotalDepositedAutomationFees(uint256 _amount) external { + onlyRegistry(); + deposit.totalDepositedAutomationFees += _amount; + } + + /// @notice Internally calls _refund, reverts if caller is not AutomationRegistry. + function refund(address _to, uint128 _amount) external { + onlyRegistry(); + _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, + uint128 _gasCommittedForThisCycle, + uint64 _expiryTime, + uint128 _maxGasAmount, + uint64 _residualInterval, + uint64 _currentTime, + uint128 _lockedFeeForNextCycle, + uint256 _cycleLockedFees + ) external returns (uint256, uint128, uint128) { + onlyRegistry(); + + uint128 cycleFeeRefund; + uint128 depositRefund; + + if(_taskState != CommonUtils.TaskState.PENDING) { + // Compute the automation fee multiplier for cycle + uint128 automationFeePerSec = calculateAutomationFeeMultiplierForCycle(_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 = _lockedFeeForNextCycle; + } else { + cycleFeeRefund = 0; + depositRefund = _lockedFeeForNextCycle / REFUND_FRACTION; + } + + bool result = _safeUnlockLockedDeposit(_taskIndex, _lockedFeeForNextCycle); + if(!result) { revert ErrorDepositRefund(); } + + (bool hasLockedFee, uint256 remainingCycleLockedFees ) = safeUnlockLockedCycleFee(_cycleLockedFees, uint64(cycleFeeRefund), _taskIndex); + if(!hasLockedFee) { revert ErrorCycleFeeRefund(); } + + return (remainingCycleLockedFees, 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 + ); + + uint128 gasCommittedForNextCycle = IAutomationRegistry(regConfig.registry).getGasCommittedForNextCycle(); + uint128 systemGasCommittedForNextCycle = IAutomationRegistry(regConfig.registry).getSystemGasCommittedForNextCycle(); + + if(gasCommittedForNextCycle > _registryMaxGasCap) { revert UnacceptableRegistryMaxGasCap(); } + if(systemGasCommittedForNextCycle > _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 enable the automation. + function enableAutomation() external onlyOwner { + if(regConfig.automationEnabled()) { revert AlreadyEnabled(); } + + IAutomationController controller = IAutomationController(regConfig.automationController()); + ( , , , CommonUtils.CycleState state) = controller.getCycleInfo(); + regConfig.setAutomationEnabled(true); + + if (state == CommonUtils.CycleState.READY) { + controller.moveToStartedState(); + _applyPendingConfig(); + } + + emit AutomationEnabled(regConfig.automationEnabled()); + } + + /// @notice Function to disable the automation. + function disableAutomation() external onlyOwner { + if(!regConfig.automationEnabled()) { revert AlreadyDisabled(); } + + IAutomationController controller = IAutomationController(regConfig.automationController()); + ( , , , CommonUtils.CycleState state) = controller.getCycleInfo(); + + regConfig.setAutomationEnabled(false); + if (state == CommonUtils.CycleState.FINISHED && !controller.isTransitionInProgress()) { + controller.tryMoveToSuspendedState(); + } + + emit AutomationDisabled(regConfig.automationEnabled()); + } + + /// @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. + function withdrawFees(uint256 _amount) external onlyOwner { + address coldWallet = deposit.coldWallet; + if(coldWallet == address(0)) { revert ColdWalletNotSet(); } + uint256 balance = IERC20(regConfig.erc20Supra).balanceOf(address(this)); + + if(balance < _amount) { revert InsufficientBalance(); } + + uint256 cycleLockedFees = IAutomationRegistry(regConfig.registry).getCycleLockedFees(); + if(balance - _amount < cycleLockedFees + deposit.totalDepositedAutomationFees) { revert RequestExceedsLockedBalance(); } + + bool sent = IERC20(regConfig.erc20Supra).transfer(coldWallet, _amount); + if(!sent) { revert TransferFailed(); } + + emit RegistryFeeWithdrawn(coldWallet, _amount); + } + + /// @notice Function to update the cold wallet address. + /// @param _coldWallet Address for the new cold wallet. + function setColdWallet(address _coldWallet) external onlyOwner { + if(_coldWallet == address(0)) { revert AddressCannotBeZero(); } + + address oldColdWallet = deposit.coldWallet; + deposit.coldWallet = _coldWallet; + + emit ColdWalletUpdated(oldColdWallet, _coldWallet); + } + + // :::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::: 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 automation is enabled. + function isAutomationEnabled() external view returns (bool) { + return regConfig.automationEnabled(); + } + + /// @notice Returns if task registration is enabled. + function isRegistrationEnabled() external view returns (bool) { + return regConfig.registrationEnabled(); + } + + /// @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 cold wallet address. + function getColdWallet() external view returns (address) { + return deposit.coldWallet; + } + + /// @notice Returns the total amount of automation fees deposited. + function getTotalDepositedAutomationFees() external view returns (uint256) { + return deposit.totalDepositedAutomationFees; + } + + // ::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::: 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 index 01484ed10a..1e32690159 100644 --- a/solidity/supra_contracts/src/AutomationRegistry.sol +++ b/solidity/supra_contracts/src/AutomationRegistry.sol @@ -5,6 +5,7 @@ import {EnumerableSet} from "../lib/openzeppelin-contracts/contracts/utils/struc 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"; @@ -16,12 +17,6 @@ contract AutomationRegistry is IAutomationRegistry, Ownable2StepUpgradeable, UUP using CommonUtils for *; using LibRegistry for *; - /// @dev Constant for 10^8 - uint256 constant DECIMAL = 100_000_000; - - /// @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; @@ -29,17 +24,12 @@ contract AutomationRegistry is IAutomationRegistry, Ownable2StepUpgradeable, UUP /// @dev Defines the cycle state, used to update the registry. uint8 constant SUSPENDED = 0; uint8 constant FINISHED = 1; - - /// @dev Constants describing REFUND TYPE - uint8 constant DEPOSIT_CYCLE_FEE = 0; - uint8 constant CYCLE_FEE = 1; /// @dev State variables - LibRegistry.ConfigBuffer configBuffer; - LibRegistry.RegistryConfig regConfig; LibRegistry.RegistryState regState; LibRegistry.RegistryStateSystemTasks regSysState; - LibRegistry.Deposit deposit; + address public automationCore; + address public automationController; // ::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::: EVENTS ::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::: @@ -66,38 +56,12 @@ contract AutomationRegistry is IAutomationRegistry, Ownable2StepUpgradeable, UUP /// @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 the AutomationCore contract address is updated. + event AutomationCoreUpdated(address indexed oldAutomationCore, address indexed newAutomationCore); - /// @notice Emitted when task registration is disabled. - event TaskRegistrationDisabled(bool indexed status); - - /// @notice Emitted when automation is enabled. - event AutomationEnabled(bool indexed status); + /// @notice Emitted when the AutomationController contract address is updated. + event AutomationControllerUpdated(address indexed oldAutomationController, address indexed newAutomationController); - /// @notice Emitted when automation is disabled. - event AutomationDisabled(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 a new config is added. - event ConfigBufferUpdated( - LibRegistry.ConfigDetails indexed pendingConfig - ); - - /// @notice Emitted when the cold wallet address is updated. - event ColdWalletUpdated(address indexed oldColdWallet, address indexed newColdWallet); - - /// @notice Emitted when the automation controller smart contract address is updated. - event AutomationControllerUpdated(address indexed oldController, address indexed newController); - - /// @notice Emitted when the registry fees is withdrawn by the admin. - event RegistryFeeWithdrawn(address indexed coldWallet, uint256 indexed feesWithdrawn); - /// @notice Emitted when a task is cancelled. event TaskCancelled( uint64 indexed taskIndex, @@ -111,41 +75,6 @@ contract AutomationRegistry is IAutomationRegistry, Ownable2StepUpgradeable, UUP address indexed owner ); - /// @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 totalDepositedAutomationFees, - uint128 lockedDeposit - ); - - /// @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 when a deposit fee is refunded for an automation task. - event TaskDepositFeeRefund(uint64 indexed taskIndex, address owner, uint128 amount); - - /// @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 _refundType, - 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 - ); - // :::::::::::::::::::::::::::::::::::::::::::::::::::::::::: CONSTRUCTOR AND INITIALIZER :::::::::::::::::::::::::::::::::::::::::::::::::::::::::: /// @dev Disables the initialization for the implementation contract. @@ -153,78 +82,12 @@ contract AutomationRegistry is IAutomationRegistry, Ownable2StepUpgradeable, UUP _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(); - - if(_erc20Supra == address(0)) revert AddressCannotBeZero(); - if(!_erc20Supra.isContract()) revert AddressCannotBeEOA(); - - LibRegistry.Config memory config = LibRegistry.createConfig( - _registryMaxGasCap, - _sysRegistryMaxGasCap, - _automationBaseFeeWeiPerSec, - _flatRegistrationFeeWei, - _congestionBaseFeeWeiPerSec, - _taskDurationCapSecs, - _sysTaskDurationCapSecs, - _cycleDurationSecs, - _taskCapacity, - _sysTaskCapacity, - _congestionThresholdPercentage, - _congestionExponent - ); + /// @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(); - regConfig = LibRegistry.createRegistryConfig( - _registryMaxGasCap, - _sysRegistryMaxGasCap, - true, - true, - _vmSigner, - _erc20Supra, - config - ); + automationCore = _automationCore; __Ownable2Step_init(); __Ownable_init(msg.sender); @@ -253,27 +116,25 @@ contract AutomationRegistry is IAutomationRegistry, Ownable2StepUpgradeable, UUP uint8 _type, bytes[] memory _auxData ) external { - // Check if automation is enabled - if (!regConfig.automationEnabled()) { revert AutomationNotEnabled(); } - if(!regConfig.registrationEnabled()) { revert RegistrationDisabled(); } - - ( , uint64 startTime, uint64 durationSecs, CommonUtils.CycleState state) = IAutomationController(regConfig.automationController()).getCycleInfo(); - if(state != CommonUtils.CycleState.STARTED) { revert CycleNotStarted(); } - if(totalTasks() >= regConfig.taskCapacity()) { revert TaskCapacityReached(); } - if(_type != uint8(CommonUtils.TaskType.UST)) { revert InvalidTaskType(); } + uint128 flatRegistrationFeeWei = IAutomationCore(automationCore).flatRegistrationFeeWei(); uint64 regTime = uint64(block.timestamp); - validateTaskDuration(regTime, _expiryTime, CommonUtils.TaskType.UST, regConfig.taskDurationCapSecs(), regConfig.sysTaskDurationCapSecs(), startTime, durationSecs); - validateInputs(_payloadTx, _maxGasAmount, _txHash); - if(_gasPriceCap == 0) { revert InvalidGasPriceCap(); } - - uint128 gasCommitted = _maxGasAmount + regState.gasCommittedForNextCycle(); - if(gasCommitted > regConfig.nextCycleRegistryMaxGasCap()) { revert GasCommittedExceedsMaxGasCap(); } - - - uint128 estimatedAutomationFeeForCycle = estimateAutomationFeeWithCommittedOccupancyInternal(_maxGasAmount, regState.gasCommittedForNextCycle(), durationSecs); - if(_automationFeeCapForCycle < estimatedAutomationFeeForCycle) { revert InsufficientFeeCapForCycle(); } + uint128 gasCommittedForNextCycle = regState.gasCommittedForNextCycle(); + IAutomationCore(automationCore).validateRegistration( + totalTasks(), + _type, + regTime, + _expiryTime, + CommonUtils.TaskType.UST, + _payloadTx, + _maxGasAmount, + _txHash, + gasCommittedForNextCycle, + _gasPriceCap, + _automationFeeCapForCycle + ); + uint128 gasCommitted = _maxGasAmount + gasCommittedForNextCycle; regState.setGasCommittedForNextCycle(gasCommitted); uint64 taskIndex = regState.currentIndex; @@ -298,12 +159,11 @@ contract AutomationRegistry is IAutomationRegistry, Ownable2StepUpgradeable, UUP require(regState.taskIdList.add(taskIndex), TaskIndexNotUnique()); regState.currentIndex += 1; - deposit.totalDepositedAutomationFees += _automationFeeCapForCycle; - - uint128 fee = regConfig.flatRegistrationFeeWei() + _automationFeeCapForCycle; - _chargeFees(msg.sender, fee); + IAutomationCore(automationCore).incTotalDepositedAutomationFees(_automationFeeCapForCycle); + uint128 fee = flatRegistrationFeeWei + _automationFeeCapForCycle; + IAutomationCore(automationCore).chargeFees(msg.sender, fee); - emit TaskRegistered(taskIndex, msg.sender, regConfig.flatRegistrationFeeWei(), _automationFeeCapForCycle, regState.tasks[taskIndex].getTaskDetails()); + emit TaskRegistered(taskIndex, msg.sender, flatRegistrationFeeWei, _automationFeeCapForCycle, regState.tasks[taskIndex].getTaskDetails()); } /// @notice Function to register a system task. Reverts if caller is not authorized. @@ -323,23 +183,25 @@ contract AutomationRegistry is IAutomationRegistry, Ownable2StepUpgradeable, UUP uint8 _type, bytes[] memory _auxData ) external { - // Check if automation is enabled - if (!regConfig.automationEnabled()) { revert AutomationNotEnabled(); } - if(!regConfig.registrationEnabled()) { revert RegistrationDisabled(); } - - ( , uint64 startTime, uint64 durationSecs, CommonUtils.CycleState state) = IAutomationController(regConfig.automationController()).getCycleInfo(); - if(state != CommonUtils.CycleState.STARTED) { revert CycleNotStarted(); } - if(totalSystemTasks() >= regConfig.sysTaskCapacity()) { revert TaskCapacityReached(); } - if(!isAuthorizedSubmitter(msg.sender)) { revert UnauthorizedAccount(); } - if(_type != uint8(CommonUtils.TaskType.GST)) { revert InvalidTaskType(); } + if(!isAuthorizedSubmitter(msg.sender)) { revert UnauthorizedAccount(); } uint64 regTime = uint64(block.timestamp); - validateTaskDuration(regTime, _expiryTime, CommonUtils.TaskType.GST, regConfig.taskDurationCapSecs(), regConfig.sysTaskDurationCapSecs(), startTime, durationSecs); - validateInputs(_payloadTx, _maxGasAmount, _txHash); - - uint128 gasCommitted = _maxGasAmount + regSysState.gasCommittedForNextCycle(); - if(gasCommitted > regConfig.nextCycleSysRegistryMaxGasCap()) { revert GasCommittedExceedsMaxGasCap(); } - + uint128 gasCommittedForNextCycle = regSysState.gasCommittedForNextCycle(); + IAutomationCore(automationCore).validateRegistration( + totalSystemTasks(), + _type, + regTime, + _expiryTime, + CommonUtils.TaskType.GST, + _payloadTx, + _maxGasAmount, + _txHash, + gasCommittedForNextCycle, + 0, + 0 + ); + + uint128 gasCommitted = _maxGasAmount + gasCommittedForNextCycle; regSysState.setGasCommittedForNextCycle(gasCommitted); uint64 taskIndex = regState.currentIndex; @@ -382,9 +244,9 @@ contract AutomationRegistry is IAutomationRegistry, Ownable2StepUpgradeable, UUP uint64 _taskIndex ) external { // Check if automation is enabled - if (!regConfig.automationEnabled()) { revert AutomationNotEnabled(); } - - ( , uint64 startTime, uint64 durationSecs, CommonUtils.CycleState state) = IAutomationController(regConfig.automationController()).getCycleInfo(); + if (!IAutomationCore(automationCore).isAutomationEnabled()) { revert AutomationNotEnabled(); } + + ( , uint64 startTime, uint64 durationSecs, CommonUtils.CycleState state) = IAutomationController(automationController).getCycleInfo(); if(state != CommonUtils.CycleState.STARTED) { revert CycleTransitionInProgress(); } if(!ifTaskExists(_taskIndex)) { revert TaskDoesNotExist(); } @@ -398,7 +260,7 @@ contract AutomationRegistry is IAutomationRegistry, Ownable2StepUpgradeable, UUP 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 = safeDepositRefund( + bool result = IAutomationCore(automationCore).safeDepositRefund( _taskIndex, task.owner, task.lockedFeeForNextCycle / REFUND_FACTOR, @@ -414,10 +276,11 @@ contract AutomationRegistry is IAutomationRegistry, Ownable2StepUpgradeable, UUP // 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 > (startTime + durationSecs)) { - if(regState.gasCommittedForNextCycle() < task.maxGasAmount) { revert GasCommittedValueUnderflow(); } + uint128 gasCommittedForNextCycle = regState.gasCommittedForNextCycle(); + if(gasCommittedForNextCycle < task.maxGasAmount) { revert GasCommittedValueUnderflow(); } // Adjust the gas committed for the next cycle by subtracting the gas amount of the cancelled task - regState.setGasCommittedForNextCycle(regState.gasCommittedForNextCycle() - task.maxGasAmount); + regState.setGasCommittedForNextCycle(gasCommittedForNextCycle - task.maxGasAmount); } emit TaskCancelled( _taskIndex, task.owner, task.txHash); @@ -435,9 +298,9 @@ contract AutomationRegistry is IAutomationRegistry, Ownable2StepUpgradeable, UUP uint64 _taskIndex ) external { // Check if automation is enabled - if (!regConfig.automationEnabled()) { revert AutomationNotEnabled(); } + if (!IAutomationCore(automationCore).isAutomationEnabled()) { revert AutomationNotEnabled(); } - ( , uint64 startTime, uint64 durationSecs, CommonUtils.CycleState state) = IAutomationController(regConfig.automationController()).getCycleInfo(); + ( , uint64 startTime, uint64 durationSecs, CommonUtils.CycleState state) = IAutomationController(automationController).getCycleInfo(); if(state != CommonUtils.CycleState.STARTED) { revert CycleTransitionInProgress(); } if(!ifTaskExists(_taskIndex)) { revert TaskDoesNotExist(); } @@ -460,10 +323,11 @@ contract AutomationRegistry is IAutomationRegistry, Ownable2StepUpgradeable, UUP // 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 > startTime + durationSecs) { - if(regSysState.gasCommittedForNextCycle() < task.maxGasAmount) { revert GasCommittedValueUnderflow(); } + uint128 gasCommittedForNextCycle = regSysState.gasCommittedForNextCycle(); + if(gasCommittedForNextCycle < task.maxGasAmount) { revert GasCommittedValueUnderflow(); } // Adjust the gas committed for the next cycle by subtracting the gas amount of the cancelled task - regSysState.setGasCommittedForNextCycle(regSysState.gasCommittedForNextCycle() - task.maxGasAmount); + regSysState.setGasCommittedForNextCycle(gasCommittedForNextCycle - task.maxGasAmount); } emit TaskCancelled(_taskIndex, msg.sender, task.txHash); @@ -479,16 +343,14 @@ contract AutomationRegistry is IAutomationRegistry, Ownable2StepUpgradeable, UUP uint64[] memory _taskIndexes ) external { // Check if automation is enabled - if (!regConfig.automationEnabled()) { revert AutomationNotEnabled(); } + if (!IAutomationCore(automationCore).isAutomationEnabled()) { revert AutomationNotEnabled(); } + + address erc20Supra = IAutomationCore(automationCore).erc20Supra(); - ( , uint64 startTime, uint64 durationSecs, CommonUtils.CycleState state) = IAutomationController(regConfig.automationController()).getCycleInfo(); + ( , uint64 startTime, uint64 durationSecs, CommonUtils.CycleState state) = IAutomationController(automationController).getCycleInfo(); if(state != CommonUtils.CycleState.STARTED) { revert CycleTransitionInProgress(); } if(_taskIndexes.length == 0) { revert TaskIndexesCannotBeEmpty(); } - // Compute the automation fee multiplier for cycle - uint128 registryMaxGasCap = getRegistryMaxGasCap(); - uint128 automationFeePerSec = calculateAutomationFeeMultiplierForCycle(regState.gasCommittedForThisCycle(), registryMaxGasCap, regConfig.automationBaseFeeWeiPerSec()); - LibRegistry.TaskStopped[] memory stoppedTaskDetails = new LibRegistry.TaskStopped[](_taskIndexes.length); uint256 counter = 0; @@ -521,37 +383,23 @@ contract AutomationRegistry is IAutomationRegistry, Ownable2StepUpgradeable, UUP // Also it checks that task should not be cancelled. if(task.state != CommonUtils.TaskState.CANCELLED && task.expiryTime > cycleEndTime) { // Prevent underflow in gas committed - if(regState.gasCommittedForNextCycle() < task.maxGasAmount) { revert GasCommittedValueUnderflow(); } + uint128 gasCommittedForNextCycle = regState.gasCommittedForNextCycle(); + if(gasCommittedForNextCycle < task.maxGasAmount) { revert GasCommittedValueUnderflow(); } // Reduce committed gas by the stopped task's max gas - regState.setGasCommittedForNextCycle(regState.gasCommittedForNextCycle() - task.maxGasAmount); - } - - uint128 cycleFeeRefund; - uint128 depositRefund; - if(task.state != CommonUtils.TaskState.PENDING) { - uint128 taskFee = _calculateTaskFee( - task.state, - task.expiryTime, - task.maxGasAmount, - residualInterval, - uint64(currentTime), - automationFeePerSec, - registryMaxGasCap - ); - - // Refund full deposit and the half of the remaining run-time fee when task is active or cancelled stage - cycleFeeRefund = taskFee / REFUND_FRACTION; - depositRefund = task.lockedFeeForNextCycle; - } else { - cycleFeeRefund = 0; - depositRefund = task.lockedFeeForNextCycle / REFUND_FRACTION; + regState.setGasCommittedForNextCycle(gasCommittedForNextCycle - task.maxGasAmount); } - bool result = _safeUnlockLockedDeposit(_taskIndexes[i], task.lockedFeeForNextCycle); - if(!result) { revert ErrorDepositRefund(); } - - (bool hasLockedFee, uint256 remainingCycleLockedFees ) = safeUnlockLockedCycleFee(cycleLockedFees, uint64(cycleFeeRefund), _taskIndexes[i]); - if(!hasLockedFee) { revert ErrorCycleFeeRefund(); } + (uint256 remainingCycleLockedFees, uint128 cycleFeeRefund, uint128 depositRefund) = IAutomationCore(automationCore).unlockDepositAndCycleFee( + _taskIndexes[i], + task.state, + regState.gasCommittedForThisCycle(), + task.expiryTime, + task.maxGasAmount, + residualInterval, + uint64(currentTime), + task.lockedFeeForNextCycle, + cycleLockedFees + ); cycleLockedFees = remainingCycleLockedFees; totalRefundFee += (cycleFeeRefund + depositRefund); @@ -570,10 +418,10 @@ contract AutomationRegistry is IAutomationRegistry, Ownable2StepUpgradeable, UUP // Refund and emit event if any tasks were stopped if(stoppedTaskDetails.length > 0) { - uint256 balance = IERC20(regConfig.erc20Supra).balanceOf(address(this)); + uint256 balance = IERC20(erc20Supra).balanceOf(address(this)); if(balance < totalRefundFee) { revert InsufficientBalanceForRefund(); } - refund(msg.sender, totalRefundFee); + IAutomationCore(automationCore).refund(msg.sender, totalRefundFee); // Emit task stopped event emit TasksStopped( @@ -593,9 +441,9 @@ contract AutomationRegistry is IAutomationRegistry, Ownable2StepUpgradeable, UUP uint64[] memory _taskIndexes ) external { // Check if automation is enabled - if (!regConfig.automationEnabled()) { revert AutomationNotEnabled(); } - - ( , uint64 startTime, uint64 durationSecs, CommonUtils.CycleState state) = IAutomationController(regConfig.automationController()).getCycleInfo(); + if (!IAutomationCore(automationCore).isAutomationEnabled()) { revert AutomationNotEnabled(); } + + ( , uint64 startTime, uint64 durationSecs, CommonUtils.CycleState state) = IAutomationController(automationController).getCycleInfo(); if(state != CommonUtils.CycleState.STARTED) { revert CycleTransitionInProgress(); } // Ensure that task indexes are provided @@ -622,8 +470,9 @@ contract AutomationRegistry is IAutomationRegistry, Ownable2StepUpgradeable, UUP if(task.state != CommonUtils.TaskState.CANCELLED && task.expiryTime > cycleEndTime) { // Prevent underflow in gas committed - if(regSysState.gasCommittedForNextCycle() < task.maxGasAmount) { revert GasCommittedValueUnderflow(); } - regSysState.setGasCommittedForNextCycle(regSysState.gasCommittedForNextCycle() - task.maxGasAmount); + uint128 gasCommittedForNextCycle = regSysState.gasCommittedForNextCycle(); + if(gasCommittedForNextCycle < task.maxGasAmount) { revert GasCommittedValueUnderflow(); } + regSysState.setGasCommittedForNextCycle(gasCommittedForNextCycle - task.maxGasAmount); } // Add to stopped tasks @@ -649,74 +498,6 @@ contract AutomationRegistry is IAutomationRegistry, Ownable2StepUpgradeable, UUP // :::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::: 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, - CommonUtils.TaskType _type, - uint64 _taskDurationCapSecs, - uint64 _sysTaskDurationCapSecs, - uint64 _cycleStartTime, - uint64 _cycleDurationSecs - ) private pure { - if(_expiryTime <= _regTime) { revert InvalidExpiryTime(); } - - uint64 taskDuration = _expiryTime - _regTime; - if(_type == CommonUtils.TaskType.UST) { - if(taskDuration > _taskDurationCapSecs) { revert InvalidTaskDuration(); } - } else if(_type == CommonUtils.TaskType.GST) { - if ( taskDuration > _sysTaskDurationCapSecs) { revert InvalidTaskDuration(); } - } else { - revert InvalidTypeForTask(); - } - - if( _expiryTime <= _cycleStartTime + _cycleDurationSecs) { revert TaskExpiresBeforeNextCycle(); } - } - - /// @notice Helper function to validate the inputs while registering a task. - function validateInputs(bytes memory _payloadTx, uint128 _maxGasAmount, bytes32 _txHash) private view { - ( , address payloadTarget, , ) = abi.decode(_payloadTx, (uint128, address, bytes, LibRegistry.AccessListEntry[])); - if(payloadTarget == address(0)) { revert AddressCannotBeZero(); } - if(!payloadTarget.isContract()) { revert AddressCannotBeEOA(); } - - if(_maxGasAmount == 0) { revert InvalidMaxGasAmount(); } - if(_txHash == bytes32(0)) { revert InvalidTxHash(); } - } - - /// @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 Helper function to update the active task indexes. function updateActiveTaskIds() private { uint256[] memory taskIds = regState.taskIdList.values(); @@ -737,314 +518,12 @@ contract AutomationRegistry is IAutomationRegistry, Ownable2StepUpgradeable, UUP require(regState.taskIdList.remove(_taskIndex), TaskIndexNotFound()); } - /// @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. - /// @param _automationBaseFeeWeiPerSec Automation base fee per second. - function calculateAutomationFeeMultiplierForCycle( - uint128 _totalCommittedGas, - uint128 _registryMaxGasCap, - uint128 _automationBaseFeeWeiPerSec - ) private view returns (uint128){ - uint128 congesionFee = calculateAutomationCongestionFee(_totalCommittedGas, _registryMaxGasCap); - return (congesionFee + _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 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, - uint64 _duration - ) private view returns (uint128) { - uint128 totalCommittedGas = _taskOccupancy + _committedOccupancy; - - uint128 automationFeePerSec = calculateAutomationFeeMultiplierForCycle(totalCommittedGas, regConfig.nextCycleRegistryMaxGasCap(), regConfig.automationBaseFeeWeiPerSec()); - - if(automationFeePerSec == 0) return 0; - - return calculateAutomationFeeForInterval(_duration, _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 = deposit.totalDepositedAutomationFees; - - if(totalDeposited >= _lockedDeposit) { - deposit.totalDepositedAutomationFees = totalDeposited - _lockedDeposit; - return true; - } - - emit ErrorUnlockTaskDepositFee(_taskIndex, totalDeposited, _lockedDeposit); - return false; - } - - /// @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 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 - /// @param _registryMaxGasCap Registry max gas cap - /// @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, - uint128 _registryMaxGasCap - ) private pure 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, - _registryMaxGasCap - ); - } - - /// @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 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 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 Function to ensure that AutomationController contract is the caller. function onlyController() private view { - if(msg.sender != regConfig.automationController()) { revert CallerNotController(); } - } - - /// @notice Helper function to charge fees from the user. - function _chargeFees(address _from, uint256 _amount) private { - bool sent = IERC20(regConfig.erc20Supra).transferFrom(_from, address(this), _amount); - if(!sent) { revert TransferFailed(); } + if(msg.sender != automationController) { revert CallerNotController(); } } - // ::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::: 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(regState.gasCommittedForNextCycle() > _registryMaxGasCap) { revert UnacceptableRegistryMaxGasCap(); } - if(regSysState.gasCommittedForNextCycle() > _sysRegistryMaxGasCap) { revert UnacceptableSysRegistryMaxGasCap(); } - - // Add new config to the buffer - LibRegistry.Config memory pendingConfig = LibRegistry.createConfig( - _registryMaxGasCap, - _sysRegistryMaxGasCap, - _automationBaseFeeWeiPerSec, - _flatRegistrationFeeWei, - _congestionBaseFeeWeiPerSec, - _taskDurationCapSecs, - _sysTaskDurationCapSecs, - _cycleDurationSecs, - _taskCapacity, - _sysTaskCapacity, - _congestionThresholdPercentage, - _congestionExponent - ); - configBuffer = LibRegistry.ConfigBuffer(pendingConfig, true); - - regConfig.setNextCycleRegistryMaxGasCap(_registryMaxGasCap); - regConfig.setNextCycleSysRegistryMaxGasCap(_sysRegistryMaxGasCap); - - emit ConfigBufferUpdated(pendingConfig.getConfig()); - } + // ::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::: ADMIN FUNCTIONS ::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::: /// @notice Grants authorization to the input account to submit system automation tasks. /// @param _account Address to grant authorization to. @@ -1060,122 +539,29 @@ contract AutomationRegistry is IAutomationRegistry, Ownable2StepUpgradeable, UUP emit AuthorizationRevoked(_account, block.timestamp); } - /// @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 enable the automation. - function enableAutomation() external onlyOwner { - if(regConfig.automationEnabled()) { revert AlreadyEnabled(); } - - IAutomationController controller = IAutomationController(regConfig.automationController()); - ( , , , CommonUtils.CycleState state) = controller.getCycleInfo(); - regConfig.setAutomationEnabled(true); + /// @notice Function to update the AutomationCore contract address. + /// @param _automationCore Address of the AutomationCore contract. + function setAutomationCore(address _automationCore) external onlyOwner { + _automationCore.validateContractAddress(); - if (state == CommonUtils.CycleState.READY) { - controller.moveToStartedState(); - controller.updateConfigFromBuffer(); - } - - emit AutomationEnabled(regConfig.automationEnabled()); - } - - /// @notice Function to disable the automation. - function disableAutomation() external onlyOwner { - if(!regConfig.automationEnabled()) { revert AlreadyDisabled(); } - - IAutomationController controller = IAutomationController(regConfig.automationController()); - ( , , , CommonUtils.CycleState state) = controller.getCycleInfo(); - - regConfig.setAutomationEnabled(false); - if (state == CommonUtils.CycleState.FINISHED && !controller.isTransitionInProgress()) { - controller.tryMoveToSuspendedState(); - } - - emit AutomationDisabled(regConfig.automationEnabled()); - } - - /// @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 { - if(_erc20Supra == address(0)) { revert AddressCannotBeZero(); } - if(!_erc20Supra.isContract()) { revert AddressCannotBeEOA(); } - - address oldErc20Supra = regConfig.erc20Supra; - regConfig.erc20Supra = _erc20Supra; - - emit Erc20SupraUpdated(oldErc20Supra, _erc20Supra); - } - - /// @notice Function to withdraw the accumulated fees. - /// @param _amount Amount to withdraw. - function withdrawFees(uint256 _amount) external onlyOwner { - address coldWallet = deposit.coldWallet; - if(coldWallet == address(0)) { revert ColdWalletNotSet(); } - uint256 balance = IERC20(regConfig.erc20Supra).balanceOf(address(this)); - - if(balance < _amount) { revert InsufficientBalance(); } - if(balance - _amount < regState.cycleLockedFees + deposit.totalDepositedAutomationFees) { revert RequestExceedsLockedBalance(); } - - bool sent = IERC20(regConfig.erc20Supra).transfer(coldWallet, _amount); - if(!sent) { revert TransferFailed(); } - - emit RegistryFeeWithdrawn(coldWallet, _amount); - } - - /// @notice Function to update the cold wallet address. - /// @param _coldWallet Address for the new cold wallet. - function setColdWallet(address _coldWallet) external onlyOwner { - if(_coldWallet == address(0)) { revert AddressCannotBeZero(); } - - address oldColdWallet = deposit.coldWallet; - deposit.coldWallet = _coldWallet; - - emit ColdWalletUpdated(oldColdWallet, _coldWallet); + address oldAutomationCore = automationCore; + automationCore = _automationCore; + + emit AutomationCoreUpdated(oldAutomationCore, _automationCore); } - /// @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 { - if (_controller == address(0)) { revert AddressCannotBeZero(); } - if(!_controller.isContract()) { revert AddressCannotBeEOA(); } - - address oldController = regConfig.automationController(); - regConfig.setAutomationController(_controller); + /// @notice Function to update the AutomationController contract address. + /// @param _automationController Address of the AutomationController contract. + function setAutomationController(address _automationController) external onlyOwner { + _automationController.validateContractAddress(); - emit AutomationControllerUpdated(oldController, _controller); + address oldAutomationController = automationController; + automationCore = _automationController; + + emit AutomationControllerUpdated(oldAutomationController, _automationController); } // ::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::: CONTROLLER FUNCTIONS ::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::: - - /// @notice Helper function to charge fees from the user. - function chargeFees(address _from, uint256 _amount) external { - onlyController(); - _chargeFees(_from, _amount); - } /// @notice Internally calls _removeTask, reverts if caller is not AutomationController. function removeTask(uint64 _taskIndex, bool _removeFromSysReg) external { @@ -1220,43 +606,6 @@ contract AutomationRegistry is IAutomationRegistry, Ownable2StepUpgradeable, UUP } } - /// @notice Function to update the registry configuration, reverts if caller is not AutomationController. - function applyPendingConfig() external { - onlyController(); - regConfig.config = configBuffer.pendingConfig; - configBuffer.ifExists = false; - } - - function safeUnlockLockedDeposit( - uint64 _taskIndex, - uint128 _lockedDeposit - ) external returns (bool) { - onlyController(); - return _safeUnlockLockedDeposit(_taskIndex, _lockedDeposit); - } - - /// @notice Internally calls _calculateTaskFee, reverts if caller is not AutomationController. - function calculateTaskFee( - CommonUtils.TaskState _state, - uint64 _expiryTime, - uint128 _maxGasAmount, - uint64 _potentialFeeTimeframe, - uint64 _currentTime, - uint128 _automationFeePerSec, - uint128 _registryMaxGasCap - ) external view returns (uint128) { - onlyController(); - return _calculateTaskFee( - _state, - _expiryTime, - _maxGasAmount, - _potentialFeeTimeframe, - _currentTime, - _automationFeePerSec, - _registryMaxGasCap - ); - } - /// @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. @@ -1276,7 +625,7 @@ contract AutomationRegistry is IAutomationRegistry, Ownable2StepUpgradeable, UUP _removeTask(_taskIndex, false); // Refund - safeDepositRefund( + IAutomationCore(automationCore).safeDepositRefund( _taskIndex, _taskOwner, _refundableDeposit, @@ -1284,115 +633,14 @@ contract AutomationRegistry is IAutomationRegistry, Ownable2StepUpgradeable, UUP ); } - /// @notice Refunds the deposit fee and any autoamtion fees of the task. - function refundTaskFees( - uint64 _taskIndex, - uint64 _currentTime, - uint256 _cycleLockedFees - ) external returns (uint256) { - onlyController(); - if(checkTaskType(_taskIndex, CommonUtils.TaskType.GST)) { revert RegisteredTaskInvalidType(); } - - CommonUtils.TaskDetails memory task = regState.tasks[_taskIndex].getTaskDetails(); - - // Do not attempt fee refund if remaining duration is 0 - (uint64 refundDuration, uint128 automationFeePerSec) = IAutomationController(regConfig.automationController()).getTransitionInfo(); - - if (task.state != CommonUtils.TaskState.PENDING && refundDuration != 0) { - uint128 registryMaxGasCap = getRegistryMaxGasCap(); - uint128 _refund = _calculateTaskFee( - task.state, - task.expiryTime, - task.maxGasAmount, - refundDuration, - _currentTime, - automationFeePerSec, - registryMaxGasCap - ); - ( , uint256 remainingCycleLockedFees) = safeFeeRefund( - _taskIndex, - task.owner, - _cycleLockedFees, - uint64(_refund) - ); - _cycleLockedFees = remainingCycleLockedFees; - } - - safeDepositRefund( - _taskIndex, - task.owner, - task.lockedFeeForNextCycle, - task.lockedFeeForNextCycle - ); - - return _cycleLockedFees; - } - - function calculateAutomationFeeMultiplierForCurrentCycleInternal() external view returns (uint128) { - onlyController(); - // Compute the automation fee multiplier for this cycle - return calculateAutomationFeeMultiplierForCycle( - regState.gasCommittedForThisCycle(), - regConfig.registryMaxGasCap(), - regConfig.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 - ) external view returns (uint128) { - onlyController(); - // Compute the automation fee multiplier for cycle - return calculateAutomationFeeMultiplierForCycle( - _totalCommittedMaxGas, - regConfig.registryMaxGasCap(), - regConfig.automationBaseFeeWeiPerSec() - ); - } - // :::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::: VIEW FUNCTIONS :::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::: - - /// @notice Returns the registry configuration. - function getConfig() external view returns (LibRegistry.ConfigDetails memory) { - return regConfig.config.getConfig(); - } - - /// @notice Returns the cold wallet address. - function getColdWallet() external view returns (address) { - return deposit.coldWallet; - } - - /// @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 if automation is enabled. - function isAutomationEnabled() external view returns (bool) { - return regConfig.automationEnabled(); - } - - /// @notice Returns if task registration is enabled. - function isRegistrationEnabled() external view returns (bool) { - return regConfig.registrationEnabled(); - } /// @notice Returns the total amount locked which comprises of 'cycleLockedFees' and 'totalDepositedAutomationFees'. function getTotalLockedBalance() external view returns (uint256) { - return regState.cycleLockedFees + deposit.totalDepositedAutomationFees; + uint256 getTotalDepositedAutomationFees = IAutomationCore(automationCore).getTotalDepositedAutomationFees(); + + // return regState.cycleLockedFees + deposit.totalDepositedAutomationFees; + return regState.cycleLockedFees + getTotalDepositedAutomationFees; } /// @notice Retrieves the details of automation tasks by their task index. Skips a task if it doesn't exist. @@ -1422,16 +670,6 @@ contract AutomationRegistry is IAutomationRegistry, Ownable2StepUpgradeable, UUP return regState.taskIdList.values(); } - /// @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 number of total tasks. function totalTasks() public view returns (uint256) { return regState.taskIdList.length(); @@ -1504,32 +742,7 @@ contract AutomationRegistry is IAutomationRegistry, Ownable2StepUpgradeable, UUP function getSystemGasCommittedForCurrentCycle() external view returns (uint128) { return regSysState.gasCommittedForThisCycle(); } - - /// @notice Returns the total amount of automation fees deposited. - function getTotalDepositedAutomationFees() external view returns (uint256) { - return deposit.totalDepositedAutomationFees; - } - - /// @notice Returns the registry max gas cap configured. - function getRegistryMaxGasCap() public 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 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) { @@ -1567,22 +780,6 @@ contract AutomationRegistry is IAutomationRegistry, Ownable2StepUpgradeable, UUP return regState.tasks[_taskIndex].owner() == _account && LibRegistry.state(regState.tasks[_taskIndex]) != CommonUtils.TaskState.PENDING && checkTaskType(_taskIndex, _type); } - /// @notice Checks if config buffer exists. - /// @return Bool representing if config buffer exists. - function ifConfigBufferExists() external view returns (bool) { - return configBuffer.ifExists; - } - - /// @notice Returns the pending configuration. - function getPendingConfig() external view returns (LibRegistry.ConfigDetails memory) { - return configBuffer.pendingConfig.getConfig(); - } - - /// @notice Returns the cycle duration of config buffer. - function getBufferCycleDurationSecs() external view returns (uint64) { - return configBuffer.pendingConfig.cycleDurationSecs(); - } - /// @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. @@ -1597,11 +794,9 @@ contract AutomationRegistry is IAutomationRegistry, Ownable2StepUpgradeable, UUP uint128 _taskOccupancy, uint128 _committedOccupancy ) public view returns (uint128) { - ( , , uint64 durationSecs, ) = IAutomationController(regConfig.automationController()).getCycleInfo(); - return estimateAutomationFeeWithCommittedOccupancyInternal( + return IAutomationCore(automationCore).estimateAutomationFeeWithCommittedOccupancyInternal( _taskOccupancy, - _committedOccupancy, - durationSecs + _committedOccupancy ); } diff --git a/solidity/supra_contracts/src/BlockMeta.sol b/solidity/supra_contracts/src/BlockMeta.sol index 34e61695ab..a94880b410 100644 --- a/solidity/supra_contracts/src/BlockMeta.sol +++ b/solidity/supra_contracts/src/BlockMeta.sol @@ -22,8 +22,6 @@ contract BlockMeta is OwnableUpgradeable, UUPSUpgradeable { mapping(address targetContract => EnumerableSet.Bytes4Set selectors) private registry; /// @dev Custom errors - error AddressCannotBeEOA(); - error AddressCannotBeZero(); error CallerNotVmSigner(); error SelectorAlreadyRegistered(); error SelectorNotRegistered(); @@ -84,8 +82,7 @@ contract BlockMeta is OwnableUpgradeable, UUPSUpgradeable { /// @param _targetContract The target contract address. /// @param _selector Function selector to be called on target contract. function register(address _targetContract, bytes4 _selector) external onlyOwner { - if (_targetContract == address(0)) revert AddressCannotBeZero(); - if (!_targetContract.isContract()) revert AddressCannotBeEOA(); + _targetContract.validateContractAddress(); // Adds a target contract if it does not exist registeredTargets.add(_targetContract); diff --git a/solidity/supra_contracts/src/CommonUtils.sol b/solidity/supra_contracts/src/CommonUtils.sol index e6f0601dc6..fb175bfdd5 100644 --- a/solidity/supra_contracts/src/CommonUtils.sol +++ b/solidity/supra_contracts/src/CommonUtils.sol @@ -5,6 +5,10 @@ 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); @@ -105,6 +109,12 @@ library CommonUtils { 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. diff --git a/solidity/supra_contracts/src/IAutomationController.sol b/solidity/supra_contracts/src/IAutomationController.sol index b8a209d79c..490704ddfe 100644 --- a/solidity/supra_contracts/src/IAutomationController.sol +++ b/solidity/supra_contracts/src/IAutomationController.sol @@ -5,12 +5,11 @@ import {CommonUtils} from "./CommonUtils.sol"; interface IAutomationController { // Custom errors - error AddressCannotBeEOA(); + error CallerNotAutomationCore(); error CallerNotRegistry(); error CallerNotVmSigner(); error ConfigUpdateFailed(); error InconsistentTransitionState(); - error AddressCannotBeZero(); error InvalidInputCycleIndex(); error InvalidRegistryState(); error OutOfOrderTaskProcessingRequest(); @@ -31,5 +30,5 @@ interface IAutomationController { function monitorCycleEnd() external; function moveToStartedState() external; function tryMoveToSuspendedState() external; - function updateConfigFromBuffer() external; + function updateCyleDuration(uint64 cycleDurationSecs) external; } diff --git a/solidity/supra_contracts/src/IAutomationCore.sol b/solidity/supra_contracts/src/IAutomationCore.sol new file mode 100644 index 0000000000..e939eadb4c --- /dev/null +++ b/solidity/supra_contracts/src/IAutomationCore.sol @@ -0,0 +1,114 @@ +// 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 ColdWalletNotSet(); + error CycleTransitionInProgress(); + error ErrorDepositRefund(); + error ErrorCycleFeeRefund(); + error InvalidMaxGasAmount(); + error InvalidTaskType(); + error InvalidTxHash(); + error AlreadyEnabled(); + error AlreadyDisabled(); + error GasCommittedExceedsMaxGasCap(); + error InsufficientBalance(); + error InsufficientFeeCapForCycle(); + 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 RegisteredTaskInvalidType(); + error RequestExceedsLockedBalance(); + error TaskCapacityReached(); + error TaskExpiresBeforeNextCycle(); + error TransferFailed(); + error UnacceptableRegistryMaxGasCap(); + error UnacceptableSysRegistryMaxGasCap(); + error UnauthorizedCaller(); + + // View functions + function isAutomationEnabled() external view returns (bool); + function flatRegistrationFeeWei() external view returns (uint128); + function getAutomationController() external view returns (address); + function erc20Supra() external view returns (address); + function estimateAutomationFeeWithCommittedOccupancyInternal( + uint128 _taskOccupancy, + uint128 _committedOccupancy + ) external view returns (uint128); + 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 getTotalDepositedAutomationFees() external view returns (uint256); + function validateRegistration( + uint256 _totalTasks, + uint8 _inputType, + uint64 _regTime, + uint64 _expiryTime, + CommonUtils.TaskType _taskType, + bytes memory _payloadTx, + uint128 _maxGasAmount, + bytes32 _txHash, + uint128 _gasCommittedForNextCycle, + uint128 _gasPriceCap, + uint128 _automationFeeCapForCycle + ) external view; + + // State updating functions + function applyPendingConfig() external; + function incTotalDepositedAutomationFees(uint256 _totalDepositedAutomationFees) external; + function chargeFees(address _from, uint256 _amount) external; + function safeUnlockLockedDeposit( + uint64 _taskIndex, + uint128 _lockedDeposit + ) external returns (bool); + function refundTaskFees( + uint64 _taskIndex, + uint64 _currentTime, + uint256 _cycleLockedFees, + uint64 _refundDuration, + uint128 _automationFeePerSec + ) external returns (uint256); + 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, + uint128 _gasCommittedForThisCycle, + uint64 _expiryTime, + uint128 _maxGasAmount, + uint64 _residualInterval, + uint64 _currentTime, + uint128 _lockedFeeForNextCycle, + uint256 _cycleLockedFees + ) external returns (uint256, uint128, uint128); +} \ No newline at end of file diff --git a/solidity/supra_contracts/src/IAutomationRegistry.sol b/solidity/supra_contracts/src/IAutomationRegistry.sol index a84f3b6e93..b9b4690795 100644 --- a/solidity/supra_contracts/src/IAutomationRegistry.sol +++ b/solidity/supra_contracts/src/IAutomationRegistry.sol @@ -5,42 +5,10 @@ import {CommonUtils} from "./CommonUtils.sol"; interface IAutomationRegistry { // Custom errors - error AddressCannotBeEOA(); - error AddressCannotBeZero(); error AddressAlreadyExists(); error AddressDoesNotExist(); error CallerNotController(); - error CycleNotStarted(); - error GasCommittedExceedsMaxGasCap(); - error InsufficientFeeCapForCycle(); - error InsufficentValueSent(); - error InvalidExpiryTime(); - error InvalidGasPriceCap(); - error InvalidMaxGasAmount(); - error InvalidTaskDuration(); - error InvalidTxHash(); - error InvalidTaskType(); - error InvalidTypeForTask(); - error RegistrationDisabled(); - error TaskCapacityReached(); - error TaskExpiresBeforeNextCycle(); - error TransferFailed(); error UnauthorizedAccount(); - error AlreadyEnabled(); - error AlreadyDisabled(); - error InvalidCycleDuration(); - error InvalidCongestionThreshold(); - error InvalidCongestionExponent(); - error InvalidSysTaskDuration(); - error InvalidRegistryMaxGasCap(); - error InvalidSysRegistryMaxGasCap(); - error InvalidTaskCapacity(); - error InvalidSysTaskCapacity(); - error UnacceptableRegistryMaxGasCap(); - error UnacceptableSysRegistryMaxGasCap(); - error ColdWalletNotSet(); - error InsufficientBalance(); - error RequestExceedsLockedBalance(); error CycleTransitionInProgress(); error TaskDoesNotExist(); error UnsupportedTaskOperation(); @@ -49,9 +17,7 @@ interface IAutomationRegistry { error GasCommittedValueUnderflow(); error SystemTaskDoesNotExist(); error TaskIndexesCannotBeEmpty(); - error ErrorCycleFeeRefund(); error InsufficientBalanceForRefund(); - error UnauthorizedCaller(); error RegisteredTaskInvalidType(); error AutomationNotEnabled(); error TaskIndexNotFound(); @@ -63,29 +29,12 @@ interface IAutomationRegistry { function getAllActiveTaskIds() external view returns (uint256[] memory); function getCycleLockedFees() external view returns (uint256); function getGasCommittedForNextCycle() external view returns (uint128); - function getRegistryMaxGasCap() external view returns (uint128); + function getSystemGasCommittedForNextCycle() external view returns (uint128); 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); - function ifConfigBufferExists() external view returns (bool); - function getBufferCycleDurationSecs() external view returns (uint64); - function getVmSigner() external returns (address); - function erc20Supra() external view returns (address); - function isAutomationEnabled() external view returns (bool); - function calculateAutomationFeeMultiplierForCurrentCycleInternal() external view returns (uint128); - function calculateAutomationFeeMultiplierForCommittedOccupancy(uint128 _totalCommittedMaxGas) external view returns (uint128); - function calculateTaskFee( - CommonUtils.TaskState _state, - uint64 _expiryTime, - uint128 _maxGasAmount, - uint64 _potentialFeeTimeframe, - uint64 _currentTime, - uint128 _automationFeePerSec, - uint128 _registryMaxGasCap - ) external view returns (uint128); - function cycleDurationSecs() external view returns (uint64); - + function getGasCommittedForCurrentCycle() external view returns (uint128); // State updating functions function removeTask(uint64 _taskIndex, bool _removeFromSysReg) external; @@ -97,21 +46,10 @@ interface IAutomationRegistry { uint256 _lockedFees, uint8 _state ) external; - function applyPendingConfig() external; - function safeUnlockLockedDeposit( - uint64 _taskIndex, - uint128 _lockedDeposit - ) external returns (bool); function refundDepositAndDrop( uint64 _taskIndex, address _taskOwner, uint128 _refundableDeposit, uint128 _lockedDeposit ) external; - function refundTaskFees( - uint64 _taskIndex, - uint64 _currentTime, - uint256 _cycleLockedFees - ) external returns (uint256); - function chargeFees(address _from, uint256 _amount) external; } diff --git a/solidity/supra_contracts/src/LibConfig.sol b/solidity/supra_contracts/src/LibConfig.sol new file mode 100644 index 0000000000..668675ecc5 --- /dev/null +++ b/solidity/supra_contracts/src/LibConfig.sol @@ -0,0 +1,347 @@ +// 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; + + // ::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::: Deposit ::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::: + + /// @notice Deposit and fee related accounting. + struct Deposit { + uint256 totalDepositedAutomationFees; + address coldWallet; + } + + // ::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::: 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 nextCycleRegistryMaxGasCap_nextCycleSysRegistryMaxGasCap; + // address | bool | bool + uint256 controller_registrationEnabled_automationEnabled; + address vmSigner; + address erc20Supra; + address registry; + Config config; + } + + function createRegistryConfig( + uint128 _nextCycleRegistryMaxGasCap, + uint128 _nextCycleSysRegistryMaxGasCap, + bool _registrationEnabled, + bool _automationEnabled, + 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) | automationEnabled (bool at bit 94) + // Sets controller as address(0) + rcfg.controller_registrationEnabled_automationEnabled = + (_registrationEnabled ? (uint256(1) << 95) : 0) | + (_automationEnabled ? (uint256(1) << 94) : 0); + + rcfg.vmSigner = _vmSigner; + rcfg.erc20Supra = _erc20Supra; + + // Assign inner Config + rcfg.config = _config; + } + + // 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) [stored at bit 95] | automationEnabled (bool) [stored at bit 94] + function automationController(RegistryConfig storage r) internal view returns (address) { + return address(uint160(r.controller_registrationEnabled_automationEnabled >> 96)); + } + + function registrationEnabled(RegistryConfig storage r) internal view returns (bool) { + return (r.controller_registrationEnabled_automationEnabled >> 95) & 1 != 0; + } + + function automationEnabled(RegistryConfig storage r) internal view returns (bool) { + return (r.controller_registrationEnabled_automationEnabled >> 94) & 1 != 0; + } + + function setAutomationController(RegistryConfig storage r, address _controller) internal { + // clear top 160 bits + r.controller_registrationEnabled_automationEnabled &= ~(MAX_UINT160 << 96); + + // insert 160-bit address + r.controller_registrationEnabled_automationEnabled |= uint256(uint160(_controller)) << 96; + } + + function setRegistrationEnabled(RegistryConfig storage r, bool enabled) internal { + // clear bit 95 + r.controller_registrationEnabled_automationEnabled &= ~(uint256(1) << 95); + + // set bit 95 if enabled + r.controller_registrationEnabled_automationEnabled |= enabled ? (uint256(1) << 95) : 0; + } + + function setAutomationEnabled(RegistryConfig storage r, bool enabled) internal { + // clear bit 94 + r.controller_registrationEnabled_automationEnabled &= ~(uint256(1) << 94); + + // set bit 94 if enabled + r.controller_registrationEnabled_automationEnabled |= enabled ? (uint256(1) << 94) : 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/LibRegistry.sol b/solidity/supra_contracts/src/LibRegistry.sol index 4b7f91098f..b3bb156205 100644 --- a/solidity/supra_contracts/src/LibRegistry.sol +++ b/solidity/supra_contracts/src/LibRegistry.sol @@ -13,282 +13,7 @@ library LibRegistry { 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 nextCycleRegistryMaxGasCap_nextCycleSysRegistryMaxGasCap; - // address | bool | bool - uint256 controller_registrationEnabled_automationEnabled; - address vmSigner; - address erc20Supra; - Config config; - } - - function createRegistryConfig( - uint128 _nextCycleRegistryMaxGasCap, - uint128 _nextCycleSysRegistryMaxGasCap, - bool _registrationEnabled, - bool _automationEnabled, - 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) | automationEnabled (bool at bit 94) - // Sets controller as address(0) - rcfg.controller_registrationEnabled_automationEnabled = - (_registrationEnabled ? (uint256(1) << 95) : 0) | - (_automationEnabled ? (uint256(1) << 94) : 0); - - rcfg.vmSigner = _vmSigner; - rcfg.erc20Supra = _erc20Supra; - - // Assign inner Config - rcfg.config = _config; - } - - // 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) [stored at bit 95] | automationEnabled (bool) [stored at bit 94] - function automationController(RegistryConfig storage r) internal view returns (address) { - return address(uint160(r.controller_registrationEnabled_automationEnabled >> 96)); - } - - function registrationEnabled(RegistryConfig storage r) internal view returns (bool) { - return (r.controller_registrationEnabled_automationEnabled >> 95) & 1 != 0; - } - - function automationEnabled(RegistryConfig storage r) internal view returns (bool) { - return (r.controller_registrationEnabled_automationEnabled >> 94) & 1 != 0; - } - - function setAutomationController(RegistryConfig storage r, address _controller) internal { - // clear top 160 bits - r.controller_registrationEnabled_automationEnabled &= ~(MAX_UINT160 << 96); - - // insert 160-bit address - r.controller_registrationEnabled_automationEnabled |= uint256(uint160(_controller)) << 96; - } - - function setRegistrationEnabled(RegistryConfig storage r, bool enabled) internal { - // clear bit 95 - r.controller_registrationEnabled_automationEnabled &= ~(uint256(1) << 95); - - // set bit 95 if enabled - r.controller_registrationEnabled_automationEnabled |= enabled ? (uint256(1) << 95) : 0; - } - - function setAutomationEnabled(RegistryConfig storage r, bool enabled) internal { - // clear bit 94 - r.controller_registrationEnabled_automationEnabled &= ~(uint256(1) << 94); - - // set bit 94 if enabled - r.controller_registrationEnabled_automationEnabled |= enabled ? (uint256(1) << 94) : 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; - } - // :::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::: TaskMetadata :::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::: /// @notice Task metadata for individual automation tasks. @@ -533,12 +258,6 @@ library LibRegistry { s.gasCommittedForNextCycle_gasCommittedForThisCycle |= uint256(_value); } - /// @notice Deposit and fee related accounting. - struct Deposit { - uint256 totalDepositedAutomationFees; - address coldWallet; - } - /// @notice Struct representing a stopped task. struct TaskStopped { uint64 taskIndex; @@ -546,57 +265,5 @@ library LibRegistry { uint128 cycleFeeRefund; bytes32 txHash; } - - /// @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); - } } From 0606947ce8df0f28b39538b42ed5474e18e786e7 Mon Sep 17 00:00:00 2001 From: Udit Yadav Date: Tue, 13 Jan 2026 14:46:22 +0530 Subject: [PATCH 15/31] fixed test cases --- .../src/AutomationController.sol | 12 +- .../src/AutomationRegistry.sol | 4 +- .../test/AutomationController.t.sol | 135 +-- .../supra_contracts/test/AutomationCore.t.sol | 963 +++++++++++++++++ .../test/AutomationRegistry.t.sol | 988 +++--------------- solidity/supra_contracts/test/BlockMeta.t.sol | 5 +- 6 files changed, 1174 insertions(+), 933 deletions(-) create mode 100644 solidity/supra_contracts/test/AutomationCore.t.sol diff --git a/solidity/supra_contracts/src/AutomationController.sol b/solidity/supra_contracts/src/AutomationController.sol index fb678745e4..841fa6414d 100644 --- a/solidity/supra_contracts/src/AutomationController.sol +++ b/solidity/supra_contracts/src/AutomationController.sol @@ -72,8 +72,8 @@ contract AutomationController is IAutomationController, Ownable2StepUpgradeable, /// When automation is in suspended state, there are no tasks expected. event ErrorInconsistentSuspendedState(); - /// @notice Emitted when the registry smart contract address is updated. - event RegistryUpdated(address indexed oldRegistryAddress, address indexed newRegistryAddress); + /// @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); @@ -728,15 +728,15 @@ contract AutomationController is IAutomationController, Ownable2StepUpgradeable, // ::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::: ADMIN FUNCTIONS ::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::: - /// @notice Function to update the registry smart contract address. - /// @param _registry Address of the registry smart contract. - function setRegistry(address _registry) external onlyOwner { + /// @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 = address(registry); registry = IAutomationRegistry(_registry); - emit RegistryUpdated(oldRegistry, _registry); + emit AutomationRegistryUpdated(oldRegistry, _registry); } /// @notice Function to update the AutomationCore contract address. diff --git a/solidity/supra_contracts/src/AutomationRegistry.sol b/solidity/supra_contracts/src/AutomationRegistry.sol index 1e32690159..e52b3281b9 100644 --- a/solidity/supra_contracts/src/AutomationRegistry.sol +++ b/solidity/supra_contracts/src/AutomationRegistry.sol @@ -418,7 +418,7 @@ contract AutomationRegistry is IAutomationRegistry, Ownable2StepUpgradeable, UUP // Refund and emit event if any tasks were stopped if(stoppedTaskDetails.length > 0) { - uint256 balance = IERC20(erc20Supra).balanceOf(address(this)); + uint256 balance = IERC20(erc20Supra).balanceOf(automationCore); if(balance < totalRefundFee) { revert InsufficientBalanceForRefund(); } IAutomationCore(automationCore).refund(msg.sender, totalRefundFee); @@ -556,7 +556,7 @@ contract AutomationRegistry is IAutomationRegistry, Ownable2StepUpgradeable, UUP _automationController.validateContractAddress(); address oldAutomationController = automationController; - automationCore = _automationController; + automationController = _automationController; emit AutomationControllerUpdated(oldAutomationController, _automationController); } diff --git a/solidity/supra_contracts/test/AutomationController.t.sol b/solidity/supra_contracts/test/AutomationController.t.sol index 15fa100b30..7a27f11caf 100644 --- a/solidity/supra_contracts/test/AutomationController.t.sol +++ b/solidity/supra_contracts/test/AutomationController.t.sol @@ -6,15 +6,17 @@ import {ERC1967Proxy} from "../lib/openzeppelin-contracts/contracts/proxy/ERC196 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"; 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 - ERC20Supra erc20Supra; // ERC20Supra contract address admin = address(0xA11CE); address vmSigner = address(0x53555000); @@ -23,28 +25,16 @@ contract AutomationControllerTest is Test { /// @dev Sets up initial state for testing. /// @dev Sets balance of 'alice' to 100 ether. - /// @dev Deploys and initializes ERC20Supra, AutomationRegistry and AutomationController contracts. + /// @dev Deploys and initializes all contracts with required parameters. function setUp() public { vm.deal(alice, 100 ether); - // Deploy ERC20Supra - vm.prank(admin); - erc20Supra = new ERC20Supra(msg.sender); - - // Deploy AutomationRegistry proxy - registry = AutomationRegistry(deployRegistry(address(erc20Supra))); - - // Deploy AutomationController proxy - controller = AutomationController(deployController(address(registry))); - } - - /// @dev Helper function to deploy AutomationRegistry proxy - function deployRegistry(address _erc20Supra) private returns (address) { vm.startPrank(admin); - AutomationRegistry impl = new AutomationRegistry(); + erc20Supra = new ERC20Supra(msg.sender); - bytes memory initData = abi.encodeCall( - AutomationRegistry.initialize, + AutomationCore automationCoreImpl = new AutomationCore(); + bytes memory automationCoreInitData = abi.encodeCall( + AutomationCore.initialize, ( 3600, // taskDurationCapSecs 10_000_000, // registryMaxGasCap @@ -59,36 +49,35 @@ contract AutomationControllerTest is Test { 5_000_000, // sysRegistryMaxGasCap 500, // sysTaskCapacity vmSigner, // VM Signer address - address(_erc20Supra) // ERC20Supra address + address(erc20Supra) // ERC20Supra address ) ); - - ERC1967Proxy proxy = new ERC1967Proxy(address(impl), initData); - vm.stopPrank(); + ERC1967Proxy automationCoreProxy = new ERC1967Proxy(address(automationCoreImpl), automationCoreInitData); + automationCore = AutomationCore(address(automationCoreProxy)); - return address(proxy); - } + 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))); + ERC1967Proxy controllerProxy = new ERC1967Proxy(address(controllerImpl), controllerInitData); + controller = AutomationController(address(controllerProxy)); - /// @dev Helper function to deploy AutomationController proxy - function deployController(address _registry) private returns (address) { - vm.startPrank(admin); - AutomationController impl = new AutomationController(); - bytes memory initData = abi.encodeCall(AutomationController.initialize,(_registry)); - ERC1967Proxy proxy = new ERC1967Proxy(address(impl), initData); vm.stopPrank(); - - return address(proxy); } /// @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)); (uint64 index, uint64 startTime, uint64 durationSecs, CommonUtils.CycleState state) = controller.getCycleInfo(); assertEq(index, 1); assertEq(startTime, block.timestamp); - assertEq(durationSecs, registry.cycleDurationSecs()); + assertEq(durationSecs, automationCore.cycleDurationSecs()); assertEq(uint8(state), uint8(CommonUtils.CycleState.STARTED)); } @@ -97,74 +86,90 @@ contract AutomationControllerTest is Test { vm.expectRevert(Initializable.InvalidInitialization.selector); vm.prank(admin); - controller.initialize(address(registry)); + controller.initialize(address(automationCore), address(registry)); + } + + /// @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))); + + vm.expectRevert(CommonUtils.AddressCannotBeZero.selector); + new ERC1967Proxy(address(impl), initData); } - /// @dev Test to ensure initialize reverts if registry address is zero. + /// @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))); + + vm.expectRevert(CommonUtils.AddressCannotBeEOA.selector); + new ERC1967Proxy(address(impl), initData); + } + + /// @dev Test to ensure initialize reverts if AutomationRegistry address is zero. function testInitializeRevertsIfRegistryZero() public { - // Deploy AutomationController proxy AutomationController impl = new AutomationController(); - bytes memory initData = abi.encodeCall(AutomationController.initialize,(address(0))); + bytes memory initData = abi.encodeCall(AutomationController.initialize, (address(automationCore), address(0))); - vm.expectRevert(IAutomationController.AddressCannotBeZero.selector); + vm.expectRevert(CommonUtils.AddressCannotBeZero.selector); new ERC1967Proxy(address(impl), initData); } - /// @dev Test to ensure initialize reverts if registry address is EOA. + /// @dev Test to ensure initialize reverts if AutomationRegistry address is EOA. function testInitializeRevertsIfRegistryEoa() public { - // Deploy AutomationController proxy AutomationController impl = new AutomationController(); - bytes memory initData = abi.encodeCall(AutomationController.initialize,(alice)); + bytes memory initData = abi.encodeCall(AutomationController.initialize, (address(automationCore), alice)); - vm.expectRevert(IAutomationController.AddressCannotBeEOA.selector); + vm.expectRevert(CommonUtils.AddressCannotBeEOA.selector); new ERC1967Proxy(address(impl), initData); } - /// @dev Test to ensure 'setRegistry' reverts if caller is not owner. - function testSetRegistryRevertsIfNotOwner() public { - address newRegistry = deployRegistry(address(erc20Supra)); + /// @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.setRegistry(newRegistry); + controller.setAutomationRegistry(address(registryImplementation)); } - /// @dev Test to ensure 'setRegistry' reverts if address is zero. - function testSetRegistryRevertsIfAddressZero() public { - vm.expectRevert(IAutomationController.AddressCannotBeZero.selector); + /// @dev Test to ensure 'setAutomationRegistry' reverts if address is zero. + function testSetAutomationRegistryRevertsIfAddressZero() public { + vm.expectRevert(CommonUtils.AddressCannotBeZero.selector); vm.prank(admin); - controller.setRegistry(address(0)); + controller.setAutomationRegistry(address(0)); } - /// @dev Test to ensure 'setRegistry' reverts if address is EOA. - function testSetRegistryRevertsIfAddressEoa() public { - vm.expectRevert(IAutomationController.AddressCannotBeEOA.selector); + /// @dev Test to ensure 'setAutomationRegistry' reverts if address is EOA. + function testSetAutomationRegistryRevertsIfAddressEoa() public { + vm.expectRevert(CommonUtils.AddressCannotBeEOA.selector); vm.prank(admin); - controller.setRegistry(alice); + controller.setAutomationRegistry(alice); } - /// @dev Test to ensure 'setRegistry' updates the registry address. - function testSetRegistry() public { - address newRegistry = deployRegistry(address(erc20Supra)); - + /// @dev Test to ensure 'setAutomationRegistry' updates the registry address. + function testSetAutomationRegistry() public { + AutomationRegistry registryImplementation = new AutomationRegistry(); + vm.prank(admin); - controller.setRegistry(newRegistry); + controller.setAutomationRegistry(address(registryImplementation)); - assertEq(address(controller.registry()), newRegistry); + assertEq(address(controller.registry()), address(registryImplementation)); } - /// @dev Test to ensure 'setRegistry' emits event 'RegistryUpdated'. - function testSetRegistryEmitsEvent() public { - address newRegistry = deployRegistry(address(erc20Supra)); + /// @dev Test to ensure 'setAutomationRegistry' emits event 'AutomationRegistryUpdated'. + function testSetAutomationRegistryEmitsEvent() public { + AutomationRegistry registryImplementation = new AutomationRegistry(); vm.expectEmit(true, true, false, false); - emit AutomationController.RegistryUpdated(address(controller.registry()), newRegistry); + emit AutomationController.AutomationRegistryUpdated(address(controller.registry()), address(registryImplementation)); vm.prank(admin); - controller.setRegistry(newRegistry); + controller.setAutomationRegistry(address(registryImplementation)); } /// @dev Test to ensure 'processTasks' reverts if caller is not VM Signer. diff --git a/solidity/supra_contracts/test/AutomationCore.t.sol b/solidity/supra_contracts/test/AutomationCore.t.sol new file mode 100644 index 0000000000..034eb7c19d --- /dev/null +++ b/solidity/supra_contracts/test/AutomationCore.t.sol @@ -0,0 +1,963 @@ +// 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 + address automationController; // AutomationController proxy address + + 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)); + automationCore.setAutomationRegistry(address(registry)); + + AutomationController controllerImpl = new AutomationController(); + bytes memory controllerInitData = abi.encodeCall(AutomationController.initialize,(address(automationCore), address(registry))); + ERC1967Proxy controllerProxy = new ERC1967Proxy(address(controllerImpl), controllerInitData); + automationController = address(controllerProxy); + automationCore.setAutomationController(automationController); + + vm.stopPrank(); + } + + /// @dev Test to ensure all state variables are initialized correctly. + function testInitialize() public view { + assertEq(automationCore.owner(), admin); + assertEq(automationCore.getNextCycleRegistryMaxGasCap(), 10_000_000); + assertEq(automationCore.getNextCycleSysRegistryMaxGasCap(), 5_000_000); + assertEq(automationCore.getAutomationController(), automationController); + assertTrue(automationCore.isRegistrationEnabled()); + assertTrue(automationCore.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 'disableAutomation' :::::::::::::::::::::::::::::::::::::::::::::::::::::: + + /// @dev Test to ensure 'disableAutomation' disables the automation. + function testDisableAutomation() public { + // Already enabled in initialize() + vm.prank(admin); + automationCore.disableAutomation(); + + assertFalse(automationCore.isAutomationEnabled()); + } + + /// @dev Test to ensure 'disableAutomation' emits event 'AutomationDisabled'. + function testDisableAutomationEmitsEvent() public { + vm.expectEmit(true, false, false, false); + emit AutomationCore.AutomationDisabled(false); + + vm.prank(admin); + automationCore.disableAutomation(); + } + + /// @dev Test to ensure 'disableAutomation' reverts if automation is already disabled. + function testDisableAutomationRevertsIfAlreadyDisabled() public { + // Disable automation + testDisableAutomation(); + + // Disable again → revert + vm.expectRevert(IAutomationCore.AlreadyDisabled.selector); + + vm.prank(admin); + automationCore.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); + automationCore.disableAutomation(); + } + + // :::::::::::::::::::::::::::::::::::::::::::::::::::::: Tests related to 'enableAutomation' :::::::::::::::::::::::::::::::::::::::::::::::::::::: + + /// @dev Test to ensure 'enableAutomation' enables the automation. + function testEnableAutomation() public { + // Disable automation + testDisableAutomation(); + + // Enable automation + vm.prank(admin); + automationCore.enableAutomation(); + + assertTrue(automationCore.isAutomationEnabled()); + } + + /// @dev Test to ensure 'enableAutomation' emits event 'AutomationEnabled'. + function testEnableAutomationEmitsEvent() public { + // Disable automation + testDisableAutomation(); + + vm.expectEmit(true, false, false, false); + emit AutomationCore.AutomationEnabled(true); + + vm.prank(admin); + automationCore.enableAutomation(); + } + + /// @dev Test to ensure 'enableAutomation' reverts if automation is already enabled. + function testEnableAutomationRevertsIfAlreadyEnabled() public { + // Already enabled in initialize() + vm.expectRevert(IAutomationCore.AlreadyEnabled.selector); + + vm.prank(admin); + automationCore.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); + automationCore.enableAutomation(); + } + + // :::::::::::::::::::::::::::::::::::::::::::::::::::::: 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))); + 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 'setColdWallet' :::::::::::::::::::::::::::::::::::::::::::::::::::::: + + /// @dev Test to ensure 'setColdWallet' updates the cold wallet address. + function testSetColdWallet() public { + vm.prank(admin); + automationCore.setColdWallet(address(0x1001)); + + assertEq(automationCore.getColdWallet(), address(0x1001)); + } + + /// @dev Test to ensure 'setColdWallet' emits event 'ColdWalletUpdated'. + function testSetColdWalletEmitsEvent() public { + address oldColdWallet = automationCore.getColdWallet(); + + vm.expectEmit(true, true, false, false); + emit AutomationCore.ColdWalletUpdated(oldColdWallet, address(0x1001)); + + vm.prank(admin); + automationCore.setColdWallet(address(0x1001)); + } + + /// @dev Test to ensure 'setColdWallet' reverts if zero address is passed. + function testSetColdWalletRevertsIfZeroAddress() public { + vm.expectRevert(IAutomationCore.AddressCannotBeZero.selector); + + vm.prank(admin); + automationCore.setColdWallet(address(0)); + } + + /// @dev Test to ensure 'setColdWallet' reverts if caller is not owner. + function testSetColdWalletRevertsIfNotOwner() public { + vm.expectRevert(abi.encodeWithSelector(OwnableUpgradeable.OwnableUnauthorizedAccount.selector, alice)); + + vm.prank(alice); + automationCore.setColdWallet(address(0x1001)); + } + + // :::::::::::::::::::::::::::::::::::::::::::::::::::::: 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 cold wallet is not set. + function testWithdrawFeesRevertsIfColdWalletNotSet() public { + vm.prank(admin); + + vm.expectRevert(IAutomationCore.ColdWalletNotSet.selector); + automationCore.withdrawFees(1 ether); + } + + /// @dev Test to ensure 'withdrawFees' reverts if contract has insufficient balance. + function testWithdrawFeesRevertsIfInsufficientBalance() public { + // Set cold wallet + vm.prank(admin); + automationCore.setColdWallet(address(0x1001)); + + vm.expectRevert(IAutomationCore.InsufficientBalance.selector); + + vm.prank(admin); + automationCore.withdrawFees(1 ether); + } + + /// @dev Test to ensure 'withdrawFees' reverts if request amount exceeds the locked balance. + function testWithdrawFeesRevertsIfRequestExceedsLockedBalance() public { + registerUST(); + + // Set cold wallet + vm.prank(admin); + automationCore.setColdWallet(address(0x1001)); + + vm.expectRevert(IAutomationCore.RequestExceedsLockedBalance.selector); + + vm.prank(admin); + automationCore.withdrawFees(0.04 ether); + } + + /// @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); + } + + /// @dev Test to ensure 'withdrawFees' withdraws the requested amount and updates the balance. + function testWithdrawFees() public { + registerUST(); + + // Set cold wallet + address coldWallet = address(0x1001); + vm.prank(admin); + automationCore.setColdWallet(coldWallet); + + assertEq(erc20Supra.balanceOf(coldWallet), 0); + assertEq(erc20Supra.balanceOf(address(automationCore)), 0.502 ether); + + vm.prank(admin); + automationCore.withdrawFees(0.002 ether); + + assertEq(erc20Supra.balanceOf(coldWallet), 0.002 ether); + assertEq(erc20Supra.balanceOf(address(automationCore)), 0.5 ether); + } + + /// @dev Test to ensure 'withdrawFees' emits event 'RegistryFeeWithdrawn'. + function testWithdrawFeesEmitsEvent() public { + registerUST(); + + // Set cold wallet + address coldWallet = address(0x1001); + vm.prank(admin); + automationCore.setColdWallet(coldWallet); + + vm.expectEmit(true, true, false, false); + emit AutomationCore.RegistryFeeWithdrawn(coldWallet, 0.002 ether); + + vm.prank(admin); + automationCore.withdrawFees(0.002 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), + keccak256("txHash"), + uint128(1_000_000), + uint128(10 gwei), + uint128(0.5 ether), + 4, + 0, + 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 index c7ad389595..4c4377eb93 100644 --- a/solidity/supra_contracts/test/AutomationRegistry.t.sol +++ b/solidity/supra_contracts/test/AutomationRegistry.t.sol @@ -6,17 +6,20 @@ import {ERC1967Proxy} from "../lib/openzeppelin-contracts/contracts/proxy/ERC196 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 { - AutomationRegistry impl; // implementation logic contract - AutomationRegistry registry; // registry instance on proxy address - ERC1967Proxy proxy; // proxy contract ERC20Supra erc20Supra; // ERC20Supra contract + AutomationCore automationCore; // AutomationCore instance on proxy address + AutomationRegistry registry; // AutomationRegistry instance on proxy address + address controller; // AutomationController proxy address address admin = address(0xA11CE); address vmSigner = address(0x53555000); @@ -25,17 +28,16 @@ contract AutomationRegistryTest is Test { /// @dev Sets up initial state for testing. /// @dev Sets balance of 'alice' to 100 ether. - /// @dev Deploys ERC20Supra and AutomationRegistry contracts. - /// @dev Initializes AutomationRegistry with required parameters. + /// @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); - impl = new AutomationRegistry(); - bytes memory initData = abi.encodeCall( - AutomationRegistry.initialize, + AutomationCore automationCoreImpl = new AutomationCore(); + bytes memory automationCoreInitData = abi.encodeCall( + AutomationCore.initialize, ( 3600, // taskDurationCapSecs 10_000_000, // registryMaxGasCap @@ -53,429 +55,61 @@ contract AutomationRegistryTest is Test { 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))); + ERC1967Proxy controllerProxy = new ERC1967Proxy(address(controllerImpl), controllerInitData); + controller = address(controllerProxy); + + automationCore.setAutomationRegistry(address(registry)); + automationCore.setAutomationController(controller); + + registry.setAutomationController(controller); - proxy = new ERC1967Proxy(address(impl), initData); - registry = AutomationRegistry(address(proxy)); vm.stopPrank(); } /// @dev Test to ensure all state variables are initialized correctly. function testInitialize() public view { assertEq(registry.owner(), admin); - - assertEq(registry.getNextCycleRegistryMaxGasCap(), 10_000_000); - assertEq(registry.getNextCycleSysRegistryMaxGasCap(), 5_000_000); - assertEq(registry.getAutomationController(), address(0)); - assertTrue(registry.isRegistrationEnabled()); - assertTrue(registry.isAutomationEnabled()); - assertEq(registry.getVmSigner(), vmSigner); - assertEq(registry.erc20Supra(), address(erc20Supra)); - - LibRegistry.ConfigDetails memory config = registry.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); - - assertEq(registry.owner(), admin); + assertEq(registry.automationCore(), address(automationCore)); + assertEq(registry.automationController(), controller); } /// @dev Test to ensure reinitialization fails. function testInitializeRevertsIfReinitialized() public { + AutomationCore automationCoreImplementation = new AutomationCore(); + vm.expectRevert(Initializable.InvalidInitialization.selector); vm.prank(admin); - registry.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 { - AutomationRegistry implementation = new AutomationRegistry(); - - bytes memory initData = abi.encodeCall( - AutomationRegistry.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(IAutomationRegistry.AddressCannotBeZero.selector); - new ERC1967Proxy(address(implementation), initData); + registry.initialize(address(automationCoreImplementation)); } - /// @dev Test to ensure initialization fails if ERC20Supra address is zero. - function testInitializeRevertsIfErc20SupraIsZero() public { + /// @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, - ( - 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 - ) - ); + bytes memory initData = abi.encodeCall(AutomationRegistry.initialize, (address(0))); - vm.expectRevert(IAutomationRegistry.AddressCannotBeZero.selector); + vm.expectRevert(CommonUtils.AddressCannotBeZero.selector); new ERC1967Proxy(address(implementation), initData); } - /// @dev Test to ensure initialization fails if EOA is passed as ERC20Supra address. - function testInitializeRevertsIfErc20SupraIsEoa() public { - AutomationRegistry implementation = new AutomationRegistry(); - - bytes memory initData = abi.encodeCall( - AutomationRegistry.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(IAutomationRegistry.AddressCannotBeEOA.selector); - new ERC1967Proxy(address(implementation), initData); - } - - /// @dev Test to ensure initialization fails if task duration is <= cycle duration. - function testInitializeRevertsIfInvalidTaskDuration() public { - AutomationRegistry implementation = new AutomationRegistry(); - - bytes memory initData = abi.encodeCall( - AutomationRegistry.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(IAutomationRegistry.InvalidTaskDuration.selector); - new ERC1967Proxy(address(implementation), initData); - } - - /// @dev Test to ensure initialization fails if registry max gas cap is zero. - function testInitializeRevertsIfRegistryMaxGasCapZero() public { - AutomationRegistry implementation = new AutomationRegistry(); - - bytes memory initData = abi.encodeCall( - AutomationRegistry.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(IAutomationRegistry.InvalidRegistryMaxGasCap.selector); - new ERC1967Proxy(address(implementation), initData); - } - - /// @dev Test to ensure initialization fails if congestion threshold percentage is > 100. - function testInitializeRevertsIfInvalidCongestionThreshold() public { - AutomationRegistry implementation = new AutomationRegistry(); - - bytes memory initData = abi.encodeCall( - AutomationRegistry.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(IAutomationRegistry.InvalidCongestionThreshold.selector); - new ERC1967Proxy(address(implementation), initData); - } - - /// @dev Test to ensure initialization fails if congestion exponent is 0. - function testInitializeRevertsIfCongestionExponentZero() public { - AutomationRegistry implementation = new AutomationRegistry(); - - bytes memory initData = abi.encodeCall( - AutomationRegistry.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(IAutomationRegistry.InvalidCongestionExponent.selector); - new ERC1967Proxy(address(implementation), initData); - } - - /// @dev Test to ensure initialization fails if task capacity is 0. - function testInitializeRevertsIfTaskCapacityZero() public { - AutomationRegistry implementation = new AutomationRegistry(); - - bytes memory initData = abi.encodeCall( - AutomationRegistry.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(IAutomationRegistry.InvalidTaskCapacity.selector); - new ERC1967Proxy(address(implementation), initData); - } - - /// @dev Test to ensure initialization fails if cycle duration is 0. - function testInitializeRevertsIfCycleDurationZero() public { - AutomationRegistry implementation = new AutomationRegistry(); - - bytes memory initData = abi.encodeCall( - AutomationRegistry.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(IAutomationRegistry.InvalidCycleDuration.selector); - new ERC1967Proxy(address(implementation), initData); - } - - /// @dev Test to ensure initialization fails if system task duration is <= cycle duration. - function testInitializeRevertsIfInvalidSysTaskDuration() public { - AutomationRegistry implementation = new AutomationRegistry(); - - bytes memory initData = abi.encodeCall( - AutomationRegistry.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(IAutomationRegistry.InvalidSysTaskDuration.selector); - new ERC1967Proxy(address(implementation), initData); - } - - /// @dev Test to ensure initialization fails if system registry max gas cap is 0. - function testInitializeRevertsIfSysRegistryMaxGasCapZero() public { - AutomationRegistry implementation = new AutomationRegistry(); - - bytes memory initData = abi.encodeCall( - AutomationRegistry.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(IAutomationRegistry.InvalidSysRegistryMaxGasCap.selector); - new ERC1967Proxy(address(implementation), initData); - } - - /// @dev Test to ensure initialization fails if system task capacity is 0. - function testInitializeRevertsIfSysTaskCapacityZero() public { + /// @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)); - bytes memory initData = abi.encodeCall( - AutomationRegistry.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(IAutomationRegistry.InvalidSysTaskCapacity.selector); + vm.expectRevert(CommonUtils.AddressCannotBeEOA.selector); new ERC1967Proxy(address(implementation), initData); } - - // :::::::::::::::::::::::::::::::::::::::::::::::::::::: Tests related to 'disableRegistration' :::::::::::::::::::::::::::::::::::::::::::::::::::::: - - /// @dev Test to ensure 'disableRegistration' disables the registration. - function testDisableRegistration() public { - vm.prank(admin); - registry.disableRegistration(); - - assertFalse(registry.isRegistrationEnabled()); - } - - /// @dev Test to ensure 'disableRegistration' emits event 'TaskRegistrationDisabled'. - function testDisableRegistrationEmitsEvent() public { - vm.expectEmit(true, false, false, false); - emit AutomationRegistry.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(IAutomationRegistry.AlreadyDisabled.selector); - - vm.prank(admin); - registry.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); - registry.disableRegistration(); - } - - // :::::::::::::::::::::::::::::::::::::::::::::::::::::: Tests related to 'enableRegistration' :::::::::::::::::::::::::::::::::::::::::::::::::::::: - - /// @dev Test to ensure 'enableRegistration' enables the registration. - function testEnableRegistration() public { - // Disable registration - testDisableRegistration(); - - // Enable registration - vm.prank(admin); - registry.enableRegistration(); - - assertTrue(registry.isRegistrationEnabled()); - } - - /// @dev Test to ensure 'enableRegistration' emits event 'TaskRegistrationEnabled'. - function testEnableRegistrationEmitsEvent() public { - // Disable registration - testDisableRegistration(); - - vm.expectEmit(true, false, false, false); - emit AutomationRegistry.TaskRegistrationEnabled(true); - - // Enable registration - vm.prank(admin); - registry.enableRegistration(); - } - - /// @dev Test to ensure 'enableRegistration' reverts if registration is already enabled. - function testEnableRegistrationRevertsIfAlreadyEnabled() public { - // Already enabled in initialize() - vm.expectRevert(IAutomationRegistry.AlreadyEnabled.selector); - - vm.prank(admin); - registry.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); - registry.enableRegistration(); - } - - // :::::::::::::::::::::::::::::::::::::::::::::::::::::: Tests related to 'disableAutomation' :::::::::::::::::::::::::::::::::::::::::::::::::::::: - - /// @dev Test to ensure 'disableAutomation' disables the automation. - function testDisableAutomation() public { - testSetAutomationController(); - - // Already enabled in initialize() - vm.prank(admin); - registry.disableAutomation(); - - assertFalse(registry.isAutomationEnabled()); - } - - /// @dev Test to ensure 'disableAutomation' emits event 'AutomationDisabled'. - function testDisableAutomationEmitsEvent() public { - testSetAutomationController(); - - vm.expectEmit(true, false, false, false); - emit AutomationRegistry.AutomationDisabled(false); - - vm.prank(admin); - registry.disableAutomation(); - } - - /// @dev Test to ensure 'disableAutomation' reverts if automation is already disabled. - function testDisableAutomationRevertsIfAlreadyDisabled() public { - // Disable automation - testDisableAutomation(); - - // Disable again → revert - vm.expectRevert(IAutomationRegistry.AlreadyDisabled.selector); - - vm.prank(admin); - registry.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); - registry.disableAutomation(); - } - - // :::::::::::::::::::::::::::::::::::::::::::::::::::::: Tests related to 'enableAutomation' :::::::::::::::::::::::::::::::::::::::::::::::::::::: - - /// @dev Test to ensure 'enableAutomation' enables the automation. - function testEnableAutomation() public { - // Disable automation - testDisableAutomation(); - - // Enable automation - vm.prank(admin); - registry.enableAutomation(); - - assertTrue(registry.isAutomationEnabled()); - } - - /// @dev Test to ensure 'enableAutomation' emits event 'AutomationEnabled'. - function testEnableAutomationEmitsEvent() public { - // Disable automation - testDisableAutomation(); - - vm.expectEmit(true, false, false, false); - emit AutomationRegistry.AutomationEnabled(true); - - vm.prank(admin); - registry.enableAutomation(); - } - - /// @dev Test to ensure 'enableAutomation' reverts if automation is already enabled. - function testEnableAutomationRevertsIfAlreadyEnabled() public { - // Already enabled in initialize() - vm.expectRevert(IAutomationRegistry.AlreadyEnabled.selector); - - vm.prank(admin); - registry.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); - registry.enableAutomation(); - } // :::::::::::::::::::::::::::::::::::::::::::::::::::::: Tests related to 'setAutomationController' :::::::::::::::::::::::::::::::::::::::::::::::::::::: @@ -483,7 +117,7 @@ contract AutomationRegistryTest is Test { function deployAutomationController() internal returns (address) { // Deploy AutomationController proxy AutomationController controllerImpl = new AutomationController(); - bytes memory controllerInitData = abi.encodeCall(AutomationController.initialize,(address(registry))); + bytes memory controllerInitData = abi.encodeCall(AutomationController.initialize,(address(automationCore), address(registry))); ERC1967Proxy controllerProxy = new ERC1967Proxy(address(controllerImpl), controllerInitData); return address(controllerProxy); @@ -491,39 +125,39 @@ contract AutomationRegistryTest is Test { /// @dev Test to ensure 'setAutomationController' updates the automation controller address. function testSetAutomationController() public { - address controller = deployAutomationController(); + address controllerAddr = deployAutomationController(); vm.prank(admin); - registry.setAutomationController(controller); + registry.setAutomationController(controllerAddr); - assertEq(registry.getAutomationController(), controller); + assertEq(registry.automationController(), controllerAddr); } /// @dev Test to ensure 'setAutomationController' emits event 'AutomationControllerUpdated'. function testSetAutomationControllerEmitsEvent() public { - address oldController = registry.getAutomationController(); - address controller = deployAutomationController(); + address oldController = registry.automationController(); + address controllerAddr = deployAutomationController(); vm.expectEmit(true, true, false, false); - emit AutomationRegistry.AutomationControllerUpdated(oldController, controller); + emit AutomationRegistry.AutomationControllerUpdated(oldController, controllerAddr); vm.prank(admin); - registry.setAutomationController(controller); + registry.setAutomationController(controllerAddr); } /// @dev Test to ensure 'setAutomationController' reverts if caller is not owner. function testSetAutomationControllerRevertsIfNotOwner() public { - address controller = deployAutomationController(); + address controllerAddr = deployAutomationController(); vm.expectRevert(abi.encodeWithSelector(OwnableUpgradeable.OwnableUnauthorizedAccount.selector,alice)); vm.prank(alice); - registry.setAutomationController(controller); + registry.setAutomationController(controllerAddr); } /// @dev Test to ensure 'setAutomationController' reverts if zero address is passed. function testSetAutomationControllerRevertsIfZeroAddress() public { - vm.expectRevert(IAutomationRegistry.AddressCannotBeZero.selector); + vm.expectRevert(CommonUtils.AddressCannotBeZero.selector); vm.prank(admin); registry.setAutomationController(address(0)); @@ -531,139 +165,12 @@ contract AutomationRegistryTest is Test { /// @dev Test to ensure 'setAutomationController' reverts if EOA is passed. function testSetAutomationControllerRevertsIfEoa() public { - vm.expectRevert(IAutomationRegistry.AddressCannotBeEOA.selector); + vm.expectRevert(CommonUtils.AddressCannotBeEOA.selector); vm.prank(admin); registry.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); - registry.setVmSigner(newVmSigner); - - assertEq(registry.getVmSigner(), newVmSigner); - } - - /// @dev Test to ensure 'setVmSigner' emits event 'VmSignerUpdated'. - function testSetVmSignerEmitsEvent() public { - address oldVmSigner = registry.getVmSigner(); - address newVmSigner = address(0x100); - - vm.expectEmit(true, true, false, false); - emit AutomationRegistry.VmSignerUpdated(oldVmSigner, newVmSigner); - - vm.prank(admin); - registry.setVmSigner(newVmSigner); - } - - /// @dev Test to ensure 'setVmSigner' reverts if zero address is passed. - function testSetVmSignerRevertsIfZeroAddress() public { - vm.expectRevert(IAutomationRegistry.AddressCannotBeZero.selector); - - vm.prank(admin); - registry.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); - registry.setVmSigner(address(0x100)); - } - - // :::::::::::::::::::::::::::::::::::::::::::::::::::::: Tests related to 'setErc20Supra' :::::::::::::::::::::::::::::::::::::::::::::::::::::: - - /// @dev Test to ensure 'setErc20Supra' updates the ERC20Supra address. - function testSetErc20Supra() public { - ERC20Supra supra = new ERC20Supra(msg.sender); - - vm.prank(admin); - registry.setErc20Supra(address(supra)); - - assertEq(registry.erc20Supra(), address(supra)); - } - - /// @dev Test to ensure 'setErc20Supra' emits event 'Erc20SupraUpdated'. - function testSetErc20SupraEmitsEvent() public { - address oldAddr = registry.erc20Supra(); - ERC20Supra supra = new ERC20Supra(msg.sender); - - vm.expectEmit(true, true, false, false); - emit AutomationRegistry.Erc20SupraUpdated(oldAddr, address(supra)); - - vm.prank(admin); - registry.setErc20Supra(address(supra)); - } - - /// @dev Test to ensure 'setErc20Supra' reverts if zero address is passed. - function testSetErc20SupraRevertsIfZeroAddress() public { - vm.expectRevert(IAutomationRegistry.AddressCannotBeZero.selector); - - vm.prank(admin); - registry.setErc20Supra(address(0)); - } - - /// @dev Test to ensure 'setErc20Supra' reverts if EOA is passed. - function testSetErc20SupraRevertsIfEoa() public { - vm.expectRevert(IAutomationRegistry.AddressCannotBeEOA.selector); - - vm.prank(admin); - registry.setErc20Supra(alice); - } - - /// @dev Test to ensure 'setErc20Supra' reverts if caller is not owner. - function testSetErc20SupraRevertsIfNotOwner() public { - ERC20Supra supra = new ERC20Supra(msg.sender); - - vm.expectRevert(abi.encodeWithSelector(OwnableUpgradeable.OwnableUnauthorizedAccount.selector, alice)); - - vm.prank(alice); - registry.setErc20Supra(address(supra)); - } - - // :::::::::::::::::::::::::::::::::::::::::::::::::::::: Tests related to 'setColdWallet' :::::::::::::::::::::::::::::::::::::::::::::::::::::: - - /// @dev Test to ensure 'setColdWallet' updates the cold wallet address. - function testSetColdWallet() public { - vm.prank(admin); - registry.setColdWallet(address(0x1001)); - - assertEq(registry.getColdWallet(), address(0x1001)); - } - - /// @dev Test to ensure 'setColdWallet' emits event 'ColdWalletUpdated'. - function testSetColdWalletEmitsEvent() public { - address oldColdWallet = registry.getColdWallet(); - - vm.expectEmit(true, true, false, false); - emit AutomationRegistry.ColdWalletUpdated(oldColdWallet, address(0x1001)); - - vm.prank(admin); - registry.setColdWallet(address(0x1001)); - } - - /// @dev Test to ensure 'setColdWallet' reverts if zero address is passed. - function testSetColdWalletRevertsIfZeroAddress() public { - vm.expectRevert(IAutomationRegistry.AddressCannotBeZero.selector); - - vm.prank(admin); - registry.setColdWallet(address(0)); - } - - /// @dev Test to ensure 'setColdWallet' reverts if caller is not owner. - function testSetColdWalletRevertsIfNotOwner() public { - vm.expectRevert(abi.encodeWithSelector(OwnableUpgradeable.OwnableUnauthorizedAccount.selector, alice)); - - vm.prank(alice); - registry.setColdWallet(address(0x1001)); - } - // :::::::::::::::::::::::::::::::::::::::::::::::::::::: Tests related to 'grantAuthorization' :::::::::::::::::::::::::::::::::::::::::::::::::::::: /// @dev Test to ensure 'grantAuthorization' grants authorization to an address. @@ -744,207 +251,24 @@ contract AutomationRegistryTest is Test { registry.revokeAuthorization(bob); } - // :::::::::::::::::::::::::::::::::::::::::::::::::::::: Tests related to 'updateConfigBuffer' :::::::::::::::::::::::::::::::::::::::::::::::::::::: - - /// @dev Helper function that returns a valid config. - function validConfig() internal pure returns (LibRegistry.ConfigDetails memory cfg) { - cfg = LibRegistry.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 { - LibRegistry.ConfigDetails memory cfg = validConfig(); - - vm.prank(admin); - registry.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 - LibRegistry.ConfigDetails memory pendingCfg = registry.getPendingConfig(); - assertTrue(registry.ifConfigBufferExists()); - 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 { - LibRegistry.ConfigDetails memory cfg = validConfig(); - - vm.expectEmit(true, false, false, false); - emit AutomationRegistry.ConfigBufferUpdated(cfg); - - vm.prank(admin); - registry.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 { - LibRegistry.ConfigDetails memory cfg = validConfig(); - - vm.expectRevert(abi.encodeWithSelector(OwnableUpgradeable.OwnableUnauthorizedAccount.selector,alice)); - - vm.prank(alice); - registry.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 cold wallet is not set. - function testWithdrawFeesRevertsIfColdWalletNotSet() public { - vm.prank(admin); - - vm.expectRevert(IAutomationRegistry.ColdWalletNotSet.selector); - registry.withdrawFees(1 ether); - } - - /// @dev Test to ensure 'withdrawFees' reverts if contract has insufficient balance. - function testWithdrawFeesRevertsIfInsufficientBalance() public { - // Set cold wallet - vm.prank(admin); - registry.setColdWallet(address(0x1001)); - - vm.expectRevert(IAutomationRegistry.InsufficientBalance.selector); - - vm.prank(admin); - registry.withdrawFees(1 ether); - } - - /// @dev Test to ensure 'withdrawFees' reverts if request amount exceeds the locked balance. - function testWithdrawFeesRevertsIfRequestExceedsLockedBalance() public { - testRegister(); - - // Set cold wallet - vm.prank(admin); - registry.setColdWallet(address(0x1001)); - - vm.expectRevert(IAutomationRegistry.RequestExceedsLockedBalance.selector); - - vm.prank(admin); - registry.withdrawFees(0.04 ether); - } - - /// @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); - registry.withdrawFees(1 ether); - } - - /// @dev Test to ensure 'withdrawFees' withdraws the requested amount and updates the balance. - function testWithdrawFees() public { - testRegister(); - - // Set cold wallet - address coldWallet = address(0x1001); - vm.prank(admin); - registry.setColdWallet(coldWallet); - - assertEq(erc20Supra.balanceOf(coldWallet), 0); - assertEq(erc20Supra.balanceOf(address(registry)), 0.502 ether); - - vm.prank(admin); - registry.withdrawFees(0.002 ether); - - assertEq(erc20Supra.balanceOf(coldWallet), 0.002 ether); - assertEq(erc20Supra.balanceOf(address(registry)), 0.5 ether); - } - - /// @dev Test to ensure 'withdrawFees' emits event 'RegistryFeeWithdrawn'. - function testWithdrawFeesEmitsEvent() public { - testRegister(); - - // Set cold wallet - address coldWallet = address(0x1001); - vm.prank(admin); - registry.setColdWallet(coldWallet); - - vm.expectEmit(true, true, false, false); - emit AutomationRegistry.RegistryFeeWithdrawn(coldWallet, 0.002 ether); - - vm.prank(admin); - registry.withdrawFees(0.002 ether); - } - // :::::::::::::::::::::::::::::::::::::::::::::::::::::: 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) { - LibRegistry.AccessListEntry[] memory accessList = new LibRegistry.AccessListEntry[](2); + 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] = LibRegistry.AccessListEntry({ + accessList[0] = LibConfig.AccessListEntry({ addr: address(0x1111), storageKeys: keys }); - accessList[1] = LibRegistry.AccessListEntry({ + accessList[1] = LibConfig.AccessListEntry({ addr: address(0x2222), storageKeys: keys }); @@ -957,11 +281,9 @@ contract AutomationRegistryTest is Test { /// @dev Test to ensure 'register' reverts if automation is not enabled. function testRegisterRevertsIfAutomationNotEnabled() public { - testSetAutomationController(); - // Disable automation vm.prank(admin); - registry.disableAutomation(); + automationCore.disableAutomation(); bytes[] memory auxData; bytes memory payload = createPayload(0, address(erc20Supra)); @@ -986,12 +308,12 @@ contract AutomationRegistryTest is Test { function testRegisterRevertsIfRegistrationDisabled() public { // Disable registration vm.prank(admin); - registry.disableRegistration(); + automationCore.disableRegistration(); bytes[] memory auxData; bytes memory payload = createPayload(0, address(erc20Supra)); - vm.expectRevert(IAutomationRegistry.RegistrationDisabled.selector); + vm.expectRevert(IAutomationCore.RegistrationDisabled.selector); vm.prank(alice); registry.register( @@ -1009,11 +331,10 @@ contract AutomationRegistryTest is Test { /// @dev Test to ensure 'register' reverts if task type is not UST. function testRegisterRevertsIfTaskTypeNotUST() public { - testSetAutomationController(); bytes[] memory auxData; bytes memory payload = createPayload(0, address(erc20Supra)); - vm.expectRevert(IAutomationRegistry.InvalidTaskType.selector); + vm.expectRevert(IAutomationCore.InvalidTaskType.selector); vm.prank(alice); registry.register( @@ -1031,11 +352,10 @@ contract AutomationRegistryTest is Test { /// @dev Test to ensure 'register' reverts if expiry time is equal to or less than registration time. function testRegisterRevertsIfInvalidExpiryTime() public { - testSetAutomationController(); bytes[] memory auxData; bytes memory payload = createPayload(0, address(erc20Supra)); - vm.expectRevert(IAutomationRegistry.InvalidExpiryTime.selector); + vm.expectRevert(IAutomationCore.InvalidExpiryTime.selector); vm.prank(alice); registry.register( @@ -1053,11 +373,10 @@ contract AutomationRegistryTest is Test { /// @dev Test to ensure 'register' reverts if task duration is greater than the task duration cap. function testRegisterRevertsIfInvalidTaskDuration() public { - testSetAutomationController(); bytes[] memory auxData; bytes memory payload = createPayload(0, address(erc20Supra)); - vm.expectRevert(IAutomationRegistry.InvalidTaskDuration.selector); + vm.expectRevert(IAutomationCore.InvalidTaskDuration.selector); vm.prank(alice); registry.register( @@ -1075,11 +394,10 @@ contract AutomationRegistryTest is Test { /// @dev Test to ensure 'register' reverts if task expires before the next cycle. function testRegisterRevertsIfTaskExpiresBeforeNextCycle() public { - testSetAutomationController(); bytes[] memory auxData; bytes memory payload = createPayload(0, address(erc20Supra)); - vm.expectRevert(IAutomationRegistry.TaskExpiresBeforeNextCycle.selector); + vm.expectRevert(IAutomationCore.TaskExpiresBeforeNextCycle.selector); vm.prank(alice); registry.register( @@ -1097,11 +415,10 @@ contract AutomationRegistryTest is Test { /// @dev Test to ensure 'register' reverts if payload target address is zero. function testRegisterRevertsIfPayloadTargetZero() public { - testSetAutomationController(); bytes[] memory auxData; bytes memory payload = createPayload(0, address(0)); // Invalid address: address(0) - vm.expectRevert(IAutomationRegistry.AddressCannotBeZero.selector); + vm.expectRevert(CommonUtils.AddressCannotBeZero.selector); vm.prank(alice); registry.register( @@ -1119,11 +436,10 @@ contract AutomationRegistryTest is Test { /// @dev Test to ensure 'register' reverts if payload target address is EOA. function testRegisterRevertsIfPayloadTargetEoa() public { - testSetAutomationController(); bytes[] memory auxData; bytes memory payload = createPayload(0, alice); // Invalid address: EOA address being passed - vm.expectRevert(IAutomationRegistry.AddressCannotBeEOA.selector); + vm.expectRevert(CommonUtils.AddressCannotBeEOA.selector); vm.prank(alice); registry.register( @@ -1141,11 +457,10 @@ contract AutomationRegistryTest is Test { /// @dev Test to ensure 'register' reverts if 0 is passed as max gas amount. function testRegisterRevertsIfMaxGasAmountZero() public { - testSetAutomationController(); bytes[] memory auxData; bytes memory payload = createPayload(0, address(erc20Supra)); - vm.expectRevert(IAutomationRegistry.InvalidMaxGasAmount.selector); + vm.expectRevert(IAutomationCore.InvalidMaxGasAmount.selector); vm.prank(alice); registry.register( @@ -1163,11 +478,10 @@ contract AutomationRegistryTest is Test { /// @dev Test to ensure 'register' reverts if 0 is passed as gas price cap. function testRegisterRevertsIfGasPriceCapZero() public { - testSetAutomationController(); bytes[] memory auxData; bytes memory payload = createPayload(0, address(erc20Supra)); - vm.expectRevert(IAutomationRegistry.InvalidGasPriceCap.selector); + vm.expectRevert(IAutomationCore.InvalidGasPriceCap.selector); vm.prank(alice); registry.register( @@ -1185,11 +499,10 @@ contract AutomationRegistryTest is Test { /// @dev Test to ensure 'register' reverts if transaction hash is bytes32(0). function testRegisterRevertsIfInvalidTxHash() public { - testSetAutomationController(); bytes[] memory auxData; bytes memory payload = createPayload(0, address(erc20Supra)); - vm.expectRevert(IAutomationRegistry.InvalidTxHash.selector); + vm.expectRevert(IAutomationCore.InvalidTxHash.selector); vm.prank(alice); registry.register( @@ -1205,44 +518,42 @@ contract AutomationRegistryTest is Test { ); } - /// @dev Test to ensure 'register' reverts if gas committed exceeds the registry max gas cap. - function testRegisterRevertsIfGasCommittedExceedsMaxGasCap() public { - testSetAutomationController(); + /// @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)); + bytes memory payload = createPayload(0, address(erc20Supra)); - vm.expectRevert(IAutomationRegistry.GasCommittedExceedsMaxGasCap.selector); + vm.expectRevert(IAutomationCore.InsufficientFeeCapForCycle.selector); vm.prank(alice); registry.register( payload, uint64(block.timestamp + 2250), keccak256("txHash"), - uint128(10_000_001), // Gas exceeds max gas cap + uint128(1_000_000), uint128(10 gwei), - uint128(0.5 ether), + uint128(0), // automationFeeCapForCycle 0, 0, auxData ); } - /// @dev Test to ensure 'register' reverts if automation fee cap is less than the estimated automation fee. - function testRegisterRevertsIfAutomationFeeCapLessThanEstimated() public { - testSetAutomationController(); + /// @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)); + bytes memory payload = createPayload(0, address(erc20Supra)); - vm.expectRevert(IAutomationRegistry.InsufficientFeeCapForCycle.selector); + vm.expectRevert(IAutomationCore.GasCommittedExceedsMaxGasCap.selector); vm.prank(alice); registry.register( payload, uint64(block.timestamp + 2250), keccak256("txHash"), - uint128(1_000_000), + uint128(10_000_001), // Gas exceeds max gas cap uint128(10 gwei), - uint128(0), // automationFeeCapForCycle + uint128(7.01 ether), 0, 0, auxData @@ -1251,13 +562,12 @@ contract AutomationRegistryTest is Test { /// @dev Test to ensure 'register' registers a UST. function testRegister() public { - testSetAutomationController(); bytes[] memory auxData; bytes memory payload = createPayload(0, address(erc20Supra)); vm.startPrank(alice); erc20Supra.nativeToErc20Supra{value: 5 ether}(); - erc20Supra.approve(address(registry), type(uint256).max); + erc20Supra.approve(address(automationCore), type(uint256).max); registry.register( payload, @@ -1277,8 +587,8 @@ contract AutomationRegistryTest is Test { assertEq(registry.totalTasks(), 1); assertEq(registry.getNextTaskIndex(), 1); assertEq(registry.getGasCommittedForNextCycle(), 1_000_000); - assertEq(registry.getTotalDepositedAutomationFees(), 0.5 ether); - assertEq(erc20Supra.balanceOf(address(registry)), 0.502 ether); + 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); @@ -1299,13 +609,12 @@ contract AutomationRegistryTest is Test { /// @dev Test to ensure 'register' emits event 'TaskRegistered'. function testRegisterEmitsEvent() public { - testSetAutomationController(); bytes[] memory auxData; bytes memory payload = createPayload(0, address(erc20Supra)); vm.startPrank(alice); erc20Supra.nativeToErc20Supra{value: 5 ether}(); - erc20Supra.approve(address(registry), type(uint256).max); + erc20Supra.approve(address(automationCore), type(uint256).max); CommonUtils.TaskDetails memory taskMetadata = CommonUtils.TaskDetails( 1_000_000, @@ -1343,17 +652,12 @@ contract AutomationRegistryTest is Test { // ::::::::::::::::::::::::::::::::::::::::::::::::::::: Tests related to 'registerSystemTask' ::::::::::::::::::::::::::::::::::::::::::::::::::::: - /// @dev Test to ensure 'registerSystemTask' reverts if automation is not enabled. - function testRegisterSystemTaskRevertsIfAutomationNotEnabled() public { - testSetAutomationController(); - - vm.prank(admin); - registry.disableAutomation(); - + /// @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.AutomationNotEnabled.selector); + vm.expectRevert(IAutomationRegistry.UnauthorizedAccount.selector); vm.prank(alice); registry.registerSystemTask( @@ -1367,17 +671,19 @@ contract AutomationRegistryTest is Test { ); } - /// @dev Test to ensure 'registerSystemTask' reverts if registration is disabled. - function testRegisterSystemTaskRevertsIfRegistrationDisabled() public { + /// @dev Test to ensure 'registerSystemTask' reverts if automation is not enabled. + function testRegisterSystemTaskRevertsIfAutomationNotEnabled() public { + testGrantAuthorization(); + vm.prank(admin); - registry.disableRegistration(); + automationCore.disableAutomation(); bytes[] memory auxData; - bytes memory payload = createPayload(0, address(erc20Supra)); + bytes memory payload = createPayload(0, address(erc20Supra)); - vm.expectRevert(IAutomationRegistry.RegistrationDisabled.selector); + vm.expectRevert(IAutomationRegistry.AutomationNotEnabled.selector); - vm.prank(alice); + vm.prank(bob); registry.registerSystemTask( payload, // payload uint64(block.timestamp + 2250), // expiryTime @@ -1389,15 +695,19 @@ contract AutomationRegistryTest is Test { ); } - /// @dev Test to ensure 'registerSystemTask' reverts if caller is not authorized. - function testRegisterSystemTaskRevertsIfUnauthorizedCaller() public { - testSetAutomationController(); + /// @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)); + bytes memory payload = createPayload(0, address(erc20Supra)); - vm.expectRevert(IAutomationRegistry.UnauthorizedAccount.selector); + vm.expectRevert(IAutomationCore.RegistrationDisabled.selector); - vm.prank(alice); + vm.prank(bob); registry.registerSystemTask( payload, // payload uint64(block.timestamp + 2250), // expiryTime @@ -1411,13 +721,12 @@ contract AutomationRegistryTest is Test { /// @dev Test to ensure 'registerSystemTask' reverts if task type is not GST. function testRegisterSystemTaskRevertsIfTaskTypeNotGST() public { - testSetAutomationController(); testGrantAuthorization(); bytes[] memory auxData; bytes memory payload = createPayload(0, address(erc20Supra)); - vm.expectRevert(IAutomationRegistry.InvalidTaskType.selector); + vm.expectRevert(IAutomationCore.InvalidTaskType.selector); vm.prank(bob); registry.registerSystemTask( @@ -1433,12 +742,11 @@ contract AutomationRegistryTest is Test { /// @dev Test to ensure 'registerSystemTask' reverts if task duration is greater than system task duration cap. function testRegisterSystemTaskRevertsIfInvalidTaskDuration() public { - testSetAutomationController(); testGrantAuthorization(); bytes[] memory auxData; bytes memory payload = createPayload(0, address(erc20Supra)); - vm.expectRevert(IAutomationRegistry.InvalidTaskDuration.selector); + vm.expectRevert(IAutomationCore.InvalidTaskDuration.selector); vm.prank(bob); registry.registerSystemTask( @@ -1454,12 +762,11 @@ contract AutomationRegistryTest is Test { /// @dev Test to ensure 'registerSystemTask' reverts if gas committed exceeds the system registry max gas cap. function testRegisterSystemTaskRevertsIfGasCommittedExceedsMaxGasCap() public { - testSetAutomationController(); testGrantAuthorization(); bytes[] memory auxData; bytes memory payload = createPayload(0, address(erc20Supra)); - vm.expectRevert(IAutomationRegistry.GasCommittedExceedsMaxGasCap.selector); + vm.expectRevert(IAutomationCore.GasCommittedExceedsMaxGasCap.selector); vm.prank(bob); registry.registerSystemTask( @@ -1475,7 +782,6 @@ contract AutomationRegistryTest is Test { /// @dev Test to ensure 'registerSystemTask' registers a GST. function testRegisterSystemTask() public { - testSetAutomationController(); testGrantAuthorization(); bytes[] memory auxData; bytes memory payload = createPayload(0, address(erc20Supra)); @@ -1517,7 +823,6 @@ contract AutomationRegistryTest is Test { /// @dev Test to ensure 'registerSystemTask' emits event 'SystemTaskRegistered'. function testRegisterSystemTaskEmitsEvent() public { - testSetAutomationController(); testGrantAuthorization(); bytes[] memory auxData; @@ -1559,10 +864,8 @@ contract AutomationRegistryTest is Test { /// @dev Test to ensure 'cancelTask' reverts if automation is not enabled. function testCancelTaskRevertsIfAutomationNotEnabled() public { - testSetAutomationController(); - vm.prank(admin); - registry.disableAutomation(); + automationCore.disableAutomation(); vm.expectRevert(IAutomationRegistry.AutomationNotEnabled.selector); @@ -1572,8 +875,6 @@ contract AutomationRegistryTest is Test { /// @dev Test to ensure 'cancelTask' reverts if task does not exist. function testCancelTaskRevertsIfTaskDoesNotExist() public { - testSetAutomationController(); - vm.expectRevert(IAutomationRegistry.TaskDoesNotExist.selector); vm.prank(alice); @@ -1582,8 +883,6 @@ contract AutomationRegistryTest is Test { /// @dev Test to ensure 'cancelTask' reverts if task type is not UST. function testCancelTaskRevertsIfTaskTypeNotUST() public { - testSetAutomationController(); - testRegisterSystemTask(); vm.expectRevert(IAutomationRegistry.UnsupportedTaskOperation.selector); @@ -1593,8 +892,6 @@ contract AutomationRegistryTest is Test { /// @dev Test to ensure 'cancelTask' reverts if caller is not the task owner. function testCancelTaskRevertsIfUnauthorizedCaller() public { - testSetAutomationController(); - testRegister(); vm.expectRevert(IAutomationRegistry.UnauthorizedAccount.selector); @@ -1604,7 +901,6 @@ contract AutomationRegistryTest is Test { /// @dev Test to ensure 'cancelTask' cancels a UST. function testCancelTask() public { - testSetAutomationController(); testRegister(); vm.prank(alice); @@ -1613,14 +909,13 @@ contract AutomationRegistryTest is Test { assertFalse(registry.ifTaskExists(0)); assertEq(registry.totalTasks(), 0); assertEq(registry.getGasCommittedForNextCycle(), 0); - assertEq(registry.getTotalDepositedAutomationFees(), 0); - assertEq(erc20Supra.balanceOf(address(registry)), 0.252 ether); + 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 { - testSetAutomationController(); testRegister(); vm.expectEmit(true, true, true, false); @@ -1634,10 +929,8 @@ contract AutomationRegistryTest is Test { /// @dev Test to ensure 'cancelSystemTask' reverts if automation is not enabled. function testCancelSystemTaskRevertsIfAutomationNotEnabled() public { - testSetAutomationController(); - vm.prank(admin); - registry.disableAutomation(); + automationCore.disableAutomation(); vm.expectRevert(IAutomationRegistry.AutomationNotEnabled.selector); @@ -1647,8 +940,6 @@ contract AutomationRegistryTest is Test { /// @dev Test to ensure 'cancelSystemTask' reverts if task does not exist. function testCancelSystemTaskRevertsIfTaskDoesNotExist() public { - testSetAutomationController(); - vm.expectRevert(IAutomationRegistry.TaskDoesNotExist.selector); vm.prank(alice); @@ -1657,8 +948,6 @@ contract AutomationRegistryTest is Test { /// @dev Test to ensure 'cancelSystemTask' reverts if task does not exist in system tasks. function testCancelSystemTaskRevertsIfSystemTaskDoesNotExist() public { - testSetAutomationController(); - testRegister(); vm.expectRevert(IAutomationRegistry.SystemTaskDoesNotExist.selector); @@ -1668,17 +957,15 @@ contract AutomationRegistryTest is Test { /// @dev Test to ensure 'cancelSystemTask' reverts if caller is not the task owner. function testCancelSystemTaskRevertsIfUnauthorizedCaller() public { - testSetAutomationController(); - testRegisterSystemTask(); vm.expectRevert(IAutomationRegistry.UnauthorizedAccount.selector); vm.prank(alice); registry.cancelSystemTask(0); } + /// @dev Test to ensure 'cancelSystemTask' cancels a GST. function testCancelSystemTask() public { - testSetAutomationController(); testRegisterSystemTask(); vm.prank(bob); @@ -1693,7 +980,6 @@ contract AutomationRegistryTest is Test { /// @dev Test to ensure 'cancelSystemTask' emits event 'TaskCancelled'. function testCancelSystemTaskEmitsEvent() public { - testSetAutomationController(); testRegisterSystemTask(); vm.expectEmit(true, true, true, false); @@ -1707,10 +993,8 @@ contract AutomationRegistryTest is Test { /// @dev Test to ensure 'stopTasks' reverts if automation is not enabled. function testStopTasksRevertsIfAutomationNotEnabled() public { - testSetAutomationController(); - vm.prank(admin); - registry.disableAutomation(); + automationCore.disableAutomation(); uint64[] memory taskIndexes; vm.expectRevert(IAutomationRegistry.AutomationNotEnabled.selector); @@ -1721,8 +1005,6 @@ contract AutomationRegistryTest is Test { /// @dev Test to ensure 'stopTasks' reverts if input array is empty. function testStopTasksRevertsIfInputArrayEmpty() public { - testSetAutomationController(); - uint64[] memory taskIndexes; vm.expectRevert(IAutomationRegistry.TaskIndexesCannotBeEmpty.selector); @@ -1732,7 +1014,6 @@ contract AutomationRegistryTest is Test { /// @dev Test to ensure 'stopTasks' reverts if caller is not the task owner. function testStopTasksRevertsIfUnauthorizedCaller() public { - testSetAutomationController(); testRegister(); uint64[] memory taskIndexes = new uint64[](1); @@ -1746,7 +1027,6 @@ contract AutomationRegistryTest is Test { /// @dev Test to ensure 'stopTasks' reverts if task type is not UST. function testStopTasksRevertsIfTaskTypeNotUST() public { - testSetAutomationController(); testRegisterSystemTask(); uint64[] memory taskIndexes = new uint64[](1); @@ -1760,7 +1040,6 @@ contract AutomationRegistryTest is Test { /// @dev Test to ensure 'stopTasks' does nothing if task does not exist. function testStopTasksDoesNothingIfTaskDoesNotExist() public { - testSetAutomationController(); testRegister(); uint64[] memory taskIndexes = new uint64[](1); @@ -1770,24 +1049,24 @@ contract AutomationRegistryTest is Test { registry.stopTasks(taskIndexes); assertEq(registry.totalTasks(), 1); - assertEq(registry.getTotalDepositedAutomationFees(), 0.5 ether); + assertEq(automationCore.getTotalDepositedAutomationFees(), 0.5 ether); } /// @dev Test to ensure 'stopTasks' stops the input UST tasks. function testStopTasks() public { testRegister(); - address controller = registry.getAutomationController(); + address controllerAddr = registry.automationController(); uint64[] memory taskIndexes = new uint64[](1); taskIndexes[0] = 0; vm.warp(2002); vm.startPrank(vmSigner, vmSigner); - AutomationController(controller).monitorCycleEnd(); - AutomationController(controller).processTasks(2, taskIndexes); + AutomationController(controllerAddr).monitorCycleEnd(); + AutomationController(controllerAddr).processTasks(2, taskIndexes); vm.stopPrank(); - assertEq(erc20Supra.balanceOf(address(registry)), 0.702 ether); + assertEq(erc20Supra.balanceOf(address(automationCore)), 0.702 ether); assertEq(erc20Supra.balanceOf(alice), 4.298 ether); vm.prank(alice); @@ -1796,23 +1075,23 @@ contract AutomationRegistryTest is Test { assertFalse(registry.ifTaskExists(0)); assertEq(registry.totalTasks(), 0); assertEq(registry.getGasCommittedForNextCycle(), 0); - assertEq(registry.getTotalDepositedAutomationFees(), 0); - assertEq(erc20Supra.balanceOf(address(registry)), 0.18955 ether); + 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 controller = registry.getAutomationController(); + address controllerAddr = registry.automationController(); uint64[] memory taskIndexes = new uint64[](1); taskIndexes[0] = 0; vm.warp(2002); vm.startPrank(vmSigner, vmSigner); - AutomationController(controller).monitorCycleEnd(); - AutomationController(controller).processTasks(2, taskIndexes); + AutomationController(controllerAddr).monitorCycleEnd(); + AutomationController(controllerAddr).processTasks(2, taskIndexes); vm.stopPrank(); LibRegistry.TaskStopped[] memory stoppedTasks = new LibRegistry.TaskStopped[](1); @@ -1829,10 +1108,8 @@ contract AutomationRegistryTest is Test { /// @dev Test to ensure 'stopSystemTasks' reverts if automation is not enabled. function testStopSystemTasksRevertsIfAutomationNotEnabled() public { - testSetAutomationController(); - vm.prank(admin); - registry.disableAutomation(); + automationCore.disableAutomation(); uint64[] memory taskIndexes; vm.expectRevert(IAutomationRegistry.AutomationNotEnabled.selector); @@ -1843,8 +1120,6 @@ contract AutomationRegistryTest is Test { /// @dev Test to ensure 'stopSystemTasks' reverts if input array is empty. function testStopSystemTasksRevertsIfInputArrayEmpty() public { - testSetAutomationController(); - uint64[] memory taskIndexes; vm.expectRevert(IAutomationRegistry.TaskIndexesCannotBeEmpty.selector); @@ -1854,7 +1129,6 @@ contract AutomationRegistryTest is Test { /// @dev Test to ensure 'stopSystemTasks' reverts if caller is not the task owner. function testStopSystemTasksRevertsIfUnauthorizedCaller() public { - testSetAutomationController(); testRegisterSystemTask(); uint64[] memory taskIndexes = new uint64[](1); @@ -1868,7 +1142,6 @@ contract AutomationRegistryTest is Test { /// @dev Test to ensure 'stopSystemTasks' reverts if task type is not GST. function testStopSystemTasksRevertsIfTaskTypeNotGST() public { - testSetAutomationController(); testRegister(); uint64[] memory taskIndexes = new uint64[](1); @@ -1882,7 +1155,6 @@ contract AutomationRegistryTest is Test { /// @dev Test to ensure 'stopSystemTasks' does nothing if task does not exist. function testStopSystemTasksDoesNothingIfTaskDoesNotExist() public { - testSetAutomationController(); testRegisterSystemTask(); uint64[] memory taskIndexes = new uint64[](1); @@ -1898,17 +1170,17 @@ contract AutomationRegistryTest is Test { /// @dev Test to ensure 'stopSystemTasks' stops the input GST tasks. function testStopSystemTasks() public { testRegisterSystemTask(); - address controller = registry.getAutomationController(); + address controllerAddr = registry.automationController(); uint64[] memory taskIndexes = new uint64[](1); taskIndexes[0] = 0; vm.warp(2002); vm.prank(vmSigner, vmSigner); - AutomationController(controller).monitorCycleEnd(); + AutomationController(controllerAddr).monitorCycleEnd(); vm.prank(vmSigner); - AutomationController(controller).processTasks(2, taskIndexes); + AutomationController(controllerAddr).processTasks(2, taskIndexes); vm.prank(bob); registry.stopSystemTasks(taskIndexes); @@ -1923,17 +1195,17 @@ contract AutomationRegistryTest is Test { /// @dev Test to ensure 'stopSystemTasks' emits event 'TasksStopped'. function testStopSystemTasksEmitsEvent() public { testRegisterSystemTask(); - address controller = registry.getAutomationController(); + address controllerAddr = registry.automationController(); uint64[] memory taskIndexes = new uint64[](1); taskIndexes[0] = 0; vm.warp(2002); vm.prank(vmSigner, vmSigner); - AutomationController(controller).monitorCycleEnd(); + AutomationController(controllerAddr).monitorCycleEnd(); vm.prank(vmSigner); - AutomationController(controller).processTasks(2, taskIndexes); + AutomationController(controllerAddr).processTasks(2, taskIndexes); LibRegistry.TaskStopped[] memory stoppedTasks = new LibRegistry.TaskStopped[](1); stoppedTasks[0] = LibRegistry.TaskStopped(0, 0, 0, keccak256("txHash")); diff --git a/solidity/supra_contracts/test/BlockMeta.t.sol b/solidity/supra_contracts/test/BlockMeta.t.sol index f17440a608..d55ffae209 100644 --- a/solidity/supra_contracts/test/BlockMeta.t.sol +++ b/solidity/supra_contracts/test/BlockMeta.t.sol @@ -6,6 +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"; contract BlockMetaTest is Test { BlockMeta blockMeta; // BlockMeta instance on proxy address @@ -83,14 +84,14 @@ contract BlockMetaTest is Test { /// @dev Test to ensure 'register' reverts if address(0) is passed. function testRegisterRevertsIfAddressZero() public { - vm.expectRevert(BlockMeta.AddressCannotBeZero.selector); + vm.expectRevert(CommonUtils.AddressCannotBeZero.selector); register(address(0), selector); } /// @dev Test to ensure 'register' reverts if EOA is passed. function testRegisterRevertsIfEOA() public { - vm.expectRevert(BlockMeta.AddressCannotBeEOA.selector); + vm.expectRevert(CommonUtils.AddressCannotBeEOA.selector); register(alice, selector); } From 9289865f1754d2a7ec422fef7e97b8759e363940 Mon Sep 17 00:00:00 2001 From: Udit Yadav Date: Thu, 22 Jan 2026 14:08:05 +0530 Subject: [PATCH 16/31] updated scripts --- .../deploy_automation_registry.sh | 73 ++++---- solidity/supra_contracts/getTaskDetails.js | 2 +- solidity/supra_contracts/package-lock.json | 2 +- solidity/supra_contracts/run.sh | 163 +++++++++++------- 4 files changed, 137 insertions(+), 103 deletions(-) diff --git a/solidity/supra_contracts/deploy_automation_registry.sh b/solidity/supra_contracts/deploy_automation_registry.sh index 38c1abbb75..4590e6b372 100755 --- a/solidity/supra_contracts/deploy_automation_registry.sh +++ b/solidity/supra_contracts/deploy_automation_registry.sh @@ -1,14 +1,28 @@ #!/bin/bash set -e +MODE=$1 # optional: "anvil" or empty + # -------------------------- -# CONFIGURATION +# USER CONFIG (used when NOT anvil) # -------------------------- -RPC_URL="http://127.0.0.1:8545" +RPC_URL="http://localhost:27002/rpc/v1/eth/wallet_integration" DEPLOY_LOG="deploy.log" ENV_FILE="deployed.env" PRIVATE_KEY="" +START_ANVIL=false +ANVIL_PID="" + +if [[ "$MODE" == "anvil" ]]; then + RPC_URL="http://127.0.0.1:8545" + START_ANVIL=true + echo "Mode: ANVIL" +else + echo "Mode: EXTERNAL RPC" + echo "RPC: $RPC_URL" +fi + # Helper for cleaner + safer extraction extract() { local result @@ -17,29 +31,19 @@ extract() { } # ------------------------------------------------------------ -# 1. START ANVIL +# 1. START ANVIL (only if mode = anvil) # ------------------------------------------------------------ -echo "=== Starting Anvil with DEFAULT settings ===" - -echo "Checking if port 8545 is in use..." -EXISTING_PID=$(lsof -ti:8545 || true) -if [ ! -z "$EXISTING_PID" ]; then - echo "Port is busy. Killing $EXISTING_PID ..." - kill -9 "$EXISTING_PID" - sleep 1 +if [[ "$START_ANVIL" == true ]]; then + echo "=== Starting Anvil ===" + anvil > anvil.log 2>&1 & + ANVIL_PID=$! + sleep 2 + echo "Anvil launched (PID $ANVIL_PID)" fi -echo "Starting Anvil..." -anvil > anvil.log 2>&1 & -ANVIL_PID=$! -sleep 2 - -echo "Anvil launched (PID $ANVIL_PID)" -echo "RPC URL: $RPC_URL" - # ------------------------------------------------------------ -# 2. RUN DEPLOY SCRIPT +# 2. RUN FOUNDRY DEPLOY SCRIPT # ------------------------------------------------------------ echo "" echo "=== Deploying contracts ===" @@ -54,16 +58,16 @@ forge script script/DeployAutomationRegistry.s.sol:DeployAutomationRegistry \ echo "Deployment logs saved to $DEPLOY_LOG" # ------------------------------------------------------------ -# 3. PARSE DEPLOYED ADDRESSES +# 3. PARSE DEPLOYED CONTRACT ADDRESSES # ------------------------------------------------------------ echo "" echo "=== Extracting deployed addresses ===" ERC20_SUPRA=$(extract "ERC20Supra deployed at:") +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:") -BLOCKMETA_IMPL=$(extract "BlockMeta implementation deployed at:") -BLOCKMETA_PROXY=$(extract "BlockMeta proxy deployed at:") AUTOMATION_CONTROLLER_IMPL=$(extract "AutomationController implementation deployed at:") AUTOMATION_CONTROLLER_PROXY=$(extract "AutomationController proxy deployed at:") @@ -79,14 +83,16 @@ 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 -BLOCKMETA_IMPL=$BLOCKMETA_IMPL -BLOCKMETA_PROXY=$BLOCKMETA_PROXY - AUTOMATION_CONTROLLER_IMPL=$AUTOMATION_CONTROLLER_IMPL AUTOMATION_CONTROLLER_PROXY=$AUTOMATION_CONTROLLER_PROXY + +RPC_URL=$RPC_URL EOF cat "$ENV_FILE" @@ -95,13 +101,10 @@ echo "" echo "=== Deployment Complete ===" # ------------------------------------------------------------ -# 5. STOP ANVIL +# 5. STOP ANVIL (only if started by this script) # ------------------------------------------------------------ -# echo "Stopping Anvil (PID $ANVIL_PID)" - -# if kill -0 "$ANVIL_PID" 2>/dev/null; then -# kill "$ANVIL_PID" -# echo "Anvil stopped successfully." -# else -# echo "Anvil already exited." -# fi + +if [[ "$START_ANVIL" == true ]]; then + echo "Stopping Anvil..." + kill "$ANVIL_PID" +fi diff --git a/solidity/supra_contracts/getTaskDetails.js b/solidity/supra_contracts/getTaskDetails.js index a305f0383a..d83b44f7b1 100755 --- a/solidity/supra_contracts/getTaskDetails.js +++ b/solidity/supra_contracts/getTaskDetails.js @@ -10,7 +10,7 @@ if (!registryAddress || !taskIndex || !rpcUrl) { // 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,address owner,uint8 state,bytes payloadTx,bytes[] auxData))" + "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); diff --git a/solidity/supra_contracts/package-lock.json b/solidity/supra_contracts/package-lock.json index f345d193bd..765ea6dd30 100644 --- a/solidity/supra_contracts/package-lock.json +++ b/solidity/supra_contracts/package-lock.json @@ -1,5 +1,5 @@ { - "name": "automation_registry", + "name": "supra_contracts", "lockfileVersion": 3, "requires": true, "packages": { diff --git a/solidity/supra_contracts/run.sh b/solidity/supra_contracts/run.sh index ade2c0bfe7..023753bdd1 100755 --- a/solidity/supra_contracts/run.sh +++ b/solidity/supra_contracts/run.sh @@ -1,34 +1,38 @@ #!/bin/bash set -e -# 1. Deploy everything -./deploy_automation_registry.sh - -echo "" +# ------------------------------- +# Load deployed contract addresses +# ------------------------------- echo "=== Loading deployed contract addresses ===" + +if [ ! -f "deployed.env" ]; then + echo "ERROR: deployed.env not found." + exit 1 +fi + source deployed.env +# ------------------------------- +# Validate env variables +# ------------------------------- +: "${RPC_URL:?Missing RPC_URL in deployed.env}" +: "${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}" + echo "" echo "Contracts Loaded:" echo "ERC20_SUPRA: $ERC20_SUPRA" +echo "AUTOMATION_CORE: $AUTOMATION_CORE_PROXY" echo "AUTOMATION_REGISTRY: $AUTOMATION_REGISTRY_PROXY" echo "" echo "=== Starting Automation CLI ===" -# ------------------------------- -# Load deployed contract addresses -# ------------------------------- -if [ ! -f "deployed.env" ]; then - echo "ERROR: deployed.env not found. Run ./run.sh first." - exit 1 -fi - -source deployed.env - +ERC20_SUPRA="$ERC20_SUPRA" +AUTOMATION_CORE="$AUTOMATION_CORE_PROXY" REGISTRY="$AUTOMATION_REGISTRY_PROXY" -TOKEN="$ERC20_SUPRA" -RPC_URL="http://127.0.0.1:8545" # ------------------------------- # Ask user for private key @@ -40,8 +44,9 @@ ADDRESS=$(cast wallet address --private-key "$PRIVATE_KEY") echo "" echo "Using RPC: $RPC_URL" echo "Wallet: $ADDRESS" -echo "Registry proxy: $REGISTRY" -echo "ERC20 token: $TOKEN" +echo "ERC20 Supra: $ERC20_SUPRA" +echo "Automation Core proxy: $AUTOMATION_CORE" +echo "Automation Registry proxy: $REGISTRY" echo "" # ------------------------------- @@ -51,21 +56,22 @@ send_tx() { cast send \ --rpc-url "$RPC_URL" \ --private-key "$PRIVATE_KEY" \ + --gas-limit 3000000 \ "$@" } # ------------------------------- # Balance + allowance helpers # ------------------------------- -get_eth_balance() { +get_native_balance() { RAW=$(cast balance "$ADDRESS" --rpc-url "$RPC_URL" 2>/dev/null) RAW=${RAW:-0} ETH=$(cast --from-wei "$RAW") echo "ETH Balance: $ETH ETH" } -get_token_balance() { - RAW=$(cast call "$TOKEN" "balanceOf(address)(uint256)" "$ADDRESS" --rpc-url "$RPC_URL" 2>/dev/null) +get_erc20Supra_balance() { + RAW=$(cast erc20-token balance "$ERC20_SUPRA" "$ADDRESS" --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") @@ -73,11 +79,11 @@ get_token_balance() { } get_allowance() { - RAW=$(cast call "$TOKEN" "allowance(address,address)(uint256)" "$ADDRESS" "$REGISTRY" --rpc-url "$RPC_URL" 2>/dev/null) + RAW=$(cast erc20-token allowance "$ERC20_SUPRA" "$ADDRESS" "$AUTOMATION_CORE" --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") - echo "Allowance to Registry: $SUPRA SUPRA" + echo "Allowance to Automation Registry: $SUPRA SUPRA" } # ------------------------------- @@ -93,6 +99,13 @@ view_task_details() { echo "" } +is_authorized_submitter() { + echo -n "Enter address: " + read -r address + RAW=$(cast call "$REGISTRY" "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") DEC=$(echo "$RAW" | awk '{print $1}') @@ -100,13 +113,13 @@ view_registry_locked_balance() { echo "Registry Locked SUPRA: $SUPRA SUPRA" } -view_registry_token_balance() { - RAW=$(cast call "$TOKEN" "balanceOf(address)(uint256)" "$REGISTRY" --rpc-url "$RPC_URL") +view_registry_erc20Supra_balance() { + RAW=$(cast erc20-token balance "$ERC20_SUPRA" "$AUTOMATION_CORE" --rpc-url "$RPC_URL") DEC=$(echo "$RAW" | awk '{print $1}') SUPRA=$(cast --from-wei "$DEC") - echo "Registry ERC20Supra Balance: $SUPRA SUPRA" + echo "Automation Registry ERC20Supra Balance: $SUPRA SUPRA" } view_task_list() { @@ -127,20 +140,23 @@ view_total_tasks() { # ------------------------------- while true; do echo "" - echo "Automation CLI - extended" + echo "Automation Registry CLI" echo "" echo "Commands:" - echo " eth-balance Show ETH balance" - echo " supra-balance Show ERC20Supra balance" + echo " native-balance Show native balance" + echo " erc20Supra-balance Show ERC20Supra balance" echo " allowance Check ERC20 approval to registry" - echo " deposit Deposit ETH → mint ERC20Supra" - echo " approve Approve ERC20 token for fees" + echo " nativeToErc20Supra Deposit native → mint ERC20Supra" + echo " approve Approve ERC20Supra for fees" echo " register Register a user task" echo " register-system Register a system task" echo " cancel Cancel a user task" echo " cancel-system Cancel a system task" echo " stop Stop user tasks" echo " stop-system Stop system tasks" + echo " grant-authorization Grant authorization to submit GST" + echo " revoke-authorization Revoke authorization to submit GST" + echo " is-submitter Check if authorized submitter" echo " task-details View details of a task" echo " registry-locked-balance View registry's locked balance" echo " registry-balance View ERC20Supra balance of registry contract" @@ -152,16 +168,16 @@ while true; do echo "" case "$CMD" in - eth-balance) get_eth_balance ;; - supra-balance) get_token_balance ;; + native-balance) get_native_balance ;; + erc20Supra-balance) get_erc20Supra_balance ;; allowance) get_allowance ;; - deposit) + nativeToErc20Supra) echo -n "Amount to deposit (ETH): " read -r ethAmount weiAmount=$(cast --to-wei "$ethAmount") echo "Depositing $ethAmount ETH..." - send_tx "$TOKEN" "deposit()" --value "$weiAmount" + send_tx "$ERC20_SUPRA" "nativeToErc20Supra()" --value "$weiAmount" ;; approve) @@ -169,7 +185,7 @@ while true; do read -r ethAmount weiAmount=$(cast --to-wei "$ethAmount") echo "Approving $ethAmount SUPRA..." - send_tx "$TOKEN" "approve(address,uint256)" "$REGISTRY" "$weiAmount" + cast erc20-token approve "$ERC20_SUPRA" "$AUTOMATION_CORE" "$weiAmount" --rpc-url "$RPC_URL" --private-key "$PRIVATE_KEY" ;; register) @@ -198,23 +214,17 @@ while true; do read -r feeCap feeCapWei=$(cast --to-wei "$feeCap") # convert ETH to wei + echo -n "Priority (uint64): " + read -r priority - echo -n "auxData count: " - read -r auxCount - - auxArray=() - for ((i=0; i Date: Thu, 22 Jan 2026 16:52:47 +0530 Subject: [PATCH 17/31] updated scripts and README --- solidity/supra_contracts/README.md | 47 +++--------- .../deploy_automation_registry.sh | 71 +++++++------------ solidity/supra_contracts/run.sh | 23 +++--- .../script/DeployAutomationRegistry.s.sol | 13 +--- 4 files changed, 48 insertions(+), 106 deletions(-) diff --git a/solidity/supra_contracts/README.md b/solidity/supra_contracts/README.md index 53ae762878..e894720164 100644 --- a/solidity/supra_contracts/README.md +++ b/solidity/supra_contracts/README.md @@ -1,6 +1,13 @@ ## Supra EVM Automation Registry -**This repository includes Supra EVM Automation Registry contract and related contracts.** +**This repository includes following smart contracts:** +- MultiSignatureWallet and MultisigBeacon +- ERC20Supra +- BlockMeta +- Automation Registry smart contracts + - AutomationCore: manages configuration, refunds, fee accounting and other helper functions + - AutomationRegistry: user facing contract to register/cancel/stop a task + - AutomationController: manages cycle transition and processing of tasks Foundry consists of: @@ -34,40 +41,8 @@ $ forge build $ forge test ``` -### Format +### Deploying Automation Registry smart contracts ```shell -$ forge fmt -``` - -### Gas Snapshots - -```shell -$ forge snapshot -``` - -### Anvil - -```shell -$ anvil -``` - -### Deploy - -```shell -$ forge script script/Counter.s.sol:CounterScript --rpc-url --private-key -``` - -### Cast - -```shell -$ cast -``` - -### Help - -```shell -$ forge --help -$ anvil --help -$ cast --help -``` +$ forge script script/DeployAutomationRegistry.s.sol:DeployAutomationRegistry --rpc-url --private-key +``` \ No newline at end of file diff --git a/solidity/supra_contracts/deploy_automation_registry.sh b/solidity/supra_contracts/deploy_automation_registry.sh index 4590e6b372..b2ce8d19cd 100755 --- a/solidity/supra_contracts/deploy_automation_registry.sh +++ b/solidity/supra_contracts/deploy_automation_registry.sh @@ -1,27 +1,12 @@ #!/bin/bash set -e -MODE=$1 # optional: "anvil" or empty +source .env +: "${RPC_URL:?Missing RPC_URL in .env}" +: "${PRIVATE_KEY:?Missing PRIVATE_KEY in .env}" -# -------------------------- -# USER CONFIG (used when NOT anvil) -# -------------------------- -RPC_URL="http://localhost:27002/rpc/v1/eth/wallet_integration" DEPLOY_LOG="deploy.log" ENV_FILE="deployed.env" -PRIVATE_KEY="" - -START_ANVIL=false -ANVIL_PID="" - -if [[ "$MODE" == "anvil" ]]; then - RPC_URL="http://127.0.0.1:8545" - START_ANVIL=true - echo "Mode: ANVIL" -else - echo "Mode: EXTERNAL RPC" - echo "RPC: $RPC_URL" -fi # Helper for cleaner + safer extraction extract() { @@ -31,39 +16,44 @@ extract() { } # ------------------------------------------------------------ -# 1. START ANVIL (only if mode = anvil) +# RUN FOUNDRY DEPLOY SCRIPT # ------------------------------------------------------------ +echo "" +echo "=== Deploying contracts ===" -if [[ "$START_ANVIL" == true ]]; then - echo "=== Starting Anvil ===" - anvil > anvil.log 2>&1 & - ANVIL_PID=$! - sleep 2 - echo "Anvil launched (PID $ANVIL_PID)" +ADDRESS=$(cast wallet address --private-key "$PRIVATE_KEY") +export OWNER=$ADDRESS + +forge script script/DeployERC20Supra.s.sol:DeployERC20Supra \ + --rpc-url "$RPC_URL" \ + --private-key "$PRIVATE_KEY" \ + --broadcast \ + --skip-simulation \ + -vvvv > "$DEPLOY_LOG" 2>&1 + +ERC20_SUPRA=$(extract "ERC20Supra deployed at: ") +if [[ "$ERC20_SUPRA" == "NOT_FOUND" ]]; then + echo "ERROR: ERC20Supra address not found" + exit 1 fi -# ------------------------------------------------------------ -# 2. RUN FOUNDRY DEPLOY SCRIPT -# ------------------------------------------------------------ -echo "" -echo "=== Deploying contracts ===" +export ERC20_SUPRA forge script script/DeployAutomationRegistry.s.sol:DeployAutomationRegistry \ --rpc-url "$RPC_URL" \ --private-key "$PRIVATE_KEY" \ --broadcast \ --skip-simulation \ - -vvvv > "$DEPLOY_LOG" 2>&1 + -vvvv >> "$DEPLOY_LOG" 2>&1 echo "Deployment logs saved to $DEPLOY_LOG" # ------------------------------------------------------------ -# 3. PARSE DEPLOYED CONTRACT ADDRESSES +# PARSE DEPLOYED CONTRACT ADDRESSES # ------------------------------------------------------------ echo "" echo "=== Extracting deployed addresses ===" -ERC20_SUPRA=$(extract "ERC20Supra deployed at:") AUTOMATION_CORE_IMPL=$(extract "AutomationCore implementation deployed at:") AUTOMATION_CORE_PROXY=$(extract "AutomationCore proxy deployed at:") AUTOMATION_REGISTRY_IMPL=$(extract "AutomationRegistry implementation deployed at:") @@ -72,7 +62,7 @@ AUTOMATION_CONTROLLER_IMPL=$(extract "AutomationController implementation deploy AUTOMATION_CONTROLLER_PROXY=$(extract "AutomationController proxy deployed at:") # ------------------------------------------------------------ -# 4. WRITE TO .env +# WRITE TO .env # ------------------------------------------------------------ echo "" echo "=== Saving contract addresses to $ENV_FILE ===" @@ -91,20 +81,9 @@ AUTOMATION_REGISTRY_PROXY=$AUTOMATION_REGISTRY_PROXY AUTOMATION_CONTROLLER_IMPL=$AUTOMATION_CONTROLLER_IMPL AUTOMATION_CONTROLLER_PROXY=$AUTOMATION_CONTROLLER_PROXY - -RPC_URL=$RPC_URL EOF cat "$ENV_FILE" echo "" -echo "=== Deployment Complete ===" - -# ------------------------------------------------------------ -# 5. STOP ANVIL (only if started by this script) -# ------------------------------------------------------------ - -if [[ "$START_ANVIL" == true ]]; then - echo "Stopping Anvil..." - kill "$ANVIL_PID" -fi +echo "=== Deployment Complete ===" \ No newline at end of file diff --git a/solidity/supra_contracts/run.sh b/solidity/supra_contracts/run.sh index 023753bdd1..c210ebec43 100755 --- a/solidity/supra_contracts/run.sh +++ b/solidity/supra_contracts/run.sh @@ -1,6 +1,12 @@ #!/bin/bash set -e +source .env + +: "${RPC_URL:?Missing RPC_URL in .env}" +: "${PRIVATE_KEY:?Missing PRIVATE_KEY in .env}" +: "${ADMIN_PRIVATE_KEY:?Missing ADMIN_PRIVATE_KEY in .env}" + # ------------------------------- # Load deployed contract addresses # ------------------------------- @@ -16,7 +22,6 @@ source deployed.env # ------------------------------- # Validate env variables # ------------------------------- -: "${RPC_URL:?Missing RPC_URL in deployed.env}" : "${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}" @@ -34,12 +39,6 @@ ERC20_SUPRA="$ERC20_SUPRA" AUTOMATION_CORE="$AUTOMATION_CORE_PROXY" REGISTRY="$AUTOMATION_REGISTRY_PROXY" -# ------------------------------- -# Ask user for private key -# ------------------------------- -echo -n "Enter PRIVATE_KEY (0x...): " -read -r PRIVATE_KEY - ADDRESS=$(cast wallet address --private-key "$PRIVATE_KEY") echo "" echo "Using RPC: $RPC_URL" @@ -282,24 +281,20 @@ while true; do ;; grant-authorization) - echo -n "Enter Admin PRIVATE_KEY (0x...): " - read -r PVT_KEY - echo -n "Address to grant authorization: " + echo -n "Address to grant authorization to: " read -r -a address cast send "$REGISTRY" "grantAuthorization(address)" "$address" \ --rpc-url "$RPC_URL" \ - --private-key "$PVT_KEY" \ + --private-key "$ADMIN_PRIVATE_KEY" \ --gas-limit 3000000 ;; revoke-authorization) - echo -n "Enter Admin PRIVATE_KEY (0x...): " - read -r PVT_KEY echo -n "Address to revoke authorization on: " read -r -a address cast send "$REGISTRY" "revokeAuthorization(address)" "$address" \ --rpc-url "$RPC_URL" \ - --private-key "$PVT_KEY" \ + --private-key "$ADMIN_PRIVATE_KEY" \ --gas-limit 3000000 ;; diff --git a/solidity/supra_contracts/script/DeployAutomationRegistry.s.sol b/solidity/supra_contracts/script/DeployAutomationRegistry.s.sol index 32bc952e18..78027751f1 100644 --- a/solidity/supra_contracts/script/DeployAutomationRegistry.s.sol +++ b/solidity/supra_contracts/script/DeployAutomationRegistry.s.sol @@ -5,7 +5,6 @@ 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 {ERC20Supra} from "../src/ERC20Supra.sol"; import {ERC1967Proxy} from "../lib/openzeppelin-contracts/contracts/proxy/ERC1967/ERC1967Proxy.sol"; contract DeployAutomationRegistry is Script { @@ -22,6 +21,7 @@ contract DeployAutomationRegistry is Script { uint128 sysRegistryMaxGasCap; uint16 sysTaskCapacity; address vmSigner; + address erc20Supra; // Config values loaded from .env file function setUp() public { @@ -38,13 +38,12 @@ contract DeployAutomationRegistry is Script { 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(); - ERC20Supra erc20Supra; // ERC20Supra contract - AutomationCore coreImpl; // AutomationCore implementation contract ERC1967Proxy coreProxy; // AutomationCore proxy contract AutomationCore automationCore; // Instance of AutomationCore at proxy address @@ -57,12 +56,6 @@ contract DeployAutomationRegistry is Script { ERC1967Proxy controllerProxy; // AutomationController proxy contract AutomationController controller; // Instance of AutomationController at proxy address - // --------------------------------------------------------------------- - // Deploy ERC20Supra - // --------------------------------------------------------------------- - erc20Supra = new ERC20Supra(msg.sender); - console.log("ERC20Supra deployed at: ", address(erc20Supra)); - // --------------------------------------------------------------------- // Deploy AutomationCore // --------------------------------------------------------------------- @@ -84,7 +77,7 @@ contract DeployAutomationRegistry is Script { sysRegistryMaxGasCap, // sysRegistryMaxGasCap sysTaskCapacity, // sysTaskCapacity vmSigner, // VM Signer address - address(erc20Supra) // ERC20Supra address + erc20Supra // ERC20Supra address ) ); coreProxy = new ERC1967Proxy(address(coreImpl), coreInitData); From 87bd13d4011411ba7e9f32bcf169e1cd76c383a4 Mon Sep 17 00:00:00 2001 From: Udit Yadav Date: Fri, 23 Jan 2026 11:03:14 +0530 Subject: [PATCH 18/31] removed coldWallet --- .../supra_contracts/src/AutomationCore.sol | 43 +++------ .../supra_contracts/src/IAutomationCore.sol | 1 + solidity/supra_contracts/src/LibConfig.sol | 9 +- .../supra_contracts/test/AutomationCore.t.sol | 87 +++++-------------- 4 files changed, 34 insertions(+), 106 deletions(-) diff --git a/solidity/supra_contracts/src/AutomationCore.sol b/solidity/supra_contracts/src/AutomationCore.sol index f750e28fec..fd433f2b80 100644 --- a/solidity/supra_contracts/src/AutomationCore.sol +++ b/solidity/supra_contracts/src/AutomationCore.sol @@ -28,7 +28,6 @@ contract AutomationCore is IAutomationCore, Ownable2StepUpgradeable, UUPSUpgrade /// @dev State variables LibConfig.ConfigBuffer configBuffer; LibConfig.RegistryConfig regConfig; - LibConfig.Deposit deposit; // ::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::: EVENTS ::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::: @@ -59,11 +58,8 @@ contract AutomationCore is IAutomationCore, Ownable2StepUpgradeable, UUPSUpgrade /// @notice Emitted when the automation registry smart contract address is updated. event AutomationRegistryUpdated(address indexed oldRegistry, address indexed newRegistry); - /// @notice Emitted when the cold wallet address is updated. - event ColdWalletUpdated(address indexed oldColdWallet, address indexed newColdWallet); - /// @notice Emitted when the registry fees is withdrawn by the admin. - event RegistryFeeWithdrawn(address indexed coldWallet, uint256 indexed feesWithdrawn); + 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( @@ -390,10 +386,10 @@ contract AutomationCore is IAutomationCore, Ownable2StepUpgradeable, UUPSUpgrade uint64 _taskIndex, uint128 _lockedDeposit ) private returns (bool) { - uint256 totalDeposited = deposit.totalDepositedAutomationFees; + uint256 totalDeposited = regConfig.totalDepositedAutomationFees; if(totalDeposited >= _lockedDeposit) { - deposit.totalDepositedAutomationFees = totalDeposited - _lockedDeposit; + regConfig.totalDepositedAutomationFees = totalDeposited - _lockedDeposit; return true; } @@ -671,7 +667,7 @@ contract AutomationCore is IAutomationCore, Ownable2StepUpgradeable, UUPSUpgrade /// @notice Helper function to increment the total deposited automation fees. function incTotalDepositedAutomationFees(uint256 _amount) external { onlyRegistry(); - deposit.totalDepositedAutomationFees += _amount; + regConfig.totalDepositedAutomationFees += _amount; } /// @notice Internally calls _refund, reverts if caller is not AutomationRegistry. @@ -889,31 +885,21 @@ contract AutomationCore is IAutomationCore, Ownable2StepUpgradeable, UUPSUpgrade /// @notice Function to withdraw the accumulated fees. /// @param _amount Amount to withdraw. - function withdrawFees(uint256 _amount) external onlyOwner { - address coldWallet = deposit.coldWallet; - if(coldWallet == address(0)) { revert ColdWalletNotSet(); } + /// @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(); } uint256 cycleLockedFees = IAutomationRegistry(regConfig.registry).getCycleLockedFees(); - if(balance - _amount < cycleLockedFees + deposit.totalDepositedAutomationFees) { revert RequestExceedsLockedBalance(); } + if(balance - _amount < cycleLockedFees + regConfig.totalDepositedAutomationFees) { revert RequestExceedsLockedBalance(); } - bool sent = IERC20(regConfig.erc20Supra).transfer(coldWallet, _amount); + bool sent = IERC20(regConfig.erc20Supra).transfer(_recipient, _amount); if(!sent) { revert TransferFailed(); } - emit RegistryFeeWithdrawn(coldWallet, _amount); - } - - /// @notice Function to update the cold wallet address. - /// @param _coldWallet Address for the new cold wallet. - function setColdWallet(address _coldWallet) external onlyOwner { - if(_coldWallet == address(0)) { revert AddressCannotBeZero(); } - - address oldColdWallet = deposit.coldWallet; - deposit.coldWallet = _coldWallet; - - emit ColdWalletUpdated(oldColdWallet, _coldWallet); + emit RegistryFeeWithdrawn(_recipient, _amount); } // :::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::: VIEW FUNCTIONS :::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::: @@ -993,14 +979,9 @@ contract AutomationCore is IAutomationCore, Ownable2StepUpgradeable, UUPSUpgrade return regConfig.config.cycleDurationSecs(); } - /// @notice Returns the cold wallet address. - function getColdWallet() external view returns (address) { - return deposit.coldWallet; - } - /// @notice Returns the total amount of automation fees deposited. function getTotalDepositedAutomationFees() external view returns (uint256) { - return deposit.totalDepositedAutomationFees; + return regConfig.totalDepositedAutomationFees; } // ::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::: UPGRADEABILITY FUNCTIONS ::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::: diff --git a/solidity/supra_contracts/src/IAutomationCore.sol b/solidity/supra_contracts/src/IAutomationCore.sol index e939eadb4c..93bff00bc1 100644 --- a/solidity/supra_contracts/src/IAutomationCore.sol +++ b/solidity/supra_contracts/src/IAutomationCore.sol @@ -13,6 +13,7 @@ interface IAutomationCore { error CycleTransitionInProgress(); error ErrorDepositRefund(); error ErrorCycleFeeRefund(); + error InvalidAmount(); error InvalidMaxGasAmount(); error InvalidTaskType(); error InvalidTxHash(); diff --git a/solidity/supra_contracts/src/LibConfig.sol b/solidity/supra_contracts/src/LibConfig.sol index 668675ecc5..a380ac466f 100644 --- a/solidity/supra_contracts/src/LibConfig.sol +++ b/solidity/supra_contracts/src/LibConfig.sol @@ -9,14 +9,6 @@ library LibConfig { uint256 private constant MAX_UINT16 = type(uint16).max; uint256 private constant MAX_UINT8 = type(uint8).max; - // ::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::: Deposit ::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::: - - /// @notice Deposit and fee related accounting. - struct Deposit { - uint256 totalDepositedAutomationFees; - address coldWallet; - } - // ::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::: AccessListEntry ::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::: /// @notice Struct representing an entry in access list. @@ -41,6 +33,7 @@ library LibConfig { uint256 nextCycleRegistryMaxGasCap_nextCycleSysRegistryMaxGasCap; // address | bool | bool uint256 controller_registrationEnabled_automationEnabled; + uint256 totalDepositedAutomationFees; address vmSigner; address erc20Supra; address registry; diff --git a/solidity/supra_contracts/test/AutomationCore.t.sol b/solidity/supra_contracts/test/AutomationCore.t.sol index 034eb7c19d..3841a06190 100644 --- a/solidity/supra_contracts/test/AutomationCore.t.sol +++ b/solidity/supra_contracts/test/AutomationCore.t.sol @@ -693,43 +693,6 @@ contract AutomationCoreTest is Test { automationCore.setErc20Supra(address(supraErc20)); } - // :::::::::::::::::::::::::::::::::::::::::::::::::::::: Tests related to 'setColdWallet' :::::::::::::::::::::::::::::::::::::::::::::::::::::: - - /// @dev Test to ensure 'setColdWallet' updates the cold wallet address. - function testSetColdWallet() public { - vm.prank(admin); - automationCore.setColdWallet(address(0x1001)); - - assertEq(automationCore.getColdWallet(), address(0x1001)); - } - - /// @dev Test to ensure 'setColdWallet' emits event 'ColdWalletUpdated'. - function testSetColdWalletEmitsEvent() public { - address oldColdWallet = automationCore.getColdWallet(); - - vm.expectEmit(true, true, false, false); - emit AutomationCore.ColdWalletUpdated(oldColdWallet, address(0x1001)); - - vm.prank(admin); - automationCore.setColdWallet(address(0x1001)); - } - - /// @dev Test to ensure 'setColdWallet' reverts if zero address is passed. - function testSetColdWalletRevertsIfZeroAddress() public { - vm.expectRevert(IAutomationCore.AddressCannotBeZero.selector); - - vm.prank(admin); - automationCore.setColdWallet(address(0)); - } - - /// @dev Test to ensure 'setColdWallet' reverts if caller is not owner. - function testSetColdWalletRevertsIfNotOwner() public { - vm.expectRevert(abi.encodeWithSelector(OwnableUpgradeable.OwnableUnauthorizedAccount.selector, alice)); - - vm.prank(alice); - automationCore.setColdWallet(address(0x1001)); - } - // :::::::::::::::::::::::::::::::::::::::::::::::::::::: Tests related to 'updateConfigBuffer' :::::::::::::::::::::::::::::::::::::::::::::::::::::: /// @dev Helper function that returns a valid config. @@ -835,38 +798,38 @@ contract AutomationCoreTest is Test { // :::::::::::::::::::::::::::::::::::::::::::::::::::::: Tests related to 'withdrawFees' :::::::::::::::::::::::::::::::::::::::::::::::::::::: - /// @dev Test to ensure 'withdrawFees' reverts if cold wallet is not set. - function testWithdrawFeesRevertsIfColdWalletNotSet() public { + /// @dev Test to ensure 'withdrawFees' reverts if amount is zero. + function testWithdrawFeesRevertsIfAmountZero() public { vm.prank(admin); - vm.expectRevert(IAutomationCore.ColdWalletNotSet.selector); - automationCore.withdrawFees(1 ether); + vm.expectRevert(IAutomationCore.InvalidAmount.selector); + automationCore.withdrawFees(0, admin); } - /// @dev Test to ensure 'withdrawFees' reverts if contract has insufficient balance. - function testWithdrawFeesRevertsIfInsufficientBalance() public { - // Set cold wallet + /// @dev Test to ensure 'withdrawFees' reverts if recipient address is zero. + function testWithdrawFeesRevertsIfRecipientAddressZero() public { vm.prank(admin); - automationCore.setColdWallet(address(0x1001)); + 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); + automationCore.withdrawFees(1 ether, admin); } /// @dev Test to ensure 'withdrawFees' reverts if request amount exceeds the locked balance. function testWithdrawFeesRevertsIfRequestExceedsLockedBalance() public { registerUST(); - // Set cold wallet - vm.prank(admin); - automationCore.setColdWallet(address(0x1001)); - vm.expectRevert(IAutomationCore.RequestExceedsLockedBalance.selector); vm.prank(admin); - automationCore.withdrawFees(0.04 ether); + automationCore.withdrawFees(0.04 ether, admin); } /// @dev Test to ensure 'withdrawFees' reverts if caller is not owner. @@ -874,25 +837,20 @@ contract AutomationCoreTest is Test { vm.expectRevert(abi.encodeWithSelector(OwnableUpgradeable.OwnableUnauthorizedAccount.selector, alice)); vm.prank(alice); - automationCore.withdrawFees(1 ether); + automationCore.withdrawFees(1 ether, admin); } /// @dev Test to ensure 'withdrawFees' withdraws the requested amount and updates the balance. function testWithdrawFees() public { registerUST(); - // Set cold wallet - address coldWallet = address(0x1001); - vm.prank(admin); - automationCore.setColdWallet(coldWallet); - - assertEq(erc20Supra.balanceOf(coldWallet), 0); + assertEq(erc20Supra.balanceOf(admin), 0); assertEq(erc20Supra.balanceOf(address(automationCore)), 0.502 ether); vm.prank(admin); - automationCore.withdrawFees(0.002 ether); + automationCore.withdrawFees(0.002 ether, admin); - assertEq(erc20Supra.balanceOf(coldWallet), 0.002 ether); + assertEq(erc20Supra.balanceOf(admin), 0.002 ether); assertEq(erc20Supra.balanceOf(address(automationCore)), 0.5 ether); } @@ -900,16 +858,11 @@ contract AutomationCoreTest is Test { function testWithdrawFeesEmitsEvent() public { registerUST(); - // Set cold wallet - address coldWallet = address(0x1001); - vm.prank(admin); - automationCore.setColdWallet(coldWallet); - vm.expectEmit(true, true, false, false); - emit AutomationCore.RegistryFeeWithdrawn(coldWallet, 0.002 ether); + emit AutomationCore.RegistryFeeWithdrawn(admin, 0.002 ether); vm.prank(admin); - automationCore.withdrawFees(0.002 ether); + automationCore.withdrawFees(0.002 ether, admin); } /// @dev Helper function to return payload. From 2342e987b575c84577601a41e93f155e1bb9be19 Mon Sep 17 00:00:00 2001 From: Udit Yadav Date: Mon, 26 Jan 2026 14:44:31 +0530 Subject: [PATCH 19/31] fixed validation to check if caller is AutomationCore --- .../supra_contracts/src/AutomationController.sol | 13 +++++++++++-- .../supra_contracts/src/IAutomationController.sol | 1 - 2 files changed, 11 insertions(+), 3 deletions(-) diff --git a/solidity/supra_contracts/src/AutomationController.sol b/solidity/supra_contracts/src/AutomationController.sol index 841fa6414d..f8972b890b 100644 --- a/solidity/supra_contracts/src/AutomationController.sol +++ b/solidity/supra_contracts/src/AutomationController.sol @@ -527,6 +527,15 @@ contract AutomationController is IAutomationController, Ownable2StepUpgradeable, // 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 startTime = cycleInfo.startTime(); @@ -567,7 +576,7 @@ contract AutomationController is IAutomationController, Ownable2StepUpgradeable, } function tryMoveToSuspendedState() external { - if (msg.sender != address(registry)) { revert CallerNotRegistry(); } + if (msg.sender != address(automationCore)) { revert CallerNotAutomationCore(); } _tryMoveToSuspendedState(); } @@ -618,7 +627,7 @@ contract AutomationController is IAutomationController, Ownable2StepUpgradeable, } function moveToStartedState() external { - if (msg.sender != address(registry)) { revert CallerNotRegistry(); } + if (msg.sender != address(automationCore)) { revert CallerNotAutomationCore(); } _moveToStartedState(); } diff --git a/solidity/supra_contracts/src/IAutomationController.sol b/solidity/supra_contracts/src/IAutomationController.sol index 490704ddfe..9a790e3967 100644 --- a/solidity/supra_contracts/src/IAutomationController.sol +++ b/solidity/supra_contracts/src/IAutomationController.sol @@ -6,7 +6,6 @@ import {CommonUtils} from "./CommonUtils.sol"; interface IAutomationController { // Custom errors error CallerNotAutomationCore(); - error CallerNotRegistry(); error CallerNotVmSigner(); error ConfigUpdateFailed(); error InconsistentTransitionState(); From 049893841445781c1ff41a09ff7b226ae6eafa01 Mon Sep 17 00:00:00 2001 From: Udit Yadav Date: Tue, 27 Jan 2026 17:51:58 +0530 Subject: [PATCH 20/31] fixed refundTaskFees and added test cases for AutomationController --- .../src/AutomationController.sol | 14 +- .../supra_contracts/src/AutomationCore.sol | 29 +- .../supra_contracts/src/IAutomationCore.sol | 5 +- .../test/AutomationController.t.sol | 441 +++++++++++++++++- 4 files changed, 461 insertions(+), 28 deletions(-) diff --git a/solidity/supra_contracts/src/AutomationController.sol b/solidity/supra_contracts/src/AutomationController.sol index f8972b890b..6f29a139f7 100644 --- a/solidity/supra_contracts/src/AutomationController.sol +++ b/solidity/supra_contracts/src/AutomationController.sol @@ -173,9 +173,7 @@ contract AutomationController is IAutomationController, Ownable2StepUpgradeable, /// @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 (_taskIndexes.length == 0) { return; } if(cycleInfo.state() != CommonUtils.CycleState.SUSPENDED) { revert InvalidRegistryState(); } if(cycleInfo.index() != _cycleIndex) { revert InvalidInputCycleIndex(); } @@ -191,6 +189,8 @@ contract AutomationController is IAutomationController, Ownable2StepUpgradeable, uint64 removedCounter; for (uint i = 0; i < taskIndexes.length; i++) { if(registry.ifTaskExists(taskIndexes[i])) { + CommonUtils.TaskDetails memory task = registry.getTaskDetails(taskIndexes[i]); + (bool removed, ) = address(registry).call(abi.encodeCall(IAutomationRegistry.removeTask, (taskIndexes[i], false))); require(removed, RemoveTaskFailed()); @@ -198,11 +198,11 @@ contract AutomationController is IAutomationController, Ownable2StepUpgradeable, markTaskProcessed(taskIndexes[i]); // Nothing to refund for GST tasks - if(registry.checkTaskType(taskIndexes[i], CommonUtils.TaskType.UST)) { + if (task.taskType == CommonUtils.TaskType.UST) { (bool refunded, bytes memory data) = address(automationCore).call( abi.encodeCall( IAutomationCore.refundTaskFees, - (taskIndexes[i], currentTime, cycleLockedFees, cycleInfo.refundDuration(), cycleInfo.automationFeePerSec()) + (currentTime, cycleLockedFees, cycleInfo.refundDuration(), cycleInfo.automationFeePerSec(), task) ) ); require(refunded, RefundFailed()); @@ -667,16 +667,16 @@ contract AutomationController is IAutomationController, Ownable2StepUpgradeable, } else { uint256[] memory expectedTasksToBeProcessed = registry.getTaskIdList().sortUint256(); + // Updates transition state cycleInfo.setRefundDuration(0); cycleInfo.setNewCycleDuration(cycleInfo.durationSecs()); - cycleInfo.setAutomationFeePerSec(0); cycleInfo.setGasCommittedForNewCycle(registry.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. diff --git a/solidity/supra_contracts/src/AutomationCore.sol b/solidity/supra_contracts/src/AutomationCore.sol index fd433f2b80..56bd2cb3fc 100644 --- a/solidity/supra_contracts/src/AutomationCore.sol +++ b/solidity/supra_contracts/src/AutomationCore.sol @@ -447,7 +447,7 @@ contract AutomationCore is IAutomationCore, Ownable2StepUpgradeable, UUPSUpgrade return result; } - result = safeRefund( _taskIndex, _taskOwner, _refundableDeposit, DEPOSIT_CYCLE_FEE); + result = safeRefund(_taskIndex, _taskOwner, _refundableDeposit, DEPOSIT_CYCLE_FEE); if (result) { emit TaskDepositFeeRefund(_taskIndex, _taskOwner, _refundableDeposit); } return result; @@ -548,30 +548,27 @@ contract AutomationCore is IAutomationCore, Ownable2StepUpgradeable, UUPSUpgrade /// @notice Refunds the deposit fee and any autoamtion fees of the task. function refundTaskFees( - uint64 _taskIndex, uint64 _currentTime, uint256 _cycleLockedFees, uint64 _refundDuration, - uint128 _automationFeePerSec + uint128 _automationFeePerSec, + CommonUtils.TaskDetails memory _task ) external returns (uint256) { onlyController(); - if(IAutomationRegistry(regConfig.registry).checkTaskType(_taskIndex, CommonUtils.TaskType.GST)) { revert RegisteredTaskInvalidType(); } - - CommonUtils.TaskDetails memory task = IAutomationRegistry(regConfig.registry).getTaskDetails(_taskIndex); // Do not attempt fee refund if remaining duration is 0 - if (task.state != CommonUtils.TaskState.PENDING && _refundDuration != 0) { + if (_task.state != CommonUtils.TaskState.PENDING && _refundDuration != 0) { uint128 _refundFee = _calculateTaskFee( - task.state, - task.expiryTime, - task.maxGasAmount, + _task.state, + _task.expiryTime, + _task.maxGasAmount, _refundDuration, _currentTime, _automationFeePerSec ); ( , uint256 remainingCycleLockedFees) = safeFeeRefund( - _taskIndex, - task.owner, + _task.taskIndex, + _task.owner, _cycleLockedFees, uint64(_refundFee) ); @@ -579,10 +576,10 @@ contract AutomationCore is IAutomationCore, Ownable2StepUpgradeable, UUPSUpgrade } _safeDepositRefund( - _taskIndex, - task.owner, - task.lockedFeeForNextCycle, - task.lockedFeeForNextCycle + _task.taskIndex, + _task.owner, + _task.lockedFeeForNextCycle, + _task.lockedFeeForNextCycle ); return _cycleLockedFees; diff --git a/solidity/supra_contracts/src/IAutomationCore.sol b/solidity/supra_contracts/src/IAutomationCore.sol index 93bff00bc1..a33541af96 100644 --- a/solidity/supra_contracts/src/IAutomationCore.sol +++ b/solidity/supra_contracts/src/IAutomationCore.sol @@ -34,7 +34,6 @@ interface IAutomationCore { error InvalidTaskCapacity(); error InvalidTaskDuration(); error RegistrationDisabled(); - error RegisteredTaskInvalidType(); error RequestExceedsLockedBalance(); error TaskCapacityReached(); error TaskExpiresBeforeNextCycle(); @@ -88,11 +87,11 @@ interface IAutomationCore { uint128 _lockedDeposit ) external returns (bool); function refundTaskFees( - uint64 _taskIndex, uint64 _currentTime, uint256 _cycleLockedFees, uint64 _refundDuration, - uint128 _automationFeePerSec + uint128 _automationFeePerSec, + CommonUtils.TaskDetails memory _task ) external returns (uint256); function safeDepositRefund( uint64 _taskIndex, diff --git a/solidity/supra_contracts/test/AutomationController.t.sol b/solidity/supra_contracts/test/AutomationController.t.sol index 7a27f11caf..a3718d1f1a 100644 --- a/solidity/supra_contracts/test/AutomationController.t.sol +++ b/solidity/supra_contracts/test/AutomationController.t.sol @@ -11,6 +11,7 @@ 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 @@ -49,7 +50,7 @@ contract AutomationControllerTest is Test { 5_000_000, // sysRegistryMaxGasCap 500, // sysTaskCapacity vmSigner, // VM Signer address - address(erc20Supra) // ERC20Supra address + address(erc20Supra) // ERC20Supra address ) ); ERC1967Proxy automationCoreProxy = new ERC1967Proxy(address(automationCoreImpl), automationCoreInitData); @@ -65,6 +66,10 @@ contract AutomationControllerTest is Test { 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(); } @@ -129,7 +134,7 @@ contract AutomationControllerTest is Test { function testSetAutomationRegistryRevertsIfNotOwner() public { AutomationRegistry registryImplementation = new AutomationRegistry(); - vm.expectRevert(abi.encodeWithSelector(OwnableUpgradeable.OwnableUnauthorizedAccount.selector,alice)); + vm.expectRevert(abi.encodeWithSelector(OwnableUpgradeable.OwnableUnauthorizedAccount.selector, alice)); vm.prank(alice); controller.setAutomationRegistry(address(registryImplementation)); @@ -172,6 +177,186 @@ contract AutomationControllerTest is Test { 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); + automationCore.disableAutomation(); + + assertFalse(automationCore.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); @@ -182,4 +367,256 @@ contract AutomationControllerTest is Test { 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(registry.getSystemGasCommittedForNextCycle(), 0); + assertEq(registry.getSystemGasCommittedForCurrentCycle(), 0); + assertEq(registry.getGasCommittedForNextCycle(), 0); + assertEq(registry.getGasCommittedForCurrentCycle(), 1000000); + assertEq(registry.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); + automationCore.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); + automationCore.disableAutomation(); + + (uint64 indexAfter, , , CommonUtils.CycleState stateAfter) = controller.getCycleInfo(); + assertEq(uint8(stateAfter), uint8(CommonUtils.CycleState.SUSPENDED)); + + // Enable automation + vm.prank(admin); + automationCore.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); + automationCore.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); + } + + /// @dev Test to ensure 'tryMoveToSuspendedState' reverts if caller is not AutomationCore. + function testTryMoveToSuspendedStateRevertsIfNotAutomationCore() public { + vm.expectRevert(IAutomationController.CallerNotAutomationCore.selector); + + vm.prank(address(registry)); + controller.tryMoveToSuspendedState(); + } + + /// @dev Test to ensure 'moveToStartedState' reverts if caller is not AutomationCore. + function testMoveToStartedStateRevertsIfNotAutomationCore() public { + vm.expectRevert(IAutomationController.CallerNotAutomationCore.selector); + + vm.prank(address(registry)); + controller.moveToStartedState(); + } + + /// @dev Test to ensure 'updateCyleDuration' reverts if caller is not AutomationCore. + function testUpdateCyleDurationRevertsIfNotAutomationCore() public { + vm.expectRevert(IAutomationController.CallerNotAutomationCore.selector); + + vm.prank(address(registry)); + controller.updateCyleDuration(3800); + } + + /// @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), + keccak256("txHash"), + uint128(1_000_000), + uint128(10 gwei), + uint128(0.5 ether), + 2, + 0, + 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 From 9ee322c8747c0e5b758510ee29415cc7395f37a4 Mon Sep 17 00:00:00 2001 From: Udit Yadav Date: Wed, 28 Jan 2026 16:17:18 +0530 Subject: [PATCH 21/31] updated script and fixed bugs --- solidity/supra_contracts/run.sh | 61 +++++++++++++------ .../src/AutomationController.sol | 2 +- .../src/AutomationRegistry.sol | 20 +++--- 3 files changed, 53 insertions(+), 30 deletions(-) diff --git a/solidity/supra_contracts/run.sh b/solidity/supra_contracts/run.sh index c210ebec43..1a8da402ec 100755 --- a/solidity/supra_contracts/run.sh +++ b/solidity/supra_contracts/run.sh @@ -142,26 +142,27 @@ while true; do echo "Automation Registry CLI" echo "" echo "Commands:" - echo " native-balance Show native balance" - echo " erc20Supra-balance Show ERC20Supra balance" - echo " allowance Check ERC20 approval to registry" - echo " nativeToErc20Supra Deposit native → mint ERC20Supra" - echo " approve Approve ERC20Supra for fees" - echo " register Register a user task" - echo " register-system Register a system task" - echo " cancel Cancel a user task" - echo " cancel-system Cancel a system task" - echo " stop Stop user tasks" - echo " stop-system Stop system tasks" - echo " grant-authorization Grant authorization to submit GST" - echo " revoke-authorization Revoke authorization to submit GST" - echo " is-submitter Check if authorized submitter" - echo " task-details View details of a task" - echo " registry-locked-balance View registry's locked balance" - echo " registry-balance View ERC20Supra balance of registry contract" - echo " task-list View all task IDs" - echo " total-tasks View number of tasks" - echo " exit Quit" + echo " native-balance Show native balance" + echo " erc20Supra-balance Show ERC20Supra balance" + echo " allowance Check ERC20 approval to registry" + echo " nativeToErc20Supra Deposit native → mint ERC20Supra" + echo " nativeToErc20SupraWithAllowance Deposit native to mint ERC20Supra and grant allowance" + echo " approve Approve ERC20Supra for fees" + echo " register Register a user task" + echo " register-system Register a system task" + echo " cancel Cancel a user task" + echo " cancel-system Cancel a system task" + echo " stop Stop user tasks" + echo " stop-system Stop system tasks" + echo " grant-authorization Grant authorization to submit GST" + echo " revoke-authorization Revoke authorization to submit GST" + echo " is-submitter Check if authorized submitter" + echo " task-details View details of a task" + echo " registry-locked-balance View registry's locked balance" + echo " registry-balance View ERC20Supra balance of registry contract" + echo " task-list View all task IDs" + echo " total-tasks View number of tasks" + echo " exit Quit" echo -n "Command> " read -r CMD echo "" @@ -179,6 +180,26 @@ while true; do send_tx "$ERC20_SUPRA" "nativeToErc20Supra()" --value "$weiAmount" ;; + nativeToErc20SupraWithAllowance) + echo "Enter: " + read -r depositEth spender allowanceEth + + if [ -z "$depositEth" ] || [ -z "$spender" ] || [ -z "$allowanceEth" ]; then + echo "Invalid input. Expected: " + exit 1 + fi + + depositWei=$(cast --to-wei "$depositEth") + allowanceWei=$(cast --to-wei "$allowanceEth") + + echo "Depositing $depositEth ETH, and approving $spender for $allowanceEth ERC20Supra..." + + send_tx "$ERC20_SUPRA" \ + "nativeToErc20SupraWithAllowance(address,uint256)" \ + "$spender" "$allowanceWei" \ + --value "$depositWei" + ;; + approve) echo -n "Amount to approve (ETH): " read -r ethAmount diff --git a/solidity/supra_contracts/src/AutomationController.sol b/solidity/supra_contracts/src/AutomationController.sol index 6f29a139f7..2d60c6f759 100644 --- a/solidity/supra_contracts/src/AutomationController.sol +++ b/solidity/supra_contracts/src/AutomationController.sol @@ -268,8 +268,8 @@ contract AutomationController is IAutomationController, Ownable2StepUpgradeable, if(registry.ifTaskExists(_taskIndex)) { markTaskProcessed(_taskIndex); - bool isUst = registry.checkTaskType(_taskIndex, CommonUtils.TaskType.UST); CommonUtils.TaskDetails memory task = registry.getTaskDetails(_taskIndex); + bool isUst = task.taskType == CommonUtils.TaskType.UST; // Task is cancelled or expired if(task.state == CommonUtils.TaskState.CANCELLED || _currentTime >= task.expiryTime) { diff --git a/solidity/supra_contracts/src/AutomationRegistry.sol b/solidity/supra_contracts/src/AutomationRegistry.sol index e52b3281b9..bbdaeb117b 100644 --- a/solidity/supra_contracts/src/AutomationRegistry.sol +++ b/solidity/supra_contracts/src/AutomationRegistry.sol @@ -253,7 +253,7 @@ contract AutomationRegistry is IAutomationRegistry, Ownable2StepUpgradeable, UUP CommonUtils.TaskDetails memory task = regState.tasks[_taskIndex].getTaskDetails(); - if(checkTaskType(_taskIndex, CommonUtils.TaskType.GST)) { revert UnsupportedTaskOperation(); } + if(task.taskType == CommonUtils.TaskType.GST) { revert UnsupportedTaskOperation(); } if(task.owner != msg.sender) { revert UnauthorizedAccount(); } if(task.state == CommonUtils.TaskState.CANCELLED) { revert AlreadyCancelled(); } @@ -305,12 +305,12 @@ contract AutomationRegistry is IAutomationRegistry, Ownable2StepUpgradeable, UUP if(state != CommonUtils.CycleState.STARTED) { revert CycleTransitionInProgress(); } if(!ifTaskExists(_taskIndex)) { revert TaskDoesNotExist(); } if(!ifSysTaskExists(_taskIndex)) { revert SystemTaskDoesNotExist(); } - - // Check if GST - if(checkTaskType(_taskIndex, CommonUtils.TaskType.UST)) { revert UnsupportedTaskOperation(); } 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(); } @@ -371,7 +371,7 @@ contract AutomationRegistry is IAutomationRegistry, Ownable2StepUpgradeable, UUP if(msg.sender != task.owner) { revert UnauthorizedAccount(); } // Check if UST - if(checkTaskType(_taskIndexes[i], CommonUtils.TaskType.GST)) { revert UnsupportedTaskOperation(); } + if(task.taskType == CommonUtils.TaskType.GST) { revert UnsupportedTaskOperation(); } // Remove task from the registry _removeTask(_taskIndexes[i], false); @@ -463,7 +463,7 @@ contract AutomationRegistry is IAutomationRegistry, Ownable2StepUpgradeable, UUP if(task.owner != msg.sender) { revert UnauthorizedAccount(); } // Check if GST - if(checkTaskType(_taskIndexes[i], CommonUtils.TaskType.UST)) { revert UnsupportedTaskOperation(); } + if(task.taskType == CommonUtils.TaskType.UST) { revert UnsupportedTaskOperation(); } _removeTask(_taskIndexes[i], true); // Remove from active tasks require(regState.activeTaskIds.remove(_taskIndexes[i]), TaskIndexNotFound()); @@ -619,7 +619,7 @@ contract AutomationRegistry is IAutomationRegistry, Ownable2StepUpgradeable, UUP ) external { onlyController(); // Check if task is UST - if(checkTaskType(_taskIndex, CommonUtils.TaskType.GST)) { revert RegisteredTaskInvalidType(); } + if (regState.tasks[_taskIndex].taskType() == CommonUtils.TaskType.GST) { revert RegisteredTaskInvalidType(); } // Remove task from the registry state _removeTask(_taskIndex, false); @@ -707,7 +707,8 @@ contract AutomationRegistry is IAutomationRegistry, Ownable2StepUpgradeable, UUP /// @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) public view returns (bool) { + function checkTaskType(uint64 _taskIndex, CommonUtils.TaskType _type) external view returns (bool) { + if (!ifTaskExists(_taskIndex)) { revert TaskDoesNotExist(); } return _type == regState.tasks[_taskIndex].taskType(); } @@ -777,7 +778,8 @@ contract AutomationRegistry is IAutomationRegistry, Ownable2StepUpgradeable, UUP /// @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) { - return regState.tasks[_taskIndex].owner() == _account && LibRegistry.state(regState.tasks[_taskIndex]) != CommonUtils.TaskState.PENDING && checkTaskType(_taskIndex, _type); + LibRegistry.TaskMetadata storage task = regState.tasks[_taskIndex]; + return task.owner() == _account && task.state() != CommonUtils.TaskState.PENDING && task.taskType() == _type; } /// @notice Estimates automation fee for the next cycle for specified task occupancy for the configured cycle-interval From d154b68c33a79c6fac373971ead70eda0ba1fcce Mon Sep 17 00:00:00 2001 From: Udit Yadav Date: Mon, 2 Feb 2026 16:06:50 +0530 Subject: [PATCH 22/31] updated storage layout and implemented relevant changes for it --- .../script/DeployAutomationRegistry.s.sol | 3 +- .../src/AutomationController.sol | 106 +++++---- .../supra_contracts/src/AutomationCore.sol | 224 +++++++++++------- .../src/AutomationRegistry.sol | 174 +++----------- .../src/IAutomationController.sol | 10 +- .../supra_contracts/src/IAutomationCore.sol | 34 +-- .../src/IAutomationRegistry.sol | 15 +- solidity/supra_contracts/src/LibConfig.sol | 91 +++++-- .../supra_contracts/src/LibController.sol | 53 +++-- solidity/supra_contracts/src/LibRegistry.sol | 64 +---- .../test/AutomationRegistry.t.sol | 43 ++++ 11 files changed, 400 insertions(+), 417 deletions(-) diff --git a/solidity/supra_contracts/script/DeployAutomationRegistry.s.sol b/solidity/supra_contracts/script/DeployAutomationRegistry.s.sol index 78027751f1..ffb15c022d 100644 --- a/solidity/supra_contracts/script/DeployAutomationRegistry.s.sol +++ b/solidity/supra_contracts/script/DeployAutomationRegistry.s.sol @@ -105,7 +105,8 @@ contract DeployAutomationRegistry is Script { AutomationController.initialize, ( address(automationCore), - address(registry) + address(registry), + true ) ); controllerProxy = new ERC1967Proxy(address(controllerImpl), controllerInitData); diff --git a/solidity/supra_contracts/src/AutomationController.sol b/solidity/supra_contracts/src/AutomationController.sol index 2d60c6f759..050b10f8c9 100644 --- a/solidity/supra_contracts/src/AutomationController.sol +++ b/solidity/supra_contracts/src/AutomationController.sol @@ -17,10 +17,6 @@ contract AutomationController is IAutomationController, Ownable2StepUpgradeable, using CommonUtils for *; using LibController for *; - /// @dev Defines the cycle state, used to update the registry. - uint8 constant SUSPENDED = 0; - uint8 constant FINISHED = 1; - /// @dev State variables LibController.AutomationCycleInfo cycleInfo; IAutomationRegistry public registry; @@ -78,6 +74,11 @@ contract AutomationController is IAutomationController, Ownable2StepUpgradeable, /// @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 :::::::::::::::::::::::::::::::::::::::::::::::::::::::::: @@ -89,20 +90,22 @@ contract AutomationController is IAutomationController, Ownable2StepUpgradeable, /// @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. - function initialize(address _automationCore, address _registry) public initializer { + /// @param _automationEnabled Bool to set automation enabled status. + function initialize(address _automationCore, address _registry, bool _automationEnabled) public initializer { _automationCore.validateContractAddress(); _registry.validateContractAddress(); automationCore = IAutomationCore(_automationCore); registry = IAutomationRegistry(_registry); - (CommonUtils.CycleState state, uint64 cycleId) = automationCore.isAutomationEnabled() ? (CommonUtils.CycleState.STARTED, 1) : (CommonUtils.CycleState.READY, 0); + (CommonUtils.CycleState state, uint64 cycleId) = _automationEnabled ? (CommonUtils.CycleState.STARTED, 1) : (CommonUtils.CycleState.READY, 0); cycleInfo.initializeCycle( cycleId, uint64(block.timestamp), automationCore.cycleDurationSecs(), - state + state, + _automationEnabled ); __Ownable2Step_init(); @@ -181,7 +184,6 @@ contract AutomationController is IAutomationController, Ownable2StepUpgradeable, if(!cycleInfo.ifTransitionStateExists()) { revert InvalidRegistryState(); } uint64 currentTime = uint64(block.timestamp); - uint256 cycleLockedFees = registry.getCycleLockedFees(); // Sort task indexes as order is important uint64[] memory taskIndexes = _taskIndexes.sortUint64(); @@ -199,14 +201,13 @@ contract AutomationController is IAutomationController, Ownable2StepUpgradeable, // Nothing to refund for GST tasks if (task.taskType == CommonUtils.TaskType.UST) { - (bool refunded, bytes memory data) = address(automationCore).call( + (bool refunded, ) = address(automationCore).call( abi.encodeCall( IAutomationCore.refundTaskFees, - (currentTime, cycleLockedFees, cycleInfo.refundDuration(), cycleInfo.automationFeePerSec(), task) + (currentTime, cycleInfo.refundDuration(), cycleInfo.automationFeePerSec(), task) ) ); require(refunded, RefundFailed()); - cycleLockedFees = abi.decode(data, (uint256)); } } } @@ -449,22 +450,23 @@ contract AutomationController is IAutomationController, Ownable2StepUpgradeable, bool transitionFinalized = isTransitionFinalized(); if (transitionFinalized) { - if (!automationCore.isAutomationEnabled() && cycleInfo.state() == CommonUtils.CycleState.FINISHED) { + if (!cycleInfo.automationEnabled() && cycleInfo.state() == CommonUtils.CycleState.FINISHED) { _tryMoveToSuspendedState(); } else { - (bool updated, ) = address(registry).call( + (bool updated, ) = address(automationCore).call( abi.encodeCall( - IAutomationRegistry.updateRegistryState, + IAutomationCore.updateGasCommittedAndCycleLockedFees, ( + cycleInfo.transitionState.lockedFees, cycleInfo.sysGasCommittedForNextCycle(), cycleInfo.gasCommittedForNextCycle(), - cycleInfo.gasCommittedForNewCycle(), - cycleInfo.transitionState.lockedFees, - FINISHED + cycleInfo.gasCommittedForNewCycle() ) ) ); - require(updated, UpdateRegistryStateFailed()); + require(updated, UpdateGasCommittedAndCycleLockedFeesFailed()); + + registry.updateTasks(CommonUtils.CycleState.FINISHED); // Set current timestamp as cycle start time // Increment the cycle and update the state to STARTED @@ -494,11 +496,13 @@ contract AutomationController is IAutomationController, Ownable2StepUpgradeable, return; } - (bool updated, )= address(registry).call(abi.encodeCall(IAutomationRegistry.updateRegistryState, (0, 0, 0, 0, SUSPENDED))); - require(updated, UpdateRegistryStateFailed()); + (bool updated, )= address(automationCore).call(abi.encodeCall(IAutomationCore.updateGasCommittedAndCycleLockedFees, (0, 0, 0, 0))); + require(updated, UpdateGasCommittedAndCycleLockedFeesFailed()); + + registry.updateTasks(CommonUtils.CycleState.SUSPENDED); // Check if automation is enabled - if (automationCore.isAutomationEnabled()) { + 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(); @@ -575,11 +579,6 @@ contract AutomationController is IAutomationController, Ownable2StepUpgradeable, } } - function tryMoveToSuspendedState() external { - if (msg.sender != address(automationCore)) { revert CallerNotAutomationCore(); } - _tryMoveToSuspendedState(); - } - /// @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 @@ -626,11 +625,6 @@ contract AutomationController is IAutomationController, Ownable2StepUpgradeable, updateCycleStateTo(CommonUtils.CycleState.STARTED); } - function moveToStartedState() external { - if (msg.sender != address(automationCore)) { revert CallerNotAutomationCore(); } - _moveToStartedState(); - } - /// @notice Updates the state of the cycle. /// @param _state Input state to update cycle state with. function updateCycleStateTo(CommonUtils.CycleState _state) private { @@ -657,7 +651,7 @@ contract AutomationController is IAutomationController, Ownable2StepUpgradeable, /// @notice Helper function called when cycle end is identified. function onCycleEndInternal() private { - if (!automationCore.isAutomationEnabled()) { + if (!cycleInfo.automationEnabled()) { _tryMoveToSuspendedState(); } else{ if(registry.totalTasks() == 0) { @@ -670,7 +664,7 @@ contract AutomationController is IAutomationController, Ownable2StepUpgradeable, // Updates transition state cycleInfo.setRefundDuration(0); cycleInfo.setNewCycleDuration(cycleInfo.durationSecs()); - cycleInfo.setGasCommittedForNewCycle(registry.getGasCommittedForNextCycle()); + cycleInfo.setGasCommittedForNewCycle(automationCore.getGasCommittedForNextCycle()); cycleInfo.setGasCommittedForNextCycle(0); cycleInfo.setSysGasCommittedForNextCycle (0); cycleInfo.transitionState.lockedFees = 0; @@ -693,20 +687,15 @@ contract AutomationController is IAutomationController, Ownable2StepUpgradeable, /// @notice Function to update the registry config structure with values extracted from the buffer, if the buffer exists. function _updateConfigFromBuffer() private { - (bool sent, ) = address(automationCore).call(abi.encodeCall(IAutomationCore.applyPendingConfig, ())); - require(sent, ConfigUpdateFailed()); - } - - /// @notice Helper function to update cycle duration. - function updateCyleDuration(uint64 _cycleDurationSecs) external { - if (msg.sender != address(automationCore)) { revert CallerNotAutomationCore(); } + (bool applied, uint64 cycleDuration) = automationCore.applyPendingConfig(); + if (!applied) return; // Check if transition state exists if (cycleInfo.ifTransitionStateExists()) { - cycleInfo.setNewCycleDuration(_cycleDurationSecs); + cycleInfo.setNewCycleDuration(cycleDuration); } else { - cycleInfo.setDurationSecs(_cycleDurationSecs); - } + cycleInfo.setDurationSecs(cycleDuration); + } } /// @notice Checks if the cycle transition is finalized. @@ -735,6 +724,11 @@ contract AutomationController is IAutomationController, Ownable2StepUpgradeable, return (cycleInfo.refundDuration(), cycleInfo.automationFeePerSec()); } + /// @notice Returns if automation is enabled. + function isAutomationEnabled() external view returns (bool) { + return cycleInfo.automationEnabled(); + } + // ::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::: ADMIN FUNCTIONS ::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::: /// @notice Function to update the AutomationRegistry contract address. @@ -759,6 +753,32 @@ contract AutomationController is IAutomationController, Ownable2StepUpgradeable, 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 ::::::::::::::::::::::::::::::::::::::::::::::::::::::::: diff --git a/solidity/supra_contracts/src/AutomationCore.sol b/solidity/supra_contracts/src/AutomationCore.sol index 56bd2cb3fc..3a701a5585 100644 --- a/solidity/supra_contracts/src/AutomationCore.sol +++ b/solidity/supra_contracts/src/AutomationCore.sol @@ -40,12 +40,6 @@ contract AutomationCore is IAutomationCore, Ownable2StepUpgradeable, UUPSUpgrade /// @notice Emitted when task registration is disabled. event TaskRegistrationDisabled(bool indexed status); - /// @notice Emitted when automation is enabled. - event AutomationEnabled(bool indexed status); - - /// @notice Emitted when automation is disabled. - event AutomationDisabled(bool indexed status); - /// @notice Emitted when the VM Signer address is updated. event VmSignerUpdated(address indexed oldVmSigner, address indexed newVmSigner); @@ -169,7 +163,6 @@ contract AutomationCore is IAutomationCore, Ownable2StepUpgradeable, UUPSUpgrade _registryMaxGasCap, _sysRegistryMaxGasCap, true, - true, _vmSigner, _erc20Supra, config @@ -365,7 +358,7 @@ contract AutomationCore is IAutomationCore, Ownable2StepUpgradeable, UUPSUpgrade function estimateAutomationFeeWithCommittedOccupancyInternal( uint128 _taskOccupancy, uint128 _committedOccupancy - ) public view returns (uint128) { + ) private view returns (uint128) { ( , , uint64 durationSecs, ) = IAutomationController(regConfig.automationController()).getCycleInfo(); uint128 totalCommittedGas = _taskOccupancy + _committedOccupancy; @@ -497,22 +490,21 @@ contract AutomationCore is IAutomationCore, Ownable2StepUpgradeable, UUPSUpgrade return (result, remainingLockedFees); } - /// @notice Helper function to update the registry configuration. - function _applyPendingConfig() private { - if (configBuffer.ifExists) { - regConfig.config = configBuffer.pendingConfig; - configBuffer.ifExists = false; - - IAutomationController(regConfig.automationController()).updateCyleDuration(configBuffer.pendingConfig.cycleDurationSecs()); - } - } - // ::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::: CONTROLLER FUNCTIONS ::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::: /// @notice Function to update the registry configuration, reverts if caller is not AutomationController. - function applyPendingConfig() external { + function applyPendingConfig() external returns (bool, uint64) { onlyController(); - _applyPendingConfig(); + + if (!configBuffer.ifExists) { + return (false, 0); + } + uint64 pendingCycleDuration = configBuffer.pendingConfig.cycleDurationSecs(); + regConfig.config = configBuffer.pendingConfig; + + delete configBuffer; + + return (true, pendingCycleDuration); } /// @notice Internally calls _calculateTaskFee, reverts if caller is not AutomationController. @@ -549,11 +541,10 @@ contract AutomationCore is IAutomationCore, Ownable2StepUpgradeable, UUPSUpgrade /// @notice Refunds the deposit fee and any autoamtion fees of the task. function refundTaskFees( uint64 _currentTime, - uint256 _cycleLockedFees, uint64 _refundDuration, uint128 _automationFeePerSec, CommonUtils.TaskDetails memory _task - ) external returns (uint256) { + ) external { onlyController(); // Do not attempt fee refund if remaining duration is 0 @@ -569,10 +560,10 @@ contract AutomationCore is IAutomationCore, Ownable2StepUpgradeable, UUPSUpgrade ( , uint256 remainingCycleLockedFees) = safeFeeRefund( _task.taskIndex, _task.owner, - _cycleLockedFees, + regConfig.cycleLockedFees, uint64(_refundFee) ); - _cycleLockedFees = remainingCycleLockedFees; + regConfig.cycleLockedFees = remainingCycleLockedFees; } _safeDepositRefund( @@ -581,16 +572,13 @@ contract AutomationCore is IAutomationCore, Ownable2StepUpgradeable, UUPSUpgrade _task.lockedFeeForNextCycle, _task.lockedFeeForNextCycle ); - - return _cycleLockedFees; } function calculateAutomationFeeMultiplierForCurrentCycleInternal() external view returns (uint128) { onlyController(); // Compute the automation fee multiplier for this cycle - uint128 gasCommittedForThisCycle = IAutomationRegistry(regConfig.registry).getGasCommittedForCurrentCycle(); return calculateAutomationFeeMultiplierForCycle( - gasCommittedForThisCycle, + regConfig.gasCommittedForThisCycle(), regConfig.registryMaxGasCap() ); } @@ -609,6 +597,26 @@ contract AutomationCore is IAutomationCore, Ownable2StepUpgradeable, UUPSUpgrade ); } + /// @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 to perform validation while registering a task. @@ -621,28 +629,28 @@ contract AutomationCore is IAutomationCore, Ownable2StepUpgradeable, UUPSUpgrade bytes memory _payloadTx, uint128 _maxGasAmount, bytes32 _txHash, - uint128 _gasCommittedForNextCycle, uint128 _gasPriceCap, - uint128 _automationFeeCapForCycle - ) external view { + uint128 _automationFeeCapForCycle, + uint64 _cycleEndTime + ) external { onlyRegistry(); - // Check if automation and registration is enabled - if (!regConfig.automationEnabled()) { revert AutomationNotEnabled(); } + // Check if registration is enabled if (!regConfig.registrationEnabled()) { revert RegistrationDisabled(); } if(_inputType != uint8(_taskType)) { revert InvalidTaskType(); } - ( , uint64 startTime, uint64 durationSecs, CommonUtils.CycleState state) = IAutomationController(regConfig.automationController()).getCycleInfo(); - if(state != CommonUtils.CycleState.STARTED) { revert CycleTransitionInProgress(); } - - uint128 nextCycleRegistryMaxGasCap; + bool isUST = _taskType == CommonUtils.TaskType.UST; + uint64 taskDurationCap; - if (_taskType == CommonUtils.TaskType.UST) { + uint128 gasCommittedForNextCycle; + uint128 nextCycleRegistryMaxGasCap; + if (isUST) { if(_totalTasks >= regConfig.taskCapacity()) { revert TaskCapacityReached(); } if(_gasPriceCap == 0) { revert InvalidGasPriceCap(); } - uint128 estimatedAutomationFeeForCycle = estimateAutomationFeeWithCommittedOccupancyInternal(_maxGasAmount, _gasCommittedForNextCycle); + gasCommittedForNextCycle = regConfig.gasCommittedForNextCycle(); + uint128 estimatedAutomationFeeForCycle = estimateAutomationFeeWithCommittedOccupancyInternal(_maxGasAmount, gasCommittedForNextCycle); if(_automationFeeCapForCycle < estimatedAutomationFeeForCycle) { revert InsufficientFeeCapForCycle(); } taskDurationCap = regConfig.taskDurationCapSecs(); @@ -650,15 +658,38 @@ contract AutomationCore is IAutomationCore, Ownable2StepUpgradeable, UUPSUpgrade } else { if(_totalTasks >= regConfig.sysTaskCapacity()) { revert TaskCapacityReached(); } + gasCommittedForNextCycle = regConfig.sysGasCommittedForNextCycle(); taskDurationCap = regConfig.sysTaskDurationCapSecs(); nextCycleRegistryMaxGasCap = regConfig.nextCycleSysRegistryMaxGasCap(); } - validateTaskDuration(_regTime, _expiryTime, taskDurationCap, startTime + durationSecs); + validateTaskDuration(_regTime, _expiryTime, taskDurationCap, _cycleEndTime); validateInputs(_payloadTx, _maxGasAmount, _txHash); - uint128 gasCommitted = _maxGasAmount + _gasCommittedForNextCycle; + 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. @@ -688,14 +719,12 @@ contract AutomationCore is IAutomationCore, Ownable2StepUpgradeable, UUPSUpgrade function unlockDepositAndCycleFee( uint64 _taskIndex, CommonUtils.TaskState _taskState, - uint128 _gasCommittedForThisCycle, uint64 _expiryTime, uint128 _maxGasAmount, uint64 _residualInterval, uint64 _currentTime, - uint128 _lockedFeeForNextCycle, - uint256 _cycleLockedFees - ) external returns (uint256, uint128, uint128) { + uint128 _lockedFeeForNextCycle + ) external returns (uint128, uint128) { onlyRegistry(); uint128 cycleFeeRefund; @@ -703,7 +732,7 @@ contract AutomationCore is IAutomationCore, Ownable2StepUpgradeable, UUPSUpgrade if(_taskState != CommonUtils.TaskState.PENDING) { // Compute the automation fee multiplier for cycle - uint128 automationFeePerSec = calculateAutomationFeeMultiplierForCycle(_gasCommittedForThisCycle, regConfig.registryMaxGasCap()); + uint128 automationFeePerSec = calculateAutomationFeeMultiplierForCycle(regConfig.gasCommittedForThisCycle(), regConfig.registryMaxGasCap()); uint128 taskFee = _calculateTaskFee( _taskState, @@ -725,10 +754,12 @@ contract AutomationCore is IAutomationCore, Ownable2StepUpgradeable, UUPSUpgrade bool result = _safeUnlockLockedDeposit(_taskIndex, _lockedFeeForNextCycle); if(!result) { revert ErrorDepositRefund(); } - (bool hasLockedFee, uint256 remainingCycleLockedFees ) = safeUnlockLockedCycleFee(_cycleLockedFees, uint64(cycleFeeRefund), _taskIndex); + (bool hasLockedFee, uint256 remainingCycleLockedFees ) = safeUnlockLockedCycleFee(regConfig.cycleLockedFees, uint64(cycleFeeRefund), _taskIndex); if(!hasLockedFee) { revert ErrorCycleFeeRefund(); } - return (remainingCycleLockedFees, cycleFeeRefund, depositRefund); + regConfig.cycleLockedFees = remainingCycleLockedFees; + + return (cycleFeeRefund, depositRefund); } // :::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::: ADMIN FUNCTIONS :::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::: @@ -760,11 +791,8 @@ contract AutomationCore is IAutomationCore, Ownable2StepUpgradeable, UUPSUpgrade _sysTaskCapacity ); - uint128 gasCommittedForNextCycle = IAutomationRegistry(regConfig.registry).getGasCommittedForNextCycle(); - uint128 systemGasCommittedForNextCycle = IAutomationRegistry(regConfig.registry).getSystemGasCommittedForNextCycle(); - - if(gasCommittedForNextCycle > _registryMaxGasCap) { revert UnacceptableRegistryMaxGasCap(); } - if(systemGasCommittedForNextCycle > _sysRegistryMaxGasCap) { revert UnacceptableSysRegistryMaxGasCap(); } + if(regConfig.gasCommittedForNextCycle() > _registryMaxGasCap) { revert UnacceptableRegistryMaxGasCap(); } + if(regConfig.sysGasCommittedForNextCycle() > _sysRegistryMaxGasCap) { revert UnacceptableSysRegistryMaxGasCap(); } // Add new config to the buffer LibConfig.Config memory pendingConfig = LibConfig.createConfig( @@ -805,37 +833,6 @@ contract AutomationCore is IAutomationCore, Ownable2StepUpgradeable, UUPSUpgrade emit TaskRegistrationDisabled(regConfig.registrationEnabled()); } - /// @notice Function to enable the automation. - function enableAutomation() external onlyOwner { - if(regConfig.automationEnabled()) { revert AlreadyEnabled(); } - - IAutomationController controller = IAutomationController(regConfig.automationController()); - ( , , , CommonUtils.CycleState state) = controller.getCycleInfo(); - regConfig.setAutomationEnabled(true); - - if (state == CommonUtils.CycleState.READY) { - controller.moveToStartedState(); - _applyPendingConfig(); - } - - emit AutomationEnabled(regConfig.automationEnabled()); - } - - /// @notice Function to disable the automation. - function disableAutomation() external onlyOwner { - if(!regConfig.automationEnabled()) { revert AlreadyDisabled(); } - - IAutomationController controller = IAutomationController(regConfig.automationController()); - ( , , , CommonUtils.CycleState state) = controller.getCycleInfo(); - - regConfig.setAutomationEnabled(false); - if (state == CommonUtils.CycleState.FINISHED && !controller.isTransitionInProgress()) { - controller.tryMoveToSuspendedState(); - } - - emit AutomationDisabled(regConfig.automationEnabled()); - } - /// @notice Function to update the VM Signer address. /// @param _vmSigner New address for VM Signer. function setVmSigner(address _vmSigner) external onlyOwner { @@ -889,9 +886,7 @@ contract AutomationCore is IAutomationCore, Ownable2StepUpgradeable, UUPSUpgrade uint256 balance = IERC20(regConfig.erc20Supra).balanceOf(address(this)); if(balance < _amount) { revert InsufficientBalance(); } - - uint256 cycleLockedFees = IAutomationRegistry(regConfig.registry).getCycleLockedFees(); - if(balance - _amount < cycleLockedFees + regConfig.totalDepositedAutomationFees) { revert RequestExceedsLockedBalance(); } + if(balance - _amount < regConfig.cycleLockedFees + regConfig.totalDepositedAutomationFees) { revert RequestExceedsLockedBalance(); } bool sent = IERC20(regConfig.erc20Supra).transfer(_recipient, _amount); if(!sent) { revert TransferFailed(); } @@ -921,16 +916,31 @@ contract AutomationCore is IAutomationCore, Ownable2StepUpgradeable, UUPSUpgrade return regConfig.registry; } - /// @notice Returns if automation is enabled. - function isAutomationEnabled() external view returns (bool) { - return regConfig.automationEnabled(); - } - /// @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(); @@ -976,11 +986,41 @@ contract AutomationCore is IAutomationCore, Ownable2StepUpgradeable, UUPSUpgrade 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. diff --git a/solidity/supra_contracts/src/AutomationRegistry.sol b/solidity/supra_contracts/src/AutomationRegistry.sol index bbdaeb117b..bad5e17a3e 100644 --- a/solidity/supra_contracts/src/AutomationRegistry.sol +++ b/solidity/supra_contracts/src/AutomationRegistry.sol @@ -21,13 +21,8 @@ contract AutomationRegistry is IAutomationRegistry, Ownable2StepUpgradeable, UUP /// Factor of `2` suggests that `1/2` of the deposit will be refunded. uint8 constant REFUND_FACTOR = 2; - /// @dev Defines the cycle state, used to update the registry. - uint8 constant SUSPENDED = 0; - uint8 constant FINISHED = 1; - /// @dev State variables LibRegistry.RegistryState regState; - LibRegistry.RegistryStateSystemTasks regSysState; address public automationCore; address public automationController; @@ -116,10 +111,12 @@ contract AutomationRegistry is IAutomationRegistry, Ownable2StepUpgradeable, UUP uint8 _type, bytes[] memory _auxData ) external { - uint128 flatRegistrationFeeWei = IAutomationCore(automationCore).flatRegistrationFeeWei(); + if (!IAutomationController(automationController).isAutomationEnabled()) { revert AutomationNotEnabled(); } + + ( , uint64 startTime, uint64 durationSecs, CommonUtils.CycleState state) = IAutomationController(automationController).getCycleInfo(); + if(state != CommonUtils.CycleState.STARTED) { revert CycleTransitionInProgress(); } uint64 regTime = uint64(block.timestamp); - uint128 gasCommittedForNextCycle = regState.gasCommittedForNextCycle(); IAutomationCore(automationCore).validateRegistration( totalTasks(), _type, @@ -129,13 +126,11 @@ contract AutomationRegistry is IAutomationRegistry, Ownable2StepUpgradeable, UUP _payloadTx, _maxGasAmount, _txHash, - gasCommittedForNextCycle, _gasPriceCap, - _automationFeeCapForCycle + _automationFeeCapForCycle, + startTime + durationSecs ); - uint128 gasCommitted = _maxGasAmount + gasCommittedForNextCycle; - regState.setGasCommittedForNextCycle(gasCommitted); uint64 taskIndex = regState.currentIndex; LibRegistry.TaskMetadata memory taskMetadata = LibRegistry.createTaskMetadata( @@ -160,6 +155,7 @@ contract AutomationRegistry is IAutomationRegistry, Ownable2StepUpgradeable, UUP regState.currentIndex += 1; IAutomationCore(automationCore).incTotalDepositedAutomationFees(_automationFeeCapForCycle); + uint128 flatRegistrationFeeWei = IAutomationCore(automationCore).flatRegistrationFeeWei(); uint128 fee = flatRegistrationFeeWei + _automationFeeCapForCycle; IAutomationCore(automationCore).chargeFees(msg.sender, fee); @@ -183,10 +179,13 @@ contract AutomationRegistry is IAutomationRegistry, Ownable2StepUpgradeable, UUP uint8 _type, bytes[] memory _auxData ) external { + if (!IAutomationController(automationController).isAutomationEnabled()) { revert AutomationNotEnabled(); } if(!isAuthorizedSubmitter(msg.sender)) { revert UnauthorizedAccount(); } + + ( , uint64 startTime, uint64 durationSecs, CommonUtils.CycleState state) = IAutomationController(automationController).getCycleInfo(); + if (state != CommonUtils.CycleState.STARTED) { revert CycleTransitionInProgress(); } uint64 regTime = uint64(block.timestamp); - uint128 gasCommittedForNextCycle = regSysState.gasCommittedForNextCycle(); IAutomationCore(automationCore).validateRegistration( totalSystemTasks(), _type, @@ -196,16 +195,12 @@ contract AutomationRegistry is IAutomationRegistry, Ownable2StepUpgradeable, UUP _payloadTx, _maxGasAmount, _txHash, - gasCommittedForNextCycle, 0, - 0 + 0, + startTime + durationSecs ); - uint128 gasCommitted = _maxGasAmount + gasCommittedForNextCycle; - regSysState.setGasCommittedForNextCycle(gasCommitted); - 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, @@ -226,7 +221,7 @@ contract AutomationRegistry is IAutomationRegistry, Ownable2StepUpgradeable, UUP regState.tasks[taskIndex] = taskMetadata; require(regState.taskIdList.add(taskIndex), TaskIndexNotUnique()); - require(regSysState.taskIds.add(taskIndex), TaskIndexNotUnique()); + require(regState.sysTaskIds.add(taskIndex), TaskIndexNotUnique()); regState.currentIndex += 1; emit SystemTaskRegistered(taskIndex, msg.sender, block.timestamp, regState.tasks[taskIndex].getTaskDetails()); @@ -244,7 +239,7 @@ contract AutomationRegistry is IAutomationRegistry, Ownable2StepUpgradeable, UUP uint64 _taskIndex ) external { // Check if automation is enabled - if (!IAutomationCore(automationCore).isAutomationEnabled()) { revert AutomationNotEnabled(); } + if (!IAutomationController(automationController).isAutomationEnabled()) { revert AutomationNotEnabled(); } ( , uint64 startTime, uint64 durationSecs, CommonUtils.CycleState state) = IAutomationController(automationController).getCycleInfo(); @@ -276,11 +271,7 @@ contract AutomationRegistry is IAutomationRegistry, Ownable2StepUpgradeable, UUP // 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 > (startTime + durationSecs)) { - uint128 gasCommittedForNextCycle = regState.gasCommittedForNextCycle(); - if(gasCommittedForNextCycle < task.maxGasAmount) { revert GasCommittedValueUnderflow(); } - - // Adjust the gas committed for the next cycle by subtracting the gas amount of the cancelled task - regState.setGasCommittedForNextCycle(gasCommittedForNextCycle - task.maxGasAmount); + IAutomationCore(automationCore).updateGasCommittedForNextCycle(task.taskType, task.maxGasAmount); } emit TaskCancelled( _taskIndex, task.owner, task.txHash); @@ -298,7 +289,7 @@ contract AutomationRegistry is IAutomationRegistry, Ownable2StepUpgradeable, UUP uint64 _taskIndex ) external { // Check if automation is enabled - if (!IAutomationCore(automationCore).isAutomationEnabled()) { revert AutomationNotEnabled(); } + if (!IAutomationController(automationController).isAutomationEnabled()) { revert AutomationNotEnabled(); } ( , uint64 startTime, uint64 durationSecs, CommonUtils.CycleState state) = IAutomationController(automationController).getCycleInfo(); @@ -323,11 +314,7 @@ contract AutomationRegistry is IAutomationRegistry, Ownable2StepUpgradeable, UUP // 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 > startTime + durationSecs) { - uint128 gasCommittedForNextCycle = regSysState.gasCommittedForNextCycle(); - if(gasCommittedForNextCycle < task.maxGasAmount) { revert GasCommittedValueUnderflow(); } - - // Adjust the gas committed for the next cycle by subtracting the gas amount of the cancelled task - regSysState.setGasCommittedForNextCycle(gasCommittedForNextCycle - task.maxGasAmount); + IAutomationCore(automationCore).updateGasCommittedForNextCycle(task.taskType, task.maxGasAmount); } emit TaskCancelled(_taskIndex, msg.sender, task.txHash); @@ -343,7 +330,7 @@ contract AutomationRegistry is IAutomationRegistry, Ownable2StepUpgradeable, UUP uint64[] memory _taskIndexes ) external { // Check if automation is enabled - if (!IAutomationCore(automationCore).isAutomationEnabled()) { revert AutomationNotEnabled(); } + if (!IAutomationController(automationController).isAutomationEnabled()) { revert AutomationNotEnabled(); } address erc20Supra = IAutomationCore(automationCore).erc20Supra(); @@ -355,7 +342,6 @@ contract AutomationRegistry is IAutomationRegistry, Ownable2StepUpgradeable, UUP uint256 counter = 0; uint128 totalRefundFee = 0; - uint256 cycleLockedFees = regState.cycleLockedFees; // Calculate refundable fee for this remaining time task in current cycle uint256 currentTime = block.timestamp; @@ -382,25 +368,19 @@ contract AutomationRegistry is IAutomationRegistry, Ownable2StepUpgradeable, UUP // 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) { - // Prevent underflow in gas committed - uint128 gasCommittedForNextCycle = regState.gasCommittedForNextCycle(); - if(gasCommittedForNextCycle < task.maxGasAmount) { revert GasCommittedValueUnderflow(); } // Reduce committed gas by the stopped task's max gas - regState.setGasCommittedForNextCycle(gasCommittedForNextCycle - task.maxGasAmount); + IAutomationCore(automationCore).updateGasCommittedForNextCycle(task.taskType, task.maxGasAmount); } - (uint256 remainingCycleLockedFees, uint128 cycleFeeRefund, uint128 depositRefund) = IAutomationCore(automationCore).unlockDepositAndCycleFee( + (uint128 cycleFeeRefund, uint128 depositRefund) = IAutomationCore(automationCore).unlockDepositAndCycleFee( _taskIndexes[i], task.state, - regState.gasCommittedForThisCycle(), task.expiryTime, task.maxGasAmount, residualInterval, uint64(currentTime), - task.lockedFeeForNextCycle, - cycleLockedFees + task.lockedFeeForNextCycle ); - cycleLockedFees = remainingCycleLockedFees; totalRefundFee += (cycleFeeRefund + depositRefund); @@ -441,7 +421,7 @@ contract AutomationRegistry is IAutomationRegistry, Ownable2StepUpgradeable, UUP uint64[] memory _taskIndexes ) external { // Check if automation is enabled - if (!IAutomationCore(automationCore).isAutomationEnabled()) { revert AutomationNotEnabled(); } + if (!IAutomationController(automationController).isAutomationEnabled()) { revert AutomationNotEnabled(); } ( , uint64 startTime, uint64 durationSecs, CommonUtils.CycleState state) = IAutomationController(automationController).getCycleInfo(); if(state != CommonUtils.CycleState.STARTED) { revert CycleTransitionInProgress(); } @@ -469,10 +449,7 @@ contract AutomationRegistry is IAutomationRegistry, Ownable2StepUpgradeable, UUP require(regState.activeTaskIds.remove(_taskIndexes[i]), TaskIndexNotFound()); if(task.state != CommonUtils.TaskState.CANCELLED && task.expiryTime > cycleEndTime) { - // Prevent underflow in gas committed - uint128 gasCommittedForNextCycle = regSysState.gasCommittedForNextCycle(); - if(gasCommittedForNextCycle < task.maxGasAmount) { revert GasCommittedValueUnderflow(); } - regSysState.setGasCommittedForNextCycle(gasCommittedForNextCycle - task.maxGasAmount); + IAutomationCore(automationCore).updateGasCommittedForNextCycle(task.taskType, task.maxGasAmount); } // Add to stopped tasks @@ -498,20 +475,12 @@ contract AutomationRegistry is IAutomationRegistry, Ownable2StepUpgradeable, UUP // :::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::: HELPER FUNCTIONS :::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::: - /// @notice Helper function to update the active task indexes. - function updateActiveTaskIds() private { - uint256[] memory taskIds = regState.taskIdList.values(); - for (uint256 i = 0; i < taskIds.length; i++) { - regState.activeTaskIds.add(taskIds[i]); - } - } - /// @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(regSysState.taskIds.remove(_taskIndex), TaskIndexNotFound()); + require(regState.sysTaskIds.remove(_taskIndex), TaskIndexNotFound()); } delete regState.tasks[_taskIndex]; @@ -528,14 +497,14 @@ contract AutomationRegistry is IAutomationRegistry, Ownable2StepUpgradeable, UUP /// @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(regSysState.authorizedAccounts.add(_account), AddressAlreadyExists()); + 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(regSysState.authorizedAccounts.remove(_account), AddressDoesNotExist()); + require(regState.authorizedAccounts.remove(_account), AddressDoesNotExist()); emit AuthorizationRevoked(_account, block.timestamp); } @@ -577,32 +546,20 @@ contract AutomationRegistry is IAutomationRegistry, Ownable2StepUpgradeable, UUP LibRegistry.setState(regState.tasks[_taskIndex], uint8(_taskState)); } - /// @notice Function to update registry state. - /// @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 _lockedFees Updated cycle locked fees + /// @notice Function to update tasks lists. /// @param _state Cycle transition state executing the update. - function updateRegistryState( - uint128 _sysGasCommittedForNextCycle, - uint128 _gasCommittedForNextCycle, - uint128 _gasCommittedForNewCycle, - uint256 _lockedFees, - uint8 _state - ) external { + function updateTasks(CommonUtils.CycleState _state) external { onlyController(); - regSysState.setGasCommittedForNextCycle(_sysGasCommittedForNextCycle); - regSysState.setGasCommittedForThisCycle(_sysGasCommittedForNextCycle); - regState.setGasCommittedForNextCycle(_gasCommittedForNextCycle); - regState.setGasCommittedForThisCycle(_gasCommittedForNewCycle); - regState.cycleLockedFees = _lockedFees; regState.activeTaskIds.clear(); - if(_state == FINISHED) { - updateActiveTaskIds(); + 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 { - regSysState.taskIds.clear(); + regState.sysTaskIds.clear(); } } @@ -633,15 +590,7 @@ contract AutomationRegistry is IAutomationRegistry, Ownable2StepUpgradeable, UUP ); } - // :::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::: VIEW FUNCTIONS :::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::: - - /// @notice Returns the total amount locked which comprises of 'cycleLockedFees' and 'totalDepositedAutomationFees'. - function getTotalLockedBalance() external view returns (uint256) { - uint256 getTotalDepositedAutomationFees = IAutomationCore(automationCore).getTotalDepositedAutomationFees(); - - // return regState.cycleLockedFees + deposit.totalDepositedAutomationFees; - return regState.cycleLockedFees + getTotalDepositedAutomationFees; - } + // :::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::: 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. @@ -677,7 +626,7 @@ contract AutomationRegistry is IAutomationRegistry, Ownable2StepUpgradeable, UUP /// @notice Returns the number of total system tasks. function totalSystemTasks() public view returns (uint256) { - return regSysState.taskIds.length(); + return regState.sysTaskIds.length(); } /// @notice Returns the next task index. @@ -701,7 +650,7 @@ contract AutomationRegistry is IAutomationRegistry, Ownable2StepUpgradeable, UUP /// @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 regSysState.taskIds.contains(_taskIndex); + return regState.sysTaskIds.contains(_taskIndex); } /// @notice Validates the input task type against the task type. @@ -723,31 +672,11 @@ contract AutomationRegistry is IAutomationRegistry, Ownable2StepUpgradeable, UUP function getTaskState(uint64 _taskIndex) external view returns (CommonUtils.TaskState) { return LibRegistry.state(regState.tasks[_taskIndex]); } - - /// @notice Returns the gas committed for the next cycle. - function getGasCommittedForNextCycle() external view returns (uint128) { - return regState.gasCommittedForNextCycle(); - } - - /// @notice Returns the gas committed for the current cycle. - function getGasCommittedForCurrentCycle() external view returns (uint128) { - return regState.gasCommittedForThisCycle(); - } - - /// @notice Returns the system gas committed for the next cycle. - function getSystemGasCommittedForNextCycle() external view returns (uint128) { - return regSysState.gasCommittedForNextCycle(); - } - - /// @notice Returns the system gas committed for the current cycle. - function getSystemGasCommittedForCurrentCycle() external view returns (uint128) { - return regSysState.gasCommittedForThisCycle(); - } /// @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 regSysState.authorizedAccounts.contains(_account); + return regState.authorizedAccounts.contains(_account); } /// @notice Returns the total number of active tasks. @@ -759,11 +688,6 @@ contract AutomationRegistry is IAutomationRegistry, Ownable2StepUpgradeable, UUP function getAllActiveTaskIds() external view returns (uint256[] memory) { return regState.activeTaskIds.values(); } - - /// @notice Returns the locked fees for the cycle. - function getCycleLockedFees() external view returns (uint256) { - return regState.cycleLockedFees; - } /// @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) { @@ -782,26 +706,6 @@ contract AutomationRegistry is IAutomationRegistry, Ownable2StepUpgradeable, UUP return task.owner() == _account && task.state() != CommonUtils.TaskState.PENDING && task.taskType() == _type; } - /// @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 estimateAutomationFeeWithCommittedOccupancy(_taskOccupancy, regState.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 - ) public view returns (uint128) { - return IAutomationCore(automationCore).estimateAutomationFeeWithCommittedOccupancyInternal( - _taskOccupancy, - _committedOccupancy - ); - } - // ::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::: UPGRADEABILITY FUNCTIONS ::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::: /// @notice Helper function that reverts when 'msg.sender' is not authorized to upgrade the contract. diff --git a/solidity/supra_contracts/src/IAutomationController.sol b/solidity/supra_contracts/src/IAutomationController.sol index 9a790e3967..006cf85f37 100644 --- a/solidity/supra_contracts/src/IAutomationController.sol +++ b/solidity/supra_contracts/src/IAutomationController.sol @@ -5,9 +5,9 @@ import {CommonUtils} from "./CommonUtils.sol"; interface IAutomationController { // Custom errors - error CallerNotAutomationCore(); + error AlreadyEnabled(); + error AlreadyDisabled(); error CallerNotVmSigner(); - error ConfigUpdateFailed(); error InconsistentTransitionState(); error InvalidInputCycleIndex(); error InvalidRegistryState(); @@ -17,17 +17,15 @@ interface IAutomationController { error RemoveTaskFailed(); error TransferFailed(); error UnlockLockedDepositFailed(); - error UpdateRegistryStateFailed(); + error UpdateGasCommittedAndCycleLockedFeesFailed(); error UpdateTaskStateFailed(); // View functions function getCycleInfo() external view returns (uint64, uint64, uint64, CommonUtils.CycleState); function getTransitionInfo() external view returns (uint64, uint128); + function isAutomationEnabled() external view returns (bool); function isTransitionInProgress() external view returns (bool); // State updating functions function monitorCycleEnd() external; - function moveToStartedState() external; - function tryMoveToSuspendedState() external; - function updateCyleDuration(uint64 cycleDurationSecs) external; } diff --git a/solidity/supra_contracts/src/IAutomationCore.sol b/solidity/supra_contracts/src/IAutomationCore.sol index a33541af96..95c683f024 100644 --- a/solidity/supra_contracts/src/IAutomationCore.sol +++ b/solidity/supra_contracts/src/IAutomationCore.sol @@ -6,10 +6,8 @@ import {CommonUtils} from "./CommonUtils.sol"; interface IAutomationCore { // Custom errors error AddressCannotBeZero(); - error AutomationNotEnabled(); error CallerNotController(); error CallerNotRegistry(); - error ColdWalletNotSet(); error CycleTransitionInProgress(); error ErrorDepositRefund(); error ErrorCycleFeeRefund(); @@ -20,6 +18,7 @@ interface IAutomationCore { error AlreadyEnabled(); error AlreadyDisabled(); error GasCommittedExceedsMaxGasCap(); + error GasCommittedValueUnderflow(); error InsufficientBalance(); error InsufficientFeeCapForCycle(); error InvalidCongestionExponent(); @@ -43,14 +42,9 @@ interface IAutomationCore { error UnauthorizedCaller(); // View functions - function isAutomationEnabled() external view returns (bool); function flatRegistrationFeeWei() external view returns (uint128); function getAutomationController() external view returns (address); function erc20Supra() external view returns (address); - function estimateAutomationFeeWithCommittedOccupancyInternal( - uint128 _taskOccupancy, - uint128 _committedOccupancy - ) external view returns (uint128); function calculateTaskFee( CommonUtils.TaskState _state, uint64 _expiryTime, @@ -63,6 +57,8 @@ interface IAutomationCore { 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 validateRegistration( uint256 _totalTasks, @@ -73,13 +69,13 @@ interface IAutomationCore { bytes memory _payloadTx, uint128 _maxGasAmount, bytes32 _txHash, - uint128 _gasCommittedForNextCycle, uint128 _gasPriceCap, - uint128 _automationFeeCapForCycle - ) external view; + uint128 _automationFeeCapForCycle, + uint64 _cycleEndTime + ) external; // State updating functions - function applyPendingConfig() external; + function applyPendingConfig() external returns (bool, uint64); function incTotalDepositedAutomationFees(uint256 _totalDepositedAutomationFees) external; function chargeFees(address _from, uint256 _amount) external; function safeUnlockLockedDeposit( @@ -88,11 +84,10 @@ interface IAutomationCore { ) external returns (bool); function refundTaskFees( uint64 _currentTime, - uint256 _cycleLockedFees, uint64 _refundDuration, uint128 _automationFeePerSec, CommonUtils.TaskDetails memory _task - ) external returns (uint256); + ) external; function safeDepositRefund( uint64 _taskIndex, address _taskOwner, @@ -103,12 +98,17 @@ interface IAutomationCore { function unlockDepositAndCycleFee( uint64 _taskIndex, CommonUtils.TaskState _taskState, - uint128 _gasCommittedForThisCycle, uint64 _expiryTime, uint128 _maxGasAmount, uint64 _residualInterval, uint64 _currentTime, - uint128 _lockedFeeForNextCycle, - uint256 _cycleLockedFees - ) external returns (uint256, uint128, uint128); + uint128 _lockedFeeForNextCycle + ) 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 index b9b4690795..9da61695f5 100644 --- a/solidity/supra_contracts/src/IAutomationRegistry.sol +++ b/solidity/supra_contracts/src/IAutomationRegistry.sol @@ -7,6 +7,7 @@ interface IAutomationRegistry { // Custom errors error AddressAlreadyExists(); error AddressDoesNotExist(); + error AutomationNotEnabled(); error CallerNotController(); error UnauthorizedAccount(); error CycleTransitionInProgress(); @@ -14,12 +15,10 @@ interface IAutomationRegistry { error UnsupportedTaskOperation(); error AlreadyCancelled(); error ErrorDepositRefund(); - error GasCommittedValueUnderflow(); error SystemTaskDoesNotExist(); error TaskIndexesCannotBeEmpty(); error InsufficientBalanceForRefund(); error RegisteredTaskInvalidType(); - error AutomationNotEnabled(); error TaskIndexNotFound(); error TaskIndexNotUnique(); @@ -27,25 +26,15 @@ interface IAutomationRegistry { 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 getCycleLockedFees() external view returns (uint256); - function getGasCommittedForNextCycle() external view returns (uint128); - function getSystemGasCommittedForNextCycle() external view returns (uint128); 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); - function getGasCommittedForCurrentCycle() external view returns (uint128); // State updating functions function removeTask(uint64 _taskIndex, bool _removeFromSysReg) external; function updateTaskState(uint64 _taskIndex, CommonUtils.TaskState _taskState) external; - function updateRegistryState( - uint128 _sysGasCommittedForNextCycle, - uint128 _gasCommittedForNextCycle, - uint128 _gasCommittedForNewCycle, - uint256 _lockedFees, - uint8 _state - ) external; + function updateTasks(CommonUtils.CycleState _state) external; function refundDepositAndDrop( uint64 _taskIndex, address _taskOwner, diff --git a/solidity/supra_contracts/src/LibConfig.sol b/solidity/supra_contracts/src/LibConfig.sol index a380ac466f..ec86fa22c1 100644 --- a/solidity/supra_contracts/src/LibConfig.sol +++ b/solidity/supra_contracts/src/LibConfig.sol @@ -29,10 +29,16 @@ library LibConfig { /// @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 | bool - uint256 controller_registrationEnabled_automationEnabled; + // address | bool(1 bit) + uint256 controller_registrationEnabled; + uint256 cycleLockedFees; uint256 totalDepositedAutomationFees; address vmSigner; address erc20Supra; @@ -44,7 +50,6 @@ library LibConfig { uint128 _nextCycleRegistryMaxGasCap, uint128 _nextCycleSysRegistryMaxGasCap, bool _registrationEnabled, - bool _automationEnabled, address _vmSigner, address _erc20Supra, Config memory _config @@ -54,11 +59,9 @@ library LibConfig { (uint256(_nextCycleRegistryMaxGasCap) << 128) | uint256(_nextCycleSysRegistryMaxGasCap); - // Pack controller (address) | registrationEnabled (bool at bit 95) | automationEnabled (bool at bit 94) + // Pack controller (address) | registrationEnabled (bool at bit 95) // Sets controller as address(0) - rcfg.controller_registrationEnabled_automationEnabled = - (_registrationEnabled ? (uint256(1) << 95) : 0) | - (_automationEnabled ? (uint256(1) << 94) : 0); + rcfg.controller_registrationEnabled = _registrationEnabled ? uint256(1) << 95 : 0; rcfg.vmSigner = _vmSigner; rcfg.erc20Supra = _erc20Supra; @@ -67,6 +70,54 @@ library LibConfig { 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); @@ -88,41 +139,29 @@ library LibConfig { r.nextCycleRegistryMaxGasCap_nextCycleSysRegistryMaxGasCap |= uint256(value); } - // controller (address) | registrationEnabled (bool) [stored at bit 95] | automationEnabled (bool) [stored at bit 94] + // controller (address) | registrationEnabled (bool)[bit 95] function automationController(RegistryConfig storage r) internal view returns (address) { - return address(uint160(r.controller_registrationEnabled_automationEnabled >> 96)); + return address(uint160(r.controller_registrationEnabled >> 96)); } function registrationEnabled(RegistryConfig storage r) internal view returns (bool) { - return (r.controller_registrationEnabled_automationEnabled >> 95) & 1 != 0; - } - - function automationEnabled(RegistryConfig storage r) internal view returns (bool) { - return (r.controller_registrationEnabled_automationEnabled >> 94) & 1 != 0; + return (r.controller_registrationEnabled >> 95) & 1 != 0; } function setAutomationController(RegistryConfig storage r, address _controller) internal { // clear top 160 bits - r.controller_registrationEnabled_automationEnabled &= ~(MAX_UINT160 << 96); + r.controller_registrationEnabled &= ~(MAX_UINT160 << 96); // insert 160-bit address - r.controller_registrationEnabled_automationEnabled |= uint256(uint160(_controller)) << 96; + r.controller_registrationEnabled |= uint256(uint160(_controller)) << 96; } function setRegistrationEnabled(RegistryConfig storage r, bool enabled) internal { // clear bit 95 - r.controller_registrationEnabled_automationEnabled &= ~(uint256(1) << 95); + r.controller_registrationEnabled &= ~(uint256(1) << 95); // set bit 95 if enabled - r.controller_registrationEnabled_automationEnabled |= enabled ? (uint256(1) << 95) : 0; - } - - function setAutomationEnabled(RegistryConfig storage r, bool enabled) internal { - // clear bit 94 - r.controller_registrationEnabled_automationEnabled &= ~(uint256(1) << 94); - - // set bit 94 if enabled - r.controller_registrationEnabled_automationEnabled |= enabled ? (uint256(1) << 94) : 0; + r.controller_registrationEnabled |= enabled ? (uint256(1) << 95) : 0; } // ::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::: Config ::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::: diff --git a/solidity/supra_contracts/src/LibController.sol b/solidity/supra_contracts/src/LibController.sol index d9a2d94df2..6ea298b744 100644 --- a/solidity/supra_contracts/src/LibController.sol +++ b/solidity/supra_contracts/src/LibController.sol @@ -14,8 +14,8 @@ library LibController { /// @notice Struct representing the state of current cycle. struct AutomationCycleInfo{ - // uint64 | uint64 | uint64 | CycleState | bool - uint256 index_startTime_durationSecs_state_ifTransitionStateExists; + // uint64 | uint64 | uint64 | CycleState(uint8) | bool(1 bit) | bool(1 bit) + uint256 index_startTime_durationSecs_state_ifTransitionStateExists_automationEnabled; TransitionState transitionState; } @@ -38,59 +38,70 @@ library LibController { uint64 _index, uint64 _startTime, uint64 _durationSecs, - CommonUtils.CycleState _cycleState + CommonUtils.CycleState _cycleState, + bool _automationEnabled ) internal { - _cycleInfo.index_startTime_durationSecs_state_ifTransitionStateExists = + _cycleInfo.index_startTime_durationSecs_state_ifTransitionStateExists_automationEnabled = (uint256(_index) << 192) | (uint256(_startTime) << 128) | (uint256(_durationSecs) << 64) | - (uint256(_cycleState) << 56); + (uint256(_cycleState) << 56) | + (_automationEnabled ? (uint256(1) << 54) : 0); } - // index (uint64) | startTime (uint64) | durationSecs (uint64) | state (CycleState/uint8) | ifTransitionStateExists (bool) [stored at bit 55] + // 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 >> 192); + 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 >> 128); + 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 >> 64); + 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 >> 56)); + 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 >> 55) & 1) != 0; + 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 &= ~(MAX_UINT64 << 192); // Clear old bits - cycle.index_startTime_durationSecs_state_ifTransitionStateExists |= uint256(_index) << 192; // Set new value + 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 &= ~(MAX_UINT64 << 128); - cycle.index_startTime_durationSecs_state_ifTransitionStateExists |= uint256(_startTime) << 128; + 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 &= ~(MAX_UINT64 << 64); - cycle.index_startTime_durationSecs_state_ifTransitionStateExists |= uint256(_durationSecs) << 64; + 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 &= ~(MAX_UINT8 << 56); - cycle.index_startTime_durationSecs_state_ifTransitionStateExists |= uint256(_state) << 56; + 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 &= ~(uint256(1) << 55); - cycle.index_startTime_durationSecs_state_ifTransitionStateExists |= exists ? (uint256(1) << 55) : 0; + 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 :::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::: diff --git a/solidity/supra_contracts/src/LibRegistry.sol b/solidity/supra_contracts/src/LibRegistry.sol index b3bb156205..357ff64ca6 100644 --- a/solidity/supra_contracts/src/LibRegistry.sol +++ b/solidity/supra_contracts/src/LibRegistry.sol @@ -11,7 +11,6 @@ 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_UINT16 = type(uint16).max; uint256 private constant MAX_UINT8 = type(uint8).max; // :::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::: TaskMetadata :::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::: @@ -186,78 +185,17 @@ library LibRegistry { /// @notice Tracks per-cycle automation state and task indexes. struct RegistryState { - uint256 cycleLockedFees; - - // uint128 | uint128 - uint256 gasCommittedForNextCycle_gasCommittedForThisCycle; - 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 - } - - // gasCommittedForNextCycle (uint128) | gasCommittedForThisCycle (uint128) - function gasCommittedForNextCycle(RegistryState storage r) internal view returns (uint128) { - return uint128(r.gasCommittedForNextCycle_gasCommittedForThisCycle >> 128); - } - - function gasCommittedForThisCycle(RegistryState storage r) internal view returns (uint128) { - return uint128(r.gasCommittedForNextCycle_gasCommittedForThisCycle); - } - - function setGasCommittedForNextCycle(RegistryState 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(RegistryState 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); - } - - // ::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::: RegistryStateSystemTasks ::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::: - /// @notice Tracks per-cycle automation state and task indexes for system tasks. - struct RegistryStateSystemTasks { - // uint128 | uint128 - uint256 gasCommittedForNextCycle_gasCommittedForThisCycle; - - EnumerableSet.UintSet taskIds; + EnumerableSet.UintSet sysTaskIds; EnumerableSet.AddressSet authorizedAccounts; } - // gasCommittedForNextCycle (uint128) | gasCommittedForThisCycle (uint128) - function gasCommittedForNextCycle(RegistryStateSystemTasks storage s) internal view returns (uint128){ - return uint128(s.gasCommittedForNextCycle_gasCommittedForThisCycle >> 128); - } - - function gasCommittedForThisCycle(RegistryStateSystemTasks storage s) internal view returns (uint128){ - return uint128(s.gasCommittedForNextCycle_gasCommittedForThisCycle); - } - - function setGasCommittedForNextCycle(RegistryStateSystemTasks storage s, uint128 _value) internal { - // Clear upper 128 bits - s.gasCommittedForNextCycle_gasCommittedForThisCycle &= MAX_UINT128; // mask = lower 128 bits all 1s - - // Insert new upper 128 bits - s.gasCommittedForNextCycle_gasCommittedForThisCycle |= uint256(_value) << 128; - } - - function setGasCommittedForThisCycle(RegistryStateSystemTasks storage s, uint128 _value) internal { - // Clear lower 128 bits - s.gasCommittedForNextCycle_gasCommittedForThisCycle &= MAX_UINT128 << 128; // mask = upper 128 bits all 1s - - // Insert new lower 128 bits - s.gasCommittedForNextCycle_gasCommittedForThisCycle |= uint256(_value); - } - /// @notice Struct representing a stopped task. struct TaskStopped { uint64 taskIndex; diff --git a/solidity/supra_contracts/test/AutomationRegistry.t.sol b/solidity/supra_contracts/test/AutomationRegistry.t.sol index 4c4377eb93..d64a49d2f9 100644 --- a/solidity/supra_contracts/test/AutomationRegistry.t.sol +++ b/solidity/supra_contracts/test/AutomationRegistry.t.sol @@ -1216,4 +1216,47 @@ contract AutomationRegistryTest is Test { 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 'updateRegistryState' reverts if caller is not AutomationController. + function testUpdateRegistryStateRevertsIfCallerNotAutomationController() public { + vm.expectRevert(IAutomationRegistry.CallerNotController.selector); + + vm.prank(address(automationCore)); + registry.updateRegistryState( + 1000000, + 1000000, + 1000000, + 0.1 ether, + 0 + ); + } + + /// @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 From bd92be517f6cb6c5bbade33c07d7155400ade479 Mon Sep 17 00:00:00 2001 From: Udit Yadav Date: Tue, 3 Feb 2026 13:19:01 +0530 Subject: [PATCH 23/31] moved cycle info in AutomationCore --- .../script/DeployAutomationRegistry.s.sol | 7 +- .../src/AutomationController.sol | 181 ++++++++---------- .../supra_contracts/src/AutomationCore.sol | 74 ++++++- .../src/AutomationRegistry.sol | 12 +- .../src/IAutomationController.sol | 1 - .../supra_contracts/src/IAutomationCore.sol | 5 +- solidity/supra_contracts/src/LibConfig.sol | 52 +++++ .../supra_contracts/src/LibController.sol | 79 +------- 8 files changed, 214 insertions(+), 197 deletions(-) diff --git a/solidity/supra_contracts/script/DeployAutomationRegistry.s.sol b/solidity/supra_contracts/script/DeployAutomationRegistry.s.sol index ffb15c022d..ef269dc1d1 100644 --- a/solidity/supra_contracts/script/DeployAutomationRegistry.s.sol +++ b/solidity/supra_contracts/script/DeployAutomationRegistry.s.sol @@ -21,7 +21,8 @@ contract DeployAutomationRegistry is Script { uint128 sysRegistryMaxGasCap; uint16 sysTaskCapacity; address vmSigner; - address erc20Supra; + address erc20Supra; + bool automationEnabled; // Config values loaded from .env file function setUp() public { @@ -39,6 +40,7 @@ contract DeployAutomationRegistry is Script { sysTaskCapacity = uint16(vm.envUint("SYS_TASK_CAPACITY")); vmSigner = vm.envAddress("VM_SIGNER"); erc20Supra = vm.envAddress("ERC20_SUPRA"); + automationEnabled = true; } function run() public { @@ -77,7 +79,8 @@ contract DeployAutomationRegistry is Script { sysRegistryMaxGasCap, // sysRegistryMaxGasCap sysTaskCapacity, // sysTaskCapacity vmSigner, // VM Signer address - erc20Supra // ERC20Supra address + erc20Supra, // ERC20Supra address + automationEnabled // automationEnabled ) ); coreProxy = new ERC1967Proxy(address(coreImpl), coreInitData); diff --git a/solidity/supra_contracts/src/AutomationController.sol b/solidity/supra_contracts/src/AutomationController.sol index 050b10f8c9..ec8b5fa553 100644 --- a/solidity/supra_contracts/src/AutomationController.sol +++ b/solidity/supra_contracts/src/AutomationController.sol @@ -49,15 +49,6 @@ contract AutomationController is IAutomationController, Ownable2StepUpgradeable, 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); @@ -97,16 +88,7 @@ contract AutomationController is IAutomationController, Ownable2StepUpgradeable, automationCore = IAutomationCore(_automationCore); registry = IAutomationRegistry(_registry); - - (CommonUtils.CycleState state, uint64 cycleId) = _automationEnabled ? (CommonUtils.CycleState.STARTED, 1) : (CommonUtils.CycleState.READY, 0); - - cycleInfo.initializeCycle( - cycleId, - uint64(block.timestamp), - automationCore.cycleDurationSecs(), - state, - _automationEnabled - ); + cycleInfo.automationEnabled = _automationEnabled; __Ownable2Step_init(); __Ownable_init(msg.sender); @@ -119,7 +101,7 @@ contract AutomationController is IAutomationController, Ownable2StepUpgradeable, // Check caller is VM Signer if (msg.sender != automationCore.getVmSigner()) { revert CallerNotVmSigner(); } - CommonUtils.CycleState state = cycleInfo.state(); + ( , , , CommonUtils.CycleState state) = automationCore.getCycleInfo(); if(state == CommonUtils.CycleState.FINISHED) { onCycleTransition(_cycleIndex, _taskIndexes); @@ -133,7 +115,8 @@ contract AutomationController is IAutomationController, Ownable2StepUpgradeable, function monitorCycleEnd() external { if (tx.origin != automationCore.getVmSigner()) { revert CallerNotVmSigner(); } - if(cycleInfo.state() != CommonUtils.CycleState.STARTED || cycleInfo.startTime() + cycleInfo.durationSecs() > block.timestamp) { + ( , uint64 startTime, uint64 durationSecs, CommonUtils.CycleState state) = automationCore.getCycleInfo(); + if(state != CommonUtils.CycleState.STARTED || startTime + durationSecs > block.timestamp) { return; } @@ -151,11 +134,13 @@ contract AutomationController is IAutomationController, Ownable2StepUpgradeable, /// @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(); } + + (uint64 index , , , CommonUtils.CycleState state) = automationCore.getCycleInfo(); + if(state != CommonUtils.CycleState.FINISHED) { revert InvalidRegistryState(); } // Check if transition state exists - if(!cycleInfo.ifTransitionStateExists()) { revert InvalidRegistryState(); } - if(cycleInfo.index() + 1 != _cycleIndex) { revert InvalidInputCycleIndex(); } + if(!cycleInfo.ifTransitionStateExists) { revert InvalidRegistryState(); } + if(index + 1 != _cycleIndex) { revert InvalidInputCycleIndex(); } LibController.IntermediateStateOfCycleChange memory intermediateState = dropOrChargeTasks(_taskIndexes); @@ -178,10 +163,11 @@ contract AutomationController is IAutomationController, Ownable2StepUpgradeable, 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(); } + (uint64 index , , , CommonUtils.CycleState state) = automationCore.getCycleInfo(); + if(state != CommonUtils.CycleState.SUSPENDED) { revert InvalidRegistryState(); } + if(index != _cycleIndex) { revert InvalidInputCycleIndex(); } // Check if transition state exists - if(!cycleInfo.ifTransitionStateExists()) { revert InvalidRegistryState(); } + if(!cycleInfo.ifTransitionStateExists) { revert InvalidRegistryState(); } uint64 currentTime = uint64(block.timestamp); @@ -446,12 +432,13 @@ contract AutomationController is IAutomationController, Ownable2StepUpgradeable, /// 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(); } + if(!cycleInfo.ifTransitionStateExists) { revert InvalidRegistryState(); } + ( , , , CommonUtils.CycleState state) = automationCore.getCycleInfo(); bool transitionFinalized = isTransitionFinalized(); if (transitionFinalized) { - if (!cycleInfo.automationEnabled() && cycleInfo.state() == CommonUtils.CycleState.FINISHED) { - _tryMoveToSuspendedState(); + if (!cycleInfo.automationEnabled && state == CommonUtils.CycleState.FINISHED) { + tryMoveToSuspendedState(); } else { (bool updated, ) = address(automationCore).call( abi.encodeCall( @@ -470,7 +457,7 @@ contract AutomationController is IAutomationController, Ownable2StepUpgradeable, // Set current timestamp as cycle start time // Increment the cycle and update the state to STARTED - _moveToStartedState(); + moveToStartedState(); if(registry.getTotalActiveTasks() > 0 ) { uint256[] memory activeTasks = registry.getAllActiveTaskIds(); emit ActiveTasks(activeTasks); @@ -491,7 +478,7 @@ contract AutomationController is IAutomationController, Ownable2StepUpgradeable, /// - or Started -> Finished -> {Started, Suspended} function updateCycleTransitionStateFromSuspended() private { // Check if transition state exists - if(!cycleInfo.ifTransitionStateExists()) { revert InvalidRegistryState(); } + if(!cycleInfo.ifTransitionStateExists) { revert InvalidRegistryState(); } if(!isTransitionFinalized()) { return; } @@ -502,11 +489,11 @@ contract AutomationController is IAutomationController, Ownable2StepUpgradeable, registry.updateTasks(CommonUtils.CycleState.SUSPENDED); // Check if automation is enabled - if (cycleInfo.automationEnabled()) { + 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(); + updateConfigFromBuffer(); + moveToStartedState(); } else { moveToReadyState(); } @@ -526,11 +513,13 @@ contract AutomationController is IAutomationController, Ownable2StepUpgradeable, /// 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 { + function tryMoveToSuspendedState() private { + ( , uint64 startTime, uint64 cycleDuration, CommonUtils.CycleState state) = automationCore.getCycleInfo(); + if(registry.totalTasks() == 0) { // Registry is empty move to ready state directly - updateCycleStateTo(CommonUtils.CycleState.READY); - } else if (!cycleInfo.ifTransitionStateExists()) { + automationCore.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. @@ -541,14 +530,11 @@ contract AutomationController is IAutomationController, Ownable2StepUpgradeable, // 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 startTime = cycleInfo.startTime(); - uint64 cycleDuration = cycleInfo.durationSecs(); uint64 cycleEndTime = startTime + cycleDuration; if(currentTime < startTime) { revert InvalidRegistryState(); } if(currentTime >= cycleEndTime) { revert InvalidRegistryState(); } - if(cycleInfo.state() != CommonUtils.CycleState.STARTED) { revert InvalidRegistryState(); } + if(state != CommonUtils.CycleState.STARTED) { revert InvalidRegistryState(); } uint256[] memory expectedTasksToBeProcessed = registry.getTaskIdList().sortUint256(); @@ -562,11 +548,11 @@ contract AutomationController is IAutomationController, Ownable2StepUpgradeable, cycleInfo.setNextTaskIndexPosition(0); updateExpectedTasks(expectedTasksToBeProcessed); - cycleInfo.setTransitionStateExists(true); + cycleInfo.ifTransitionStateExists = true; - updateCycleStateTo(CommonUtils.CycleState.SUSPENDED); + automationCore.updateCycleStateTo(CommonUtils.CycleState.SUSPENDED); } else { - if(cycleInfo.state() != CommonUtils.CycleState.FINISHED) { revert InvalidRegistryState(); } + if(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 @@ -575,7 +561,7 @@ contract AutomationController is IAutomationController, Ownable2StepUpgradeable, cycleInfo.setAutomationFeePerSec(0); cycleInfo.setGasCommittedForNewCycle(0); - updateCycleStateTo(CommonUtils.CycleState.SUSPENDED); + automationCore.updateCycleStateTo(CommonUtils.CycleState.SUSPENDED); } } @@ -590,12 +576,14 @@ contract AutomationController is IAutomationController, Ownable2StepUpgradeable, // match the finalized/summerized cycle since its start, including cycle duration. // Check if transition state exists - if(cycleInfo.ifTransitionStateExists()) { - if (cycleInfo.newCycleDuration() == cycleInfo.durationSecs()) { + if(cycleInfo.ifTransitionStateExists) { + ( , , uint64 durationSecs, ) = automationCore.getCycleInfo(); + + if (cycleInfo.newCycleDuration() == durationSecs) { // Delete transition state cycleInfo.transitionState.expectedTasksToBeProcessed.clear(); delete cycleInfo.transitionState; - cycleInfo.setTransitionStateExists(false); + cycleInfo.ifTransitionStateExists = false; } else { // Reset all except new cycle duration cycleInfo.setRefundDuration(0); @@ -608,36 +596,19 @@ contract AutomationController is IAutomationController, Ownable2StepUpgradeable, cycleInfo.transitionState.expectedTasksToBeProcessed.clear(); } } - updateCycleStateTo(CommonUtils.CycleState.READY); + automationCore.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()); + function moveToStartedState() private { + bool ifTransitionStateExists = cycleInfo.ifTransitionStateExists; + + uint64 newCycleDuration; + if (ifTransitionStateExists) { + newCycleDuration = 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 - ); + automationCore.moveToStarted(ifTransitionStateExists, newCycleDuration); } /// @notice Helper function to update the expected tasks of the transition state. @@ -651,19 +622,20 @@ contract AutomationController is IAutomationController, Ownable2StepUpgradeable, /// @notice Helper function called when cycle end is identified. function onCycleEndInternal() private { - if (!cycleInfo.automationEnabled()) { - _tryMoveToSuspendedState(); + if (!cycleInfo.automationEnabled) { + tryMoveToSuspendedState(); } else{ if(registry.totalTasks() == 0) { // Registry is empty update config buffer and move to STARTED state directly - _updateConfigFromBuffer(); - _moveToStartedState(); + updateConfigFromBuffer(); + moveToStartedState(); } else { uint256[] memory expectedTasksToBeProcessed = registry.getTaskIdList().sortUint256(); // Updates transition state + ( , , uint64 durationSecs, ) = automationCore.getCycleInfo(); cycleInfo.setRefundDuration(0); - cycleInfo.setNewCycleDuration(cycleInfo.durationSecs()); + cycleInfo.setNewCycleDuration(durationSecs); cycleInfo.setGasCommittedForNewCycle(automationCore.getGasCommittedForNextCycle()); cycleInfo.setGasCommittedForNextCycle(0); cycleInfo.setSysGasCommittedForNextCycle (0); @@ -671,31 +643,31 @@ contract AutomationController is IAutomationController, Ownable2StepUpgradeable, cycleInfo.setNextTaskIndexPosition(0); updateExpectedTasks(expectedTasksToBeProcessed); - cycleInfo.setTransitionStateExists(true); + cycleInfo.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(); + 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(automationCore.calculateAutomationFeeMultiplierForCommittedOccupancy(cycleInfo.gasCommittedForNewCycle())); - updateCycleStateTo(CommonUtils.CycleState.FINISHED); + automationCore.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) = automationCore.applyPendingConfig(); + function updateConfigFromBuffer() private { + bool ifTransitionStateExists = cycleInfo.ifTransitionStateExists; + + (bool applied, uint64 cycleDuration) = automationCore.applyPendingConfig(ifTransitionStateExists); if (!applied) return; // Check if transition state exists - if (cycleInfo.ifTransitionStateExists()) { + if (ifTransitionStateExists) { cycleInfo.setNewCycleDuration(cycleDuration); - } else { - cycleInfo.setDurationSecs(cycleDuration); - } + } } /// @notice Checks if the cycle transition is finalized. @@ -712,11 +684,6 @@ contract AutomationController is IAutomationController, Ownable2StepUpgradeable, // :::::::::::::::::::::::::::::::::::::::::::::::::::::::::::: VIEW FUNCTIONS :::::::::::::::::::::::::::::::::::::::::::::::::::::::::::: - /// @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 refund duration and automation fee per sec of the transtition state. /// @return Refund duration /// @return Automation fee per sec @@ -726,7 +693,7 @@ contract AutomationController is IAutomationController, Ownable2StepUpgradeable, /// @notice Returns if automation is enabled. function isAutomationEnabled() external view returns (bool) { - return cycleInfo.automationEnabled(); + return cycleInfo.automationEnabled; } // ::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::: ADMIN FUNCTIONS ::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::: @@ -755,29 +722,33 @@ contract AutomationController is IAutomationController, Ownable2StepUpgradeable, /// @notice Function to enable the automation. function enableAutomation() external onlyOwner { - if (cycleInfo.automationEnabled()) { revert AlreadyEnabled(); } + if (cycleInfo.automationEnabled) { revert AlreadyEnabled(); } - cycleInfo.setAutomationEnabled(true); + cycleInfo.automationEnabled = true; - if (cycleInfo.state() == CommonUtils.CycleState.READY) { - _moveToStartedState(); - _updateConfigFromBuffer(); + ( , , , CommonUtils.CycleState state) = automationCore.getCycleInfo(); + + if (state == CommonUtils.CycleState.READY) { + moveToStartedState(); + updateConfigFromBuffer(); } - emit AutomationEnabled(cycleInfo.automationEnabled()); + emit AutomationEnabled(cycleInfo.automationEnabled); } /// @notice Function to disable the automation. function disableAutomation() external onlyOwner { - if(!cycleInfo.automationEnabled()) { revert AlreadyDisabled(); } + if(!cycleInfo.automationEnabled) { revert AlreadyDisabled(); } - cycleInfo.setAutomationEnabled(false); + cycleInfo.automationEnabled = false; + + ( , , , CommonUtils.CycleState state) = automationCore.getCycleInfo(); - if (cycleInfo.state() == CommonUtils.CycleState.FINISHED && !isTransitionInProgress()) { - _tryMoveToSuspendedState(); + if (state == CommonUtils.CycleState.FINISHED && !isTransitionInProgress()) { + tryMoveToSuspendedState(); } - emit AutomationDisabled(cycleInfo.automationEnabled()); + emit AutomationDisabled(cycleInfo.automationEnabled); } // ::::::::::::::::::::::::::::::::::::::::::::::::::::::::: UPGRADEABILITY FUNCTIONS ::::::::::::::::::::::::::::::::::::::::::::::::::::::::: diff --git a/solidity/supra_contracts/src/AutomationCore.sol b/solidity/supra_contracts/src/AutomationCore.sol index 3a701a5585..be3ccdd992 100644 --- a/solidity/supra_contracts/src/AutomationCore.sol +++ b/solidity/supra_contracts/src/AutomationCore.sol @@ -55,6 +55,15 @@ contract AutomationCore is IAutomationCore, Ownable2StepUpgradeable, UUPSUpgrade /// @notice Emitted when the registry fees is withdrawn by the admin. event RegistryFeeWithdrawn(address indexed recipient, uint256 indexed feesWithdrawn); + /// @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 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, @@ -113,6 +122,7 @@ contract AutomationCore is IAutomationCore, Ownable2StepUpgradeable, UUPSUpgrade /// @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 Bool to set automation enabled status. function initialize( uint64 _taskDurationCapSecs, uint128 _registryMaxGasCap, @@ -127,7 +137,8 @@ contract AutomationCore is IAutomationCore, Ownable2StepUpgradeable, UUPSUpgrade uint128 _sysRegistryMaxGasCap, uint16 _sysTaskCapacity, address _vmSigner, - address _erc20Supra + address _erc20Supra, + bool _automationEnabled ) public initializer { validateConfigParameters( _taskDurationCapSecs, @@ -159,7 +170,12 @@ contract AutomationCore is IAutomationCore, Ownable2StepUpgradeable, UUPSUpgrade _congestionExponent ); + (CommonUtils.CycleState state, uint64 cycleId) = _automationEnabled ? (CommonUtils.CycleState.STARTED, 1) : (CommonUtils.CycleState.READY, 0); regConfig = LibConfig.createRegistryConfig( + cycleId, + uint64(block.timestamp), + regConfig.config.cycleDurationSecs(), + state, _registryMaxGasCap, _sysRegistryMaxGasCap, true, @@ -359,15 +375,13 @@ contract AutomationCore is IAutomationCore, Ownable2StepUpgradeable, UUPSUpgrade uint128 _taskOccupancy, uint128 _committedOccupancy ) private view returns (uint128) { - ( , , uint64 durationSecs, ) = IAutomationController(regConfig.automationController()).getCycleInfo(); - uint128 totalCommittedGas = _taskOccupancy + _committedOccupancy; uint128 automationFeePerSec = calculateAutomationFeeMultiplierForCycle(totalCommittedGas, regConfig.nextCycleRegistryMaxGasCap()); if(automationFeePerSec == 0) return 0; - return calculateAutomationFeeForInterval(durationSecs, _taskOccupancy, automationFeePerSec, regConfig.nextCycleRegistryMaxGasCap()); + return calculateAutomationFeeForInterval(regConfig.durationSecs(), _taskOccupancy, automationFeePerSec, regConfig.nextCycleRegistryMaxGasCap()); } /// @notice Unlocks the deposit paid by the task from the total automation fees deposited. @@ -490,10 +504,26 @@ contract AutomationCore is IAutomationCore, Ownable2StepUpgradeable, UUPSUpgrade return (result, remainingLockedFees); } + /// @notice Helper function to update the cycle state. + /// @param _state Input state to update cycle state with. + function _updateCycleStateTo(CommonUtils.CycleState _state) private { + CommonUtils.CycleState oldState = regConfig.state(); + regConfig.setState(uint8(_state)); + + emit AutomationCycleEvent ( + regConfig.index(), + regConfig.state(), + regConfig.startTime(), + regConfig.durationSecs(), + oldState + ); + } + // ::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::: CONTROLLER FUNCTIONS ::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::: /// @notice Function to update the registry configuration, reverts if caller is not AutomationController. - function applyPendingConfig() external returns (bool, uint64) { + /// @param _ifTransitionStateExists Bool representing if transition state exists. + function applyPendingConfig(bool _ifTransitionStateExists) external returns (bool, uint64) { onlyController(); if (!configBuffer.ifExists) { @@ -501,6 +531,11 @@ contract AutomationCore is IAutomationCore, Ownable2StepUpgradeable, UUPSUpgrade } uint64 pendingCycleDuration = configBuffer.pendingConfig.cycleDurationSecs(); regConfig.config = configBuffer.pendingConfig; + + // Check if transition state does not exist + if (!_ifTransitionStateExists) { + regConfig.setDurationSecs(pendingCycleDuration); + } delete configBuffer; @@ -617,6 +652,30 @@ contract AutomationCore is IAutomationCore, Ownable2StepUpgradeable, UUPSUpgrade regConfig.setGasCommittedForThisCycle(_gasCommittedForNewCycle); } + /// @notice Function to update the cycle state. + /// @param _state Input state to update cycle state with. + function updateCycleStateTo(CommonUtils.CycleState _state) external { + onlyController(); + _updateCycleStateTo(_state); + } + + /// @notice Transitions cycle state to the STARTED state. + /// @param _ifTransitionStateExists Bool representing if transition state exists. + /// @param _newCycleDuration New cycle duration. + function moveToStarted(bool _ifTransitionStateExists, uint64 _newCycleDuration) external { + onlyController(); + + regConfig.setIndex(regConfig.index() + 1); + regConfig.setStartTime(uint64(block.timestamp)); + + // Check if the transition state exists + if (_ifTransitionStateExists) { + regConfig.setDurationSecs(_newCycleDuration); + } + + _updateCycleStateTo(CommonUtils.CycleState.STARTED); + } + // :::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::: REGISTRY FUNCTIONS ::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::: /// @notice Helper function to perform validation while registering a task. @@ -896,6 +955,11 @@ contract AutomationCore is IAutomationCore, Ownable2StepUpgradeable, UUPSUpgrade // :::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::: VIEW FUNCTIONS :::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::: + /// @notice Returns the index, start time, duration and state of the current cycle. + function getCycleInfo() external view returns (uint64, uint64, uint64, CommonUtils.CycleState) { + return (regConfig.index(), regConfig.startTime(), regConfig.durationSecs(), regConfig.state()); + } + /// @notice Returns the VM Signer address. function getVmSigner() external view returns (address) { return regConfig.vmSigner; diff --git a/solidity/supra_contracts/src/AutomationRegistry.sol b/solidity/supra_contracts/src/AutomationRegistry.sol index bad5e17a3e..3428673f1d 100644 --- a/solidity/supra_contracts/src/AutomationRegistry.sol +++ b/solidity/supra_contracts/src/AutomationRegistry.sol @@ -113,7 +113,7 @@ contract AutomationRegistry is IAutomationRegistry, Ownable2StepUpgradeable, UUP ) external { if (!IAutomationController(automationController).isAutomationEnabled()) { revert AutomationNotEnabled(); } - ( , uint64 startTime, uint64 durationSecs, CommonUtils.CycleState state) = IAutomationController(automationController).getCycleInfo(); + ( , uint64 startTime, uint64 durationSecs, CommonUtils.CycleState state) = IAutomationCore(automationCore).getCycleInfo(); if(state != CommonUtils.CycleState.STARTED) { revert CycleTransitionInProgress(); } uint64 regTime = uint64(block.timestamp); @@ -182,7 +182,7 @@ contract AutomationRegistry is IAutomationRegistry, Ownable2StepUpgradeable, UUP if (!IAutomationController(automationController).isAutomationEnabled()) { revert AutomationNotEnabled(); } if(!isAuthorizedSubmitter(msg.sender)) { revert UnauthorizedAccount(); } - ( , uint64 startTime, uint64 durationSecs, CommonUtils.CycleState state) = IAutomationController(automationController).getCycleInfo(); + ( , uint64 startTime, uint64 durationSecs, CommonUtils.CycleState state) = IAutomationCore(automationCore).getCycleInfo(); if (state != CommonUtils.CycleState.STARTED) { revert CycleTransitionInProgress(); } uint64 regTime = uint64(block.timestamp); @@ -241,7 +241,7 @@ contract AutomationRegistry is IAutomationRegistry, Ownable2StepUpgradeable, UUP // Check if automation is enabled if (!IAutomationController(automationController).isAutomationEnabled()) { revert AutomationNotEnabled(); } - ( , uint64 startTime, uint64 durationSecs, CommonUtils.CycleState state) = IAutomationController(automationController).getCycleInfo(); + ( , uint64 startTime, uint64 durationSecs, CommonUtils.CycleState state) = IAutomationCore(automationCore).getCycleInfo(); if(state != CommonUtils.CycleState.STARTED) { revert CycleTransitionInProgress(); } if(!ifTaskExists(_taskIndex)) { revert TaskDoesNotExist(); } @@ -291,7 +291,7 @@ contract AutomationRegistry is IAutomationRegistry, Ownable2StepUpgradeable, UUP // Check if automation is enabled if (!IAutomationController(automationController).isAutomationEnabled()) { revert AutomationNotEnabled(); } - ( , uint64 startTime, uint64 durationSecs, CommonUtils.CycleState state) = IAutomationController(automationController).getCycleInfo(); + ( , uint64 startTime, uint64 durationSecs, CommonUtils.CycleState state) = IAutomationCore(automationCore).getCycleInfo(); if(state != CommonUtils.CycleState.STARTED) { revert CycleTransitionInProgress(); } if(!ifTaskExists(_taskIndex)) { revert TaskDoesNotExist(); } @@ -334,7 +334,7 @@ contract AutomationRegistry is IAutomationRegistry, Ownable2StepUpgradeable, UUP address erc20Supra = IAutomationCore(automationCore).erc20Supra(); - ( , uint64 startTime, uint64 durationSecs, CommonUtils.CycleState state) = IAutomationController(automationController).getCycleInfo(); + ( , uint64 startTime, uint64 durationSecs, CommonUtils.CycleState state) = IAutomationCore(automationCore).getCycleInfo(); if(state != CommonUtils.CycleState.STARTED) { revert CycleTransitionInProgress(); } if(_taskIndexes.length == 0) { revert TaskIndexesCannotBeEmpty(); } @@ -423,7 +423,7 @@ contract AutomationRegistry is IAutomationRegistry, Ownable2StepUpgradeable, UUP // Check if automation is enabled if (!IAutomationController(automationController).isAutomationEnabled()) { revert AutomationNotEnabled(); } - ( , uint64 startTime, uint64 durationSecs, CommonUtils.CycleState state) = IAutomationController(automationController).getCycleInfo(); + ( , uint64 startTime, uint64 durationSecs, CommonUtils.CycleState state) = IAutomationCore(automationCore).getCycleInfo(); if(state != CommonUtils.CycleState.STARTED) { revert CycleTransitionInProgress(); } // Ensure that task indexes are provided diff --git a/solidity/supra_contracts/src/IAutomationController.sol b/solidity/supra_contracts/src/IAutomationController.sol index 006cf85f37..93081eed58 100644 --- a/solidity/supra_contracts/src/IAutomationController.sol +++ b/solidity/supra_contracts/src/IAutomationController.sol @@ -21,7 +21,6 @@ interface IAutomationController { error UpdateTaskStateFailed(); // View functions - function getCycleInfo() external view returns (uint64, uint64, uint64, CommonUtils.CycleState); function getTransitionInfo() external view returns (uint64, uint128); function isAutomationEnabled() external view returns (bool); function isTransitionInProgress() external view returns (bool); diff --git a/solidity/supra_contracts/src/IAutomationCore.sol b/solidity/supra_contracts/src/IAutomationCore.sol index 95c683f024..442fb22415 100644 --- a/solidity/supra_contracts/src/IAutomationCore.sol +++ b/solidity/supra_contracts/src/IAutomationCore.sol @@ -44,6 +44,7 @@ interface IAutomationCore { // View functions function flatRegistrationFeeWei() external view returns (uint128); function getAutomationController() external view returns (address); + function getCycleInfo() external view returns (uint64, uint64, uint64, CommonUtils.CycleState); function erc20Supra() external view returns (address); function calculateTaskFee( CommonUtils.TaskState _state, @@ -75,7 +76,7 @@ interface IAutomationCore { ) external; // State updating functions - function applyPendingConfig() external returns (bool, uint64); + function applyPendingConfig(bool _ifTransitionStateExists) external returns (bool, uint64); function incTotalDepositedAutomationFees(uint256 _totalDepositedAutomationFees) external; function chargeFees(address _from, uint256 _amount) external; function safeUnlockLockedDeposit( @@ -111,4 +112,6 @@ interface IAutomationCore { uint128 _gasCommittedForNextCycle, uint128 _gasCommittedForNewCycle ) external; + function updateCycleStateTo(CommonUtils.CycleState _state) external; + function moveToStarted(bool _ifTransitionStateExists, uint64 _newCycleDuration) external; } \ No newline at end of file diff --git a/solidity/supra_contracts/src/LibConfig.sol b/solidity/supra_contracts/src/LibConfig.sol index ec86fa22c1..bed7ae483c 100644 --- a/solidity/supra_contracts/src/LibConfig.sol +++ b/solidity/supra_contracts/src/LibConfig.sol @@ -1,6 +1,8 @@ // SPDX-License-Identifier: MIT pragma solidity 0.8.27; +import {CommonUtils} from "./CommonUtils.sol"; + // Helper library used by AutomationConfig. library LibConfig { uint256 private constant MAX_UINT128 = type(uint128).max; @@ -29,6 +31,8 @@ library LibConfig { /// @notice Configuration of the automation registry. struct RegistryConfig { + // uint64 | uint64 | uint64 | CycleState(uint8) + uint256 index_startTime_durationSecs_state; // uint128 | uint128 uint256 gasCommittedForNextCycle_gasCommittedForThisCycle; // uint128 | uint128 @@ -47,6 +51,10 @@ library LibConfig { } function createRegistryConfig( + uint64 _index, + uint64 _startTime, + uint64 _durationSecs, + CommonUtils.CycleState _cycleState, uint128 _nextCycleRegistryMaxGasCap, uint128 _nextCycleSysRegistryMaxGasCap, bool _registrationEnabled, @@ -54,6 +62,13 @@ library LibConfig { address _erc20Supra, Config memory _config ) internal pure returns (RegistryConfig memory rcfg) { + // Pack index(uint64) | startTime(uint64) | durationSecs(uint64) | cycleState(uint8) + rcfg.index_startTime_durationSecs_state = + (uint256(_index) << 192) | + (uint256(_startTime) << 128) | + (uint256(_durationSecs) << 64) | + (uint256(uint8(_cycleState)) << 56); + // Pack nextCycleRegistryMaxGasCap | nextCycleSysRegistryMaxGasCap rcfg.nextCycleRegistryMaxGasCap_nextCycleSysRegistryMaxGasCap = (uint256(_nextCycleRegistryMaxGasCap) << 128) | @@ -70,6 +85,43 @@ library LibConfig { rcfg.config = _config; } + // index(uint64) | startTime(uint64) | durationSecs(uint64) | state(CycleState/uint8) + function index(RegistryConfig storage r) internal view returns (uint64) { + return uint64(r.index_startTime_durationSecs_state >> 192); + } + + function startTime(RegistryConfig storage r) internal view returns (uint64) { + return uint64(r.index_startTime_durationSecs_state >> 128); + } + + function durationSecs(RegistryConfig storage r) internal view returns (uint64) { + return uint64(r.index_startTime_durationSecs_state >> 64); + } + + function state(RegistryConfig storage r) internal view returns (CommonUtils.CycleState) { + return CommonUtils.CycleState(uint8(r.index_startTime_durationSecs_state >> 56)); + } + + function setIndex(RegistryConfig storage r, uint64 _index) internal { + r.index_startTime_durationSecs_state &= ~(MAX_UINT64 << 192); // Clear old bits + r.index_startTime_durationSecs_state |= uint256(_index) << 192; // Set new value + } + + function setStartTime(RegistryConfig storage r, uint64 _startTime) internal { + r.index_startTime_durationSecs_state &= ~(MAX_UINT64 << 128); + r.index_startTime_durationSecs_state |= uint256(_startTime) << 128; + } + + function setDurationSecs(RegistryConfig storage r, uint64 _durationSecs) internal { + r.index_startTime_durationSecs_state &= ~(MAX_UINT64 << 64); + r.index_startTime_durationSecs_state |= uint256(_durationSecs) << 64; + } + + function setState(RegistryConfig storage r, uint8 _state) internal { + r.index_startTime_durationSecs_state &= ~(MAX_UINT8 << 56); + r.index_startTime_durationSecs_state |= uint256(_state) << 56; + } + // gasCommittedForNextCycle (uint128) | gasCommittedForThisCycle (uint128) function gasCommittedForNextCycle(RegistryConfig storage r) internal view returns (uint128) { return uint128(r.gasCommittedForNextCycle_gasCommittedForThisCycle >> 128); diff --git a/solidity/supra_contracts/src/LibController.sol b/solidity/supra_contracts/src/LibController.sol index 6ea298b744..6ec28dd20b 100644 --- a/solidity/supra_contracts/src/LibController.sol +++ b/solidity/supra_contracts/src/LibController.sol @@ -3,7 +3,6 @@ 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 { @@ -14,8 +13,8 @@ library LibController { /// @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; + bool automationEnabled; + bool ifTransitionStateExists; TransitionState transitionState; } @@ -30,79 +29,6 @@ library LibController { 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 :::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::: @@ -145,7 +71,6 @@ library LibController { 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); From b29cd48c0788142f8a80bbcb0e5539b4d5d8a3bb Mon Sep 17 00:00:00 2001 From: Udit Yadav Date: Tue, 3 Feb 2026 17:09:07 +0530 Subject: [PATCH 24/31] updated test cases --- .../supra_contracts/src/AutomationCore.sol | 2 +- .../src/IAutomationController.sol | 2 - .../test/AutomationController.t.sol | 219 ++++++++------ .../supra_contracts/test/AutomationCore.t.sol | 269 +++++++++++------- .../test/AutomationRegistry.t.sol | 54 ++-- 5 files changed, 326 insertions(+), 220 deletions(-) diff --git a/solidity/supra_contracts/src/AutomationCore.sol b/solidity/supra_contracts/src/AutomationCore.sol index be3ccdd992..dc047dd4a6 100644 --- a/solidity/supra_contracts/src/AutomationCore.sol +++ b/solidity/supra_contracts/src/AutomationCore.sol @@ -174,7 +174,7 @@ contract AutomationCore is IAutomationCore, Ownable2StepUpgradeable, UUPSUpgrade regConfig = LibConfig.createRegistryConfig( cycleId, uint64(block.timestamp), - regConfig.config.cycleDurationSecs(), + _cycleDurationSecs, state, _registryMaxGasCap, _sysRegistryMaxGasCap, diff --git a/solidity/supra_contracts/src/IAutomationController.sol b/solidity/supra_contracts/src/IAutomationController.sol index 93081eed58..339d4be4c2 100644 --- a/solidity/supra_contracts/src/IAutomationController.sol +++ b/solidity/supra_contracts/src/IAutomationController.sol @@ -1,8 +1,6 @@ // SPDX-License-Identifier: MIT pragma solidity 0.8.27; -import {CommonUtils} from "./CommonUtils.sol"; - interface IAutomationController { // Custom errors error AlreadyEnabled(); diff --git a/solidity/supra_contracts/test/AutomationController.t.sol b/solidity/supra_contracts/test/AutomationController.t.sol index a3718d1f1a..f53867c7db 100644 --- a/solidity/supra_contracts/test/AutomationController.t.sol +++ b/solidity/supra_contracts/test/AutomationController.t.sol @@ -50,7 +50,8 @@ contract AutomationControllerTest is Test { 5_000_000, // sysRegistryMaxGasCap 500, // sysTaskCapacity vmSigner, // VM Signer address - address(erc20Supra) // ERC20Supra address + address(erc20Supra), // ERC20Supra address + true // automationEnabled ) ); ERC1967Proxy automationCoreProxy = new ERC1967Proxy(address(automationCoreImpl), automationCoreInitData); @@ -62,7 +63,7 @@ contract AutomationControllerTest is Test { registry = AutomationRegistry(address(registryProxy)); AutomationController controllerImpl = new AutomationController(); - bytes memory controllerInitData = abi.encodeCall(AutomationController.initialize,(address(automationCore), address(registry))); + bytes memory controllerInitData = abi.encodeCall(AutomationController.initialize,(address(automationCore), address(registry), true)); ERC1967Proxy controllerProxy = new ERC1967Proxy(address(controllerImpl), controllerInitData); controller = AutomationController(address(controllerProxy)); @@ -78,12 +79,7 @@ contract AutomationControllerTest is Test { assertEq(controller.owner(), admin); assertEq(address(controller.automationCore()), address(automationCore)); assertEq(address(controller.registry()), address(registry)); - - (uint64 index, uint64 startTime, uint64 durationSecs, CommonUtils.CycleState state) = controller.getCycleInfo(); - assertEq(index, 1); - assertEq(startTime, block.timestamp); - assertEq(durationSecs, automationCore.cycleDurationSecs()); - assertEq(uint8(state), uint8(CommonUtils.CycleState.STARTED)); + assertTrue(controller.isAutomationEnabled()); } /// @dev Test to ensure initialize reverts if reinitialized. @@ -91,13 +87,13 @@ contract AutomationControllerTest is Test { vm.expectRevert(Initializable.InvalidInitialization.selector); vm.prank(admin); - controller.initialize(address(automationCore), address(registry)); + 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))); + bytes memory initData = abi.encodeCall(AutomationController.initialize, (address(0), address(registry), true)); vm.expectRevert(CommonUtils.AddressCannotBeZero.selector); new ERC1967Proxy(address(impl), initData); @@ -106,7 +102,7 @@ contract AutomationControllerTest is Test { /// @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))); + bytes memory initData = abi.encodeCall(AutomationController.initialize, (alice, address(registry), true)); vm.expectRevert(CommonUtils.AddressCannotBeEOA.selector); new ERC1967Proxy(address(impl), initData); @@ -115,7 +111,7 @@ contract AutomationControllerTest is Test { /// @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))); + bytes memory initData = abi.encodeCall(AutomationController.initialize, (address(automationCore), address(0), true)); vm.expectRevert(CommonUtils.AddressCannotBeZero.selector); new ERC1967Proxy(address(impl), initData); @@ -124,7 +120,7 @@ contract AutomationControllerTest is Test { /// @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)); + bytes memory initData = abi.encodeCall(AutomationController.initialize, (address(automationCore), alice, true)); vm.expectRevert(CommonUtils.AddressCannotBeEOA.selector); new ERC1967Proxy(address(impl), initData); @@ -234,12 +230,12 @@ contract AutomationControllerTest is Test { /// @dev Test to ensure 'monitorCycleEnd' does nothing before cycle expiry. function testMonitorCycleEndDoesNothingBeforeCycleExpiry() public { - (uint64 indexBefore, uint64 startBefore, uint64 durationBefore, CommonUtils.CycleState stateBefore) = controller.getCycleInfo(); + (uint64 indexBefore, uint64 startBefore, uint64 durationBefore, CommonUtils.CycleState stateBefore) = automationCore.getCycleInfo(); vm.prank(vmSigner, vmSigner); controller.monitorCycleEnd(); - (uint64 indexAfter, uint64 startAfter, uint64 durationAfter, CommonUtils.CycleState stateAfter) = controller.getCycleInfo(); + (uint64 indexAfter, uint64 startAfter, uint64 durationAfter, CommonUtils.CycleState stateAfter) = automationCore.getCycleInfo(); assertEq(indexAfter, indexBefore); assertEq(startAfter, startBefore); @@ -247,41 +243,41 @@ contract AutomationControllerTest is Test { 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(); + // /// @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)); + // (uint64 indexBefore, uint64 startBefore, uint64 durationBefore, CommonUtils.CycleState stateBefore) = controller.getCycleInfo(); + // assertEq(uint8(stateBefore), uint8(CommonUtils.CycleState.READY)); - vm.warp(startBefore + durationBefore); + // vm.warp(startBefore + durationBefore); - vm.prank(vmSigner, vmSigner); - controller.monitorCycleEnd(); + // vm.prank(vmSigner, vmSigner); + // controller.monitorCycleEnd(); - (uint64 indexAfter, uint64 startAfter, uint64 durationAfter, CommonUtils.CycleState stateAfter) = controller.getCycleInfo(); + // (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)); - } + // 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); - automationCore.disableAutomation(); + controller.disableAutomation(); - assertFalse(automationCore.isAutomationEnabled()); + assertFalse(controller.isAutomationEnabled()); - (uint64 indexBefore, uint64 startBefore, uint64 durationBefore, CommonUtils.CycleState stateBefore) = controller.getCycleInfo(); + (uint64 indexBefore, uint64 startBefore, uint64 durationBefore, CommonUtils.CycleState stateBefore) = automationCore.getCycleInfo(); vm.warp(startBefore + durationBefore); vm.expectEmit(true, true, false, true); - emit AutomationController.AutomationCycleEvent( + emit AutomationCore.AutomationCycleEvent( indexBefore, CommonUtils.CycleState.READY, startBefore, @@ -292,7 +288,7 @@ contract AutomationControllerTest is Test { vm.prank(vmSigner, vmSigner); controller.monitorCycleEnd(); - (uint64 indexAfter, uint64 startAfter, uint64 durationAfter, CommonUtils.CycleState stateAfter) = controller.getCycleInfo(); + (uint64 indexAfter, uint64 startAfter, uint64 durationAfter, CommonUtils.CycleState stateAfter) = automationCore.getCycleInfo(); assertEq(indexAfter, indexBefore); assertEq(startAfter, startBefore); @@ -302,12 +298,12 @@ contract AutomationControllerTest is Test { /// @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(); + (uint64 indexBefore, uint64 startBefore, uint64 durationBefore, CommonUtils.CycleState stateBefore) = automationCore.getCycleInfo(); vm.warp(startBefore + durationBefore); vm.expectEmit(true, true, false, true); - emit AutomationController.AutomationCycleEvent( + emit AutomationCore.AutomationCycleEvent( indexBefore + 1, CommonUtils.CycleState.STARTED, uint64(block.timestamp), @@ -318,7 +314,7 @@ contract AutomationControllerTest is Test { vm.prank(vmSigner, vmSigner); controller.monitorCycleEnd(); - (uint64 indexAfter, uint64 startAfter, uint64 durationAfter, CommonUtils.CycleState stateAfter) = controller.getCycleInfo(); + (uint64 indexAfter, uint64 startAfter, uint64 durationAfter, CommonUtils.CycleState stateAfter) = automationCore.getCycleInfo(); assertEq(indexAfter, indexBefore + 1); assertEq(startAfter, block.timestamp); @@ -330,11 +326,11 @@ contract AutomationControllerTest is Test { function testMonitorCycleEndWhenAutomationEnabledAndTasksExist() public { registerTask(); - (uint64 indexBefore, uint64 startBefore, uint64 durationBefore, CommonUtils.CycleState stateBefore) = controller.getCycleInfo(); + (uint64 indexBefore, uint64 startBefore, uint64 durationBefore, CommonUtils.CycleState stateBefore) = automationCore.getCycleInfo(); vm.warp(startBefore + durationBefore); vm.expectEmit(true, true, false, true); - emit AutomationController.AutomationCycleEvent( + emit AutomationCore.AutomationCycleEvent( indexBefore, CommonUtils.CycleState.FINISHED, startBefore, @@ -345,7 +341,7 @@ contract AutomationControllerTest is Test { vm.prank(vmSigner, vmSigner); controller.monitorCycleEnd(); - (uint64 indexAfter, uint64 startAfter, uint64 durationAfter, CommonUtils.CycleState stateAfter) = controller.getCycleInfo(); + (uint64 indexAfter, uint64 startAfter, uint64 durationAfter, CommonUtils.CycleState stateAfter) = automationCore.getCycleInfo(); assertEq(indexAfter, indexBefore); assertEq(startAfter, startBefore); @@ -383,13 +379,13 @@ contract AutomationControllerTest is Test { function testProcessTasksWhenCycleStateFinished() public { registerTask(); - ( , uint64 startTime, uint64 duration, ) = controller.getCycleInfo(); + ( , uint64 startTime, uint64 duration, ) = automationCore.getCycleInfo(); vm.warp(startTime + duration); vm.prank(vmSigner, vmSigner); controller.monitorCycleEnd(); - (uint64 index, , , CommonUtils.CycleState state) = controller.getCycleInfo(); + (uint64 index, , , CommonUtils.CycleState state) = automationCore.getCycleInfo(); assertEq(uint8(state), uint8(CommonUtils.CycleState.FINISHED)); uint64[] memory tasks = new uint64[](1); @@ -404,31 +400,31 @@ contract AutomationControllerTest is Test { vm.prank(vmSigner, vmSigner); controller.processTasks(index + 1, tasks); - (uint64 newIndex, uint64 newStart, uint64 newDuration, CommonUtils.CycleState newState) = controller.getCycleInfo(); + (uint64 newIndex, uint64 newStart, uint64 newDuration, CommonUtils.CycleState newState) = automationCore.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(registry.getSystemGasCommittedForNextCycle(), 0); - assertEq(registry.getSystemGasCommittedForCurrentCycle(), 0); - assertEq(registry.getGasCommittedForNextCycle(), 0); - assertEq(registry.getGasCommittedForCurrentCycle(), 1000000); - assertEq(registry.getCycleLockedFees(), 200000000000000000); + 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(); + ( , uint64 startTime, uint64 duration, ) = automationCore.getCycleInfo(); vm.warp(startTime + duration); vm.prank(vmSigner, vmSigner); controller.monitorCycleEnd(); - (uint64 index, , , CommonUtils.CycleState state) = controller.getCycleInfo(); + (uint64 index, , , CommonUtils.CycleState state) = automationCore.getCycleInfo(); assertEq(uint8(state), uint8(CommonUtils.CycleState.FINISHED)); uint64[] memory tasks = new uint64[](1); @@ -444,21 +440,21 @@ contract AutomationControllerTest is Test { function testProcessTasksWhenCycleStateSuspendedAutomationDisabled() public { registerTask(); - ( , uint64 start, uint64 duration, ) = controller.getCycleInfo(); + ( , uint64 start, uint64 duration, ) = automationCore.getCycleInfo(); vm.warp(start + duration); // Moves state to FINISHED vm.prank(vmSigner, vmSigner); controller.monitorCycleEnd(); - ( , , , CommonUtils.CycleState stateBefore) = controller.getCycleInfo(); + ( , , , CommonUtils.CycleState stateBefore) = automationCore.getCycleInfo(); assertEq(uint8(stateBefore), uint8(CommonUtils.CycleState.FINISHED)); // Disable automation → moves state to SUSPENDED vm.prank(admin); - automationCore.disableAutomation(); + controller.disableAutomation(); - (uint64 indexAfter, , , CommonUtils.CycleState stateAfter) = controller.getCycleInfo(); + (uint64 indexAfter, , , CommonUtils.CycleState stateAfter) = automationCore.getCycleInfo(); assertEq(uint8(stateAfter), uint8(CommonUtils.CycleState.SUSPENDED)); uint64[] memory tasks = new uint64[](1); @@ -470,7 +466,7 @@ contract AutomationControllerTest is Test { vm.prank(vmSigner, vmSigner); controller.processTasks(indexAfter, tasks); - ( , , , CommonUtils.CycleState newState) = controller.getCycleInfo(); + ( , , , CommonUtils.CycleState newState) = automationCore.getCycleInfo(); assertEq(uint8(newState), uint8(CommonUtils.CycleState.READY)); assertFalse(registry.ifTaskExists(tasks[0])); } @@ -479,26 +475,26 @@ contract AutomationControllerTest is Test { function testProcessTasksWhenCycleStateSuspendedAutomationEnabled() public { registerTask(); - ( , uint64 start, uint64 duration, ) = controller.getCycleInfo(); + ( , uint64 start, uint64 duration, ) = automationCore.getCycleInfo(); vm.warp(start + duration); // Moves state to FINISHED vm.prank(vmSigner, vmSigner); controller.monitorCycleEnd(); - ( , , , CommonUtils.CycleState stateBefore) = controller.getCycleInfo(); + ( , , , CommonUtils.CycleState stateBefore) = automationCore.getCycleInfo(); assertEq(uint8(stateBefore), uint8(CommonUtils.CycleState.FINISHED)); // Disable automation → moves state to SUSPENDED vm.prank(admin); - automationCore.disableAutomation(); + controller.disableAutomation(); - (uint64 indexAfter, , , CommonUtils.CycleState stateAfter) = controller.getCycleInfo(); + (uint64 indexAfter, , , CommonUtils.CycleState stateAfter) = automationCore.getCycleInfo(); assertEq(uint8(stateAfter), uint8(CommonUtils.CycleState.SUSPENDED)); // Enable automation vm.prank(admin); - automationCore.enableAutomation(); + controller.enableAutomation(); uint64[] memory tasks = new uint64[](1); tasks[0] = 0; @@ -509,7 +505,7 @@ contract AutomationControllerTest is Test { vm.prank(vmSigner, vmSigner); controller.processTasks(indexAfter, tasks); - (uint64 newIndex, uint64 newStart, uint64 newDuration, CommonUtils.CycleState newState) = controller.getCycleInfo(); + (uint64 newIndex, uint64 newStart, uint64 newDuration, CommonUtils.CycleState newState) = automationCore.getCycleInfo(); assertEq(newIndex, indexAfter + 1); assertEq(newStart, uint64(block.timestamp)); assertEq(newDuration, 2000); @@ -521,21 +517,21 @@ contract AutomationControllerTest is Test { function testProcessTasksRevertsIfInvalidCycleIndexWhenCycleStateSuspended() public { registerTask(); - ( , uint64 start, uint64 duration, ) = controller.getCycleInfo(); + ( , uint64 start, uint64 duration, ) = automationCore.getCycleInfo(); vm.warp(start + duration); // Moves state to FINISHED vm.prank(vmSigner, vmSigner); controller.monitorCycleEnd(); - ( , , , CommonUtils.CycleState stateBefore) = controller.getCycleInfo(); + ( , , , CommonUtils.CycleState stateBefore) = automationCore.getCycleInfo(); assertEq(uint8(stateBefore), uint8(CommonUtils.CycleState.FINISHED)); // Disable automation → moves state to SUSPENDED vm.prank(admin); - automationCore.disableAutomation(); + controller.disableAutomation(); - (uint64 indexAfter, , , CommonUtils.CycleState stateAfter) = controller.getCycleInfo(); + (uint64 indexAfter, , , CommonUtils.CycleState stateAfter) = automationCore.getCycleInfo(); assertEq(uint8(stateAfter), uint8(CommonUtils.CycleState.SUSPENDED)); uint64[] memory tasks = new uint64[](1); @@ -547,28 +543,87 @@ contract AutomationControllerTest is Test { controller.processTasks(indexAfter + 1, tasks); } - /// @dev Test to ensure 'tryMoveToSuspendedState' reverts if caller is not AutomationCore. - function testTryMoveToSuspendedStateRevertsIfNotAutomationCore() public { - vm.expectRevert(IAutomationController.CallerNotAutomationCore.selector); + // :::::::::::::::::::::::::::::::::::::::::::::::::::::: 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(address(registry)); - controller.tryMoveToSuspendedState(); + vm.prank(admin); + controller.disableAutomation(); } - /// @dev Test to ensure 'moveToStartedState' reverts if caller is not AutomationCore. - function testMoveToStartedStateRevertsIfNotAutomationCore() public { - vm.expectRevert(IAutomationController.CallerNotAutomationCore.selector); + /// @dev Test to ensure 'disableAutomation' reverts if automation is already disabled. + function testDisableAutomationRevertsIfAlreadyDisabled() public { + // Disable automation + testDisableAutomation(); - vm.prank(address(registry)); - controller.moveToStartedState(); + // Disable again → revert + vm.expectRevert(IAutomationController.AlreadyDisabled.selector); + + vm.prank(admin); + controller.disableAutomation(); } - /// @dev Test to ensure 'updateCyleDuration' reverts if caller is not AutomationCore. - function testUpdateCyleDurationRevertsIfNotAutomationCore() public { - vm.expectRevert(IAutomationController.CallerNotAutomationCore.selector); + /// @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(); + } - vm.prank(address(registry)); - controller.updateCyleDuration(3800); + // :::::::::::::::::::::::::::::::::::::::::::::::::::::: 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. diff --git a/solidity/supra_contracts/test/AutomationCore.t.sol b/solidity/supra_contracts/test/AutomationCore.t.sol index 3841a06190..b2db3d7a88 100644 --- a/solidity/supra_contracts/test/AutomationCore.t.sol +++ b/solidity/supra_contracts/test/AutomationCore.t.sol @@ -17,7 +17,7 @@ contract AutomationCoreTest is Test { ERC20Supra erc20Supra; // ERC20Supra contract AutomationCore automationCore; // AutomationCore instance on proxy address AutomationRegistry registry; // AutomationRegistry instance on proxy address - address automationController; // AutomationController proxy address + AutomationController automationController; // AutomationController instance on proxy address address admin = address(0xA11CE); address vmSigner = address(0x53555000); @@ -50,7 +50,8 @@ contract AutomationCoreTest is Test { 5_000_000, // sysRegistryMaxGasCap 500, // sysTaskCapacity vmSigner, // VM Signer address - address(erc20Supra) // ERC20Supra address + address(erc20Supra), // ERC20Supra address + true // automationEnabled ) ); ERC1967Proxy automationCoreProxy = new ERC1967Proxy(address(automationCoreImpl), automationCoreInitData); @@ -60,13 +61,15 @@ contract AutomationCoreTest is Test { bytes memory registryInitData = abi.encodeCall(AutomationRegistry.initialize, (address(automationCore))); ERC1967Proxy registryProxy = new ERC1967Proxy(address(registryImpl), registryInitData); registry = AutomationRegistry(address(registryProxy)); - automationCore.setAutomationRegistry(address(registry)); AutomationController controllerImpl = new AutomationController(); - bytes memory controllerInitData = abi.encodeCall(AutomationController.initialize,(address(automationCore), address(registry))); + bytes memory controllerInitData = abi.encodeCall(AutomationController.initialize,(address(automationCore), address(registry), true)); ERC1967Proxy controllerProxy = new ERC1967Proxy(address(controllerImpl), controllerInitData); - automationController = address(controllerProxy); - automationCore.setAutomationController(automationController); + automationController = AutomationController(address(controllerProxy)); + + automationCore.setAutomationRegistry(address(registry)); + automationCore.setAutomationController(address(automationController)); + registry.setAutomationController(address(automationController)); vm.stopPrank(); } @@ -74,11 +77,18 @@ contract AutomationCoreTest is Test { /// @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) = automationCore.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(), automationController); + assertEq(automationCore.getAutomationController(), address(automationController)); assertTrue(automationCore.isRegistrationEnabled()); - assertTrue(automationCore.isAutomationEnabled()); + assertTrue(automationController.isAutomationEnabled()); assertEq(automationCore.getVmSigner(), vmSigner); assertEq(automationCore.erc20Supra(), address(erc20Supra)); @@ -105,7 +115,7 @@ contract AutomationCoreTest is Test { 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) + 500, 2000, 3600, 5_000_000, 500, vmSigner, address(erc20Supra), true ); } @@ -119,7 +129,7 @@ contract AutomationCoreTest is Test { 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) + address(erc20Supra), true ) ); @@ -136,7 +146,8 @@ contract AutomationCoreTest is Test { ( 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 + address(0), // address(0) as ERC20Supra + true ) ); @@ -153,7 +164,8 @@ contract AutomationCoreTest is Test { ( 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 + admin, // EOA address as ERC20Supra + true ) ); @@ -171,7 +183,7 @@ contract AutomationCoreTest is Test { 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) + 3600, 5_000_000, 500, vmSigner, address(erc20Supra), true ) ); @@ -189,7 +201,7 @@ contract AutomationCoreTest is Test { 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) + 2000, 3600, 5_000_000, 500, vmSigner, address(erc20Supra), true ) ); @@ -206,7 +218,7 @@ contract AutomationCoreTest is Test { ( 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) + 0.002 ether, 2, 500, 2000, 3600, 5_000_000, 500, vmSigner, address(erc20Supra), true ) ); @@ -223,7 +235,7 @@ contract AutomationCoreTest is Test { ( 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) + 500, 2000, 3600, 5_000_000, 500, vmSigner, address(erc20Supra), true ) ); @@ -240,7 +252,7 @@ contract AutomationCoreTest is Test { ( 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) + 2000, 3600, 5_000_000, 500, vmSigner, address(erc20Supra), true ) ); @@ -257,7 +269,7 @@ contract AutomationCoreTest is Test { ( 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) + 3600, 5_000_000, 500, vmSigner, address(erc20Supra), true ) ); @@ -275,7 +287,7 @@ contract AutomationCoreTest is Test { 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) + 5_000_000, 500, vmSigner, address(erc20Supra), true ) ); @@ -292,7 +304,7 @@ contract AutomationCoreTest is Test { ( 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) + 500, vmSigner, address(erc20Supra), true ) ); @@ -310,7 +322,7 @@ contract AutomationCoreTest is Test { 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) + vmSigner, address(erc20Supra), true ) ); @@ -400,89 +412,6 @@ contract AutomationCoreTest is Test { automationCore.enableRegistration(); } - // :::::::::::::::::::::::::::::::::::::::::::::::::::::: Tests related to 'disableAutomation' :::::::::::::::::::::::::::::::::::::::::::::::::::::: - - /// @dev Test to ensure 'disableAutomation' disables the automation. - function testDisableAutomation() public { - // Already enabled in initialize() - vm.prank(admin); - automationCore.disableAutomation(); - - assertFalse(automationCore.isAutomationEnabled()); - } - - /// @dev Test to ensure 'disableAutomation' emits event 'AutomationDisabled'. - function testDisableAutomationEmitsEvent() public { - vm.expectEmit(true, false, false, false); - emit AutomationCore.AutomationDisabled(false); - - vm.prank(admin); - automationCore.disableAutomation(); - } - - /// @dev Test to ensure 'disableAutomation' reverts if automation is already disabled. - function testDisableAutomationRevertsIfAlreadyDisabled() public { - // Disable automation - testDisableAutomation(); - - // Disable again → revert - vm.expectRevert(IAutomationCore.AlreadyDisabled.selector); - - vm.prank(admin); - automationCore.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); - automationCore.disableAutomation(); - } - - // :::::::::::::::::::::::::::::::::::::::::::::::::::::: Tests related to 'enableAutomation' :::::::::::::::::::::::::::::::::::::::::::::::::::::: - - /// @dev Test to ensure 'enableAutomation' enables the automation. - function testEnableAutomation() public { - // Disable automation - testDisableAutomation(); - - // Enable automation - vm.prank(admin); - automationCore.enableAutomation(); - - assertTrue(automationCore.isAutomationEnabled()); - } - - /// @dev Test to ensure 'enableAutomation' emits event 'AutomationEnabled'. - function testEnableAutomationEmitsEvent() public { - // Disable automation - testDisableAutomation(); - - vm.expectEmit(true, false, false, false); - emit AutomationCore.AutomationEnabled(true); - - vm.prank(admin); - automationCore.enableAutomation(); - } - - /// @dev Test to ensure 'enableAutomation' reverts if automation is already enabled. - function testEnableAutomationRevertsIfAlreadyEnabled() public { - // Already enabled in initialize() - vm.expectRevert(IAutomationCore.AlreadyEnabled.selector); - - vm.prank(admin); - automationCore.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); - automationCore.enableAutomation(); - } - // :::::::::::::::::::::::::::::::::::::::::::::::::::::: Tests related to 'setAutomationRegistry' :::::::::::::::::::::::::::::::::::::::::::::::::::::: /// @dev Helper function that deploys AutomationRegistry and returns its address. @@ -549,7 +478,7 @@ contract AutomationCoreTest is Test { function deployAutomationController() internal returns (address) { // Deploy AutomationController proxy AutomationController controllerImpl = new AutomationController(); - bytes memory controllerInitData = abi.encodeCall(AutomationController.initialize,(address(automationCore), address(registry))); + bytes memory controllerInitData = abi.encodeCall(AutomationController.initialize,(address(automationCore), address(registry), true)); ERC1967Proxy controllerProxy = new ERC1967Proxy(address(controllerImpl), controllerInitData); return address(controllerProxy); @@ -865,6 +794,136 @@ contract AutomationCoreTest is Test { 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(false); + } + + /// @dev Test to ensure 'calculateTaskFee' reverts if caller is not AutomationController. + function testCalculateTaskFeeRevertsIfCallerNotAutomationController() public { + vm.expectRevert(IAutomationCore.CallerNotController.selector); + + vm.prank(address(registry)); + automationCore.calculateTaskFee( + CommonUtils.TaskState.ACTIVE , + uint64(block.timestamp) + 10000000, + 1000000, + 10000000, + uint64(block.timestamp), + 0.0001 ether + ); + } + + /// @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 'calculateAutomationFeeMultiplierForCurrentCycleInternal' reverts if caller is not AutomationController. + function test_calculateAutomationFeeMultiplierForCurrentCycleInternal_RevertsIfCallerNotAutomationController() public { + vm.expectRevert(IAutomationCore.CallerNotController.selector); + + vm.prank(address(registry)); + automationCore.calculateAutomationFeeMultiplierForCurrentCycleInternal(); + } + + /// @dev Test to ensure 'calculateAutomationFeeMultiplierForCommittedOccupancy' reverts if caller is not AutomationController. + function test_calculateAutomationFeeMultiplierForCommittedOccupancy_RevertsIfCallerNotAutomationController() public { + vm.expectRevert(IAutomationCore.CallerNotController.selector); + + vm.prank(address(registry)); + automationCore.calculateAutomationFeeMultiplierForCommittedOccupancy(1000000); + } + + /// @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 'validateRegistration' reverts if caller is not AutomationRegistry. + function testValidateRegistrationRevertsIfCallerNotAutomationRegistry() public { + bytes memory payload = createPayload(0, address(erc20Supra)); + + vm.expectRevert(IAutomationCore.CallerNotRegistry.selector); + + vm.prank(address(automationController)); + automationCore.validateRegistration( + 10, + 0, + uint64(block.timestamp), + uint64(block.timestamp) + 2250, + CommonUtils.TaskType.UST, + payload, + 1000000, + keccak256("txHash"), + 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. diff --git a/solidity/supra_contracts/test/AutomationRegistry.t.sol b/solidity/supra_contracts/test/AutomationRegistry.t.sol index d64a49d2f9..ec61ce24e2 100644 --- a/solidity/supra_contracts/test/AutomationRegistry.t.sol +++ b/solidity/supra_contracts/test/AutomationRegistry.t.sol @@ -19,7 +19,7 @@ contract AutomationRegistryTest is Test { ERC20Supra erc20Supra; // ERC20Supra contract AutomationCore automationCore; // AutomationCore instance on proxy address AutomationRegistry registry; // AutomationRegistry instance on proxy address - address controller; // AutomationController proxy address + AutomationController controller; // AutomationController instance on proxy address address admin = address(0xA11CE); address vmSigner = address(0x53555000); @@ -52,7 +52,8 @@ contract AutomationRegistryTest is Test { 5_000_000, // sysRegistryMaxGasCap 500, // sysTaskCapacity vmSigner, // VM Signer address - address(erc20Supra) // ERC20Supra address + address(erc20Supra), // ERC20Supra address + true // automationEnabled ) ); ERC1967Proxy automationCoreProxy = new ERC1967Proxy(address(automationCoreImpl), automationCoreInitData); @@ -64,14 +65,13 @@ contract AutomationRegistryTest is Test { registry = AutomationRegistry(address(registryProxy)); AutomationController controllerImpl = new AutomationController(); - bytes memory controllerInitData = abi.encodeCall(AutomationController.initialize,(address(automationCore), address(registry))); + bytes memory controllerInitData = abi.encodeCall(AutomationController.initialize,(address(automationCore), address(registry), true)); ERC1967Proxy controllerProxy = new ERC1967Proxy(address(controllerImpl), controllerInitData); - controller = address(controllerProxy); + controller = AutomationController(address(controllerProxy)); automationCore.setAutomationRegistry(address(registry)); - automationCore.setAutomationController(controller); - - registry.setAutomationController(controller); + automationCore.setAutomationController(address(controller)); + registry.setAutomationController(address(controller)); vm.stopPrank(); } @@ -80,7 +80,7 @@ contract AutomationRegistryTest is Test { function testInitialize() public view { assertEq(registry.owner(), admin); assertEq(registry.automationCore(), address(automationCore)); - assertEq(registry.automationController(), controller); + assertEq(registry.automationController(), address(controller)); } /// @dev Test to ensure reinitialization fails. @@ -117,7 +117,7 @@ contract AutomationRegistryTest is Test { function deployAutomationController() internal returns (address) { // Deploy AutomationController proxy AutomationController controllerImpl = new AutomationController(); - bytes memory controllerInitData = abi.encodeCall(AutomationController.initialize,(address(automationCore), address(registry))); + bytes memory controllerInitData = abi.encodeCall(AutomationController.initialize,(address(automationCore), address(registry), true)); ERC1967Proxy controllerProxy = new ERC1967Proxy(address(controllerImpl), controllerInitData); return address(controllerProxy); @@ -283,7 +283,7 @@ contract AutomationRegistryTest is Test { function testRegisterRevertsIfAutomationNotEnabled() public { // Disable automation vm.prank(admin); - automationCore.disableAutomation(); + controller.disableAutomation(); bytes[] memory auxData; bytes memory payload = createPayload(0, address(erc20Supra)); @@ -586,7 +586,7 @@ contract AutomationRegistryTest is Test { assertTrue(registry.ifTaskExists(0)); assertEq(registry.totalTasks(), 1); assertEq(registry.getNextTaskIndex(), 1); - assertEq(registry.getGasCommittedForNextCycle(), 1_000_000); + 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); @@ -676,7 +676,7 @@ contract AutomationRegistryTest is Test { testGrantAuthorization(); vm.prank(admin); - automationCore.disableAutomation(); + controller.disableAutomation(); bytes[] memory auxData; bytes memory payload = createPayload(0, address(erc20Supra)); @@ -803,7 +803,7 @@ contract AutomationRegistryTest is Test { assertEq(registry.totalTasks(), 1); assertEq(registry.totalSystemTasks(), 1); assertEq(registry.getNextTaskIndex(), 1); - assertEq(registry.getSystemGasCommittedForNextCycle(), 1_000_000); + assertEq(automationCore.getSystemGasCommittedForNextCycle(), 1_000_000); assertEq(taskMetadata.maxGasAmount, 1_000_000); assertEq(taskMetadata.gasPriceCap, 0); @@ -865,7 +865,7 @@ contract AutomationRegistryTest is Test { /// @dev Test to ensure 'cancelTask' reverts if automation is not enabled. function testCancelTaskRevertsIfAutomationNotEnabled() public { vm.prank(admin); - automationCore.disableAutomation(); + controller.disableAutomation(); vm.expectRevert(IAutomationRegistry.AutomationNotEnabled.selector); @@ -908,7 +908,7 @@ contract AutomationRegistryTest is Test { assertFalse(registry.ifTaskExists(0)); assertEq(registry.totalTasks(), 0); - assertEq(registry.getGasCommittedForNextCycle(), 0); + assertEq(automationCore.getGasCommittedForNextCycle(), 0); assertEq(automationCore.getTotalDepositedAutomationFees(), 0); assertEq(erc20Supra.balanceOf(address(automationCore)), 0.252 ether); assertEq(erc20Supra.balanceOf(alice), 4.748 ether); @@ -930,7 +930,7 @@ contract AutomationRegistryTest is Test { /// @dev Test to ensure 'cancelSystemTask' reverts if automation is not enabled. function testCancelSystemTaskRevertsIfAutomationNotEnabled() public { vm.prank(admin); - automationCore.disableAutomation(); + controller.disableAutomation(); vm.expectRevert(IAutomationRegistry.AutomationNotEnabled.selector); @@ -975,7 +975,7 @@ contract AutomationRegistryTest is Test { assertFalse(registry.ifSysTaskExists(0)); assertEq(registry.totalTasks(), 0); assertEq(registry.totalSystemTasks(), 0); - assertEq(registry.getSystemGasCommittedForNextCycle(), 0); + assertEq(automationCore.getSystemGasCommittedForNextCycle(), 0); } /// @dev Test to ensure 'cancelSystemTask' emits event 'TaskCancelled'. @@ -994,7 +994,7 @@ contract AutomationRegistryTest is Test { /// @dev Test to ensure 'stopTasks' reverts if automation is not enabled. function testStopTasksRevertsIfAutomationNotEnabled() public { vm.prank(admin); - automationCore.disableAutomation(); + controller.disableAutomation(); uint64[] memory taskIndexes; vm.expectRevert(IAutomationRegistry.AutomationNotEnabled.selector); @@ -1074,7 +1074,7 @@ contract AutomationRegistryTest is Test { assertFalse(registry.ifTaskExists(0)); assertEq(registry.totalTasks(), 0); - assertEq(registry.getGasCommittedForNextCycle(), 0); + assertEq(automationCore.getGasCommittedForNextCycle(), 0); assertEq(automationCore.getTotalDepositedAutomationFees(), 0); assertEq(erc20Supra.balanceOf(address(automationCore)), 0.18955 ether); assertEq(erc20Supra.balanceOf(alice), 4.81045 ether); @@ -1109,7 +1109,7 @@ contract AutomationRegistryTest is Test { /// @dev Test to ensure 'stopSystemTasks' reverts if automation is not enabled. function testStopSystemTasksRevertsIfAutomationNotEnabled() public { vm.prank(admin); - automationCore.disableAutomation(); + controller.disableAutomation(); uint64[] memory taskIndexes; vm.expectRevert(IAutomationRegistry.AutomationNotEnabled.selector); @@ -1189,7 +1189,7 @@ contract AutomationRegistryTest is Test { assertFalse(registry.ifSysTaskExists(0)); assertEq(registry.totalTasks(), 0); assertEq(registry.totalSystemTasks(), 0); - assertEq(registry.getSystemGasCommittedForNextCycle(), 1000000); + assertEq(automationCore.getSystemGasCommittedForNextCycle(), 1000000); } /// @dev Test to ensure 'stopSystemTasks' emits event 'TasksStopped'. @@ -1233,18 +1233,12 @@ contract AutomationRegistryTest is Test { registry.updateTaskState(0, CommonUtils.TaskState.ACTIVE); } - /// @dev Test to ensure 'updateRegistryState' reverts if caller is not AutomationController. - function testUpdateRegistryStateRevertsIfCallerNotAutomationController() public { + /// @dev Test to ensure 'updateTasks' reverts if caller is not AutomationController. + function testUpdateTasksRevertsIfCallerNotAutomationController() public { vm.expectRevert(IAutomationRegistry.CallerNotController.selector); vm.prank(address(automationCore)); - registry.updateRegistryState( - 1000000, - 1000000, - 1000000, - 0.1 ether, - 0 - ); + registry.updateTasks(CommonUtils.CycleState.STARTED); } /// @dev Test to ensure 'refundDepositAndDrop' reverts if caller is not AutomationController. From 75db172ab522e4aa07ec21b73bedd13654f0bde9 Mon Sep 17 00:00:00 2001 From: Udit Yadav Date: Wed, 4 Feb 2026 15:14:00 +0530 Subject: [PATCH 25/31] moved cycleInfo to AutomationController --- .../script/DeployAutomationRegistry.s.sol | 5 +- .../src/AutomationController.sol | 158 +++++++++++------- .../supra_contracts/src/AutomationCore.sol | 78 +-------- .../src/AutomationRegistry.sol | 16 +- .../src/IAutomationController.sol | 3 + .../supra_contracts/src/IAutomationCore.sol | 7 +- solidity/supra_contracts/src/LibConfig.sol | 52 ------ .../supra_contracts/src/LibController.sol | 78 ++++++++- .../test/AutomationController.t.sol | 57 ++++--- .../supra_contracts/test/AutomationCore.t.sol | 41 +++-- .../test/AutomationRegistry.t.sol | 3 +- 11 files changed, 241 insertions(+), 257 deletions(-) diff --git a/solidity/supra_contracts/script/DeployAutomationRegistry.s.sol b/solidity/supra_contracts/script/DeployAutomationRegistry.s.sol index ef269dc1d1..5955c1bcc7 100644 --- a/solidity/supra_contracts/script/DeployAutomationRegistry.s.sol +++ b/solidity/supra_contracts/script/DeployAutomationRegistry.s.sol @@ -22,7 +22,6 @@ contract DeployAutomationRegistry is Script { uint16 sysTaskCapacity; address vmSigner; address erc20Supra; - bool automationEnabled; // Config values loaded from .env file function setUp() public { @@ -40,7 +39,6 @@ contract DeployAutomationRegistry is Script { sysTaskCapacity = uint16(vm.envUint("SYS_TASK_CAPACITY")); vmSigner = vm.envAddress("VM_SIGNER"); erc20Supra = vm.envAddress("ERC20_SUPRA"); - automationEnabled = true; } function run() public { @@ -79,8 +77,7 @@ contract DeployAutomationRegistry is Script { sysRegistryMaxGasCap, // sysRegistryMaxGasCap sysTaskCapacity, // sysTaskCapacity vmSigner, // VM Signer address - erc20Supra, // ERC20Supra address - automationEnabled // automationEnabled + erc20Supra // ERC20Supra address ) ); coreProxy = new ERC1967Proxy(address(coreImpl), coreInitData); diff --git a/solidity/supra_contracts/src/AutomationController.sol b/solidity/supra_contracts/src/AutomationController.sol index ec8b5fa553..96a7cde69c 100644 --- a/solidity/supra_contracts/src/AutomationController.sol +++ b/solidity/supra_contracts/src/AutomationController.sol @@ -49,6 +49,15 @@ contract AutomationController is IAutomationController, Ownable2StepUpgradeable, 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); @@ -88,7 +97,16 @@ contract AutomationController is IAutomationController, Ownable2StepUpgradeable, automationCore = IAutomationCore(_automationCore); registry = IAutomationRegistry(_registry); - cycleInfo.automationEnabled = _automationEnabled; + + (CommonUtils.CycleState state, uint64 cycleId) = _automationEnabled ? (CommonUtils.CycleState.STARTED, 1) : (CommonUtils.CycleState.READY, 0); + + cycleInfo.initializeCycle( + cycleId, + uint64(block.timestamp), + automationCore.cycleDurationSecs(), + state, + _automationEnabled + ); __Ownable2Step_init(); __Ownable_init(msg.sender); @@ -101,7 +119,7 @@ contract AutomationController is IAutomationController, Ownable2StepUpgradeable, // Check caller is VM Signer if (msg.sender != automationCore.getVmSigner()) { revert CallerNotVmSigner(); } - ( , , , CommonUtils.CycleState state) = automationCore.getCycleInfo(); + CommonUtils.CycleState state = cycleInfo.state(); if(state == CommonUtils.CycleState.FINISHED) { onCycleTransition(_cycleIndex, _taskIndexes); @@ -115,8 +133,7 @@ contract AutomationController is IAutomationController, Ownable2StepUpgradeable, function monitorCycleEnd() external { if (tx.origin != automationCore.getVmSigner()) { revert CallerNotVmSigner(); } - ( , uint64 startTime, uint64 durationSecs, CommonUtils.CycleState state) = automationCore.getCycleInfo(); - if(state != CommonUtils.CycleState.STARTED || startTime + durationSecs > block.timestamp) { + if(!isCycleStarted() || cycleInfo.startTime() + cycleInfo.durationSecs() > block.timestamp) { return; } @@ -135,12 +152,11 @@ contract AutomationController is IAutomationController, Ownable2StepUpgradeable, function onCycleTransition(uint64 _cycleIndex, uint64[] memory _taskIndexes) private { if(_taskIndexes.length == 0) { return; } - (uint64 index , , , CommonUtils.CycleState state) = automationCore.getCycleInfo(); - if(state != CommonUtils.CycleState.FINISHED) { revert InvalidRegistryState(); } + if(cycleInfo.state() != CommonUtils.CycleState.FINISHED) { revert InvalidRegistryState(); } // Check if transition state exists - if(!cycleInfo.ifTransitionStateExists) { revert InvalidRegistryState(); } - if(index + 1 != _cycleIndex) { revert InvalidInputCycleIndex(); } + if(!cycleInfo.ifTransitionStateExists()) { revert InvalidRegistryState(); } + if(cycleInfo.index() + 1 != _cycleIndex) { revert InvalidInputCycleIndex(); } LibController.IntermediateStateOfCycleChange memory intermediateState = dropOrChargeTasks(_taskIndexes); @@ -163,11 +179,10 @@ contract AutomationController is IAutomationController, Ownable2StepUpgradeable, function onCycleSuspend(uint64 _cycleIndex, uint64[] memory _taskIndexes) private { if (_taskIndexes.length == 0) { return; } - (uint64 index , , , CommonUtils.CycleState state) = automationCore.getCycleInfo(); - if(state != CommonUtils.CycleState.SUSPENDED) { revert InvalidRegistryState(); } - if(index != _cycleIndex) { revert InvalidInputCycleIndex(); } + if(cycleInfo.state() != CommonUtils.CycleState.SUSPENDED) { revert InvalidRegistryState(); } + if(cycleInfo.index() != _cycleIndex) { revert InvalidInputCycleIndex(); } // Check if transition state exists - if(!cycleInfo.ifTransitionStateExists) { revert InvalidRegistryState(); } + if(!cycleInfo.ifTransitionStateExists()) { revert InvalidRegistryState(); } uint64 currentTime = uint64(block.timestamp); @@ -432,12 +447,11 @@ contract AutomationController is IAutomationController, Ownable2StepUpgradeable, /// 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(); } - ( , , , CommonUtils.CycleState state) = automationCore.getCycleInfo(); + if(!cycleInfo.ifTransitionStateExists()) { revert InvalidRegistryState(); } bool transitionFinalized = isTransitionFinalized(); if (transitionFinalized) { - if (!cycleInfo.automationEnabled && state == CommonUtils.CycleState.FINISHED) { + if (!cycleInfo.automationEnabled() && cycleInfo.state() == CommonUtils.CycleState.FINISHED) { tryMoveToSuspendedState(); } else { (bool updated, ) = address(automationCore).call( @@ -478,7 +492,7 @@ contract AutomationController is IAutomationController, Ownable2StepUpgradeable, /// - or Started -> Finished -> {Started, Suspended} function updateCycleTransitionStateFromSuspended() private { // Check if transition state exists - if(!cycleInfo.ifTransitionStateExists) { revert InvalidRegistryState(); } + if(!cycleInfo.ifTransitionStateExists()) { revert InvalidRegistryState(); } if(!isTransitionFinalized()) { return; } @@ -489,7 +503,7 @@ contract AutomationController is IAutomationController, Ownable2StepUpgradeable, registry.updateTasks(CommonUtils.CycleState.SUSPENDED); // Check if automation is enabled - if (cycleInfo.automationEnabled) { + 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(); @@ -514,12 +528,10 @@ contract AutomationController is IAutomationController, Ownable2StepUpgradeable, /// 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 { - ( , uint64 startTime, uint64 cycleDuration, CommonUtils.CycleState state) = automationCore.getCycleInfo(); - if(registry.totalTasks() == 0) { // Registry is empty move to ready state directly - automationCore.updateCycleStateTo(CommonUtils.CycleState.READY); - } else if (!cycleInfo.ifTransitionStateExists) { + 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. @@ -530,11 +542,13 @@ contract AutomationController is IAutomationController, Ownable2StepUpgradeable, // 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 startTime = cycleInfo.startTime(); + uint64 cycleDuration = cycleInfo.durationSecs(); uint64 cycleEndTime = startTime + cycleDuration; if(currentTime < startTime) { revert InvalidRegistryState(); } if(currentTime >= cycleEndTime) { revert InvalidRegistryState(); } - if(state != CommonUtils.CycleState.STARTED) { revert InvalidRegistryState(); } + if(!isCycleStarted()) { revert InvalidRegistryState(); } uint256[] memory expectedTasksToBeProcessed = registry.getTaskIdList().sortUint256(); @@ -548,11 +562,11 @@ contract AutomationController is IAutomationController, Ownable2StepUpgradeable, cycleInfo.setNextTaskIndexPosition(0); updateExpectedTasks(expectedTasksToBeProcessed); - cycleInfo.ifTransitionStateExists = true; + cycleInfo.setTransitionStateExists(true); - automationCore.updateCycleStateTo(CommonUtils.CycleState.SUSPENDED); + updateCycleStateTo(CommonUtils.CycleState.SUSPENDED); } else { - if(state != CommonUtils.CycleState.FINISHED) { revert InvalidRegistryState(); } + 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 @@ -561,7 +575,7 @@ contract AutomationController is IAutomationController, Ownable2StepUpgradeable, cycleInfo.setAutomationFeePerSec(0); cycleInfo.setGasCommittedForNewCycle(0); - automationCore.updateCycleStateTo(CommonUtils.CycleState.SUSPENDED); + updateCycleStateTo(CommonUtils.CycleState.SUSPENDED); } } @@ -576,14 +590,12 @@ contract AutomationController is IAutomationController, Ownable2StepUpgradeable, // match the finalized/summerized cycle since its start, including cycle duration. // Check if transition state exists - if(cycleInfo.ifTransitionStateExists) { - ( , , uint64 durationSecs, ) = automationCore.getCycleInfo(); - - if (cycleInfo.newCycleDuration() == durationSecs) { + if(cycleInfo.ifTransitionStateExists()) { + if (cycleInfo.newCycleDuration() == cycleInfo.durationSecs()) { // Delete transition state cycleInfo.transitionState.expectedTasksToBeProcessed.clear(); delete cycleInfo.transitionState; - cycleInfo.ifTransitionStateExists = false; + cycleInfo.setTransitionStateExists(false); } else { // Reset all except new cycle duration cycleInfo.setRefundDuration(0); @@ -596,19 +608,36 @@ contract AutomationController is IAutomationController, Ownable2StepUpgradeable, cycleInfo.transitionState.expectedTasksToBeProcessed.clear(); } } - automationCore.updateCycleStateTo(CommonUtils.CycleState.READY); + updateCycleStateTo(CommonUtils.CycleState.READY); } /// @notice Transitions cycle state to the STARTED state. function moveToStartedState() private { - bool ifTransitionStateExists = cycleInfo.ifTransitionStateExists; - - uint64 newCycleDuration; - if (ifTransitionStateExists) { - newCycleDuration = cycleInfo.newCycleDuration(); + cycleInfo.setIndex(cycleInfo.index() + 1); + + cycleInfo.setStartTime(uint64(block.timestamp)); + + // Check if the transition state exists + if(cycleInfo.ifTransitionStateExists()) { + cycleInfo.setDurationSecs(cycleInfo.newCycleDuration()); } - automationCore.moveToStarted(ifTransitionStateExists, 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. @@ -622,7 +651,7 @@ contract AutomationController is IAutomationController, Ownable2StepUpgradeable, /// @notice Helper function called when cycle end is identified. function onCycleEndInternal() private { - if (!cycleInfo.automationEnabled) { + if (!cycleInfo.automationEnabled()) { tryMoveToSuspendedState(); } else{ if(registry.totalTasks() == 0) { @@ -633,9 +662,8 @@ contract AutomationController is IAutomationController, Ownable2StepUpgradeable, uint256[] memory expectedTasksToBeProcessed = registry.getTaskIdList().sortUint256(); // Updates transition state - ( , , uint64 durationSecs, ) = automationCore.getCycleInfo(); cycleInfo.setRefundDuration(0); - cycleInfo.setNewCycleDuration(durationSecs); + cycleInfo.setNewCycleDuration(cycleInfo.durationSecs()); cycleInfo.setGasCommittedForNewCycle(automationCore.getGasCommittedForNextCycle()); cycleInfo.setGasCommittedForNextCycle(0); cycleInfo.setSysGasCommittedForNextCycle (0); @@ -643,7 +671,7 @@ contract AutomationController is IAutomationController, Ownable2StepUpgradeable, cycleInfo.setNextTaskIndexPosition(0); updateExpectedTasks(expectedTasksToBeProcessed); - cycleInfo.ifTransitionStateExists = true; + 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(); @@ -652,22 +680,22 @@ contract AutomationController is IAutomationController, Ownable2StepUpgradeable, // 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(automationCore.calculateAutomationFeeMultiplierForCommittedOccupancy(cycleInfo.gasCommittedForNewCycle())); - automationCore.updateCycleStateTo(CommonUtils.CycleState.FINISHED); + 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 ifTransitionStateExists = cycleInfo.ifTransitionStateExists; - - (bool applied, uint64 cycleDuration) = automationCore.applyPendingConfig(ifTransitionStateExists); + (bool applied, uint64 cycleDuration) = automationCore.applyPendingConfig(); if (!applied) return; // Check if transition state exists - if (ifTransitionStateExists) { + if (cycleInfo.ifTransitionStateExists()) { cycleInfo.setNewCycleDuration(cycleDuration); - } + } else { + cycleInfo.setDurationSecs(cycleDuration); + } } /// @notice Checks if the cycle transition is finalized. @@ -676,13 +704,23 @@ contract AutomationController is IAutomationController, Ownable2StepUpgradeable, return cycleInfo.transitionState.expectedTasksToBeProcessed.length() == cycleInfo.nextTaskIndexPosition(); } + /// @notice Checks whether cycle is in STARTED state. + function isCycleStarted() private view returns (bool) { + return cycleInfo.state() == CommonUtils.CycleState.STARTED; + } + + // :::::::::::::::::::::::::::::::::::::::::::::::::::::::::::: 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; } - // :::::::::::::::::::::::::::::::::::::::::::::::::::::::::::: VIEW FUNCTIONS :::::::::::::::::::::::::::::::::::::::::::::::::::::::::::: + /// @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 refund duration and automation fee per sec of the transtition state. /// @return Refund duration @@ -693,7 +731,7 @@ contract AutomationController is IAutomationController, Ownable2StepUpgradeable, /// @notice Returns if automation is enabled. function isAutomationEnabled() external view returns (bool) { - return cycleInfo.automationEnabled; + return cycleInfo.automationEnabled(); } // ::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::: ADMIN FUNCTIONS ::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::: @@ -722,33 +760,29 @@ contract AutomationController is IAutomationController, Ownable2StepUpgradeable, /// @notice Function to enable the automation. function enableAutomation() external onlyOwner { - if (cycleInfo.automationEnabled) { revert AlreadyEnabled(); } + if (cycleInfo.automationEnabled()) { revert AlreadyEnabled(); } - cycleInfo.automationEnabled = true; + cycleInfo.setAutomationEnabled(true); - ( , , , CommonUtils.CycleState state) = automationCore.getCycleInfo(); - - if (state == CommonUtils.CycleState.READY) { + if (cycleInfo.state() == CommonUtils.CycleState.READY) { moveToStartedState(); updateConfigFromBuffer(); } - emit AutomationEnabled(cycleInfo.automationEnabled); + emit AutomationEnabled(cycleInfo.automationEnabled()); } /// @notice Function to disable the automation. function disableAutomation() external onlyOwner { - if(!cycleInfo.automationEnabled) { revert AlreadyDisabled(); } + if(!cycleInfo.automationEnabled()) { revert AlreadyDisabled(); } - cycleInfo.automationEnabled = false; - - ( , , , CommonUtils.CycleState state) = automationCore.getCycleInfo(); + cycleInfo.setAutomationEnabled(false); - if (state == CommonUtils.CycleState.FINISHED && !isTransitionInProgress()) { + if (cycleInfo.state() == CommonUtils.CycleState.FINISHED && !isTransitionInProgress()) { tryMoveToSuspendedState(); } - emit AutomationDisabled(cycleInfo.automationEnabled); + emit AutomationDisabled(cycleInfo.automationEnabled()); } // ::::::::::::::::::::::::::::::::::::::::::::::::::::::::: UPGRADEABILITY FUNCTIONS ::::::::::::::::::::::::::::::::::::::::::::::::::::::::: diff --git a/solidity/supra_contracts/src/AutomationCore.sol b/solidity/supra_contracts/src/AutomationCore.sol index dc047dd4a6..3e972d4d0c 100644 --- a/solidity/supra_contracts/src/AutomationCore.sol +++ b/solidity/supra_contracts/src/AutomationCore.sol @@ -55,15 +55,6 @@ contract AutomationCore is IAutomationCore, Ownable2StepUpgradeable, UUPSUpgrade /// @notice Emitted when the registry fees is withdrawn by the admin. event RegistryFeeWithdrawn(address indexed recipient, uint256 indexed feesWithdrawn); - /// @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 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, @@ -122,7 +113,6 @@ contract AutomationCore is IAutomationCore, Ownable2StepUpgradeable, UUPSUpgrade /// @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 Bool to set automation enabled status. function initialize( uint64 _taskDurationCapSecs, uint128 _registryMaxGasCap, @@ -137,8 +127,7 @@ contract AutomationCore is IAutomationCore, Ownable2StepUpgradeable, UUPSUpgrade uint128 _sysRegistryMaxGasCap, uint16 _sysTaskCapacity, address _vmSigner, - address _erc20Supra, - bool _automationEnabled + address _erc20Supra ) public initializer { validateConfigParameters( _taskDurationCapSecs, @@ -170,12 +159,7 @@ contract AutomationCore is IAutomationCore, Ownable2StepUpgradeable, UUPSUpgrade _congestionExponent ); - (CommonUtils.CycleState state, uint64 cycleId) = _automationEnabled ? (CommonUtils.CycleState.STARTED, 1) : (CommonUtils.CycleState.READY, 0); regConfig = LibConfig.createRegistryConfig( - cycleId, - uint64(block.timestamp), - _cycleDurationSecs, - state, _registryMaxGasCap, _sysRegistryMaxGasCap, true, @@ -375,13 +359,15 @@ contract AutomationCore is IAutomationCore, Ownable2StepUpgradeable, UUPSUpgrade uint128 _taskOccupancy, uint128 _committedOccupancy ) private view returns (uint128) { + ( , , uint64 durationSecs, ) = IAutomationController(regConfig.automationController()).getCycleInfo(); + uint128 totalCommittedGas = _taskOccupancy + _committedOccupancy; uint128 automationFeePerSec = calculateAutomationFeeMultiplierForCycle(totalCommittedGas, regConfig.nextCycleRegistryMaxGasCap()); if(automationFeePerSec == 0) return 0; - return calculateAutomationFeeForInterval(regConfig.durationSecs(), _taskOccupancy, automationFeePerSec, regConfig.nextCycleRegistryMaxGasCap()); + return calculateAutomationFeeForInterval(durationSecs, _taskOccupancy, automationFeePerSec, regConfig.nextCycleRegistryMaxGasCap()); } /// @notice Unlocks the deposit paid by the task from the total automation fees deposited. @@ -504,26 +490,10 @@ contract AutomationCore is IAutomationCore, Ownable2StepUpgradeable, UUPSUpgrade return (result, remainingLockedFees); } - /// @notice Helper function to update the cycle state. - /// @param _state Input state to update cycle state with. - function _updateCycleStateTo(CommonUtils.CycleState _state) private { - CommonUtils.CycleState oldState = regConfig.state(); - regConfig.setState(uint8(_state)); - - emit AutomationCycleEvent ( - regConfig.index(), - regConfig.state(), - regConfig.startTime(), - regConfig.durationSecs(), - oldState - ); - } - // ::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::: CONTROLLER FUNCTIONS ::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::: /// @notice Function to update the registry configuration, reverts if caller is not AutomationController. - /// @param _ifTransitionStateExists Bool representing if transition state exists. - function applyPendingConfig(bool _ifTransitionStateExists) external returns (bool, uint64) { + function applyPendingConfig() external returns (bool, uint64) { onlyController(); if (!configBuffer.ifExists) { @@ -531,11 +501,6 @@ contract AutomationCore is IAutomationCore, Ownable2StepUpgradeable, UUPSUpgrade } uint64 pendingCycleDuration = configBuffer.pendingConfig.cycleDurationSecs(); regConfig.config = configBuffer.pendingConfig; - - // Check if transition state does not exist - if (!_ifTransitionStateExists) { - regConfig.setDurationSecs(pendingCycleDuration); - } delete configBuffer; @@ -652,34 +617,10 @@ contract AutomationCore is IAutomationCore, Ownable2StepUpgradeable, UUPSUpgrade regConfig.setGasCommittedForThisCycle(_gasCommittedForNewCycle); } - /// @notice Function to update the cycle state. - /// @param _state Input state to update cycle state with. - function updateCycleStateTo(CommonUtils.CycleState _state) external { - onlyController(); - _updateCycleStateTo(_state); - } - - /// @notice Transitions cycle state to the STARTED state. - /// @param _ifTransitionStateExists Bool representing if transition state exists. - /// @param _newCycleDuration New cycle duration. - function moveToStarted(bool _ifTransitionStateExists, uint64 _newCycleDuration) external { - onlyController(); - - regConfig.setIndex(regConfig.index() + 1); - regConfig.setStartTime(uint64(block.timestamp)); - - // Check if the transition state exists - if (_ifTransitionStateExists) { - regConfig.setDurationSecs(_newCycleDuration); - } - - _updateCycleStateTo(CommonUtils.CycleState.STARTED); - } - // :::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::: REGISTRY FUNCTIONS ::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::: - /// @notice Helper function to perform validation while registering a task. - function validateRegistration( + /// @notice Helper function that performs validation and updates state for a valid task. + function updateStateForValidRegistration( uint256 _totalTasks, uint8 _inputType, uint64 _regTime, @@ -955,11 +896,6 @@ contract AutomationCore is IAutomationCore, Ownable2StepUpgradeable, UUPSUpgrade // :::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::: VIEW FUNCTIONS :::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::: - /// @notice Returns the index, start time, duration and state of the current cycle. - function getCycleInfo() external view returns (uint64, uint64, uint64, CommonUtils.CycleState) { - return (regConfig.index(), regConfig.startTime(), regConfig.durationSecs(), regConfig.state()); - } - /// @notice Returns the VM Signer address. function getVmSigner() external view returns (address) { return regConfig.vmSigner; diff --git a/solidity/supra_contracts/src/AutomationRegistry.sol b/solidity/supra_contracts/src/AutomationRegistry.sol index 3428673f1d..36ce36c62c 100644 --- a/solidity/supra_contracts/src/AutomationRegistry.sol +++ b/solidity/supra_contracts/src/AutomationRegistry.sol @@ -113,11 +113,11 @@ contract AutomationRegistry is IAutomationRegistry, Ownable2StepUpgradeable, UUP ) external { if (!IAutomationController(automationController).isAutomationEnabled()) { revert AutomationNotEnabled(); } - ( , uint64 startTime, uint64 durationSecs, CommonUtils.CycleState state) = IAutomationCore(automationCore).getCycleInfo(); + ( , uint64 startTime, uint64 durationSecs, CommonUtils.CycleState state) = IAutomationController(automationController).getCycleInfo(); if(state != CommonUtils.CycleState.STARTED) { revert CycleTransitionInProgress(); } uint64 regTime = uint64(block.timestamp); - IAutomationCore(automationCore).validateRegistration( + IAutomationCore(automationCore).updateStateForValidRegistration( totalTasks(), _type, regTime, @@ -182,11 +182,11 @@ contract AutomationRegistry is IAutomationRegistry, Ownable2StepUpgradeable, UUP if (!IAutomationController(automationController).isAutomationEnabled()) { revert AutomationNotEnabled(); } if(!isAuthorizedSubmitter(msg.sender)) { revert UnauthorizedAccount(); } - ( , uint64 startTime, uint64 durationSecs, CommonUtils.CycleState state) = IAutomationCore(automationCore).getCycleInfo(); + ( , uint64 startTime, uint64 durationSecs, CommonUtils.CycleState state) = IAutomationController(automationController).getCycleInfo(); if (state != CommonUtils.CycleState.STARTED) { revert CycleTransitionInProgress(); } uint64 regTime = uint64(block.timestamp); - IAutomationCore(automationCore).validateRegistration( + IAutomationCore(automationCore).updateStateForValidRegistration( totalSystemTasks(), _type, regTime, @@ -241,7 +241,7 @@ contract AutomationRegistry is IAutomationRegistry, Ownable2StepUpgradeable, UUP // Check if automation is enabled if (!IAutomationController(automationController).isAutomationEnabled()) { revert AutomationNotEnabled(); } - ( , uint64 startTime, uint64 durationSecs, CommonUtils.CycleState state) = IAutomationCore(automationCore).getCycleInfo(); + ( , uint64 startTime, uint64 durationSecs, CommonUtils.CycleState state) = IAutomationController(automationController).getCycleInfo(); if(state != CommonUtils.CycleState.STARTED) { revert CycleTransitionInProgress(); } if(!ifTaskExists(_taskIndex)) { revert TaskDoesNotExist(); } @@ -291,7 +291,7 @@ contract AutomationRegistry is IAutomationRegistry, Ownable2StepUpgradeable, UUP // Check if automation is enabled if (!IAutomationController(automationController).isAutomationEnabled()) { revert AutomationNotEnabled(); } - ( , uint64 startTime, uint64 durationSecs, CommonUtils.CycleState state) = IAutomationCore(automationCore).getCycleInfo(); + ( , uint64 startTime, uint64 durationSecs, CommonUtils.CycleState state) = IAutomationController(automationController).getCycleInfo(); if(state != CommonUtils.CycleState.STARTED) { revert CycleTransitionInProgress(); } if(!ifTaskExists(_taskIndex)) { revert TaskDoesNotExist(); } @@ -334,7 +334,7 @@ contract AutomationRegistry is IAutomationRegistry, Ownable2StepUpgradeable, UUP address erc20Supra = IAutomationCore(automationCore).erc20Supra(); - ( , uint64 startTime, uint64 durationSecs, CommonUtils.CycleState state) = IAutomationCore(automationCore).getCycleInfo(); + ( , uint64 startTime, uint64 durationSecs, CommonUtils.CycleState state) = IAutomationController(automationController).getCycleInfo(); if(state != CommonUtils.CycleState.STARTED) { revert CycleTransitionInProgress(); } if(_taskIndexes.length == 0) { revert TaskIndexesCannotBeEmpty(); } @@ -423,7 +423,7 @@ contract AutomationRegistry is IAutomationRegistry, Ownable2StepUpgradeable, UUP // Check if automation is enabled if (!IAutomationController(automationController).isAutomationEnabled()) { revert AutomationNotEnabled(); } - ( , uint64 startTime, uint64 durationSecs, CommonUtils.CycleState state) = IAutomationCore(automationCore).getCycleInfo(); + ( , uint64 startTime, uint64 durationSecs, CommonUtils.CycleState state) = IAutomationController(automationController).getCycleInfo(); if(state != CommonUtils.CycleState.STARTED) { revert CycleTransitionInProgress(); } // Ensure that task indexes are provided diff --git a/solidity/supra_contracts/src/IAutomationController.sol b/solidity/supra_contracts/src/IAutomationController.sol index 339d4be4c2..006cf85f37 100644 --- a/solidity/supra_contracts/src/IAutomationController.sol +++ b/solidity/supra_contracts/src/IAutomationController.sol @@ -1,6 +1,8 @@ // SPDX-License-Identifier: MIT pragma solidity 0.8.27; +import {CommonUtils} from "./CommonUtils.sol"; + interface IAutomationController { // Custom errors error AlreadyEnabled(); @@ -19,6 +21,7 @@ interface IAutomationController { error UpdateTaskStateFailed(); // View functions + function getCycleInfo() external view returns (uint64, uint64, uint64, CommonUtils.CycleState); function getTransitionInfo() external view returns (uint64, uint128); function isAutomationEnabled() external view returns (bool); function isTransitionInProgress() external view returns (bool); diff --git a/solidity/supra_contracts/src/IAutomationCore.sol b/solidity/supra_contracts/src/IAutomationCore.sol index 442fb22415..403579e012 100644 --- a/solidity/supra_contracts/src/IAutomationCore.sol +++ b/solidity/supra_contracts/src/IAutomationCore.sol @@ -44,7 +44,6 @@ interface IAutomationCore { // View functions function flatRegistrationFeeWei() external view returns (uint128); function getAutomationController() external view returns (address); - function getCycleInfo() external view returns (uint64, uint64, uint64, CommonUtils.CycleState); function erc20Supra() external view returns (address); function calculateTaskFee( CommonUtils.TaskState _state, @@ -61,7 +60,7 @@ interface IAutomationCore { function getGasCommittedForNextCycle() external view returns (uint128); function getCycleLockedFees() external view returns (uint256); function getTotalDepositedAutomationFees() external view returns (uint256); - function validateRegistration( + function updateStateForValidRegistration( uint256 _totalTasks, uint8 _inputType, uint64 _regTime, @@ -76,7 +75,7 @@ interface IAutomationCore { ) external; // State updating functions - function applyPendingConfig(bool _ifTransitionStateExists) external returns (bool, uint64); + function applyPendingConfig() external returns (bool, uint64); function incTotalDepositedAutomationFees(uint256 _totalDepositedAutomationFees) external; function chargeFees(address _from, uint256 _amount) external; function safeUnlockLockedDeposit( @@ -112,6 +111,4 @@ interface IAutomationCore { uint128 _gasCommittedForNextCycle, uint128 _gasCommittedForNewCycle ) external; - function updateCycleStateTo(CommonUtils.CycleState _state) external; - function moveToStarted(bool _ifTransitionStateExists, uint64 _newCycleDuration) external; } \ No newline at end of file diff --git a/solidity/supra_contracts/src/LibConfig.sol b/solidity/supra_contracts/src/LibConfig.sol index bed7ae483c..ec86fa22c1 100644 --- a/solidity/supra_contracts/src/LibConfig.sol +++ b/solidity/supra_contracts/src/LibConfig.sol @@ -1,8 +1,6 @@ // SPDX-License-Identifier: MIT pragma solidity 0.8.27; -import {CommonUtils} from "./CommonUtils.sol"; - // Helper library used by AutomationConfig. library LibConfig { uint256 private constant MAX_UINT128 = type(uint128).max; @@ -31,8 +29,6 @@ library LibConfig { /// @notice Configuration of the automation registry. struct RegistryConfig { - // uint64 | uint64 | uint64 | CycleState(uint8) - uint256 index_startTime_durationSecs_state; // uint128 | uint128 uint256 gasCommittedForNextCycle_gasCommittedForThisCycle; // uint128 | uint128 @@ -51,10 +47,6 @@ library LibConfig { } function createRegistryConfig( - uint64 _index, - uint64 _startTime, - uint64 _durationSecs, - CommonUtils.CycleState _cycleState, uint128 _nextCycleRegistryMaxGasCap, uint128 _nextCycleSysRegistryMaxGasCap, bool _registrationEnabled, @@ -62,13 +54,6 @@ library LibConfig { address _erc20Supra, Config memory _config ) internal pure returns (RegistryConfig memory rcfg) { - // Pack index(uint64) | startTime(uint64) | durationSecs(uint64) | cycleState(uint8) - rcfg.index_startTime_durationSecs_state = - (uint256(_index) << 192) | - (uint256(_startTime) << 128) | - (uint256(_durationSecs) << 64) | - (uint256(uint8(_cycleState)) << 56); - // Pack nextCycleRegistryMaxGasCap | nextCycleSysRegistryMaxGasCap rcfg.nextCycleRegistryMaxGasCap_nextCycleSysRegistryMaxGasCap = (uint256(_nextCycleRegistryMaxGasCap) << 128) | @@ -85,43 +70,6 @@ library LibConfig { rcfg.config = _config; } - // index(uint64) | startTime(uint64) | durationSecs(uint64) | state(CycleState/uint8) - function index(RegistryConfig storage r) internal view returns (uint64) { - return uint64(r.index_startTime_durationSecs_state >> 192); - } - - function startTime(RegistryConfig storage r) internal view returns (uint64) { - return uint64(r.index_startTime_durationSecs_state >> 128); - } - - function durationSecs(RegistryConfig storage r) internal view returns (uint64) { - return uint64(r.index_startTime_durationSecs_state >> 64); - } - - function state(RegistryConfig storage r) internal view returns (CommonUtils.CycleState) { - return CommonUtils.CycleState(uint8(r.index_startTime_durationSecs_state >> 56)); - } - - function setIndex(RegistryConfig storage r, uint64 _index) internal { - r.index_startTime_durationSecs_state &= ~(MAX_UINT64 << 192); // Clear old bits - r.index_startTime_durationSecs_state |= uint256(_index) << 192; // Set new value - } - - function setStartTime(RegistryConfig storage r, uint64 _startTime) internal { - r.index_startTime_durationSecs_state &= ~(MAX_UINT64 << 128); - r.index_startTime_durationSecs_state |= uint256(_startTime) << 128; - } - - function setDurationSecs(RegistryConfig storage r, uint64 _durationSecs) internal { - r.index_startTime_durationSecs_state &= ~(MAX_UINT64 << 64); - r.index_startTime_durationSecs_state |= uint256(_durationSecs) << 64; - } - - function setState(RegistryConfig storage r, uint8 _state) internal { - r.index_startTime_durationSecs_state &= ~(MAX_UINT8 << 56); - r.index_startTime_durationSecs_state |= uint256(_state) << 56; - } - // gasCommittedForNextCycle (uint128) | gasCommittedForThisCycle (uint128) function gasCommittedForNextCycle(RegistryConfig storage r) internal view returns (uint128) { return uint128(r.gasCommittedForNextCycle_gasCommittedForThisCycle >> 128); diff --git a/solidity/supra_contracts/src/LibController.sol b/solidity/supra_contracts/src/LibController.sol index 6ec28dd20b..f6f07b7a7e 100644 --- a/solidity/supra_contracts/src/LibController.sol +++ b/solidity/supra_contracts/src/LibController.sol @@ -3,6 +3,7 @@ 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 { @@ -13,8 +14,8 @@ library LibController { /// @notice Struct representing the state of current cycle. struct AutomationCycleInfo{ - bool automationEnabled; - bool ifTransitionStateExists; + // uint64 | uint64 | uint64 | CycleState(uint8) | bool(1 bit) | bool(1 bit) + uint256 index_startTime_durationSecs_state_ifTransitionStateExists_automationEnabled; TransitionState transitionState; } @@ -30,6 +31,79 @@ library LibController { 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) diff --git a/solidity/supra_contracts/test/AutomationController.t.sol b/solidity/supra_contracts/test/AutomationController.t.sol index f53867c7db..330c2e3c52 100644 --- a/solidity/supra_contracts/test/AutomationController.t.sol +++ b/solidity/supra_contracts/test/AutomationController.t.sol @@ -50,8 +50,7 @@ contract AutomationControllerTest is Test { 5_000_000, // sysRegistryMaxGasCap 500, // sysTaskCapacity vmSigner, // VM Signer address - address(erc20Supra), // ERC20Supra address - true // automationEnabled + address(erc20Supra) // ERC20Supra address ) ); ERC1967Proxy automationCoreProxy = new ERC1967Proxy(address(automationCoreImpl), automationCoreInitData); @@ -230,12 +229,12 @@ contract AutomationControllerTest is Test { /// @dev Test to ensure 'monitorCycleEnd' does nothing before cycle expiry. function testMonitorCycleEndDoesNothingBeforeCycleExpiry() public { - (uint64 indexBefore, uint64 startBefore, uint64 durationBefore, CommonUtils.CycleState stateBefore) = automationCore.getCycleInfo(); + (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) = automationCore.getCycleInfo(); + (uint64 indexAfter, uint64 startAfter, uint64 durationAfter, CommonUtils.CycleState stateAfter) = controller.getCycleInfo(); assertEq(indexAfter, indexBefore); assertEq(startAfter, startBefore); @@ -273,11 +272,11 @@ contract AutomationControllerTest is Test { assertFalse(controller.isAutomationEnabled()); - (uint64 indexBefore, uint64 startBefore, uint64 durationBefore, CommonUtils.CycleState stateBefore) = automationCore.getCycleInfo(); + (uint64 indexBefore, uint64 startBefore, uint64 durationBefore, CommonUtils.CycleState stateBefore) = controller.getCycleInfo(); vm.warp(startBefore + durationBefore); vm.expectEmit(true, true, false, true); - emit AutomationCore.AutomationCycleEvent( + emit AutomationController.AutomationCycleEvent( indexBefore, CommonUtils.CycleState.READY, startBefore, @@ -288,7 +287,7 @@ contract AutomationControllerTest is Test { vm.prank(vmSigner, vmSigner); controller.monitorCycleEnd(); - (uint64 indexAfter, uint64 startAfter, uint64 durationAfter, CommonUtils.CycleState stateAfter) = automationCore.getCycleInfo(); + (uint64 indexAfter, uint64 startAfter, uint64 durationAfter, CommonUtils.CycleState stateAfter) = controller.getCycleInfo(); assertEq(indexAfter, indexBefore); assertEq(startAfter, startBefore); @@ -298,12 +297,12 @@ contract AutomationControllerTest is Test { /// @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) = automationCore.getCycleInfo(); + (uint64 indexBefore, uint64 startBefore, uint64 durationBefore, CommonUtils.CycleState stateBefore) = controller.getCycleInfo(); vm.warp(startBefore + durationBefore); vm.expectEmit(true, true, false, true); - emit AutomationCore.AutomationCycleEvent( + emit AutomationController.AutomationCycleEvent( indexBefore + 1, CommonUtils.CycleState.STARTED, uint64(block.timestamp), @@ -314,7 +313,7 @@ contract AutomationControllerTest is Test { vm.prank(vmSigner, vmSigner); controller.monitorCycleEnd(); - (uint64 indexAfter, uint64 startAfter, uint64 durationAfter, CommonUtils.CycleState stateAfter) = automationCore.getCycleInfo(); + (uint64 indexAfter, uint64 startAfter, uint64 durationAfter, CommonUtils.CycleState stateAfter) = controller.getCycleInfo(); assertEq(indexAfter, indexBefore + 1); assertEq(startAfter, block.timestamp); @@ -326,11 +325,11 @@ contract AutomationControllerTest is Test { function testMonitorCycleEndWhenAutomationEnabledAndTasksExist() public { registerTask(); - (uint64 indexBefore, uint64 startBefore, uint64 durationBefore, CommonUtils.CycleState stateBefore) = automationCore.getCycleInfo(); + (uint64 indexBefore, uint64 startBefore, uint64 durationBefore, CommonUtils.CycleState stateBefore) = controller.getCycleInfo(); vm.warp(startBefore + durationBefore); vm.expectEmit(true, true, false, true); - emit AutomationCore.AutomationCycleEvent( + emit AutomationController.AutomationCycleEvent( indexBefore, CommonUtils.CycleState.FINISHED, startBefore, @@ -341,7 +340,7 @@ contract AutomationControllerTest is Test { vm.prank(vmSigner, vmSigner); controller.monitorCycleEnd(); - (uint64 indexAfter, uint64 startAfter, uint64 durationAfter, CommonUtils.CycleState stateAfter) = automationCore.getCycleInfo(); + (uint64 indexAfter, uint64 startAfter, uint64 durationAfter, CommonUtils.CycleState stateAfter) = controller.getCycleInfo(); assertEq(indexAfter, indexBefore); assertEq(startAfter, startBefore); @@ -379,13 +378,13 @@ contract AutomationControllerTest is Test { function testProcessTasksWhenCycleStateFinished() public { registerTask(); - ( , uint64 startTime, uint64 duration, ) = automationCore.getCycleInfo(); + ( , uint64 startTime, uint64 duration, ) = controller.getCycleInfo(); vm.warp(startTime + duration); vm.prank(vmSigner, vmSigner); controller.monitorCycleEnd(); - (uint64 index, , , CommonUtils.CycleState state) = automationCore.getCycleInfo(); + (uint64 index, , , CommonUtils.CycleState state) = controller.getCycleInfo(); assertEq(uint8(state), uint8(CommonUtils.CycleState.FINISHED)); uint64[] memory tasks = new uint64[](1); @@ -400,7 +399,7 @@ contract AutomationControllerTest is Test { vm.prank(vmSigner, vmSigner); controller.processTasks(index + 1, tasks); - (uint64 newIndex, uint64 newStart, uint64 newDuration, CommonUtils.CycleState newState) = automationCore.getCycleInfo(); + (uint64 newIndex, uint64 newStart, uint64 newDuration, CommonUtils.CycleState newState) = controller.getCycleInfo(); assertEq(newIndex, index + 1); assertEq(newStart, uint64(block.timestamp)); assertEq(newDuration, 2000); @@ -418,13 +417,13 @@ contract AutomationControllerTest is Test { function testProcessTasksRevertsIfInvalidCycleIndexWhenCycleStateFinished() public { registerTask(); - ( , uint64 startTime, uint64 duration, ) = automationCore.getCycleInfo(); + ( , uint64 startTime, uint64 duration, ) = controller.getCycleInfo(); vm.warp(startTime + duration); vm.prank(vmSigner, vmSigner); controller.monitorCycleEnd(); - (uint64 index, , , CommonUtils.CycleState state) = automationCore.getCycleInfo(); + (uint64 index, , , CommonUtils.CycleState state) = controller.getCycleInfo(); assertEq(uint8(state), uint8(CommonUtils.CycleState.FINISHED)); uint64[] memory tasks = new uint64[](1); @@ -440,21 +439,21 @@ contract AutomationControllerTest is Test { function testProcessTasksWhenCycleStateSuspendedAutomationDisabled() public { registerTask(); - ( , uint64 start, uint64 duration, ) = automationCore.getCycleInfo(); + ( , uint64 start, uint64 duration, ) = controller.getCycleInfo(); vm.warp(start + duration); // Moves state to FINISHED vm.prank(vmSigner, vmSigner); controller.monitorCycleEnd(); - ( , , , CommonUtils.CycleState stateBefore) = automationCore.getCycleInfo(); + ( , , , 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) = automationCore.getCycleInfo(); + (uint64 indexAfter, , , CommonUtils.CycleState stateAfter) = controller.getCycleInfo(); assertEq(uint8(stateAfter), uint8(CommonUtils.CycleState.SUSPENDED)); uint64[] memory tasks = new uint64[](1); @@ -466,7 +465,7 @@ contract AutomationControllerTest is Test { vm.prank(vmSigner, vmSigner); controller.processTasks(indexAfter, tasks); - ( , , , CommonUtils.CycleState newState) = automationCore.getCycleInfo(); + ( , , , CommonUtils.CycleState newState) = controller.getCycleInfo(); assertEq(uint8(newState), uint8(CommonUtils.CycleState.READY)); assertFalse(registry.ifTaskExists(tasks[0])); } @@ -475,21 +474,21 @@ contract AutomationControllerTest is Test { function testProcessTasksWhenCycleStateSuspendedAutomationEnabled() public { registerTask(); - ( , uint64 start, uint64 duration, ) = automationCore.getCycleInfo(); + ( , uint64 start, uint64 duration, ) = controller.getCycleInfo(); vm.warp(start + duration); // Moves state to FINISHED vm.prank(vmSigner, vmSigner); controller.monitorCycleEnd(); - ( , , , CommonUtils.CycleState stateBefore) = automationCore.getCycleInfo(); + ( , , , 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) = automationCore.getCycleInfo(); + (uint64 indexAfter, , , CommonUtils.CycleState stateAfter) = controller.getCycleInfo(); assertEq(uint8(stateAfter), uint8(CommonUtils.CycleState.SUSPENDED)); // Enable automation @@ -505,7 +504,7 @@ contract AutomationControllerTest is Test { vm.prank(vmSigner, vmSigner); controller.processTasks(indexAfter, tasks); - (uint64 newIndex, uint64 newStart, uint64 newDuration, CommonUtils.CycleState newState) = automationCore.getCycleInfo(); + (uint64 newIndex, uint64 newStart, uint64 newDuration, CommonUtils.CycleState newState) = controller.getCycleInfo(); assertEq(newIndex, indexAfter + 1); assertEq(newStart, uint64(block.timestamp)); assertEq(newDuration, 2000); @@ -517,21 +516,21 @@ contract AutomationControllerTest is Test { function testProcessTasksRevertsIfInvalidCycleIndexWhenCycleStateSuspended() public { registerTask(); - ( , uint64 start, uint64 duration, ) = automationCore.getCycleInfo(); + ( , uint64 start, uint64 duration, ) = controller.getCycleInfo(); vm.warp(start + duration); // Moves state to FINISHED vm.prank(vmSigner, vmSigner); controller.monitorCycleEnd(); - ( , , , CommonUtils.CycleState stateBefore) = automationCore.getCycleInfo(); + ( , , , 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) = automationCore.getCycleInfo(); + (uint64 indexAfter, , , CommonUtils.CycleState stateAfter) = controller.getCycleInfo(); assertEq(uint8(stateAfter), uint8(CommonUtils.CycleState.SUSPENDED)); uint64[] memory tasks = new uint64[](1); diff --git a/solidity/supra_contracts/test/AutomationCore.t.sol b/solidity/supra_contracts/test/AutomationCore.t.sol index b2db3d7a88..b0b72257c4 100644 --- a/solidity/supra_contracts/test/AutomationCore.t.sol +++ b/solidity/supra_contracts/test/AutomationCore.t.sol @@ -50,8 +50,7 @@ contract AutomationCoreTest is Test { 5_000_000, // sysRegistryMaxGasCap 500, // sysTaskCapacity vmSigner, // VM Signer address - address(erc20Supra), // ERC20Supra address - true // automationEnabled + address(erc20Supra) // ERC20Supra address ) ); ERC1967Proxy automationCoreProxy = new ERC1967Proxy(address(automationCoreImpl), automationCoreInitData); @@ -78,7 +77,7 @@ contract AutomationCoreTest is Test { function testInitialize() public view { assertEq(automationCore.owner(), admin); - (uint64 index, uint64 startTime, uint64 durationSecs, CommonUtils.CycleState state) = automationCore.getCycleInfo(); + (uint64 index, uint64 startTime, uint64 durationSecs, CommonUtils.CycleState state) = automationController.getCycleInfo(); assertEq(index, 1); assertEq(startTime, block.timestamp); assertEq(durationSecs, 2000); @@ -115,7 +114,7 @@ contract AutomationCoreTest is Test { 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), true + 500, 2000, 3600, 5_000_000, 500, vmSigner, address(erc20Supra) ); } @@ -129,7 +128,7 @@ contract AutomationCoreTest is Test { 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), true + address(erc20Supra) ) ); @@ -146,8 +145,7 @@ contract AutomationCoreTest is Test { ( 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 - true + address(0) // address(0) as ERC20Supra ) ); @@ -164,8 +162,7 @@ contract AutomationCoreTest is Test { ( 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 - true + admin // EOA address as ERC20Supra ) ); @@ -183,7 +180,7 @@ contract AutomationCoreTest is Test { 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), true + 3600, 5_000_000, 500, vmSigner, address(erc20Supra) ) ); @@ -201,7 +198,7 @@ contract AutomationCoreTest is Test { 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), true + 2000, 3600, 5_000_000, 500, vmSigner, address(erc20Supra) ) ); @@ -218,7 +215,7 @@ contract AutomationCoreTest is Test { ( 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), true + 0.002 ether, 2, 500, 2000, 3600, 5_000_000, 500, vmSigner, address(erc20Supra) ) ); @@ -235,7 +232,7 @@ contract AutomationCoreTest is Test { ( 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), true + 500, 2000, 3600, 5_000_000, 500, vmSigner, address(erc20Supra) ) ); @@ -252,7 +249,7 @@ contract AutomationCoreTest is Test { ( 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), true + 2000, 3600, 5_000_000, 500, vmSigner, address(erc20Supra) ) ); @@ -269,7 +266,7 @@ contract AutomationCoreTest is Test { ( 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), true + 3600, 5_000_000, 500, vmSigner, address(erc20Supra) ) ); @@ -287,7 +284,7 @@ contract AutomationCoreTest is Test { 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), true + 5_000_000, 500, vmSigner, address(erc20Supra) ) ); @@ -304,7 +301,7 @@ contract AutomationCoreTest is Test { ( 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), true + 500, vmSigner, address(erc20Supra) ) ); @@ -322,7 +319,7 @@ contract AutomationCoreTest is Test { 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), true + vmSigner, address(erc20Supra) ) ); @@ -799,7 +796,7 @@ contract AutomationCoreTest is Test { vm.expectRevert(IAutomationCore.CallerNotController.selector); vm.prank(address(registry)); - automationCore.applyPendingConfig(false); + automationCore.applyPendingConfig(); } /// @dev Test to ensure 'calculateTaskFee' reverts if caller is not AutomationController. @@ -857,14 +854,14 @@ contract AutomationCoreTest is Test { ); } - /// @dev Test to ensure 'validateRegistration' reverts if caller is not AutomationRegistry. - function testValidateRegistrationRevertsIfCallerNotAutomationRegistry() public { + /// @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.validateRegistration( + automationCore.updateStateForValidRegistration( 10, 0, uint64(block.timestamp), diff --git a/solidity/supra_contracts/test/AutomationRegistry.t.sol b/solidity/supra_contracts/test/AutomationRegistry.t.sol index ec61ce24e2..057f9db8a1 100644 --- a/solidity/supra_contracts/test/AutomationRegistry.t.sol +++ b/solidity/supra_contracts/test/AutomationRegistry.t.sol @@ -52,8 +52,7 @@ contract AutomationRegistryTest is Test { 5_000_000, // sysRegistryMaxGasCap 500, // sysTaskCapacity vmSigner, // VM Signer address - address(erc20Supra), // ERC20Supra address - true // automationEnabled + address(erc20Supra) // ERC20Supra address ) ); ERC1967Proxy automationCoreProxy = new ERC1967Proxy(address(automationCoreImpl), automationCoreInitData); From 79eac7332c6deb6152966759ae3443d8f4847f6f Mon Sep 17 00:00:00 2001 From: Udit Yadav Date: Wed, 4 Feb 2026 17:33:27 +0530 Subject: [PATCH 26/31] -added view funcitons for cycle details -gas optimization --- .../src/AutomationController.sol | 125 ++++++++++-------- .../supra_contracts/src/AutomationCore.sol | 13 +- .../src/AutomationRegistry.sol | 87 ++++++------ .../src/IAutomationController.sol | 3 + .../supra_contracts/src/IAutomationCore.sol | 4 +- .../supra_contracts/test/AutomationCore.t.sol | 1 - 6 files changed, 120 insertions(+), 113 deletions(-) diff --git a/solidity/supra_contracts/src/AutomationController.sol b/solidity/supra_contracts/src/AutomationController.sol index 96a7cde69c..b1b0b1abee 100644 --- a/solidity/supra_contracts/src/AutomationController.sol +++ b/solidity/supra_contracts/src/AutomationController.sol @@ -19,8 +19,8 @@ contract AutomationController is IAutomationController, Ownable2StepUpgradeable, /// @dev State variables LibController.AutomationCycleInfo cycleInfo; - IAutomationRegistry public registry; - IAutomationCore public automationCore; + address public registry; + address public automationCore; // ::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::: EVENTS ::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::: @@ -95,15 +95,15 @@ contract AutomationController is IAutomationController, Ownable2StepUpgradeable, _automationCore.validateContractAddress(); _registry.validateContractAddress(); - automationCore = IAutomationCore(_automationCore); - registry = IAutomationRegistry(_registry); + automationCore = _automationCore; + registry = _registry; (CommonUtils.CycleState state, uint64 cycleId) = _automationEnabled ? (CommonUtils.CycleState.STARTED, 1) : (CommonUtils.CycleState.READY, 0); cycleInfo.initializeCycle( cycleId, uint64(block.timestamp), - automationCore.cycleDurationSecs(), + IAutomationCore(_automationCore).cycleDurationSecs(), state, _automationEnabled ); @@ -117,7 +117,7 @@ contract AutomationController is IAutomationController, Ownable2StepUpgradeable, /// @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 != automationCore.getVmSigner()) { revert CallerNotVmSigner(); } + if (msg.sender != IAutomationCore(automationCore).getVmSigner()) { revert CallerNotVmSigner(); } CommonUtils.CycleState state = cycleInfo.state(); @@ -131,9 +131,9 @@ contract AutomationController is IAutomationController, Ownable2StepUpgradeable, /// @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 != automationCore.getVmSigner()) { revert CallerNotVmSigner(); } + if (tx.origin != IAutomationCore(automationCore).getVmSigner()) { revert CallerNotVmSigner(); } - if(!isCycleStarted() || cycleInfo.startTime() + cycleInfo.durationSecs() > block.timestamp) { + if(!isCycleStarted() || getCycleEndTime() > block.timestamp) { return; } @@ -189,12 +189,14 @@ contract AutomationController is IAutomationController, Ownable2StepUpgradeable, // 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(registry.ifTaskExists(taskIndexes[i])) { - CommonUtils.TaskDetails memory task = registry.getTaskDetails(taskIndexes[i]); + if(automationRegistry.ifTaskExists(taskIndexes[i])) { + CommonUtils.TaskDetails memory task = automationRegistry.getTaskDetails(taskIndexes[i]); - (bool removed, ) = address(registry).call(abi.encodeCall(IAutomationRegistry.removeTask, (taskIndexes[i], false))); + (bool removed, ) = address(automationRegistry).call(abi.encodeCall(IAutomationRegistry.removeTask, (taskIndexes[i], false))); require(removed, RemoveTaskFailed()); removedTasks[removedCounter++] = taskIndexes[i]; @@ -202,7 +204,7 @@ contract AutomationController is IAutomationController, Ownable2StepUpgradeable, // Nothing to refund for GST tasks if (task.taskType == CommonUtils.TaskType.UST) { - (bool refunded, ) = address(automationCore).call( + (bool refunded, ) = automationCore.call( abi.encodeCall( IAutomationCore.refundTaskFees, (currentTime, cycleInfo.refundDuration(), cycleInfo.automationFeePerSec(), task) @@ -267,16 +269,17 @@ contract AutomationController is IAutomationController, Ownable2StepUpgradeable, uint64 _currentTime, uint64 _currentCycleEndTime ) private returns (LibController.TransitionResult memory result){ - if(registry.ifTaskExists(_taskIndex)) { + address registryAddr = registry; + if(IAutomationRegistry(registryAddr).ifTaskExists(_taskIndex)) { markTaskProcessed(_taskIndex); - CommonUtils.TaskDetails memory task = registry.getTaskDetails(_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, ) = address(registry).call( + (bool sent, ) = registryAddr.call( abi.encodeCall( IAutomationRegistry.refundDepositAndDrop, (_taskIndex, task.owner, task.lockedFeeForNextCycle, task.lockedFeeForNextCycle) @@ -285,7 +288,7 @@ contract AutomationController is IAutomationController, Ownable2StepUpgradeable, require(sent, RefundDepositAndDropFailed()); } else { // Remove the task from registry and system registry - (bool removed, ) = address(registry).call(abi.encodeCall(IAutomationRegistry.removeTask, (_taskIndex, true))); + (bool removed, ) = registryAddr.call(abi.encodeCall(IAutomationRegistry.removeTask, (_taskIndex, true))); require(removed, RemoveTaskFailed()); } result.isRemoved = true; @@ -294,11 +297,11 @@ contract AutomationController is IAutomationController, Ownable2StepUpgradeable, // Governance submitted tasks are not charged result.sysGas = task.maxGasAmount; - (bool updated, ) = address(registry).call(abi.encodeCall(IAutomationRegistry.updateTaskState, (_taskIndex, CommonUtils.TaskState.ACTIVE))); + (bool updated, ) = registryAddr.call(abi.encodeCall(IAutomationRegistry.updateTaskState, (_taskIndex, CommonUtils.TaskState.ACTIVE))); require(updated, UpdateTaskStateFailed()); } else { // Active UST - uint128 fee = automationCore.calculateTaskFee( + uint128 fee = IAutomationCore(automationCore).calculateTaskFee( task.state, task.expiryTime, task.maxGasAmount, @@ -312,7 +315,7 @@ contract AutomationController is IAutomationController, Ownable2StepUpgradeable, // 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, ) = address(registry).call(abi.encodeCall(IAutomationRegistry.updateTaskState, (_taskIndex, CommonUtils.TaskState.ACTIVE))); + (bool updated, ) = registryAddr.call(abi.encodeCall(IAutomationRegistry.updateTaskState, (_taskIndex, CommonUtils.TaskState.ACTIVE))); require(updated, UpdateTaskStateFailed()); (result.isRemoved, result.gas, result.fees) = tryWithdrawTaskAutomationFee( @@ -370,12 +373,14 @@ contract AutomationController is IAutomationController, Ownable2StepUpgradeable, // 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 erc20Supra = automationCore.erc20Supra(); + address automationCoreAddr = automationCore; + address erc20Supra = IAutomationCore(automationCoreAddr).erc20Supra(); bool isRemoved; uint128 gas; uint128 fees; + address registryAddr = registry; if(_fee > _automationFeeCapForCycle) { - (bool sent, ) = address(registry).call( + (bool sent, ) = registryAddr.call( abi.encodeCall( IAutomationRegistry.refundDepositAndDrop, (_taskIndex, _owner, _lockedFeeForNextCycle, _lockedFeeForNextCycle) @@ -397,7 +402,7 @@ contract AutomationController is IAutomationController, Ownable2StepUpgradeable, 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, ) = address(automationCore).call( + (bool unlocked, ) = automationCoreAddr.call( abi.encodeCall( IAutomationCore.safeUnlockLockedDeposit, (_taskIndex, _lockedFeeForNextCycle) @@ -405,7 +410,7 @@ contract AutomationController is IAutomationController, Ownable2StepUpgradeable, ); require(unlocked, UnlockLockedDepositFailed()); - (bool removed, ) = address(registry).call(abi.encodeCall(IAutomationRegistry.removeTask, (_taskIndex, false))); + (bool removed, ) = registryAddr.call(abi.encodeCall(IAutomationRegistry.removeTask, (_taskIndex, false))); require(removed, RemoveTaskFailed()); isRemoved = true; @@ -419,7 +424,7 @@ contract AutomationController is IAutomationController, Ownable2StepUpgradeable, } else { if(_fee != 0) { // Charge the fee - (bool sent, ) = address(automationCore).call(abi.encodeCall(IAutomationCore.chargeFees, (_owner, _fee))); + (bool sent, ) = automationCoreAddr.call(abi.encodeCall(IAutomationCore.chargeFees, (_owner, _fee))); if (!sent) { revert TransferFailed(); } fees = _fee; @@ -454,7 +459,7 @@ contract AutomationController is IAutomationController, Ownable2StepUpgradeable, if (!cycleInfo.automationEnabled() && cycleInfo.state() == CommonUtils.CycleState.FINISHED) { tryMoveToSuspendedState(); } else { - (bool updated, ) = address(automationCore).call( + (bool updated, ) = automationCore.call( abi.encodeCall( IAutomationCore.updateGasCommittedAndCycleLockedFees, ( @@ -467,13 +472,14 @@ contract AutomationController is IAutomationController, Ownable2StepUpgradeable, ); require(updated, UpdateGasCommittedAndCycleLockedFeesFailed()); - registry.updateTasks(CommonUtils.CycleState.FINISHED); + IAutomationRegistry automationRegistry = IAutomationRegistry(registry); + automationRegistry.updateTasks(CommonUtils.CycleState.FINISHED); // Set current timestamp as cycle start time // Increment the cycle and update the state to STARTED moveToStartedState(); - if(registry.getTotalActiveTasks() > 0 ) { - uint256[] memory activeTasks = registry.getAllActiveTaskIds(); + if(automationRegistry.getTotalActiveTasks() > 0 ) { + uint256[] memory activeTasks = automationRegistry.getAllActiveTaskIds(); emit ActiveTasks(activeTasks); } } @@ -497,10 +503,10 @@ contract AutomationController is IAutomationController, Ownable2StepUpgradeable, return; } - (bool updated, )= address(automationCore).call(abi.encodeCall(IAutomationCore.updateGasCommittedAndCycleLockedFees, (0, 0, 0, 0))); + (bool updated, )= automationCore.call(abi.encodeCall(IAutomationCore.updateGasCommittedAndCycleLockedFees, (0, 0, 0, 0))); require(updated, UpdateGasCommittedAndCycleLockedFeesFailed()); - registry.updateTasks(CommonUtils.CycleState.SUSPENDED); + IAutomationRegistry(registry).updateTasks(CommonUtils.CycleState.SUSPENDED); // Check if automation is enabled if (cycleInfo.automationEnabled()) { @@ -528,7 +534,8 @@ contract AutomationController is IAutomationController, Ownable2StepUpgradeable, /// 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 { - if(registry.totalTasks() == 0) { + IAutomationRegistry automationRegistry = IAutomationRegistry(registry); + if(automationRegistry.totalTasks() == 0) { // Registry is empty move to ready state directly updateCycleStateTo(CommonUtils.CycleState.READY); } else if (!cycleInfo.ifTransitionStateExists()) { @@ -541,20 +548,18 @@ contract AutomationController is IAutomationController, Ownable2StepUpgradeable, // 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 startTime = cycleInfo.startTime(); - uint64 cycleDuration = cycleInfo.durationSecs(); - uint64 cycleEndTime = startTime + cycleDuration; + uint64 currentTime = uint64(block.timestamp); + uint64 cycleEndTime = getCycleEndTime(); - if(currentTime < startTime) { revert InvalidRegistryState(); } + if(currentTime < cycleInfo.startTime()) { revert InvalidRegistryState(); } if(currentTime >= cycleEndTime) { revert InvalidRegistryState(); } if(!isCycleStarted()) { revert InvalidRegistryState(); } - uint256[] memory expectedTasksToBeProcessed = registry.getTaskIdList().sortUint256(); + uint256[] memory expectedTasksToBeProcessed = automationRegistry.getTaskIdList().sortUint256(); cycleInfo.setRefundDuration(cycleEndTime - currentTime); - cycleInfo.setNewCycleDuration(cycleDuration); - cycleInfo.setAutomationFeePerSec(automationCore.calculateAutomationFeeMultiplierForCurrentCycleInternal()); + cycleInfo.setNewCycleDuration(cycleInfo.durationSecs()); + cycleInfo.setAutomationFeePerSec(IAutomationCore(automationCore).calculateAutomationFeeMultiplierForCurrentCycleInternal()); cycleInfo.setGasCommittedForNewCycle(0); cycleInfo.setGasCommittedForNextCycle(0); cycleInfo.setSysGasCommittedForNextCycle(0); @@ -654,17 +659,19 @@ contract AutomationController is IAutomationController, Ownable2StepUpgradeable, if (!cycleInfo.automationEnabled()) { tryMoveToSuspendedState(); } else{ - if(registry.totalTasks() == 0) { + IAutomationRegistry automationRegistry = IAutomationRegistry(registry); + if(automationRegistry.totalTasks() == 0) { // Registry is empty update config buffer and move to STARTED state directly updateConfigFromBuffer(); moveToStartedState(); } else { - uint256[] memory expectedTasksToBeProcessed = registry.getTaskIdList().sortUint256(); + IAutomationCore core = IAutomationCore(automationCore); + uint256[] memory expectedTasksToBeProcessed = automationRegistry.getTaskIdList().sortUint256(); // Updates transition state cycleInfo.setRefundDuration(0); cycleInfo.setNewCycleDuration(cycleInfo.durationSecs()); - cycleInfo.setGasCommittedForNewCycle(automationCore.getGasCommittedForNextCycle()); + cycleInfo.setGasCommittedForNewCycle(core.getGasCommittedForNextCycle()); cycleInfo.setGasCommittedForNextCycle(0); cycleInfo.setSysGasCommittedForNextCycle (0); cycleInfo.transitionState.lockedFees = 0; @@ -679,7 +686,7 @@ contract AutomationController is IAutomationController, Ownable2StepUpgradeable, // 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(automationCore.calculateAutomationFeeMultiplierForCommittedOccupancy(cycleInfo.gasCommittedForNewCycle())); + cycleInfo.setAutomationFeePerSec(core.calculateAutomationFeeMultiplierForCommittedOccupancy(cycleInfo.gasCommittedForNewCycle())); updateCycleStateTo(CommonUtils.CycleState.FINISHED); } } @@ -687,7 +694,7 @@ contract AutomationController is IAutomationController, Ownable2StepUpgradeable, /// @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) = automationCore.applyPendingConfig(); + (bool applied, uint64 cycleDuration) = IAutomationCore(automationCore).applyPendingConfig(); if (!applied) return; // Check if transition state exists @@ -703,11 +710,6 @@ contract AutomationController is IAutomationController, Ownable2StepUpgradeable, function isTransitionFinalized() private view returns (bool) { return cycleInfo.transitionState.expectedTasksToBeProcessed.length() == cycleInfo.nextTaskIndexPosition(); } - - /// @notice Checks whether cycle is in STARTED state. - function isCycleStarted() private view returns (bool) { - return cycleInfo.state() == CommonUtils.CycleState.STARTED; - } // :::::::::::::::::::::::::::::::::::::::::::::::::::::::::::: VIEW FUNCTIONS :::::::::::::::::::::::::::::::::::::::::::::::::::::::::::: @@ -716,12 +718,22 @@ contract AutomationController is IAutomationController, Ownable2StepUpgradeable, 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 @@ -734,6 +746,11 @@ contract AutomationController is IAutomationController, Ownable2StepUpgradeable, 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. @@ -741,8 +758,8 @@ contract AutomationController is IAutomationController, Ownable2StepUpgradeable, function setAutomationRegistry(address _registry) external onlyOwner { _registry.validateContractAddress(); - address oldRegistry = address(registry); - registry = IAutomationRegistry(_registry); + address oldRegistry = registry; + registry = _registry; emit AutomationRegistryUpdated(oldRegistry, _registry); } @@ -752,8 +769,8 @@ contract AutomationController is IAutomationController, Ownable2StepUpgradeable, function setAutomationCore(address _automationCore) external onlyOwner { _automationCore.validateContractAddress(); - address oldAutomationCore = address(automationCore); - automationCore = IAutomationCore(_automationCore); + address oldAutomationCore = automationCore; + automationCore = _automationCore; emit AutomationCoreUpdated(oldAutomationCore, _automationCore); } diff --git a/solidity/supra_contracts/src/AutomationCore.sol b/solidity/supra_contracts/src/AutomationCore.sol index 3e972d4d0c..ecf7644af8 100644 --- a/solidity/supra_contracts/src/AutomationCore.sol +++ b/solidity/supra_contracts/src/AutomationCore.sol @@ -359,14 +359,13 @@ contract AutomationCore is IAutomationCore, Ownable2StepUpgradeable, UUPSUpgrade uint128 _taskOccupancy, uint128 _committedOccupancy ) private view returns (uint128) { - ( , , uint64 durationSecs, ) = IAutomationController(regConfig.automationController()).getCycleInfo(); - 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()); } @@ -630,14 +629,16 @@ contract AutomationCore is IAutomationCore, Ownable2StepUpgradeable, UUPSUpgrade uint128 _maxGasAmount, bytes32 _txHash, uint128 _gasPriceCap, - uint128 _automationFeeCapForCycle, - uint64 _cycleEndTime + uint128 _automationFeeCapForCycle ) external { onlyRegistry(); - // Check if registration is enabled + // 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(); } if(_inputType != uint8(_taskType)) { revert InvalidTaskType(); } bool isUST = _taskType == CommonUtils.TaskType.UST; @@ -663,7 +664,7 @@ contract AutomationCore is IAutomationCore, Ownable2StepUpgradeable, UUPSUpgrade nextCycleRegistryMaxGasCap = regConfig.nextCycleSysRegistryMaxGasCap(); } - validateTaskDuration(_regTime, _expiryTime, taskDurationCap, _cycleEndTime); + validateTaskDuration(_regTime, _expiryTime, taskDurationCap, automationController.getCycleEndTime()); validateInputs(_payloadTx, _maxGasAmount, _txHash); uint128 gasCommitted = _maxGasAmount + gasCommittedForNextCycle; diff --git a/solidity/supra_contracts/src/AutomationRegistry.sol b/solidity/supra_contracts/src/AutomationRegistry.sol index 36ce36c62c..d978df81bc 100644 --- a/solidity/supra_contracts/src/AutomationRegistry.sol +++ b/solidity/supra_contracts/src/AutomationRegistry.sol @@ -110,14 +110,11 @@ contract AutomationRegistry is IAutomationRegistry, Ownable2StepUpgradeable, UUP uint64 _priority, uint8 _type, bytes[] memory _auxData - ) external { - if (!IAutomationController(automationController).isAutomationEnabled()) { revert AutomationNotEnabled(); } - - ( , uint64 startTime, uint64 durationSecs, CommonUtils.CycleState state) = IAutomationController(automationController).getCycleInfo(); - if(state != CommonUtils.CycleState.STARTED) { revert CycleTransitionInProgress(); } - + ) external { uint64 regTime = uint64(block.timestamp); - IAutomationCore(automationCore).updateStateForValidRegistration( + + IAutomationCore core = IAutomationCore(automationCore); + core.updateStateForValidRegistration( totalTasks(), _type, regTime, @@ -127,8 +124,7 @@ contract AutomationRegistry is IAutomationRegistry, Ownable2StepUpgradeable, UUP _maxGasAmount, _txHash, _gasPriceCap, - _automationFeeCapForCycle, - startTime + durationSecs + _automationFeeCapForCycle ); uint64 taskIndex = regState.currentIndex; @@ -154,10 +150,10 @@ contract AutomationRegistry is IAutomationRegistry, Ownable2StepUpgradeable, UUP require(regState.taskIdList.add(taskIndex), TaskIndexNotUnique()); regState.currentIndex += 1; - IAutomationCore(automationCore).incTotalDepositedAutomationFees(_automationFeeCapForCycle); - uint128 flatRegistrationFeeWei = IAutomationCore(automationCore).flatRegistrationFeeWei(); + core.incTotalDepositedAutomationFees(_automationFeeCapForCycle); + uint128 flatRegistrationFeeWei = core.flatRegistrationFeeWei(); uint128 fee = flatRegistrationFeeWei + _automationFeeCapForCycle; - IAutomationCore(automationCore).chargeFees(msg.sender, fee); + core.chargeFees(msg.sender, fee); emit TaskRegistered(taskIndex, msg.sender, flatRegistrationFeeWei, _automationFeeCapForCycle, regState.tasks[taskIndex].getTaskDetails()); } @@ -179,12 +175,8 @@ contract AutomationRegistry is IAutomationRegistry, Ownable2StepUpgradeable, UUP uint8 _type, bytes[] memory _auxData ) external { - if (!IAutomationController(automationController).isAutomationEnabled()) { revert AutomationNotEnabled(); } if(!isAuthorizedSubmitter(msg.sender)) { revert UnauthorizedAccount(); } - ( , uint64 startTime, uint64 durationSecs, CommonUtils.CycleState state) = IAutomationController(automationController).getCycleInfo(); - if (state != CommonUtils.CycleState.STARTED) { revert CycleTransitionInProgress(); } - uint64 regTime = uint64(block.timestamp); IAutomationCore(automationCore).updateStateForValidRegistration( totalSystemTasks(), @@ -196,8 +188,7 @@ contract AutomationRegistry is IAutomationRegistry, Ownable2StepUpgradeable, UUP _maxGasAmount, _txHash, 0, - 0, - startTime + durationSecs + 0 ); uint64 taskIndex = regState.currentIndex; @@ -239,11 +230,10 @@ contract AutomationRegistry is IAutomationRegistry, Ownable2StepUpgradeable, UUP uint64 _taskIndex ) external { // Check if automation is enabled - if (!IAutomationController(automationController).isAutomationEnabled()) { revert AutomationNotEnabled(); } - - ( , uint64 startTime, uint64 durationSecs, CommonUtils.CycleState state) = IAutomationController(automationController).getCycleInfo(); + IAutomationController controller = IAutomationController(automationController); + if (!controller.isAutomationEnabled()) { revert AutomationNotEnabled(); } - if(state != CommonUtils.CycleState.STARTED) { revert CycleTransitionInProgress(); } + if(!controller.isCycleStarted()) { revert CycleTransitionInProgress(); } if(!ifTaskExists(_taskIndex)) { revert TaskDoesNotExist(); } CommonUtils.TaskDetails memory task = regState.tasks[_taskIndex].getTaskDetails(); @@ -252,10 +242,11 @@ contract AutomationRegistry is IAutomationRegistry, Ownable2StepUpgradeable, UUP 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 = IAutomationCore(automationCore).safeDepositRefund( + bool result = core.safeDepositRefund( _taskIndex, task.owner, task.lockedFeeForNextCycle / REFUND_FACTOR, @@ -270,8 +261,8 @@ contract AutomationRegistry is IAutomationRegistry, Ownable2StepUpgradeable, UUP // 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 > (startTime + durationSecs)) { - IAutomationCore(automationCore).updateGasCommittedForNextCycle(task.taskType, task.maxGasAmount); + if (task.expiryTime > controller.getCycleEndTime()) { + core.updateGasCommittedForNextCycle(task.taskType, task.maxGasAmount); } emit TaskCancelled( _taskIndex, task.owner, task.txHash); @@ -289,11 +280,10 @@ contract AutomationRegistry is IAutomationRegistry, Ownable2StepUpgradeable, UUP uint64 _taskIndex ) external { // Check if automation is enabled - if (!IAutomationController(automationController).isAutomationEnabled()) { revert AutomationNotEnabled(); } - - ( , uint64 startTime, uint64 durationSecs, CommonUtils.CycleState state) = IAutomationController(automationController).getCycleInfo(); + IAutomationController controller = IAutomationController(automationController); + if (!controller.isAutomationEnabled()) { revert AutomationNotEnabled(); } - if(state != CommonUtils.CycleState.STARTED) { revert CycleTransitionInProgress(); } + if(!controller.isCycleStarted()) { revert CycleTransitionInProgress(); } if(!ifTaskExists(_taskIndex)) { revert TaskDoesNotExist(); } if(!ifSysTaskExists(_taskIndex)) { revert SystemTaskDoesNotExist(); } @@ -313,7 +303,7 @@ contract AutomationRegistry is IAutomationRegistry, Ownable2StepUpgradeable, UUP // 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 > startTime + durationSecs) { + if(task.expiryTime > controller.getCycleEndTime()) { IAutomationCore(automationCore).updateGasCommittedForNextCycle(task.taskType, task.maxGasAmount); } @@ -330,12 +320,10 @@ contract AutomationRegistry is IAutomationRegistry, Ownable2StepUpgradeable, UUP uint64[] memory _taskIndexes ) external { // Check if automation is enabled - if (!IAutomationController(automationController).isAutomationEnabled()) { revert AutomationNotEnabled(); } + IAutomationController controller = IAutomationController(automationController); + if (!controller.isAutomationEnabled()) { revert AutomationNotEnabled(); } - address erc20Supra = IAutomationCore(automationCore).erc20Supra(); - - ( , uint64 startTime, uint64 durationSecs, CommonUtils.CycleState state) = IAutomationController(automationController).getCycleInfo(); - if(state != CommonUtils.CycleState.STARTED) { revert CycleTransitionInProgress(); } + if(!controller.isCycleStarted()) { revert CycleTransitionInProgress(); } if(_taskIndexes.length == 0) { revert TaskIndexesCannotBeEmpty(); } LibRegistry.TaskStopped[] memory stoppedTaskDetails = new LibRegistry.TaskStopped[](_taskIndexes.length); @@ -344,9 +332,11 @@ contract AutomationRegistry is IAutomationRegistry, Ownable2StepUpgradeable, UUP uint128 totalRefundFee = 0; // Calculate refundable fee for this remaining time task in current cycle - uint256 currentTime = block.timestamp; - uint128 cycleEndTime = startTime + durationSecs; - uint64 residualInterval = cycleEndTime <= currentTime ? 0 : uint64(cycleEndTime - currentTime); + 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++) { @@ -369,10 +359,10 @@ contract AutomationRegistry is IAutomationRegistry, Ownable2StepUpgradeable, UUP // 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 - IAutomationCore(automationCore).updateGasCommittedForNextCycle(task.taskType, task.maxGasAmount); + core.updateGasCommittedForNextCycle(task.taskType, task.maxGasAmount); } - (uint128 cycleFeeRefund, uint128 depositRefund) = IAutomationCore(automationCore).unlockDepositAndCycleFee( + (uint128 cycleFeeRefund, uint128 depositRefund) = core.unlockDepositAndCycleFee( _taskIndexes[i], task.state, task.expiryTime, @@ -397,11 +387,11 @@ contract AutomationRegistry is IAutomationRegistry, Ownable2StepUpgradeable, UUP } // Refund and emit event if any tasks were stopped - if(stoppedTaskDetails.length > 0) { - uint256 balance = IERC20(erc20Supra).balanceOf(automationCore); + if(stoppedTaskDetails.length > 0) { + uint256 balance = IERC20(core.erc20Supra()).balanceOf(automationCore); if(balance < totalRefundFee) { revert InsufficientBalanceForRefund(); } - IAutomationCore(automationCore).refund(msg.sender, totalRefundFee); + core.refund(msg.sender, totalRefundFee); // Emit task stopped event emit TasksStopped( @@ -421,10 +411,10 @@ contract AutomationRegistry is IAutomationRegistry, Ownable2StepUpgradeable, UUP uint64[] memory _taskIndexes ) external { // Check if automation is enabled - if (!IAutomationController(automationController).isAutomationEnabled()) { revert AutomationNotEnabled(); } + IAutomationController controller = IAutomationController(automationController); + if (!controller.isAutomationEnabled()) { revert AutomationNotEnabled(); } - ( , uint64 startTime, uint64 durationSecs, CommonUtils.CycleState state) = IAutomationController(automationController).getCycleInfo(); - if(state != CommonUtils.CycleState.STARTED) { revert CycleTransitionInProgress(); } + if(!controller.isCycleStarted()) { revert CycleTransitionInProgress(); } // Ensure that task indexes are provided if(_taskIndexes.length == 0) { revert TaskIndexesCannotBeEmpty(); } @@ -432,9 +422,6 @@ contract AutomationRegistry is IAutomationRegistry, Ownable2StepUpgradeable, UUP LibRegistry.TaskStopped[] memory stoppedTaskDetails = new LibRegistry.TaskStopped[](_taskIndexes.length); uint256 counter = 0; - // Calculate refundable fee for this remaining time task in current cycle - uint128 cycleEndTime = startTime + durationSecs; - // Loop through each task index to validate and stop the task for (uint256 i = 0; i < _taskIndexes.length; i++) { if(ifTaskExists(_taskIndexes[i])) { @@ -448,7 +435,7 @@ contract AutomationRegistry is IAutomationRegistry, Ownable2StepUpgradeable, UUP // Remove from active tasks require(regState.activeTaskIds.remove(_taskIndexes[i]), TaskIndexNotFound()); - if(task.state != CommonUtils.TaskState.CANCELLED && task.expiryTime > cycleEndTime) { + if(task.state != CommonUtils.TaskState.CANCELLED && task.expiryTime > controller.getCycleEndTime()) { IAutomationCore(automationCore).updateGasCommittedForNextCycle(task.taskType, task.maxGasAmount); } diff --git a/solidity/supra_contracts/src/IAutomationController.sol b/solidity/supra_contracts/src/IAutomationController.sol index 006cf85f37..465789d8bb 100644 --- a/solidity/supra_contracts/src/IAutomationController.sol +++ b/solidity/supra_contracts/src/IAutomationController.sol @@ -22,8 +22,11 @@ interface IAutomationController { // 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 diff --git a/solidity/supra_contracts/src/IAutomationCore.sol b/solidity/supra_contracts/src/IAutomationCore.sol index 403579e012..45af5e23ee 100644 --- a/solidity/supra_contracts/src/IAutomationCore.sol +++ b/solidity/supra_contracts/src/IAutomationCore.sol @@ -6,6 +6,7 @@ import {CommonUtils} from "./CommonUtils.sol"; interface IAutomationCore { // Custom errors error AddressCannotBeZero(); + error AutomationNotEnabled(); error CallerNotController(); error CallerNotRegistry(); error CycleTransitionInProgress(); @@ -70,8 +71,7 @@ interface IAutomationCore { uint128 _maxGasAmount, bytes32 _txHash, uint128 _gasPriceCap, - uint128 _automationFeeCapForCycle, - uint64 _cycleEndTime + uint128 _automationFeeCapForCycle ) external; // State updating functions diff --git a/solidity/supra_contracts/test/AutomationCore.t.sol b/solidity/supra_contracts/test/AutomationCore.t.sol index b0b72257c4..6f035bf552 100644 --- a/solidity/supra_contracts/test/AutomationCore.t.sol +++ b/solidity/supra_contracts/test/AutomationCore.t.sol @@ -870,7 +870,6 @@ contract AutomationCoreTest is Test { payload, 1000000, keccak256("txHash"), - 1000000, 0.001 ether, 0.01 ether ); From 4fdf9ce685d79647470d660a24199d8410c2c4af Mon Sep 17 00:00:00 2001 From: Aregnaz Harutyunyan <> Date: Sat, 7 Feb 2026 22:33:38 +0400 Subject: [PATCH 27/31] Small cosmentic changes after review --- solidity/supra_contracts/lib/openzeppelin-contracts | 2 +- .../supra_contracts/lib/openzeppelin-contracts-upgradeable | 2 +- solidity/supra_contracts/src/AutomationController.sol | 4 ++-- solidity/supra_contracts/src/AutomationCore.sol | 3 +++ solidity/supra_contracts/src/AutomationRegistry.sol | 5 +---- solidity/supra_contracts/src/IAutomationCore.sol | 1 + solidity/supra_contracts/src/IAutomationRegistry.sol | 3 +-- solidity/supra_contracts/test/AutomationRegistry.t.sol | 2 +- 8 files changed, 11 insertions(+), 11 deletions(-) diff --git a/solidity/supra_contracts/lib/openzeppelin-contracts b/solidity/supra_contracts/lib/openzeppelin-contracts index 353f564d1d..fcbae5394a 160000 --- a/solidity/supra_contracts/lib/openzeppelin-contracts +++ b/solidity/supra_contracts/lib/openzeppelin-contracts @@ -1 +1 @@ -Subproject commit 353f564d1db53c1d30cfa8a631771c205e41107b +Subproject commit fcbae5394ae8ad52d8e580a3477db99814b9d565 diff --git a/solidity/supra_contracts/lib/openzeppelin-contracts-upgradeable b/solidity/supra_contracts/lib/openzeppelin-contracts-upgradeable index c1f5d81e2f..aa677e9d28 160000 --- a/solidity/supra_contracts/lib/openzeppelin-contracts-upgradeable +++ b/solidity/supra_contracts/lib/openzeppelin-contracts-upgradeable @@ -1 +1 @@ -Subproject commit c1f5d81e2f53599bc9e4653bbc7c126032c96bd1 +Subproject commit aa677e9d28ed78fc427ec47ba2baef2030c58e7c diff --git a/solidity/supra_contracts/src/AutomationController.sol b/solidity/supra_contracts/src/AutomationController.sol index b1b0b1abee..b50c09cf2a 100644 --- a/solidity/supra_contracts/src/AutomationController.sol +++ b/solidity/supra_contracts/src/AutomationController.sol @@ -473,7 +473,7 @@ contract AutomationController is IAutomationController, Ownable2StepUpgradeable, require(updated, UpdateGasCommittedAndCycleLockedFeesFailed()); IAutomationRegistry automationRegistry = IAutomationRegistry(registry); - automationRegistry.updateTasks(CommonUtils.CycleState.FINISHED); + automationRegistry.updateTaskIds(CommonUtils.CycleState.FINISHED); // Set current timestamp as cycle start time // Increment the cycle and update the state to STARTED @@ -506,7 +506,7 @@ contract AutomationController is IAutomationController, Ownable2StepUpgradeable, (bool updated, )= automationCore.call(abi.encodeCall(IAutomationCore.updateGasCommittedAndCycleLockedFees, (0, 0, 0, 0))); require(updated, UpdateGasCommittedAndCycleLockedFeesFailed()); - IAutomationRegistry(registry).updateTasks(CommonUtils.CycleState.SUSPENDED); + IAutomationRegistry(registry).updateTaskIds(CommonUtils.CycleState.SUSPENDED); // Check if automation is enabled if (cycleInfo.automationEnabled()) { diff --git a/solidity/supra_contracts/src/AutomationCore.sol b/solidity/supra_contracts/src/AutomationCore.sol index ecf7644af8..68eb8d1b3b 100644 --- a/solidity/supra_contracts/src/AutomationCore.sol +++ b/solidity/supra_contracts/src/AutomationCore.sol @@ -702,6 +702,9 @@ contract AutomationCore is IAutomationCore, Ownable2StepUpgradeable, UUPSUpgrade /// @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); } diff --git a/solidity/supra_contracts/src/AutomationRegistry.sol b/solidity/supra_contracts/src/AutomationRegistry.sol index d978df81bc..8c3fbeb170 100644 --- a/solidity/supra_contracts/src/AutomationRegistry.sol +++ b/solidity/supra_contracts/src/AutomationRegistry.sol @@ -388,9 +388,6 @@ contract AutomationRegistry is IAutomationRegistry, Ownable2StepUpgradeable, UUP // Refund and emit event if any tasks were stopped if(stoppedTaskDetails.length > 0) { - uint256 balance = IERC20(core.erc20Supra()).balanceOf(automationCore); - - if(balance < totalRefundFee) { revert InsufficientBalanceForRefund(); } core.refund(msg.sender, totalRefundFee); // Emit task stopped event @@ -535,7 +532,7 @@ contract AutomationRegistry is IAutomationRegistry, Ownable2StepUpgradeable, UUP /// @notice Function to update tasks lists. /// @param _state Cycle transition state executing the update. - function updateTasks(CommonUtils.CycleState _state) external { + function updateTaskIds(CommonUtils.CycleState _state) external { onlyController(); regState.activeTaskIds.clear(); diff --git a/solidity/supra_contracts/src/IAutomationCore.sol b/solidity/supra_contracts/src/IAutomationCore.sol index 45af5e23ee..79b4580ea7 100644 --- a/solidity/supra_contracts/src/IAutomationCore.sol +++ b/solidity/supra_contracts/src/IAutomationCore.sol @@ -22,6 +22,7 @@ interface IAutomationCore { error GasCommittedValueUnderflow(); error InsufficientBalance(); error InsufficientFeeCapForCycle(); + error InsufficientBalanceForRefund(); error InvalidCongestionExponent(); error InvalidCongestionThreshold(); error InvalidCycleDuration(); diff --git a/solidity/supra_contracts/src/IAutomationRegistry.sol b/solidity/supra_contracts/src/IAutomationRegistry.sol index 9da61695f5..0d71a9f9bc 100644 --- a/solidity/supra_contracts/src/IAutomationRegistry.sol +++ b/solidity/supra_contracts/src/IAutomationRegistry.sol @@ -17,7 +17,6 @@ interface IAutomationRegistry { error ErrorDepositRefund(); error SystemTaskDoesNotExist(); error TaskIndexesCannotBeEmpty(); - error InsufficientBalanceForRefund(); error RegisteredTaskInvalidType(); error TaskIndexNotFound(); error TaskIndexNotUnique(); @@ -34,7 +33,7 @@ interface IAutomationRegistry { // State updating functions function removeTask(uint64 _taskIndex, bool _removeFromSysReg) external; function updateTaskState(uint64 _taskIndex, CommonUtils.TaskState _taskState) external; - function updateTasks(CommonUtils.CycleState _state) external; + function updateTaskIds(CommonUtils.CycleState _state) external; function refundDepositAndDrop( uint64 _taskIndex, address _taskOwner, diff --git a/solidity/supra_contracts/test/AutomationRegistry.t.sol b/solidity/supra_contracts/test/AutomationRegistry.t.sol index 057f9db8a1..9282b1698f 100644 --- a/solidity/supra_contracts/test/AutomationRegistry.t.sol +++ b/solidity/supra_contracts/test/AutomationRegistry.t.sol @@ -1237,7 +1237,7 @@ contract AutomationRegistryTest is Test { vm.expectRevert(IAutomationRegistry.CallerNotController.selector); vm.prank(address(automationCore)); - registry.updateTasks(CommonUtils.CycleState.STARTED); + registry.updateTaskIds(CommonUtils.CycleState.STARTED); } /// @dev Test to ensure 'refundDepositAndDrop' reverts if caller is not AutomationController. From 26ca1a5f7eb49b25745798a9da7f4fef0a443f0c Mon Sep 17 00:00:00 2001 From: Udit Yadav Date: Tue, 10 Feb 2026 14:24:51 +0530 Subject: [PATCH 28/31] -updated register function -removed access control for external view functions -updated lockedFeeForNextCycle to depositFee --- .../lib/openzeppelin-contracts | 2 +- .../src/AutomationController.sol | 12 +- .../supra_contracts/src/AutomationCore.sol | 26 ++--- .../src/AutomationRegistry.sol | 36 +++--- solidity/supra_contracts/src/CommonUtils.sol | 8 +- .../supra_contracts/src/IAutomationCore.sol | 4 +- .../src/IAutomationRegistry.sol | 2 + solidity/supra_contracts/src/LibRegistry.sol | 24 ++-- .../test/AutomationController.t.sol | 2 - .../supra_contracts/test/AutomationCore.t.sol | 35 ------ .../test/AutomationRegistry.t.sol | 107 +----------------- 11 files changed, 56 insertions(+), 202 deletions(-) diff --git a/solidity/supra_contracts/lib/openzeppelin-contracts b/solidity/supra_contracts/lib/openzeppelin-contracts index fcbae5394a..8614ef7a24 160000 --- a/solidity/supra_contracts/lib/openzeppelin-contracts +++ b/solidity/supra_contracts/lib/openzeppelin-contracts @@ -1 +1 @@ -Subproject commit fcbae5394ae8ad52d8e580a3477db99814b9d565 +Subproject commit 8614ef7a24d476e37db66054e5237faaf7f43717 diff --git a/solidity/supra_contracts/src/AutomationController.sol b/solidity/supra_contracts/src/AutomationController.sol index b50c09cf2a..5f035786ff 100644 --- a/solidity/supra_contracts/src/AutomationController.sol +++ b/solidity/supra_contracts/src/AutomationController.sol @@ -282,7 +282,7 @@ contract AutomationController is IAutomationController, Ownable2StepUpgradeable, (bool sent, ) = registryAddr.call( abi.encodeCall( IAutomationRegistry.refundDepositAndDrop, - (_taskIndex, task.owner, task.lockedFeeForNextCycle, task.lockedFeeForNextCycle) + (_taskIndex, task.owner, task.depositFee, task.depositFee) ) ); require(sent, RefundDepositAndDropFailed()); @@ -323,7 +323,7 @@ contract AutomationController is IAutomationController, Ownable2StepUpgradeable, task.owner, task.maxGasAmount, task.expiryTime, - task.lockedFeeForNextCycle, + task.depositFee, fee, _currentCycleEndTime, task.automationFeeCapForCycle, @@ -350,7 +350,7 @@ contract AutomationController is IAutomationController, Ownable2StepUpgradeable, /// @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 _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. @@ -363,7 +363,7 @@ contract AutomationController is IAutomationController, Ownable2StepUpgradeable, address _owner, uint128 _maxGasAmount, uint64 _expiryTime, - uint128 _lockedFeeForNextCycle, + uint128 _depositFee, uint128 _fee, uint64 _currentCycleEndTime, uint128 _automationFeeCapForCycle, @@ -383,7 +383,7 @@ contract AutomationController is IAutomationController, Ownable2StepUpgradeable, (bool sent, ) = registryAddr.call( abi.encodeCall( IAutomationRegistry.refundDepositAndDrop, - (_taskIndex, _owner, _lockedFeeForNextCycle, _lockedFeeForNextCycle) + (_taskIndex, _owner, _depositFee, _depositFee) ) ); require(sent, RefundDepositAndDropFailed()); @@ -405,7 +405,7 @@ contract AutomationController is IAutomationController, Ownable2StepUpgradeable, (bool unlocked, ) = automationCoreAddr.call( abi.encodeCall( IAutomationCore.safeUnlockLockedDeposit, - (_taskIndex, _lockedFeeForNextCycle) + (_taskIndex, _depositFee) ) ); require(unlocked, UnlockLockedDepositFailed()); diff --git a/solidity/supra_contracts/src/AutomationCore.sol b/solidity/supra_contracts/src/AutomationCore.sol index 68eb8d1b3b..313e34d109 100644 --- a/solidity/supra_contracts/src/AutomationCore.sol +++ b/solidity/supra_contracts/src/AutomationCore.sol @@ -213,12 +213,11 @@ contract AutomationCore is IAutomationCore, Ownable2StepUpgradeable, UUPSUpgrade } /// @notice Helper function to validate the inputs while registering a task. - function validateInputs(bytes memory _payloadTx, uint128 _maxGasAmount, bytes32 _txHash) private view { + 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(); } - if(_txHash == bytes32(0)) { revert InvalidTxHash(); } } /// @notice Function to ensure that AutomationController contract is the caller. @@ -506,7 +505,7 @@ contract AutomationCore is IAutomationCore, Ownable2StepUpgradeable, UUPSUpgrade return (true, pendingCycleDuration); } - /// @notice Internally calls _calculateTaskFee, reverts if caller is not AutomationController. + /// @notice Internally calls _calculateTaskFee. function calculateTaskFee( CommonUtils.TaskState _state, uint64 _expiryTime, @@ -515,8 +514,6 @@ contract AutomationCore is IAutomationCore, Ownable2StepUpgradeable, UUPSUpgrade uint64 _currentTime, uint128 _automationFeePerSec ) external view returns (uint128) { - onlyController(); - return _calculateTaskFee( _state, _expiryTime, @@ -568,13 +565,12 @@ contract AutomationCore is IAutomationCore, Ownable2StepUpgradeable, UUPSUpgrade _safeDepositRefund( _task.taskIndex, _task.owner, - _task.lockedFeeForNextCycle, - _task.lockedFeeForNextCycle + _task.depositFee, + _task.depositFee ); } function calculateAutomationFeeMultiplierForCurrentCycleInternal() external view returns (uint128) { - onlyController(); // Compute the automation fee multiplier for this cycle return calculateAutomationFeeMultiplierForCycle( regConfig.gasCommittedForThisCycle(), @@ -588,7 +584,6 @@ contract AutomationCore is IAutomationCore, Ownable2StepUpgradeable, UUPSUpgrade function calculateAutomationFeeMultiplierForCommittedOccupancy( uint128 _totalCommittedMaxGas ) external view returns (uint128) { - onlyController(); // Compute the automation fee multiplier for cycle return calculateAutomationFeeMultiplierForCycle( _totalCommittedMaxGas, @@ -621,13 +616,11 @@ contract AutomationCore is IAutomationCore, Ownable2StepUpgradeable, UUPSUpgrade /// @notice Helper function that performs validation and updates state for a valid task. function updateStateForValidRegistration( uint256 _totalTasks, - uint8 _inputType, uint64 _regTime, uint64 _expiryTime, CommonUtils.TaskType _taskType, bytes memory _payloadTx, uint128 _maxGasAmount, - bytes32 _txHash, uint128 _gasPriceCap, uint128 _automationFeeCapForCycle ) external { @@ -639,7 +632,6 @@ contract AutomationCore is IAutomationCore, Ownable2StepUpgradeable, UUPSUpgrade if (!regConfig.registrationEnabled()) { revert RegistrationDisabled(); } if (!automationController.isCycleStarted()) { revert CycleTransitionInProgress(); } - if(_inputType != uint8(_taskType)) { revert InvalidTaskType(); } bool isUST = _taskType == CommonUtils.TaskType.UST; @@ -665,7 +657,7 @@ contract AutomationCore is IAutomationCore, Ownable2StepUpgradeable, UUPSUpgrade } validateTaskDuration(_regTime, _expiryTime, taskDurationCap, automationController.getCycleEndTime()); - validateInputs(_payloadTx, _maxGasAmount, _txHash); + validateInputs(_payloadTx, _maxGasAmount); uint128 gasCommitted = _maxGasAmount + gasCommittedForNextCycle; if(gasCommitted > nextCycleRegistryMaxGasCap) { revert GasCommittedExceedsMaxGasCap(); } @@ -727,7 +719,7 @@ contract AutomationCore is IAutomationCore, Ownable2StepUpgradeable, UUPSUpgrade uint128 _maxGasAmount, uint64 _residualInterval, uint64 _currentTime, - uint128 _lockedFeeForNextCycle + uint128 _depositFee ) external returns (uint128, uint128) { onlyRegistry(); @@ -749,13 +741,13 @@ contract AutomationCore is IAutomationCore, Ownable2StepUpgradeable, UUPSUpgrade // Refund full deposit and the half of the remaining run-time fee when task is active or cancelled stage cycleFeeRefund = taskFee / REFUND_FRACTION; - depositRefund = _lockedFeeForNextCycle; + depositRefund = _depositFee; } else { cycleFeeRefund = 0; - depositRefund = _lockedFeeForNextCycle / REFUND_FRACTION; + depositRefund = _depositFee / REFUND_FRACTION; } - bool result = _safeUnlockLockedDeposit(_taskIndex, _lockedFeeForNextCycle); + bool result = _safeUnlockLockedDeposit(_taskIndex, _depositFee); if(!result) { revert ErrorDepositRefund(); } (bool hasLockedFee, uint256 remainingCycleLockedFees ) = safeUnlockLockedCycleFee(regConfig.cycleLockedFees, uint64(cycleFeeRefund), _taskIndex); diff --git a/solidity/supra_contracts/src/AutomationRegistry.sol b/solidity/supra_contracts/src/AutomationRegistry.sol index 8c3fbeb170..374c9c85cb 100644 --- a/solidity/supra_contracts/src/AutomationRegistry.sol +++ b/solidity/supra_contracts/src/AutomationRegistry.sol @@ -21,6 +21,10 @@ contract AutomationRegistry is IAutomationRegistry, Ownable2StepUpgradeable, UUP /// 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. + // TO_DO: Update the precompile address once it's finalized and available. + address public constant TX_HASH_PRECOMPILE = 0x0000000000000000000000000000000053555001; + /// @dev State variables LibRegistry.RegistryState regState; address public automationCore; @@ -93,22 +97,18 @@ contract AutomationRegistry is IAutomationRegistry, Ownable2StepUpgradeable, UUP /// @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 _txHash Transaction hash of the request transaction. /// @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 _type Type of task. /// @param _auxData Auxiliary data to be passed. function register( bytes memory _payloadTx, uint64 _expiryTime, - bytes32 _txHash, uint128 _maxGasAmount, uint128 _gasPriceCap, uint128 _automationFeeCapForCycle, uint64 _priority, - uint8 _type, bytes[] memory _auxData ) external { uint64 regTime = uint64(block.timestamp); @@ -116,13 +116,11 @@ contract AutomationRegistry is IAutomationRegistry, Ownable2StepUpgradeable, UUP IAutomationCore core = IAutomationCore(automationCore); core.updateStateForValidRegistration( totalTasks(), - _type, regTime, _expiryTime, CommonUtils.TaskType.UST, _payloadTx, _maxGasAmount, - _txHash, _gasPriceCap, _automationFeeCapForCycle ); @@ -134,7 +132,7 @@ contract AutomationRegistry is IAutomationRegistry, Ownable2StepUpgradeable, UUP _gasPriceCap, _automationFeeCapForCycle, _automationFeeCapForCycle , - _txHash, + readTxHash(), taskIndex, regTime, _expiryTime, @@ -161,18 +159,14 @@ contract AutomationRegistry is IAutomationRegistry, Ownable2StepUpgradeable, UUP /// @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 _txHash Transaction hash of the request transaction. /// @param _maxGasAmount Maximum amount of gas for the automation task. /// @param _priority Priority for the task. 0 for default priority. - /// @param _type Type of task. /// @param _auxData Auxiliary data to be passed. function registerSystemTask( bytes memory _payloadTx, uint64 _expiryTime, - bytes32 _txHash, uint128 _maxGasAmount, uint64 _priority, - uint8 _type, bytes[] memory _auxData ) external { if(!isAuthorizedSubmitter(msg.sender)) { revert UnauthorizedAccount(); } @@ -180,13 +174,11 @@ contract AutomationRegistry is IAutomationRegistry, Ownable2StepUpgradeable, UUP uint64 regTime = uint64(block.timestamp); IAutomationCore(automationCore).updateStateForValidRegistration( totalSystemTasks(), - _type, regTime, _expiryTime, CommonUtils.TaskType.GST, _payloadTx, _maxGasAmount, - _txHash, 0, 0 ); @@ -198,7 +190,7 @@ contract AutomationRegistry is IAutomationRegistry, Ownable2StepUpgradeable, UUP 0, 0, 0, - _txHash, + readTxHash(), taskIndex, regTime, _expiryTime, @@ -249,8 +241,8 @@ contract AutomationRegistry is IAutomationRegistry, Ownable2StepUpgradeable, UUP bool result = core.safeDepositRefund( _taskIndex, task.owner, - task.lockedFeeForNextCycle / REFUND_FACTOR, - task.lockedFeeForNextCycle + task.depositFee / REFUND_FACTOR, + task.depositFee ); if(!result) { revert ErrorDepositRefund(); } } else { @@ -369,7 +361,7 @@ contract AutomationRegistry is IAutomationRegistry, Ownable2StepUpgradeable, UUP task.maxGasAmount, residualInterval, uint64(currentTime), - task.lockedFeeForNextCycle + task.depositFee ); totalRefundFee += (cycleFeeRefund + depositRefund); @@ -459,6 +451,16 @@ contract AutomationRegistry is IAutomationRegistry, Ownable2StepUpgradeable, UUP // :::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::: HELPER FUNCTIONS :::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::: + /// @notice Read tx hash via precompile. Reverts if precompile missing/fails. + function readTxHash() private pure returns (bytes32) { + // (bool ok, bytes memory out) = TX_HASH_PRECOMPILE.staticcall(""); + // require(ok, FailedToCallTxHashPrecompile()); + // require(out.length == 32, InvalidTxHashLength()); + // return abi.decode(out, (bytes32)); + // TO_DO + return keccak256("txHash"); // --- IGNORE --- + } + /// @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. diff --git a/solidity/supra_contracts/src/CommonUtils.sol b/solidity/supra_contracts/src/CommonUtils.sol index fb175bfdd5..14215de15a 100644 --- a/solidity/supra_contracts/src/CommonUtils.sol +++ b/solidity/supra_contracts/src/CommonUtils.sol @@ -39,7 +39,7 @@ library CommonUtils { uint128 maxGasAmount; uint128 gasPriceCap; uint128 automationFeeCapForCycle; - uint128 lockedFeeForNextCycle; + uint128 depositFee; bytes32 txHash; uint64 taskIndex; uint64 registrationTime; @@ -60,10 +60,10 @@ library CommonUtils { details.gasPriceCap = uint128(t.maxGasAmount_gasPriceCap); // --- Decode automationFeeCapForCycle (upper 128 bits) --- - details.automationFeeCapForCycle = uint128(t.automationFeeCapForCycle_lockedFeeForNextCycle >> 128); + details.automationFeeCapForCycle = uint128(t.automationFeeCapForCycle_depositFee >> 128); - // --- Decode lockedFeeForNextCycle (lower 128 bits) --- - details.lockedFeeForNextCycle = uint128(t.automationFeeCapForCycle_lockedFeeForNextCycle); + // --- Decode depositFee (lower 128 bits) --- + details.depositFee = uint128(t.automationFeeCapForCycle_depositFee); // --- Direct values --- details.txHash = t.txHash; diff --git a/solidity/supra_contracts/src/IAutomationCore.sol b/solidity/supra_contracts/src/IAutomationCore.sol index 79b4580ea7..1fc8a80b6f 100644 --- a/solidity/supra_contracts/src/IAutomationCore.sol +++ b/solidity/supra_contracts/src/IAutomationCore.sol @@ -64,13 +64,11 @@ interface IAutomationCore { function getTotalDepositedAutomationFees() external view returns (uint256); function updateStateForValidRegistration( uint256 _totalTasks, - uint8 _inputType, uint64 _regTime, uint64 _expiryTime, CommonUtils.TaskType _taskType, bytes memory _payloadTx, uint128 _maxGasAmount, - bytes32 _txHash, uint128 _gasPriceCap, uint128 _automationFeeCapForCycle ) external; @@ -103,7 +101,7 @@ interface IAutomationCore { uint128 _maxGasAmount, uint64 _residualInterval, uint64 _currentTime, - uint128 _lockedFeeForNextCycle + uint128 _depositFee ) external returns (uint128, uint128); function updateGasCommittedForNextCycle(CommonUtils.TaskType _taskType, uint128 _maxGasAmount) external; function updateGasCommittedAndCycleLockedFees( diff --git a/solidity/supra_contracts/src/IAutomationRegistry.sol b/solidity/supra_contracts/src/IAutomationRegistry.sol index 0d71a9f9bc..701ea074cb 100644 --- a/solidity/supra_contracts/src/IAutomationRegistry.sol +++ b/solidity/supra_contracts/src/IAutomationRegistry.sol @@ -20,6 +20,8 @@ interface IAutomationRegistry { error RegisteredTaskInvalidType(); error TaskIndexNotFound(); error TaskIndexNotUnique(); + error FailedToCallTxHashPrecompile(); + error InvalidTxHashLength(); // View functions function ifTaskExists(uint64 _taskIndex) external view returns (bool); diff --git a/solidity/supra_contracts/src/LibRegistry.sol b/solidity/supra_contracts/src/LibRegistry.sol index 357ff64ca6..c0efccdb28 100644 --- a/solidity/supra_contracts/src/LibRegistry.sol +++ b/solidity/supra_contracts/src/LibRegistry.sol @@ -21,7 +21,7 @@ library LibRegistry { uint256 maxGasAmount_gasPriceCap; // uint128 | uint128 - uint256 automationFeeCapForCycle_lockedFeeForNextCycle; + uint256 automationFeeCapForCycle_depositFee; bytes32 txHash; @@ -39,7 +39,7 @@ library LibRegistry { uint128 _maxGasAmount, uint128 _gasPriceCap, uint128 _automationFeeCapForCycle, - uint128 _lockedFeeForNextCycle, + uint128 _depositFee, bytes32 _txHash, uint64 _taskIndex, uint64 _registrationTime, @@ -55,7 +55,7 @@ library LibRegistry { t.maxGasAmount_gasPriceCap = (uint256(_maxGasAmount) << 128) | uint256(_gasPriceCap); // Pack (uint128 | uint128) - t.automationFeeCapForCycle_lockedFeeForNextCycle = (uint256(_automationFeeCapForCycle) << 128) | uint256(_lockedFeeForNextCycle); + t.automationFeeCapForCycle_depositFee = (uint256(_automationFeeCapForCycle) << 128) | uint256(_depositFee); // Direct fields t.txHash = _txHash; @@ -97,23 +97,23 @@ library LibRegistry { t.maxGasAmount_gasPriceCap |= uint256(_value); // insert lower 128 } - // automationFeeCapForCycle (uint128) | lockedFeeForNextCycle (uint128) + // automationFeeCapForCycle (uint128) | depositFee (uint128) function automationFeeCapForCycle(TaskMetadata storage t) internal view returns (uint128) { - return uint128(t.automationFeeCapForCycle_lockedFeeForNextCycle >> 128); + return uint128(t.automationFeeCapForCycle_depositFee >> 128); } - function lockedFeeForNextCycle(TaskMetadata storage t) internal view returns (uint128) { - return uint128(t.automationFeeCapForCycle_lockedFeeForNextCycle); + function depositFee(TaskMetadata storage t) internal view returns (uint128) { + return uint128(t.automationFeeCapForCycle_depositFee); } function setAutomationFeeCapForCycle(TaskMetadata storage t, uint128 _value) internal { - t.automationFeeCapForCycle_lockedFeeForNextCycle &= MAX_UINT128; - t.automationFeeCapForCycle_lockedFeeForNextCycle |= uint256(_value) << 128; + t.automationFeeCapForCycle_depositFee &= MAX_UINT128; + t.automationFeeCapForCycle_depositFee |= uint256(_value) << 128; } - function setLockedFeeForNextCycle(TaskMetadata storage t, uint128 _value) internal { - t.automationFeeCapForCycle_lockedFeeForNextCycle &= (MAX_UINT128 << 128); - t.automationFeeCapForCycle_lockedFeeForNextCycle |= uint256(_value); + 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) diff --git a/solidity/supra_contracts/test/AutomationController.t.sol b/solidity/supra_contracts/test/AutomationController.t.sol index 330c2e3c52..30a7fd8229 100644 --- a/solidity/supra_contracts/test/AutomationController.t.sol +++ b/solidity/supra_contracts/test/AutomationController.t.sol @@ -637,12 +637,10 @@ contract AutomationControllerTest is Test { registry.register( payload, uint64(block.timestamp + 2250), - keccak256("txHash"), uint128(1_000_000), uint128(10 gwei), uint128(0.5 ether), 2, - 0, auxData ); vm.stopPrank(); diff --git a/solidity/supra_contracts/test/AutomationCore.t.sol b/solidity/supra_contracts/test/AutomationCore.t.sol index 6f035bf552..53b5c368bd 100644 --- a/solidity/supra_contracts/test/AutomationCore.t.sol +++ b/solidity/supra_contracts/test/AutomationCore.t.sol @@ -799,21 +799,6 @@ contract AutomationCoreTest is Test { automationCore.applyPendingConfig(); } - /// @dev Test to ensure 'calculateTaskFee' reverts if caller is not AutomationController. - function testCalculateTaskFeeRevertsIfCallerNotAutomationController() public { - vm.expectRevert(IAutomationCore.CallerNotController.selector); - - vm.prank(address(registry)); - automationCore.calculateTaskFee( - CommonUtils.TaskState.ACTIVE , - uint64(block.timestamp) + 10000000, - 1000000, - 10000000, - uint64(block.timestamp), - 0.0001 ether - ); - } - /// @dev Test to ensure 'safeUnlockLockedDeposit' reverts if caller is not AutomationController. function testSafeUnlockLockedDepositRevertsIfCallerNotAutomationController() public { vm.expectRevert(IAutomationCore.CallerNotController.selector); @@ -822,22 +807,6 @@ contract AutomationCoreTest is Test { automationCore.safeUnlockLockedDeposit(0, 0.01 ether); } - /// @dev Test to ensure 'calculateAutomationFeeMultiplierForCurrentCycleInternal' reverts if caller is not AutomationController. - function test_calculateAutomationFeeMultiplierForCurrentCycleInternal_RevertsIfCallerNotAutomationController() public { - vm.expectRevert(IAutomationCore.CallerNotController.selector); - - vm.prank(address(registry)); - automationCore.calculateAutomationFeeMultiplierForCurrentCycleInternal(); - } - - /// @dev Test to ensure 'calculateAutomationFeeMultiplierForCommittedOccupancy' reverts if caller is not AutomationController. - function test_calculateAutomationFeeMultiplierForCommittedOccupancy_RevertsIfCallerNotAutomationController() public { - vm.expectRevert(IAutomationCore.CallerNotController.selector); - - vm.prank(address(registry)); - automationCore.calculateAutomationFeeMultiplierForCommittedOccupancy(1000000); - } - /// @dev Test to ensure 'refundTaskFees' reverts if caller is not AutomationController. function testRefundTaskFeesRevertsIfCallerNotAutomationController() public { registerUST(); @@ -863,13 +832,11 @@ contract AutomationCoreTest is Test { vm.prank(address(automationController)); automationCore.updateStateForValidRegistration( 10, - 0, uint64(block.timestamp), uint64(block.timestamp) + 2250, CommonUtils.TaskType.UST, payload, 1000000, - keccak256("txHash"), 0.001 ether, 0.01 ether ); @@ -958,12 +925,10 @@ contract AutomationCoreTest is Test { registry.register( payload, uint64(block.timestamp + 2250), - keccak256("txHash"), uint128(1_000_000), uint128(10 gwei), uint128(0.5 ether), 4, - 0, auxData ); vm.stopPrank(); diff --git a/solidity/supra_contracts/test/AutomationRegistry.t.sol b/solidity/supra_contracts/test/AutomationRegistry.t.sol index 9282b1698f..0f83b96bdf 100644 --- a/solidity/supra_contracts/test/AutomationRegistry.t.sol +++ b/solidity/supra_contracts/test/AutomationRegistry.t.sol @@ -293,12 +293,10 @@ contract AutomationRegistryTest is Test { registry.register( payload, // payload uint64(block.timestamp + 2250), // expiryTime - keccak256("txHash"), // txHash uint128(1_000_000), // maxGasAmount uint128(10 gwei), // gasPriceCap uint128(0.5 ether), // automationFeeCapForCycle 0, // priority - 0, // task type auxData // aux data ); } @@ -318,37 +316,14 @@ contract AutomationRegistryTest is Test { registry.register( payload, // payload uint64(block.timestamp + 2250), // expiryTime - keccak256("txHash"), // txHash uint128(1_000_000), // maxGasAmount uint128(10 gwei), // gasPriceCap uint128(0.5 ether), // automationFeeCapForCycle 0, // priority - 0, // task type auxData // aux data ); } - /// @dev Test to ensure 'register' reverts if task type is not UST. - function testRegisterRevertsIfTaskTypeNotUST() public { - bytes[] memory auxData; - bytes memory payload = createPayload(0, address(erc20Supra)); - - vm.expectRevert(IAutomationCore.InvalidTaskType.selector); - - vm.prank(alice); - registry.register( - payload, - uint64(block.timestamp + 2250), - keccak256("txHash"), - uint128(1_000_000), - uint128(10 gwei), - uint128(0.5 ether), - 0, - 1, // Task type not UST - auxData - ); - } - /// @dev Test to ensure 'register' reverts if expiry time is equal to or less than registration time. function testRegisterRevertsIfInvalidExpiryTime() public { bytes[] memory auxData; @@ -360,12 +335,10 @@ contract AutomationRegistryTest is Test { registry.register( payload, uint64(block.timestamp), // Invalid expiryTime - keccak256("txHash"), uint128(1_000_000), uint128(10 gwei), uint128(0.5 ether), 0, - 0, auxData ); } @@ -381,12 +354,10 @@ contract AutomationRegistryTest is Test { registry.register( payload, uint64(block.timestamp + 3601), // Invalid task duration - keccak256("txHash"), uint128(1_000_000), uint128(10 gwei), uint128(0.5 ether), 0, - 0, auxData ); } @@ -402,12 +373,10 @@ contract AutomationRegistryTest is Test { registry.register( payload, uint64(block.timestamp + 2000), // Task expires before next cycle - keccak256("txHash"), uint128(1_000_000), uint128(10 gwei), uint128(0.5 ether), 0, - 0, auxData ); } @@ -423,12 +392,10 @@ contract AutomationRegistryTest is Test { registry.register( payload, uint64(block.timestamp + 2250), - keccak256("txHash"), uint128(1_000_000), uint128(10 gwei), uint128(0.5 ether), 0, - 0, auxData ); } @@ -444,12 +411,10 @@ contract AutomationRegistryTest is Test { registry.register( payload, uint64(block.timestamp + 2250), - keccak256("txHash"), uint128(1_000_000), uint128(10 gwei), uint128(0.5 ether), 0, - 0, auxData ); } @@ -465,12 +430,10 @@ contract AutomationRegistryTest is Test { registry.register( payload, uint64(block.timestamp + 2250), - keccak256("txHash"), uint128(0), // maxGasAmount uint128(10 gwei), uint128(0.5 ether), 0, - 0, auxData ); } @@ -486,33 +449,10 @@ contract AutomationRegistryTest is Test { registry.register( payload, uint64(block.timestamp + 2250), - keccak256("txHash"), uint128(1_000_000), uint128(0), // gasPriceCap uint128(0.5 ether), 0, - 0, - auxData - ); - } - - /// @dev Test to ensure 'register' reverts if transaction hash is bytes32(0). - function testRegisterRevertsIfInvalidTxHash() public { - bytes[] memory auxData; - bytes memory payload = createPayload(0, address(erc20Supra)); - - vm.expectRevert(IAutomationCore.InvalidTxHash.selector); - - vm.prank(alice); - registry.register( - payload, - uint64(block.timestamp + 2250), - bytes32(0), // Invalid tx hash - uint128(1_000_000), - uint128(10 gwei), - uint128(0.5 ether), - 0, - 0, auxData ); } @@ -528,12 +468,10 @@ contract AutomationRegistryTest is Test { registry.register( payload, uint64(block.timestamp + 2250), - keccak256("txHash"), uint128(1_000_000), uint128(10 gwei), uint128(0), // automationFeeCapForCycle 0, - 0, auxData ); } @@ -549,12 +487,10 @@ contract AutomationRegistryTest is Test { registry.register( payload, uint64(block.timestamp + 2250), - keccak256("txHash"), uint128(10_000_001), // Gas exceeds max gas cap uint128(10 gwei), uint128(7.01 ether), 0, - 0, auxData ); } @@ -571,12 +507,10 @@ contract AutomationRegistryTest is Test { registry.register( payload, uint64(block.timestamp + 2250), - keccak256("txHash"), uint128(1_000_000), uint128(10 gwei), uint128(0.5 ether), 4, - 0, auxData ); vm.stopPrank(); @@ -593,7 +527,7 @@ contract AutomationRegistryTest is Test { assertEq(taskMetadata.maxGasAmount, 1_000_000); assertEq(taskMetadata.gasPriceCap, 10 gwei); assertEq(taskMetadata.automationFeeCapForCycle, 0.5 ether); - assertEq(taskMetadata.lockedFeeForNextCycle, 0.5 ether); + assertEq(taskMetadata.depositFee, 0.5 ether); assertEq(taskMetadata.txHash, keccak256("txHash")); assertEq(taskMetadata.taskIndex, 0); assertEq(taskMetadata.registrationTime, uint64(block.timestamp)); @@ -638,12 +572,10 @@ contract AutomationRegistryTest is Test { registry.register( payload, uint64(block.timestamp + 2250), - keccak256("txHash"), uint128(1_000_000), uint128(10 gwei), uint128(0.5 ether), 0, - 0, auxData ); vm.stopPrank(); @@ -662,10 +594,8 @@ contract AutomationRegistryTest is Test { registry.registerSystemTask( payload, // payload uint64(block.timestamp + 2250), // expiryTime - keccak256("txHash"), // txHash uint128(1_000_000), // maxGasAmount 2, // priority - 1, // task type auxData // aux data ); } @@ -686,10 +616,8 @@ contract AutomationRegistryTest is Test { registry.registerSystemTask( payload, // payload uint64(block.timestamp + 2250), // expiryTime - keccak256("txHash"), // txHash uint128(1_000_000), // maxGasAmount 2, // priority - 1, // task type auxData // aux data ); } @@ -710,35 +638,12 @@ contract AutomationRegistryTest is Test { registry.registerSystemTask( payload, // payload uint64(block.timestamp + 2250), // expiryTime - keccak256("txHash"), // txHash uint128(1_000_000), // maxGasAmount 2, // priority - 1, // task type auxData // aux data ); } - /// @dev Test to ensure 'registerSystemTask' reverts if task type is not GST. - function testRegisterSystemTaskRevertsIfTaskTypeNotGST() public { - testGrantAuthorization(); - - bytes[] memory auxData; - bytes memory payload = createPayload(0, address(erc20Supra)); - - vm.expectRevert(IAutomationCore.InvalidTaskType.selector); - - vm.prank(bob); - registry.registerSystemTask( - payload, - uint64(block.timestamp + 2250), - keccak256("txHash"), - uint128(1_000_000), - 2, - 0, // Task type not GST - auxData - ); - } - /// @dev Test to ensure 'registerSystemTask' reverts if task duration is greater than system task duration cap. function testRegisterSystemTaskRevertsIfInvalidTaskDuration() public { testGrantAuthorization(); @@ -751,10 +656,8 @@ contract AutomationRegistryTest is Test { registry.registerSystemTask( payload, uint64(block.timestamp + 3601), // Invalid task duration - keccak256("txHash"), uint128(1_000_000), 2, - 1, auxData ); } @@ -771,10 +674,8 @@ contract AutomationRegistryTest is Test { registry.registerSystemTask( payload, uint64(block.timestamp + 2250), - keccak256("txHash"), uint128(5_000_001), // Gas exceeds max gas cap 2, - 1, auxData ); } @@ -789,10 +690,8 @@ contract AutomationRegistryTest is Test { registry.registerSystemTask( payload, // payload uint64(block.timestamp + 2250), // expiryTime - keccak256("txHash"), // txHash uint128(1_000_000), // maxGasAmount 2, // priority - 1, // task type auxData // aux data ); @@ -807,7 +706,7 @@ contract AutomationRegistryTest is Test { assertEq(taskMetadata.maxGasAmount, 1_000_000); assertEq(taskMetadata.gasPriceCap, 0); assertEq(taskMetadata.automationFeeCapForCycle, 0); - assertEq(taskMetadata.lockedFeeForNextCycle, 0); + assertEq(taskMetadata.depositFee, 0); assertEq(taskMetadata.txHash, keccak256("txHash")); assertEq(taskMetadata.taskIndex, 0); assertEq(taskMetadata.registrationTime, uint64(block.timestamp)); @@ -851,10 +750,8 @@ contract AutomationRegistryTest is Test { registry.registerSystemTask( payload, // payload uint64(block.timestamp + 2250), // expiryTime - keccak256("txHash"), // txHash uint128(1_000_000), // maxGasAmount 2, // priority - 1, // task type auxData // aux data ); } From 36569bd2ff362f848f9d5af78ba94a0bc02ac8a6 Mon Sep 17 00:00:00 2001 From: Udit Yadav Date: Tue, 10 Feb 2026 14:27:05 +0530 Subject: [PATCH 29/31] updated libraries --- solidity/supra_contracts/lib/forge-std | 2 +- solidity/supra_contracts/lib/openzeppelin-contracts-upgradeable | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/solidity/supra_contracts/lib/forge-std b/solidity/supra_contracts/lib/forge-std index 27ba11c86a..aeb45e9f32 160000 --- a/solidity/supra_contracts/lib/forge-std +++ b/solidity/supra_contracts/lib/forge-std @@ -1 +1 @@ -Subproject commit 27ba11c86ac93d8d4a50437ae26621468fe63c20 +Subproject commit aeb45e9f32ef8ca78f0aeda17596e9c46374da41 diff --git a/solidity/supra_contracts/lib/openzeppelin-contracts-upgradeable b/solidity/supra_contracts/lib/openzeppelin-contracts-upgradeable index aa677e9d28..a73231f64c 160000 --- a/solidity/supra_contracts/lib/openzeppelin-contracts-upgradeable +++ b/solidity/supra_contracts/lib/openzeppelin-contracts-upgradeable @@ -1 +1 @@ -Subproject commit aa677e9d28ed78fc427ec47ba2baef2030c58e7c +Subproject commit a73231f64c2a4ab1c0bceb43ba8333be45d2df0a From 77fe1e2014043e043509fe1111ef5268a7cf3e52 Mon Sep 17 00:00:00 2001 From: Udit Yadav Date: Tue, 10 Feb 2026 15:15:41 +0530 Subject: [PATCH 30/31] updated readTxHash --- solidity/supra_contracts/src/AutomationRegistry.sol | 13 +++++-------- .../supra_contracts/src/IAutomationRegistry.sol | 2 +- 2 files changed, 6 insertions(+), 9 deletions(-) diff --git a/solidity/supra_contracts/src/AutomationRegistry.sol b/solidity/supra_contracts/src/AutomationRegistry.sol index 374c9c85cb..cbd6071532 100644 --- a/solidity/supra_contracts/src/AutomationRegistry.sol +++ b/solidity/supra_contracts/src/AutomationRegistry.sol @@ -22,7 +22,6 @@ contract AutomationRegistry is IAutomationRegistry, Ownable2StepUpgradeable, UUP uint8 constant REFUND_FACTOR = 2; /// @notice Address of the transaction hash precompile. - // TO_DO: Update the precompile address once it's finalized and available. address public constant TX_HASH_PRECOMPILE = 0x0000000000000000000000000000000053555001; /// @dev State variables @@ -452,13 +451,11 @@ contract AutomationRegistry is IAutomationRegistry, Ownable2StepUpgradeable, UUP // :::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::: HELPER FUNCTIONS :::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::: /// @notice Read tx hash via precompile. Reverts if precompile missing/fails. - function readTxHash() private pure returns (bytes32) { - // (bool ok, bytes memory out) = TX_HASH_PRECOMPILE.staticcall(""); - // require(ok, FailedToCallTxHashPrecompile()); - // require(out.length == 32, InvalidTxHashLength()); - // return abi.decode(out, (bytes32)); - // TO_DO - return keccak256("txHash"); // --- IGNORE --- + 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. diff --git a/solidity/supra_contracts/src/IAutomationRegistry.sol b/solidity/supra_contracts/src/IAutomationRegistry.sol index 701ea074cb..8c0462a0cb 100644 --- a/solidity/supra_contracts/src/IAutomationRegistry.sol +++ b/solidity/supra_contracts/src/IAutomationRegistry.sol @@ -21,7 +21,7 @@ interface IAutomationRegistry { error TaskIndexNotFound(); error TaskIndexNotUnique(); error FailedToCallTxHashPrecompile(); - error InvalidTxHashLength(); + error TxnHashLengthShouldBe32(uint64); // View functions function ifTaskExists(uint64 _taskIndex) external view returns (bool); From 323813bfa505a579f82e3e69b094e402df37b93b Mon Sep 17 00:00:00 2001 From: Udit Yadav Date: Wed, 11 Feb 2026 11:16:02 +0530 Subject: [PATCH 31/31] added mockCall for readTxHash --- solidity/supra_contracts/test/AutomationController.t.sol | 9 +++++++++ solidity/supra_contracts/test/AutomationCore.t.sol | 9 +++++++++ solidity/supra_contracts/test/AutomationRegistry.t.sol | 9 +++++++++ 3 files changed, 27 insertions(+) diff --git a/solidity/supra_contracts/test/AutomationController.t.sol b/solidity/supra_contracts/test/AutomationController.t.sol index 30a7fd8229..21c4db7ddb 100644 --- a/solidity/supra_contracts/test/AutomationController.t.sol +++ b/solidity/supra_contracts/test/AutomationController.t.sol @@ -19,6 +19,9 @@ contract AutomationControllerTest is Test { 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); @@ -71,6 +74,12 @@ contract AutomationControllerTest is Test { 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. diff --git a/solidity/supra_contracts/test/AutomationCore.t.sol b/solidity/supra_contracts/test/AutomationCore.t.sol index 53b5c368bd..2350735502 100644 --- a/solidity/supra_contracts/test/AutomationCore.t.sol +++ b/solidity/supra_contracts/test/AutomationCore.t.sol @@ -19,6 +19,9 @@ contract AutomationCoreTest is Test { 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); @@ -71,6 +74,12 @@ contract AutomationCoreTest is Test { 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. diff --git a/solidity/supra_contracts/test/AutomationRegistry.t.sol b/solidity/supra_contracts/test/AutomationRegistry.t.sol index 0f83b96bdf..8eb4d6e2d6 100644 --- a/solidity/supra_contracts/test/AutomationRegistry.t.sol +++ b/solidity/supra_contracts/test/AutomationRegistry.t.sol @@ -21,6 +21,9 @@ contract AutomationRegistryTest is Test { 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); @@ -73,6 +76,12 @@ contract AutomationRegistryTest is Test { 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.