diff --git a/contracts/protocol/utils/common/Structs.sol b/contracts/protocol/utils/common/Structs.sol index 301cca0c..2b9f3838 100644 --- a/contracts/protocol/utils/common/Structs.sol +++ b/contracts/protocol/utils/common/Structs.sol @@ -103,7 +103,6 @@ struct TriggerParams { } // timeout: struct TimeoutRequest { - bytes32 timeoutId; address target; uint256 delayInSeconds; uint256 executeAt; diff --git a/contracts/protocol/watcherPrecompile/WatcherPrecompileConfig.sol b/contracts/protocol/watcherPrecompile/WatcherPrecompileConfig.sol index 22c85d4f..3d9b4f09 100644 --- a/contracts/protocol/watcherPrecompile/WatcherPrecompileConfig.sol +++ b/contracts/protocol/watcherPrecompile/WatcherPrecompileConfig.sol @@ -7,7 +7,7 @@ import {Ownable} from "solady/auth/Ownable.sol"; import "../../interfaces/IWatcherPrecompileConfig.sol"; import {AddressResolverUtil} from "../utils/AddressResolverUtil.sol"; import {InvalidWatcherSignature, NonceUsed} from "../utils/common/Errors.sol"; -import "./core/WatcherPrecompileUtils.sol"; +import "./core/WatcherIdUtils.sol"; /// @title WatcherPrecompileConfig /// @notice Configuration contract for the Watcher Precompile system @@ -16,8 +16,7 @@ contract WatcherPrecompileConfig is IWatcherPrecompileConfig, Initializable, Ownable, - AddressResolverUtil, - WatcherPrecompileUtils + AddressResolverUtil { // slots 0-50 (51) reserved for addr resolver util @@ -201,7 +200,7 @@ contract WatcherPrecompileConfig is ) return; (bytes32 appGatewayId, address switchboard) = getPlugConfigs(chainSlug_, target_); - if (appGatewayId != _encodeAppGatewayId(appGateway_)) revert InvalidGateway(); + if (appGatewayId != WatcherIdUtils.encodeAppGatewayId(appGateway_)) revert InvalidGateway(); if (switchboard != switchboard_) revert InvalidSwitchboard(); } diff --git a/contracts/protocol/watcherPrecompile/core/RequestHandler.sol b/contracts/protocol/watcherPrecompile/core/RequestHandler.sol index 1cb77e73..d6f620eb 100644 --- a/contracts/protocol/watcherPrecompile/core/RequestHandler.sol +++ b/contracts/protocol/watcherPrecompile/core/RequestHandler.sol @@ -19,7 +19,7 @@ abstract contract RequestHandler is WatcherPrecompileCore { /// @dev This function processes a batch of payload requests and assigns them to batches /// @dev It also consumes limits for the app gateway based on the number of reads and writes function submitRequest( - PayloadSubmitParams[] calldata payloadSubmitParams_ + PayloadSubmitParams[] memory payloadSubmitParams_ ) public returns (uint40 requestCount) { address appGateway = _checkAppGateways(payloadSubmitParams_); @@ -50,7 +50,7 @@ abstract contract RequestHandler is WatcherPrecompileCore { } uint40 localPayloadCount = payloadCounter++; - bytes32 payloadId = _createPayloadId( + bytes32 payloadId = WatcherIdUtils.createPayloadId( requestCount, batchCount, localPayloadCount, @@ -59,14 +59,15 @@ abstract contract RequestHandler is WatcherPrecompileCore { ); batchPayloadIds[batchCount].push(payloadId); - bytes32 payloadHeader; - payloadHeader = payloadHeader.setRequestCount(requestCount); - payloadHeader = payloadHeader.setBatchCount(batchCount); - payloadHeader = payloadHeader.setPayloadCount(localPayloadCount); - payloadHeader = payloadHeader.setChainSlug(p.chainSlug); - payloadHeader = payloadHeader.setCallType(p.callType); - payloadHeader = payloadHeader.setIsParallel(p.isParallel); - payloadHeader = payloadHeader.setWriteFinality(p.writeFinality); + bytes32 payloadHeader = PayloadHeaderDecoder.createPayloadHeader( + requestCount, + batchCount, + localPayloadCount, + p.chainSlug, + p.callType, + p.isParallel, + p.writeFinality + ); payloads[payloadId].payloadHeader = payloadHeader; payloads[payloadId].asyncPromise = p.asyncPromise; @@ -89,9 +90,6 @@ abstract contract RequestHandler is WatcherPrecompileCore { // This is needed because the last batch in the loop above doesn't get added since there's no next level to trigger it requestBatchIds[requestCount].push(nextBatchCount++); - watcherPrecompileLimits__.consumeLimit(appGateway, QUERY, readCount); - watcherPrecompileLimits__.consumeLimit(appGateway, FINALIZE, writeCount); - requestParams[requestCount].queryCount = readCount; requestParams[requestCount].finalizeCount = writeCount; @@ -111,7 +109,7 @@ abstract contract RequestHandler is WatcherPrecompileCore { /// @param payloadSubmitParams Array of payload submit parameters /// @return appGateway The core app gateway address function _checkAppGateways( - PayloadSubmitParams[] calldata payloadSubmitParams + PayloadSubmitParams[] memory payloadSubmitParams ) internal view returns (address appGateway) { bool isDeliveryHelper = msg.sender == addressResolver__.deliveryHelper(); diff --git a/contracts/protocol/watcherPrecompile/core/WatcherIdUtils.sol b/contracts/protocol/watcherPrecompile/core/WatcherIdUtils.sol new file mode 100644 index 00000000..5121b3da --- /dev/null +++ b/contracts/protocol/watcherPrecompile/core/WatcherIdUtils.sol @@ -0,0 +1,32 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.22; + +library WatcherIdUtils { + function encodeAppGatewayId(address appGateway_) internal pure returns (bytes32) { + return bytes32(uint256(uint160(appGateway_))); + } + + function decodeAppGatewayId(bytes32 appGatewayId_) internal pure returns (address) { + return address(uint160(uint256(appGatewayId_))); + } + + /// @notice Creates a payload ID from the given parameters + /// @param requestCount_ The request count + /// @param batchCount_ The batch count + /// @param payloadCount_ The payload count + /// @param switchboard_ The switchboard address + /// @param chainSlug_ The chain slug + /// @return The created payload ID + function createPayloadId( + uint40 requestCount_, + uint40 batchCount_, + uint40 payloadCount_, + address switchboard_, + uint32 chainSlug_ + ) internal pure returns (bytes32) { + return + keccak256( + abi.encode(requestCount_, batchCount_, payloadCount_, switchboard_, chainSlug_) + ); + } +} diff --git a/contracts/protocol/watcherPrecompile/core/WatcherPrecompile.sol b/contracts/protocol/watcherPrecompile/core/WatcherPrecompile.sol index 48cae8fe..13e72e43 100644 --- a/contracts/protocol/watcherPrecompile/core/WatcherPrecompile.sol +++ b/contracts/protocol/watcherPrecompile/core/WatcherPrecompile.sol @@ -40,8 +40,9 @@ contract WatcherPrecompile is RequestHandler { watcherPrecompileConfig__ = IWatcherPrecompileConfig(watcherPrecompileConfig_); maxTimeoutDelayInSeconds = 24 * 60 * 60; // 24 hours expiryTime = expiryTime_; - evmxSlug = evmxSlug_; + + timeoutIdPrefix = (uint256(evmxSlug_) << 224) | (uint256(uint160(address(this))) << 64); } // ================== Timeout functions ================== @@ -53,10 +54,7 @@ contract WatcherPrecompile is RequestHandler { /// @param delayInSeconds_ The delay in seconds before the timeout executes /// @param payload_ The payload data to be executed after the timeout /// @return The unique identifier for the timeout request - function setTimeout( - uint256 delayInSeconds_, - bytes calldata payload_ - ) external returns (bytes32) { + function setTimeout(uint256 delayInSeconds_, bytes memory payload_) external returns (bytes32) { return _setTimeout(delayInSeconds_, payload_); } @@ -68,7 +66,7 @@ contract WatcherPrecompile is RequestHandler { function resolveTimeout( bytes32 timeoutId_, uint256 signatureNonce_, - bytes calldata signature_ + bytes memory signature_ ) external { _isWatcherSignatureValid( abi.encode(this.resolveTimeout.selector, timeoutId_), @@ -135,9 +133,9 @@ contract WatcherPrecompile is RequestHandler { /// @dev keccak256(abi.encode(switchboard, digest)) function finalized( bytes32 payloadId_, - bytes calldata proof_, + bytes memory proof_, uint256 signatureNonce_, - bytes calldata signature_ + bytes memory signature_ ) external { _isWatcherSignatureValid( abi.encode(this.finalized.selector, payloadId_, proof_), @@ -186,9 +184,9 @@ contract WatcherPrecompile is RequestHandler { /// @dev It verifies that the signature is valid /// @dev It also processes the next batch if the current batch is complete function resolvePromises( - ResolvedPromises[] calldata resolvedPromises_, + ResolvedPromises[] memory resolvedPromises_, uint256 signatureNonce_, - bytes calldata signature_ + bytes memory signature_ ) external { _isWatcherSignatureValid( abi.encode(this.resolvePromises.selector, resolvedPromises_), @@ -197,45 +195,13 @@ contract WatcherPrecompile is RequestHandler { ); for (uint256 i = 0; i < resolvedPromises_.length; i++) { - // Get the array of promise addresses for this payload - PayloadParams memory payloadParams = payloads[resolvedPromises_[i].payloadId]; - address asyncPromise = payloadParams.asyncPromise; - - uint40 requestCount = payloadParams.payloadHeader.getRequestCount(); - - // todo: non trusted call - if (asyncPromise != address(0)) { - // todo: limit the gas used for promise resolution - // Resolve each promise with its corresponding return data - bool success = IPromise(asyncPromise).markResolved( - requestCount, - resolvedPromises_[i].payloadId, - resolvedPromises_[i].returnData - ); - - if (!success) { - emit PromiseNotResolved(resolvedPromises_[i].payloadId, asyncPromise); - continue; - } - } - - isPromiseExecuted[resolvedPromises_[i].payloadId] = true; + uint40 requestCount = payloads[resolvedPromises_[i].payloadId] + .payloadHeader + .getRequestCount(); RequestParams storage requestParams_ = requestParams[requestCount]; - requestParams_.currentBatchPayloadsLeft--; - requestParams_.payloadsRemaining--; - // if all payloads of a batch are executed, process the next batch - if ( - requestParams_.currentBatchPayloadsLeft == 0 && requestParams_.payloadsRemaining > 0 - ) { - _processBatch(requestCount, ++requestParams_.currentBatch); - } - - // if all payloads of a request are executed, finish the request - if (requestParams_.payloadsRemaining == 0) { - IMiddleware(requestParams_.middleware).finishRequest(requestCount); - } - emit PromiseResolved(resolvedPromises_[i].payloadId, asyncPromise); + _processPromiseResolution(resolvedPromises_[i], requestParams_); + _checkAndProcessBatch(requestParams_, requestCount); } } @@ -251,7 +217,7 @@ contract WatcherPrecompile is RequestHandler { bool isRevertingOnchain_, bytes32 payloadId_, uint256 signatureNonce_, - bytes calldata signature_ + bytes memory signature_ ) external { _isWatcherSignatureValid( abi.encode(this.markRevert.selector, isRevertingOnchain_, payloadId_), @@ -289,9 +255,9 @@ contract WatcherPrecompile is RequestHandler { /// @dev This function calls app gateways with the specified parameters /// @dev It verifies that the signature is valid and that the app gateway hasn't been called yet function callAppGateways( - TriggerParams[] calldata params_, + TriggerParams[] memory params_, uint256 signatureNonce_, - bytes calldata signature_ + bytes memory signature_ ) external { _isWatcherSignatureValid( abi.encode(this.callAppGateways.selector, params_), @@ -302,7 +268,7 @@ contract WatcherPrecompile is RequestHandler { for (uint256 i = 0; i < params_.length; i++) { if (appGatewayCalled[params_[i].triggerId]) revert AppGatewayAlreadyCalled(); - address appGateway = _decodeAppGatewayId(params_[i].appGatewayId); + address appGateway = WatcherIdUtils.decodeAppGatewayId(params_[i].appGatewayId); if ( !watcherPrecompileConfig__.isValidPlug( appGateway, @@ -312,9 +278,9 @@ contract WatcherPrecompile is RequestHandler { ) revert InvalidCallerTriggered(); IFeesManager(addressResolver__.feesManager()).assignWatcherPrecompileCreditsFromAddress( - watcherPrecompileLimits__.callBackFees(), - appGateway - ); + watcherPrecompileLimits__.callBackFees(), + appGateway + ); appGatewayCaller = appGateway; appGatewayCalled[params_[i].triggerId] = true; @@ -379,4 +345,45 @@ contract WatcherPrecompile is RequestHandler { function getRequestParams(uint40 requestCount) external view returns (RequestParams memory) { return requestParams[requestCount]; } + + function _processPromiseResolution( + ResolvedPromises memory resolvedPromise_, + RequestParams storage requestParams_ + ) internal { + PayloadParams memory payloadParams = payloads[resolvedPromise_.payloadId]; + address asyncPromise = payloadParams.asyncPromise; + uint40 requestCount = payloadParams.payloadHeader.getRequestCount(); + + if (asyncPromise != address(0)) { + bool success = IPromise(asyncPromise).markResolved( + requestCount, + resolvedPromise_.payloadId, + resolvedPromise_.returnData + ); + + if (!success) { + emit PromiseNotResolved(resolvedPromise_.payloadId, asyncPromise); + return; + } + } + + isPromiseExecuted[resolvedPromise_.payloadId] = true; + requestParams_.currentBatchPayloadsLeft--; + requestParams_.payloadsRemaining--; + + emit PromiseResolved(resolvedPromise_.payloadId, asyncPromise); + } + + function _checkAndProcessBatch( + RequestParams storage requestParams_, + uint40 requestCount + ) internal { + if (requestParams_.currentBatchPayloadsLeft == 0 && requestParams_.payloadsRemaining > 0) { + _processBatch(requestCount, ++requestParams_.currentBatch); + } + + if (requestParams_.payloadsRemaining == 0) { + IMiddleware(requestParams_.middleware).finishRequest(requestCount); + } + } } diff --git a/contracts/protocol/watcherPrecompile/core/WatcherPrecompileCore.sol b/contracts/protocol/watcherPrecompile/core/WatcherPrecompileCore.sol index 402532bc..85fc60f7 100644 --- a/contracts/protocol/watcherPrecompile/core/WatcherPrecompileCore.sol +++ b/contracts/protocol/watcherPrecompile/core/WatcherPrecompileCore.sol @@ -3,11 +3,12 @@ pragma solidity ^0.8.21; import "./WatcherPrecompileStorage.sol"; import {ECDSA} from "solady/utils/ECDSA.sol"; -import {AccessControl} from "../../utils/AccessControl.sol"; +import {Ownable} from "solady/auth/Ownable.sol"; + import "solady/utils/Initializable.sol"; import {AddressResolverUtil} from "../../utils/AddressResolverUtil.sol"; import {IFeesManager} from "../../../interfaces/IFeesManager.sol"; -import "./WatcherPrecompileUtils.sol"; +import "./WatcherIdUtils.sol"; /// @title WatcherPrecompileCore /// @notice Core functionality for the WatcherPrecompile system @@ -17,9 +18,8 @@ abstract contract WatcherPrecompileCore is IWatcherPrecompile, WatcherPrecompileStorage, Initializable, - AccessControl, - AddressResolverUtil, - WatcherPrecompileUtils + Ownable, + AddressResolverUtil { using PayloadHeaderDecoder for bytes32; @@ -32,28 +32,18 @@ abstract contract WatcherPrecompileCore is /// @return timeoutId The unique identifier for the timeout request function _setTimeout( uint256 delayInSeconds_, - bytes calldata payload_ + bytes memory payload_ ) internal returns (bytes32 timeoutId) { if (delayInSeconds_ > maxTimeoutDelayInSeconds) revert TimeoutDelayTooLarge(); - _consumeCallbackFeesFromAddress(watcherPrecompileLimits__.timeoutFees(), msg.sender); uint256 executeAt = block.timestamp + delayInSeconds_; timeoutId = _encodeTimeoutId(); - // stores timeout request for watcher to track and resolve when timeout is reached - timeoutRequests[timeoutId] = TimeoutRequest( - timeoutId, - msg.sender, - delayInSeconds_, - executeAt, - 0, - false, - payload_ - ); - - // consumes limit for SCHEDULE precompile - watcherPrecompileLimits__.consumeLimit(_getCoreAppGateway(msg.sender), SCHEDULE, 1); + timeoutRequests[timeoutId].target = msg.sender; + timeoutRequests[timeoutId].delayInSeconds = delayInSeconds_; + timeoutRequests[timeoutId].executeAt = executeAt; + timeoutRequests[timeoutId].payload = payload_; // emits event for watcher to track timeout and resolve when timeout is reached emit TimeoutRequested(timeoutId, msg.sender, payload_, executeAt); @@ -102,7 +92,7 @@ abstract contract WatcherPrecompileCore is params_.value, params_.payload, params_.target, - _encodeAppGatewayId(params_.appGateway), + WatcherIdUtils.encodeAppGatewayId(params_.appGateway), prevDigestsHash ); @@ -173,7 +163,7 @@ abstract contract WatcherPrecompileCore is p.value, p.payload, p.target, - _encodeAppGatewayId(p.appGateway), + WatcherIdUtils.encodeAppGatewayId(p.appGateway), p.prevDigestsHash ); prevDigestsHash = keccak256(abi.encodePacked(prevDigestsHash, getDigest(digestParams))); @@ -201,32 +191,7 @@ abstract contract WatcherPrecompileCore is function _encodeTimeoutId() internal returns (bytes32) { // Encode timeout ID by bit-shifting and combining: // EVMx chainSlug (32 bits) | watcher precompile address (160 bits) | counter (64 bits) - return - bytes32( - (uint256(evmxSlug) << 224) | - (uint256(uint160(address(this))) << 64) | - payloadCounter++ - ); - } - - /// @notice Creates a payload ID from the given parameters - /// @param requestCount_ The request count - /// @param batchCount_ The batch count - /// @param payloadCount_ The payload count - /// @param switchboard_ The switchboard address - /// @param chainSlug_ The chain slug - /// @return The created payload ID - function _createPayloadId( - uint40 requestCount_, - uint40 batchCount_, - uint40 payloadCount_, - address switchboard_, - uint32 chainSlug_ - ) internal pure returns (bytes32) { - return - keccak256( - abi.encode(requestCount_, batchCount_, payloadCount_, switchboard_, chainSlug_) - ); + return bytes32(timeoutIdPrefix | payloadCounter++); } /// @notice Verifies that a watcher signature is valid diff --git a/contracts/protocol/watcherPrecompile/core/WatcherPrecompileStorage.sol b/contracts/protocol/watcherPrecompile/core/WatcherPrecompileStorage.sol index 12dd3d53..d90706f9 100644 --- a/contracts/protocol/watcherPrecompile/core/WatcherPrecompileStorage.sol +++ b/contracts/protocol/watcherPrecompile/core/WatcherPrecompileStorage.sol @@ -47,6 +47,10 @@ abstract contract WatcherPrecompileStorage is IWatcherPrecompile { address public appGatewayCaller; // slot 54 + /// @notice The prefix for timeout IDs + uint256 public timeoutIdPrefix; + + // slot 55 /// @notice Maps nonce to whether it has been used /// @dev Used to prevent replay attacks with signature nonces /// @dev signatureNonce => isValid diff --git a/contracts/protocol/watcherPrecompile/core/WatcherPrecompileUtils.sol b/contracts/protocol/watcherPrecompile/core/WatcherPrecompileUtils.sol deleted file mode 100644 index c47786e8..00000000 --- a/contracts/protocol/watcherPrecompile/core/WatcherPrecompileUtils.sol +++ /dev/null @@ -1,12 +0,0 @@ -// SPDX-License-Identifier: MIT -pragma solidity ^0.8.22; - -abstract contract WatcherPrecompileUtils { - function _encodeAppGatewayId(address appGateway_) internal pure returns (bytes32) { - return bytes32(uint256(uint160(appGateway_))); - } - - function _decodeAppGatewayId(bytes32 appGatewayId_) internal pure returns (address) { - return address(uint160(uint256(appGatewayId_))); - } -} diff --git a/package.json b/package.json index 80d12f1f..2610da7a 100644 --- a/package.json +++ b/package.json @@ -9,7 +9,7 @@ "publishConfig": { "access": "public" }, - "version": "1.1.9", + "version": "1.1.11", "description": "socket protocol", "scripts": { "build": "yarn abi && tsc --project lib.tsconfig.json", diff --git a/publish.sh b/publish.sh deleted file mode 100755 index 6d4331ee..00000000 --- a/publish.sh +++ /dev/null @@ -1,2 +0,0 @@ -yarn build -yarn publish \ No newline at end of file diff --git a/test/mock/MockWatcherPrecompile.sol b/test/mock/MockWatcherPrecompile.sol index dfd3711b..3cd1681c 100644 --- a/test/mock/MockWatcherPrecompile.sol +++ b/test/mock/MockWatcherPrecompile.sol @@ -73,7 +73,6 @@ contract MockWatcherPrecompile { uint256 executeAt = block.timestamp + delayInSeconds_; bytes32 timeoutId = _encodeTimeoutId(); timeoutRequests[timeoutId] = TimeoutRequest( - timeoutId, msg.sender, delayInSeconds_, executeAt,