diff --git a/README.md b/README.md index dd8337b0..7807e036 100644 --- a/README.md +++ b/README.md @@ -17,3 +17,5 @@ Find more information at [docs](https://docs.socket.tech) - update version after every change - never remove code - inherited contracts should have gaps at the end to avoid storage collision +- write tests for migration checking slots after the change +- diff --git a/contracts/interfaces/IDeliveryHelper.sol b/contracts/interfaces/IDeliveryHelper.sol index d6cd4fd3..2f9d15ae 100644 --- a/contracts/interfaces/IDeliveryHelper.sol +++ b/contracts/interfaces/IDeliveryHelper.sol @@ -13,7 +13,7 @@ interface IDeliveryHelper { Bid winningBid // Replaced winningTransmitter and winningBid with Bid struct ); - function bidTimeout() external view returns (uint256); + function bidTimeout() external view returns (uint128); function payloadBatches(bytes32) external view returns (PayloadBatch memory); diff --git a/contracts/protocol/AddressResolver.sol b/contracts/protocol/AddressResolver.sol index 264f6206..238d0a81 100644 --- a/contracts/protocol/AddressResolver.sol +++ b/contracts/protocol/AddressResolver.sol @@ -9,32 +9,54 @@ import "../interfaces/IAddressResolver.sol"; import {Forwarder} from "./Forwarder.sol"; import {AsyncPromise} from "./AsyncPromise.sol"; -/// @title AddressResolver Contract -/// @notice This contract is responsible for fetching latest core addresses and deploying Forwarder and AsyncPromise contracts. -/// @dev Inherits the Ownable contract and implements the IAddressResolver interface. -contract AddressResolver is Ownable, IAddressResolver, Initializable { +abstract contract AddressResolverStorage is IAddressResolver { + // slots [0-49] reserved for gap + uint256[50] _gap_before; + + // slot 50 IWatcherPrecompile public override watcherPrecompile__; + + // slot 51 address public override deliveryHelper; + + // slot 52 address public override feesManager; - // Beacons for managing upgrades + // slot 53 UpgradeableBeacon public forwarderBeacon; + + // slot 54 UpgradeableBeacon public asyncPromiseBeacon; + // slot 55 address public forwarderImplementation; + + // slot 56 address public asyncPromiseImplementation; - // Array to store promises + // slot 57 address[] internal _promises; + // slot 58 uint256 public asyncPromiseCounter; + + // slot 59 uint64 public version; - // contracts to gateway map + // slot 60 mapping(address => address) public override contractsToGateways; - // gateway to contract map + + // slot 61 mapping(address => address) public override gatewaysToContracts; + // slots [62-111] reserved for gap + uint256[50] _gap_after; +} + +/// @title AddressResolver Contract +/// @notice This contract is responsible for fetching latest core addresses and deploying Forwarder and AsyncPromise contracts. +/// @dev Inherits the Ownable contract and implements the IAddressResolver interface. +contract AddressResolver is AddressResolverStorage, Initializable, Ownable { /// @notice Error thrown if AppGateway contract was already set by a different address error InvalidAppGateway(address contractAddress_); diff --git a/contracts/protocol/AsyncPromise.sol b/contracts/protocol/AsyncPromise.sol index 99f5e08e..4f5b0664 100644 --- a/contracts/protocol/AsyncPromise.sol +++ b/contracts/protocol/AsyncPromise.sol @@ -15,29 +15,43 @@ enum AsyncPromiseState { RESOLVED } -/// @title AsyncPromise -/// @notice this contract stores the callback address and data to be executed once the previous call is executed -/// This promise expires once the callback is executed -contract AsyncPromise is IPromise, Initializable, AddressResolverUtil { +abstract contract AsyncPromiseStorage is IPromise { + // slots [0-49] reserved for gap + uint256[50] _gap_before; + + // slot 50 + // bytes1 /// @notice The callback selector to be called on the invoker. bytes4 public callbackSelector; - + // bytes4 /// @notice Indicates whether the promise has been resolved. bool public override resolved; - + // bytes8 /// @notice The current state of the async promise. AsyncPromiseState public state; - + // bytes20 /// @notice The local contract which initiated the async call. /// @dev The callback will be executed on this address address public localInvoker; + // slot 51 /// @notice The forwarder address which can call the callback address public forwarder; + // slot 52 /// @notice The callback data to be used when the promise is resolved. bytes public callbackData; + // slots [53-102] reserved for gap + uint256[50] _gap_after; + + // slots 103-154 reserved for addr resolver util +} + +/// @title AsyncPromise +/// @notice this contract stores the callback address and data to be executed once the previous call is executed +/// This promise expires once the callback is executed +contract AsyncPromise is AsyncPromiseStorage, Initializable, AddressResolverUtil { /// @notice Error thrown when attempting to resolve an already resolved promise. error PromiseAlreadyResolved(); /// @notice Only the forwarder or local invoker can set then's promise callback diff --git a/contracts/protocol/Forwarder.sol b/contracts/protocol/Forwarder.sol index 0c45a45c..13785b7b 100644 --- a/contracts/protocol/Forwarder.sol +++ b/contracts/protocol/Forwarder.sol @@ -5,25 +5,36 @@ import "../interfaces/IAddressResolver.sol"; import "../interfaces/IDeliveryHelper.sol"; import "../interfaces/IAppGateway.sol"; import "../interfaces/IPromise.sol"; -import "./AsyncPromise.sol"; import "../interfaces/IForwarder.sol"; import "solady/utils/Initializable.sol"; -/// @title Forwarder Contract -/// @notice This contract acts as a forwarder for async calls to the on-chain contracts. -contract Forwarder is IForwarder, Initializable { +abstract contract ForwarderStorage is IForwarder { + // slots [0-49] reserved for gap + uint256[50] _gap_before; + + // slot 50 /// @notice chain id uint32 public chainSlug; + // slot 51 /// @notice on-chain address associated with this forwarder address public onChainAddress; + // slot 52 /// @notice address resolver contract address for imp addresses address public addressResolver; + // slot 53 /// @notice caches the latest async promise address for the last call address public latestAsyncPromise; + // slots [54-103] reserved for gap + uint256[50] _gap_after; +} + +/// @title Forwarder Contract +/// @notice This contract acts as a forwarder for async calls to the on-chain contracts. +contract Forwarder is ForwarderStorage, Initializable { error AsyncModifierNotUsed(); constructor() { diff --git a/contracts/protocol/payload-delivery/app-gateway/AuctionManager.sol b/contracts/protocol/payload-delivery/AuctionManager.sol similarity index 85% rename from contracts/protocol/payload-delivery/app-gateway/AuctionManager.sol rename to contracts/protocol/payload-delivery/AuctionManager.sol index 863e969a..dd12f475 100644 --- a/contracts/protocol/payload-delivery/app-gateway/AuctionManager.sol +++ b/contracts/protocol/payload-delivery/AuctionManager.sol @@ -1,38 +1,51 @@ // SPDX-License-Identifier: MIT pragma solidity ^0.8.0; -import {Ownable} from "solady/auth/Ownable.sol"; import {ECDSA} from "solady/utils/ECDSA.sol"; -import {AddressResolverUtil} from "../../utils/AddressResolverUtil.sol"; -import {Fees, Bid, PayloadBatch} from "../../utils/common/Structs.sol"; -import {IDeliveryHelper} from "../../../interfaces/IDeliveryHelper.sol"; -import {IFeesManager} from "../../../interfaces/IFeesManager.sol"; -import {IAuctionManager} from "../../../interfaces/IAuctionManager.sol"; import "solady/utils/Initializable.sol"; +import {Ownable} from "solady/auth/Ownable.sol"; -/// @title AuctionManager -/// @notice Contract for managing auctions and placing bids -contract AuctionManager is AddressResolverUtil, Ownable, IAuctionManager, Initializable { +import {IDeliveryHelper} from "../../interfaces/IDeliveryHelper.sol"; +import {IFeesManager} from "../../interfaces/IFeesManager.sol"; +import {IAuctionManager} from "../../interfaces/IAuctionManager.sol"; + +import {AddressResolverUtil} from "../utils/AddressResolverUtil.sol"; +import {Fees, Bid, PayloadBatch} from "../utils/common/Structs.sol"; +import {AuctionClosed, AuctionAlreadyStarted, BidExceedsMaxFees, LowerBidAlreadyExists, InvalidTransmitter} from "../utils/common/Errors.sol"; + +abstract contract AuctionManagerStorage is IAuctionManager { + // slots [0-49] reserved for gap + uint256[50] _gap_before; + + // slot 50 uint32 public evmxSlug; + + // slot 51 mapping(bytes32 => Bid) public winningBids; + + // slot 52 // asyncId => auction status mapping(bytes32 => bool) public override auctionClosed; + + // slot 53 mapping(bytes32 => bool) public override auctionStarted; + // slot 54 uint256 public auctionEndDelaySeconds; - /// @notice Error thrown when trying to start or bid a closed auction - error AuctionClosed(); - /// @notice Error thrown when trying to start an ongoing auction - error AuctionAlreadyStarted(); - /// @notice Error thrown if fees exceed the maximum set fees - error BidExceedsMaxFees(); - /// @notice Error thrown if winning bid is assigned to an invalid transmitter - error InvalidTransmitter(); - /// @notice Error thrown if a lower bid already exists - error LowerBidAlreadyExists(); + // slots [55-104] reserved for gap + uint256[50] _gap_after; + // slots 105-155 reserved for addr resolver util +} + +/// @title AuctionManager +/// @notice Contract for managing auctions and placing bids +contract AuctionManager is AuctionManagerStorage, Initializable, Ownable, AddressResolverUtil { event AuctionRestarted(bytes32 asyncId); + event AuctionStarted(bytes32 asyncId); + event AuctionEnded(bytes32 asyncId, Bid winningBid); + event BidPlaced(bytes32 asyncId, Bid bid); constructor() { _disableInitializers(); // disable for implementation @@ -55,10 +68,6 @@ contract AuctionManager is AddressResolverUtil, Ownable, IAuctionManager, Initia auctionEndDelaySeconds = auctionEndDelaySeconds_; } - event AuctionStarted(bytes32 asyncId); - event AuctionEnded(bytes32 asyncId, Bid winningBid); - event BidPlaced(bytes32 asyncId, Bid bid); - function setAuctionEndDelaySeconds(uint256 auctionEndDelaySeconds_) external onlyOwner { auctionEndDelaySeconds = auctionEndDelaySeconds_; } diff --git a/contracts/protocol/payload-delivery/app-gateway/FeesManager.sol b/contracts/protocol/payload-delivery/FeesManager.sol similarity index 93% rename from contracts/protocol/payload-delivery/app-gateway/FeesManager.sol rename to contracts/protocol/payload-delivery/FeesManager.sol index 736f4191..58a9323f 100644 --- a/contracts/protocol/payload-delivery/app-gateway/FeesManager.sol +++ b/contracts/protocol/payload-delivery/FeesManager.sol @@ -4,19 +4,23 @@ pragma solidity ^0.8.0; import {Ownable} from "solady/auth/Ownable.sol"; import "solady/utils/Initializable.sol"; import "solady/utils/ECDSA.sol"; -import {AddressResolverUtil} from "../../../protocol/utils/AddressResolverUtil.sol"; -import {Bid, Fees, PayloadDetails, CallType, FinalizeParams, PayloadBatch, Parallel} from "../../../protocol/utils/common/Structs.sol"; -import {IDeliveryHelper} from "../../../interfaces/IDeliveryHelper.sol"; -import {FORWARD_CALL, DISTRIBUTE_FEE, DEPLOY, WITHDRAW} from "../../../protocol/utils/common/Constants.sol"; -import {IFeesPlug} from "../../../interfaces/IFeesPlug.sol"; -import {IFeesManager} from "../../../interfaces/IFeesManager.sol"; -import {NotAuctionManager} from "../../../protocol/utils/common/Errors.sol"; +import {IFeesPlug} from "../../interfaces/IFeesPlug.sol"; +import {IFeesManager} from "../../interfaces/IFeesManager.sol"; -/// @title FeesManager -/// @notice Contract for managing fees -contract FeesManager is IFeesManager, AddressResolverUtil, Ownable, Initializable { +import {AddressResolverUtil} from "../utils/AddressResolverUtil.sol"; +import {WITHDRAW} from "../utils/common/Constants.sol"; +import {NotAuctionManager} from "../utils/common/Errors.sol"; +import {Bid, Fees, PayloadDetails, CallType, FinalizeParams, Parallel} from "../utils/common/Structs.sol"; + +abstract contract FeesManagerStorage is IFeesManager { + // slots [0-49] reserved for gap + uint256[50] _gap_before; + + // slot 50 uint256 public feesCounter; + + // slot 51 uint32 public evmxSlug; /// @notice Struct containing fee amounts and status @@ -25,23 +29,36 @@ contract FeesManager is IFeesManager, AddressResolverUtil, Ownable, Initializabl uint256 blocked; // Amount blocked } + // slot 52 /// @notice Master mapping tracking all fee information /// @dev appGateway => chainSlug => token => TokenBalance mapping(address => mapping(uint32 => mapping(address => TokenBalance))) public appGatewayFeeBalances; + // slot 53 /// @notice Mapping to track blocked fees for each async id /// @dev asyncId => Fees mapping(bytes32 => Fees) public asyncIdBlockedFees; + // slot 54 /// @notice Mapping to track fees to be distributed to transmitters /// @dev transmitter => chainSlug => token => amount mapping(address => mapping(uint32 => mapping(address => uint256))) public transmitterFees; + // slot 55 /// @notice Mapping to track nonce to whether it has been used - /// @dev signatureNonce => isValid + /// @dev signatureNonce => isNonceUsed mapping(uint256 => bool) public isNonceUsed; + // slots [56-105] reserved for gap + uint256[50] _gap_after; + + // slots 106-156 reserved for addr resolver util +} + +/// @title FeesManager +/// @notice Contract for managing fees +contract FeesManager is FeesManagerStorage, Initializable, Ownable, AddressResolverUtil { /// @notice Emitted when fees are blocked for a batch /// @param asyncId The batch identifier /// @param chainSlug The chain identifier diff --git a/contracts/protocol/payload-delivery/app-gateway/BatchAsync.sol b/contracts/protocol/payload-delivery/app-gateway/BatchAsync.sol index db2cf1e4..f4415f3c 100644 --- a/contracts/protocol/payload-delivery/app-gateway/BatchAsync.sol +++ b/contracts/protocol/payload-delivery/app-gateway/BatchAsync.sol @@ -3,18 +3,12 @@ pragma solidity ^0.8.21; import "./QueueAsync.sol"; -import {IDeliveryHelper} from "../../../interfaces/IDeliveryHelper.sol"; -import {IAppGateway} from "../../../interfaces/IAppGateway.sol"; -import {IAddressResolver} from "../../../interfaces/IAddressResolver.sol"; -import {IAuctionManager} from "../../../interfaces/IAuctionManager.sol"; -import {IFeesManager} from "../../../interfaces/IFeesManager.sol"; - -import {Bid, PayloadBatch, Fees, PayloadDetails} from "../../../protocol/utils/common/Structs.sol"; -import {FORWARD_CALL, DISTRIBUTE_FEE, DEPLOY, WITHDRAW, QUERY, FINALIZE} from "../../../protocol/utils/common/Constants.sol"; - /// @title BatchAsync /// @notice Abstract contract for managing asynchronous payload batches abstract contract BatchAsync is QueueAsync { + // slots [210-259] reserved for gap + uint256[50] _gap_batch_async; + /// @notice Error thrown when attempting to executed payloads after all have been executed error AllPayloadsExecuted(); /// @notice Error thrown request did not come from Forwarder address diff --git a/contracts/protocol/payload-delivery/app-gateway/DeliveryHelper.sol b/contracts/protocol/payload-delivery/app-gateway/DeliveryHelper.sol index e0a85af0..df7cb9bc 100644 --- a/contracts/protocol/payload-delivery/app-gateway/DeliveryHelper.sol +++ b/contracts/protocol/payload-delivery/app-gateway/DeliveryHelper.sol @@ -1,19 +1,10 @@ // SPDX-License-Identifier: MIT pragma solidity ^0.8.0; -import "solady/utils/Initializable.sol"; - -import {IAppGateway} from "../../../interfaces/IAppGateway.sol"; -import {Bid, PayloadBatch, Fees, PayloadDetails, FinalizeParams} from "../../../protocol/utils/common/Structs.sol"; -import {DISTRIBUTE_FEE, DEPLOY} from "../../../protocol/utils/common/Constants.sol"; -import {PromisesNotResolved, InvalidTransmitter} from "../../../protocol/utils/common/Errors.sol"; import "./BatchAsync.sol"; -contract DeliveryHelper is BatchAsync, Initializable { +contract DeliveryHelper is BatchAsync { event CallBackReverted(bytes32 asyncId_, bytes32 payloadId_); - uint64 public version; - - bytes32[] public tempPayloadIds; constructor() { _disableInitializers(); // disable for implementation @@ -25,10 +16,9 @@ contract DeliveryHelper is BatchAsync, Initializable { function initialize( address addressResolver_, address owner_, - uint256 bidTimeout_ + uint128 bidTimeout_ ) public reinitializer(1) { _setAddressResolver(addressResolver_); - version = 1; bidTimeout = bidTimeout_; _initializeOwner(owner_); } @@ -44,7 +34,7 @@ contract DeliveryHelper is BatchAsync, Initializable { if (!isRestarted) return _process(asyncId_, false); - // Refinalize all payloads in the batch if a new transmitter is assigned + // Re-finalize all payloads in the batch if a new transmitter is assigned bytes32[] memory payloadIds = _payloadBatches[asyncId_].lastBatchOfPayloads; for (uint256 i = 0; i < payloadIds.length; i++) { watcherPrecompile__().refinalize( diff --git a/contracts/protocol/payload-delivery/app-gateway/DeliveryHelperStorage.sol b/contracts/protocol/payload-delivery/app-gateway/DeliveryHelperStorage.sol new file mode 100644 index 00000000..1801385a --- /dev/null +++ b/contracts/protocol/payload-delivery/app-gateway/DeliveryHelperStorage.sol @@ -0,0 +1,57 @@ +// SPDX-License-Identifier: Unlicense +pragma solidity ^0.8.21; + +import {IDeliveryHelper} from "../../../interfaces/IDeliveryHelper.sol"; +import {IPromise} from "../../../interfaces/IPromise.sol"; +import {IAddressResolver} from "../../../interfaces/IAddressResolver.sol"; +import {IContractFactoryPlug} from "../../../interfaces/IContractFactoryPlug.sol"; +import {IAppGateway} from "../../../interfaces/IAppGateway.sol"; +import {IAppGateway} from "../../../interfaces/IAppGateway.sol"; +import {IAddressResolver} from "../../../interfaces/IAddressResolver.sol"; +import {IAuctionManager} from "../../../interfaces/IAuctionManager.sol"; +import {IFeesManager} from "../../../interfaces/IFeesManager.sol"; + +import {CallParams, Fees, PayloadDetails, CallType, Bid, PayloadBatch, Parallel, IsPlug, FinalizeParams} from "../../utils/common/Structs.sol"; +import {NotAuctionManager, InvalidPromise, InvalidIndex, PromisesNotResolved, InvalidTransmitter} from "../../utils/common/Errors.sol"; +import {FORWARD_CALL, DISTRIBUTE_FEE, DEPLOY, WITHDRAW, QUERY, FINALIZE} from "../../utils/common/Constants.sol"; + +/// @title DeliveryHelperStorage +/// @notice Storage contract for DeliveryHelper +abstract contract DeliveryHelperStorage is IDeliveryHelper { + // slots [0-49] reserved for gap + uint256[50] _gap_before; + + // slot 50 + uint256 public saltCounter; + + // slot 51 + uint128 public asyncCounter; + uint128 public bidTimeout; + + // slot 52 + bytes32[] public tempPayloadIds; + + // slot 53 + /// @notice The call parameters array + CallParams[] public callParamsArray; + + // slot 54 + /// @notice The mapping of valid promises + mapping(address => bool) public isValidPromise; + + // slot 55 - payloadIdToBatchHash + mapping(bytes32 => bytes32) public payloadIdToBatchHash; + // slot 56 - payloadIdToPayloadDetails + mapping(bytes32 => PayloadDetails) public payloadIdToPayloadDetails; + + // slot 57 + // asyncId => PayloadDetails[] + mapping(bytes32 => PayloadDetails[]) public payloadBatchDetails; + + // slot 58 + // asyncId => PayloadBatch + mapping(bytes32 => PayloadBatch) internal _payloadBatches; + + // slots [59-108] reserved for gap + uint256[50] _gap_after; +} diff --git a/contracts/protocol/payload-delivery/app-gateway/QueueAsync.sol b/contracts/protocol/payload-delivery/app-gateway/QueueAsync.sol index 85f13c65..c08b0f93 100644 --- a/contracts/protocol/payload-delivery/app-gateway/QueueAsync.sol +++ b/contracts/protocol/payload-delivery/app-gateway/QueueAsync.sol @@ -1,50 +1,22 @@ // SPDX-License-Identifier: Unlicense pragma solidity ^0.8.21; -import {AddressResolverUtil} from "../../../protocol/utils/AddressResolverUtil.sol"; -import {CallParams, Fees, PayloadDetails, CallType, Bid, PayloadBatch, Parallel, IsPlug} from "../../../protocol/utils/common/Structs.sol"; -import {NotAuctionManager, InvalidPromise, InvalidIndex} from "../../../protocol/utils/common/Errors.sol"; -import {AsyncPromise} from "../../AsyncPromise.sol"; -import {IPromise} from "../../../interfaces/IPromise.sol"; -import {IAppDeployer} from "../../../interfaces/IAppDeployer.sol"; -import {IAddressResolver} from "../../../interfaces/IAddressResolver.sol"; -import {IContractFactoryPlug} from "../../../interfaces/IContractFactoryPlug.sol"; -import {IDeliveryHelper} from "../../../interfaces/IDeliveryHelper.sol"; import {Ownable} from "solady/auth/Ownable.sol"; +import "solady/utils/Initializable.sol"; -/// @notice Abstract contract for managing asynchronous payloads -abstract contract QueueAsync is AddressResolverUtil, IDeliveryHelper, Ownable { - uint256 public saltCounter; - uint256 public asyncCounter; - - uint256 public bidTimeout; - - /// @notice The call parameters array - CallParams[] public callParamsArray; - /// @notice The mapping of valid promises - mapping(address => bool) public isValidPromise; +import {AddressResolverUtil} from "../../utils/AddressResolverUtil.sol"; - // payloadId => asyncId - mapping(bytes32 => bytes32) public payloadIdToBatchHash; - mapping(bytes32 => PayloadDetails) public payloadIdToPayloadDetails; +import "./DeliveryHelperStorage.sol"; - // asyncId => PayloadBatch - mapping(bytes32 => PayloadBatch) internal _payloadBatches; +/// @notice Abstract contract for managing asynchronous payloads +abstract contract QueueAsync is DeliveryHelperStorage, Initializable, Ownable, AddressResolverUtil { + // slots [0-108] reserved for delivery helper storage and [109-159] reserved for addr resolver util + // slots [160-209] reserved for gap + uint256[50] _gap_queue_async; event PayloadBatchCancelled(bytes32 asyncId); event BidTimeoutUpdated(uint256 newBidTimeout); - function payloadBatches(bytes32 asyncId_) external view override returns (PayloadBatch memory) { - return _payloadBatches[asyncId_]; - } - - function getPayloadDetails(bytes32 payloadId_) external view returns (PayloadDetails memory) { - return payloadIdToPayloadDetails[payloadId_]; - } - - // asyncId => PayloadDetails[] - mapping(bytes32 => PayloadDetails[]) public payloadBatchDetails; - modifier onlyPromises() { if (!isValidPromise[msg.sender]) revert InvalidPromise(); _; @@ -55,6 +27,14 @@ abstract contract QueueAsync is AddressResolverUtil, IDeliveryHelper, Ownable { _; } + function payloadBatches(bytes32 asyncId_) external view override returns (PayloadBatch memory) { + return _payloadBatches[asyncId_]; + } + + function getPayloadDetails(bytes32 payloadId_) external view returns (PayloadDetails memory) { + return payloadIdToPayloadDetails[payloadId_]; + } + /// @notice Clears the call parameters array function clearQueue() public { delete callParamsArray; @@ -99,6 +79,8 @@ abstract contract QueueAsync is AddressResolverUtil, IDeliveryHelper, Ownable { function _createPayloadDetailsArray( bytes32 sbType_ ) internal returns (PayloadDetails[] memory payloadDetailsArray) { + if (callParamsArray.length == 0) return payloadDetailsArray; + payloadDetailsArray = new PayloadDetails[](callParamsArray.length); for (uint256 i = 0; i < callParamsArray.length; i++) { CallParams memory params = callParamsArray[i]; @@ -164,7 +146,7 @@ abstract contract QueueAsync is AddressResolverUtil, IDeliveryHelper, Ownable { /// @notice Updates the bid timeout /// @param newBidTimeout_ The new bid timeout value - function updateBidTimeout(uint256 newBidTimeout_) external onlyOwner { + function updateBidTimeout(uint128 newBidTimeout_) external onlyOwner { bidTimeout = newBidTimeout_; emit BidTimeoutUpdated(newBidTimeout_); } @@ -184,6 +166,4 @@ abstract contract QueueAsync is AddressResolverUtil, IDeliveryHelper, Ownable { function getAsyncBatchDetails(bytes32 asyncId_) external view returns (PayloadBatch memory) { return _payloadBatches[asyncId_]; } - - uint256[49] __gap; } diff --git a/contracts/protocol/utils/AccessControl.sol b/contracts/protocol/utils/AccessControl.sol index 1f050375..c25f0645 100644 --- a/contracts/protocol/utils/AccessControl.sol +++ b/contracts/protocol/utils/AccessControl.sol @@ -12,9 +12,13 @@ import "solady/auth/Ownable.sol"; abstract contract AccessControl is Ownable { /** * @dev A mapping of roles to a mapping of addresses to boolean values indicating whether or not they have the role. + * @dev slot 0 */ mapping(bytes32 => mapping(address => bool)) private _permits; + // slots 1-50: gap for future storage variables + uint256[50] _gap_access_control; + /** * @dev Emitted when a role is granted to an address. */ diff --git a/contracts/protocol/utils/AddressResolverUtil.sol b/contracts/protocol/utils/AddressResolverUtil.sol index a0dcd789..38005e15 100644 --- a/contracts/protocol/utils/AddressResolverUtil.sol +++ b/contracts/protocol/utils/AddressResolverUtil.sol @@ -11,8 +11,12 @@ import "../../interfaces/IWatcherPrecompile.sol"; abstract contract AddressResolverUtil { /// @notice The address resolver contract reference /// @dev Used to look up system contract addresses + // slot 0 IAddressResolver public addressResolver__; + // slots 1-50 reserved for future use + uint256[50] __gap_resolver_util; + /// @notice Error thrown when an invalid address attempts to call the Payload Delivery only function error OnlyPayloadDelivery(); /// @notice Error thrown when an invalid address attempts to call the Watcher only function @@ -65,7 +69,4 @@ abstract contract AddressResolverUtil { appGateway = addressResolver__.contractsToGateways(originAppGateway_); if (appGateway == address(0)) appGateway = originAppGateway_; } - - // for proxy contracts - uint256[49] __gap_resolver_util; } diff --git a/contracts/protocol/utils/Gauge.sol b/contracts/protocol/utils/Gauge.sol index 1aa4cebe..0a0d7b59 100644 --- a/contracts/protocol/utils/Gauge.sol +++ b/contracts/protocol/utils/Gauge.sol @@ -4,6 +4,9 @@ import {LimitParams} from "../utils/common/Structs.sol"; import {LimitReached} from "../utils/common/Errors.sol"; abstract contract Gauge { + // slot 0-49: gap for future storage variables + uint256[50] _gap_gauge; + function _getCurrentLimit(LimitParams storage params_) internal view returns (uint256 _limit) { uint256 timeElapsed = block.timestamp - params_.lastUpdateTimestamp; uint256 limitIncrease = timeElapsed * params_.ratePerSecond; diff --git a/contracts/protocol/utils/common/Errors.sol b/contracts/protocol/utils/common/Errors.sol index f129a2a2..9a1fe4ca 100644 --- a/contracts/protocol/utils/common/Errors.sol +++ b/contracts/protocol/utils/common/Errors.sol @@ -31,3 +31,11 @@ error FeesNotSet(); error InvalidTokenAddress(); error InvalidWatcherSignature(); error NonceUsed(); +/// @notice Error thrown when trying to start or bid a closed auction +error AuctionClosed(); +/// @notice Error thrown when trying to start an ongoing auction +error AuctionAlreadyStarted(); +/// @notice Error thrown if fees exceed the maximum set fees +error BidExceedsMaxFees(); +/// @notice Error thrown if a lower bid already exists +error LowerBidAlreadyExists(); diff --git a/contracts/protocol/watcherPrecompile/WatcherPrecompile.sol b/contracts/protocol/watcherPrecompile/WatcherPrecompile.sol index 966e5342..d9c55f32 100644 --- a/contracts/protocol/watcherPrecompile/WatcherPrecompile.sol +++ b/contracts/protocol/watcherPrecompile/WatcherPrecompile.sol @@ -2,35 +2,10 @@ pragma solidity ^0.8.21; import "./WatcherPrecompileConfig.sol"; -import "../../interfaces/IAppGateway.sol"; -import "../../interfaces/IPromise.sol"; -import "../../interfaces/IFeesManager.sol"; -import "solady/utils/Initializable.sol"; -import {PayloadDigestParams, AsyncRequest, FinalizeParams, TimeoutRequest, CallFromChainParams} from "../utils/common/Structs.sol"; /// @title WatcherPrecompile /// @notice Contract that handles payload verification, execution and app configurations -contract WatcherPrecompile is WatcherPrecompileConfig, Initializable { - uint256 public maxTimeoutDelayInSeconds; - /// @notice Counter for tracking payload requests - uint256 public payloadCounter; - /// @notice The expiry time for the payload - uint256 public expiryTime; - - /// @notice Mapping to store async requests - /// @dev payloadId => AsyncRequest struct - mapping(bytes32 => AsyncRequest) public asyncRequests; - /// @notice Mapping to store timeout requests - /// @dev timeoutId => TimeoutRequest struct - mapping(bytes32 => TimeoutRequest) public timeoutRequests; - /// @notice Mapping to store watcher proofs - /// @dev payloadId => proof bytes - mapping(bytes32 => bytes) public watcherProofs; - - /// @notice Mapping to store if appGateway has been called with trigger from on-chain Inbox - /// @dev callId => bool - mapping(bytes32 => bool) public appGatewayCalled; - +contract WatcherPrecompile is WatcherPrecompileConfig { /// @notice Error thrown when an invalid chain slug is provided error InvalidChainSlug(); /// @notice Error thrown when an invalid app gateway reaches a plug diff --git a/contracts/protocol/watcherPrecompile/WatcherPrecompileConfig.sol b/contracts/protocol/watcherPrecompile/WatcherPrecompileConfig.sol index 0f855adb..ca5e6fac 100644 --- a/contracts/protocol/watcherPrecompile/WatcherPrecompileConfig.sol +++ b/contracts/protocol/watcherPrecompile/WatcherPrecompileConfig.sol @@ -8,32 +8,8 @@ import {ECDSA} from "solady/utils/ECDSA.sol"; /// @notice Configuration contract for the Watcher Precompile system /// @dev Handles the mapping between networks, plugs, and app gateways for payload execution abstract contract WatcherPrecompileConfig is WatcherPrecompileLimits { - /// @notice Maps network and plug to their configuration - /// @dev chainSlug => plug => PlugConfig - mapping(uint32 => mapping(address => PlugConfig)) internal _plugConfigs; - - /// @notice Maps chain slug to their associated switchboard - /// @dev chainSlug => sb type => switchboard address - mapping(uint32 => mapping(bytes32 => address)) public switchboards; - - /// @notice Maps chain slug to their associated socket - /// @dev chainSlug => socket address - mapping(uint32 => address) public sockets; - - /// @notice Maps chain slug to their associated contract factory plug - /// @dev chainSlug => contract factory plug address - mapping(uint32 => address) public contractFactoryPlug; - - /// @notice Maps chain slug to their associated fees plug - /// @dev chainSlug => fees plug address - mapping(uint32 => address) public feesPlug; - - /// @notice Maps nonce to whether it has been used - /// @dev signatureNonce => isValid - mapping(uint256 => bool) public isNonceUsed; - - // appGateway => chainSlug => plug => isValid - mapping(address => mapping(uint32 => mapping(address => bool))) public isValidPlug; + // slot 322-371: gap for future storage variables + uint256[50] _gap_watcher_precompile_config; /// @notice Emitted when a new plug is configured for an app gateway /// @param appGateway The address of the app gateway @@ -150,6 +126,4 @@ abstract contract WatcherPrecompileConfig is WatcherPrecompileLimits { address signer = ECDSA.recover(digest, signature_); if (signer != owner()) revert InvalidWatcherSignature(); } - - uint256[49] __gap_config; } diff --git a/contracts/protocol/watcherPrecompile/WatcherPrecompileLimits.sol b/contracts/protocol/watcherPrecompile/WatcherPrecompileLimits.sol index 8bc10bc9..7f62d344 100644 --- a/contracts/protocol/watcherPrecompile/WatcherPrecompileLimits.sol +++ b/contracts/protocol/watcherPrecompile/WatcherPrecompileLimits.sol @@ -3,35 +3,26 @@ pragma solidity ^0.8.21; import {AccessControl} from "../utils/AccessControl.sol"; import {Gauge} from "../utils/Gauge.sol"; -import {LimitParams, UpdateLimitParams} from "../utils/common/Structs.sol"; import {AddressResolverUtil} from "../utils/AddressResolverUtil.sol"; -import {QUERY, FINALIZE, SCHEDULE} from "../utils/common/Constants.sol"; -import "../../interfaces/IWatcherPrecompile.sol"; import {WATCHER_ROLE} from "../utils/common/AccessRoles.sol"; -import {TimeoutDelayTooLarge, TimeoutAlreadyResolved, InvalidInboxCaller, ResolvingTimeoutTooEarly, CallFailed, AppGatewayAlreadyCalled, InvalidWatcherSignature, NonceUsed} from "../utils/common/Errors.sol"; +import "./WatcherPrecompileStorage.sol"; +import "solady/utils/Initializable.sol"; abstract contract WatcherPrecompileLimits is - Gauge, - AddressResolverUtil, + WatcherPrecompileStorage, + Initializable, AccessControl, - IWatcherPrecompile + Gauge, + AddressResolverUtil { - /// @notice Number of decimals used in limit calculations - uint256 public constant LIMIT_DECIMALS = 18; - - /// @notice Default limit value for any app gateway - uint256 public defaultLimit; - /// @notice Rate at which limit replenishes per second - uint256 public defaultRatePerSecond; - - /// @notice The chain slug of the watcher precompile - uint32 public evmxSlug; - - // appGateway => limitType => receivingLimitParams - mapping(address => mapping(bytes32 => LimitParams)) internal _limitParams; - - // Mapping to track active app gateways - mapping(address => bool) private _activeAppGateways; + // Slots from parent contracts: + // slot 0-118: watcher precompile storage + // 0 slots for initializable and ownable + // slots 119-169: access control (gap + 1) + // slots 170-219: gauge (gap) + // slots 220-270: address resolver util (gap + 1) + // slots 271-320: gap for future storage variables + uint256[50] _gap_watcher_precompile_limits; //////////////////////////////////////////////////////// ////////////////////// EVENTS ////////////////////////// @@ -183,6 +174,4 @@ abstract contract WatcherPrecompileLimits is function setDefaultRatePerSecond(uint256 defaultRatePerSecond_) external onlyOwner { defaultRatePerSecond = defaultRatePerSecond_; } - - uint256[49] __gap; } diff --git a/contracts/protocol/watcherPrecompile/WatcherPrecompileStorage.sol b/contracts/protocol/watcherPrecompile/WatcherPrecompileStorage.sol new file mode 100644 index 00000000..e5142824 --- /dev/null +++ b/contracts/protocol/watcherPrecompile/WatcherPrecompileStorage.sol @@ -0,0 +1,102 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.0; + +import {IWatcherPrecompile} from "../../interfaces/IWatcherPrecompile.sol"; +import {IAppGateway} from "../../interfaces/IAppGateway.sol"; +import {IFeesManager} from "../../interfaces/IFeesManager.sol"; +import {IPromise} from "../../interfaces/IPromise.sol"; + +import {QUERY, FINALIZE, SCHEDULE} from "../utils/common/Constants.sol"; +import {TimeoutDelayTooLarge, TimeoutAlreadyResolved, InvalidInboxCaller, ResolvingTimeoutTooEarly, CallFailed, AppGatewayAlreadyCalled, InvalidWatcherSignature, NonceUsed} from "../utils/common/Errors.sol"; +import {ResolvedPromises, AppGatewayConfig, LimitParams, UpdateLimitParams, PlugConfig, PayloadDigestParams, AsyncRequest, FinalizeParams, TimeoutRequest, CallFromChainParams} from "../utils/common/Structs.sol"; + +abstract contract WatcherPrecompileStorage is IWatcherPrecompile { + // slot 0-49: gap for future storage variables + uint256[50] _gap_before; + + /// @notice Number of decimals used in limit calculations + uint256 public constant LIMIT_DECIMALS = 18; + + // slot 50: defaultLimit + /// @notice Default limit value for any app gateway + uint256 public defaultLimit; + // slot 51: defaultRatePerSecond + /// @notice Rate at which limit replenishes per second + uint256 public defaultRatePerSecond; + + // slot 52: evmxSlug + /// @notice The chain slug of the watcher precompile + uint32 public evmxSlug; + + // slot 53: _limitParams + // appGateway => limitType => receivingLimitParams + mapping(address => mapping(bytes32 => LimitParams)) internal _limitParams; + + // slot 54: _activeAppGateways + // Mapping to track active app gateways + mapping(address => bool) internal _activeAppGateways; + + // slot 55: _plugConfigs + /// @notice Maps network and plug to their configuration + /// @dev chainSlug => plug => PlugConfig + mapping(uint32 => mapping(address => PlugConfig)) internal _plugConfigs; + + // slot 56: switchboards + /// @notice Maps chain slug to their associated switchboard + /// @dev chainSlug => sb type => switchboard address + mapping(uint32 => mapping(bytes32 => address)) public switchboards; + + // slot 57: sockets + /// @notice Maps chain slug to their associated socket + /// @dev chainSlug => socket address + mapping(uint32 => address) public sockets; + + // slot 58: contractFactoryPlug + /// @notice Maps chain slug to their associated contract factory plug + /// @dev chainSlug => contract factory plug address + mapping(uint32 => address) public contractFactoryPlug; + + // slot 59: feesPlug + /// @notice Maps chain slug to their associated fees plug + /// @dev chainSlug => fees plug address + mapping(uint32 => address) public feesPlug; + + // slot 60: isNonceUsed + /// @notice Maps nonce to whether it has been used + /// @dev signatureNonce => isValid + mapping(uint256 => bool) public isNonceUsed; + + // slot 61: isValidPlug + // appGateway => chainSlug => plug => isValid + mapping(address => mapping(uint32 => mapping(address => bool))) public isValidPlug; + + // slot 62: maxTimeoutDelayInSeconds + uint256 public maxTimeoutDelayInSeconds; + // slot 63: payloadCounter + /// @notice Counter for tracking payload requests + uint256 public payloadCounter; + // slot 64: expiryTime + /// @notice The expiry time for the payload + uint256 public expiryTime; + + // slot 65: asyncRequests + /// @notice Mapping to store async requests + /// @dev payloadId => AsyncRequest struct + mapping(bytes32 => AsyncRequest) public asyncRequests; + // slot 66: timeoutRequests + /// @notice Mapping to store timeout requests + /// @dev timeoutId => TimeoutRequest struct + mapping(bytes32 => TimeoutRequest) public timeoutRequests; + // slot 67: watcherProofs + /// @notice Mapping to store watcher proofs + /// @dev payloadId => proof bytes + mapping(bytes32 => bytes) public watcherProofs; + + // slot 68: appGatewayCalled + /// @notice Mapping to store if appGateway has been called with trigger from on-chain Inbox + /// @dev callId => bool + mapping(bytes32 => bool) public appGatewayCalled; + + // slots 69-118: gap for future storage variables + uint256[50] _gap_after; +} diff --git a/script/AppGatewayFeeBalance.s.sol b/script/AppGatewayFeeBalance.s.sol index 70934724..cb773904 100644 --- a/script/AppGatewayFeeBalance.s.sol +++ b/script/AppGatewayFeeBalance.s.sol @@ -3,7 +3,7 @@ pragma solidity ^0.8.0; import {Script} from "forge-std/Script.sol"; import {console} from "forge-std/console.sol"; -import {FeesManager} from "../contracts/protocol/payload-delivery/app-gateway/FeesManager.sol"; +import {FeesManager} from "../contracts/protocol/payload-delivery/FeesManager.sol"; import {Fees} from "../contracts/protocol/utils/common/Structs.sol"; import {ETH_ADDRESS} from "../contracts/protocol/utils/common/Constants.sol"; diff --git a/script/counter/WithdrawFeesArbitrumFeesPlug.s.sol b/script/counter/WithdrawFeesArbitrumFeesPlug.s.sol index 098f58bc..a7dbc9f1 100644 --- a/script/counter/WithdrawFeesArbitrumFeesPlug.s.sol +++ b/script/counter/WithdrawFeesArbitrumFeesPlug.s.sol @@ -3,7 +3,7 @@ pragma solidity ^0.8.0; import {Script} from "forge-std/Script.sol"; import {console} from "forge-std/console.sol"; -import {FeesManager} from "../../contracts/protocol/payload-delivery/app-gateway/FeesManager.sol"; +import {FeesManager} from "../../contracts/protocol/payload-delivery/FeesManager.sol"; import {ETH_ADDRESS} from "../../contracts/protocol/utils/common/Constants.sol"; import {CounterAppGateway} from "../../contracts/apps/counter/CounterAppGateway.sol"; diff --git a/test/DeliveryHelper.t.sol b/test/DeliveryHelper.t.sol index 0214ddbb..96639dd0 100644 --- a/test/DeliveryHelper.t.sol +++ b/test/DeliveryHelper.t.sol @@ -2,8 +2,8 @@ pragma solidity ^0.8.0; import "../contracts/protocol/payload-delivery/app-gateway/DeliveryHelper.sol"; -import "../contracts/protocol/payload-delivery/app-gateway/FeesManager.sol"; -import "../contracts/protocol/payload-delivery/app-gateway/AuctionManager.sol"; +import "../contracts/protocol/payload-delivery/FeesManager.sol"; +import "../contracts/protocol/payload-delivery/AuctionManager.sol"; import "../contracts/protocol/Forwarder.sol"; import "../contracts/interfaces/IAppDeployer.sol"; diff --git a/test/Storage.t.sol b/test/Storage.t.sol new file mode 100644 index 00000000..d5e91d60 --- /dev/null +++ b/test/Storage.t.sol @@ -0,0 +1,75 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.0; + +import "./DeliveryHelper.t.sol"; + +contract StorageTest is DeliveryHelperTest { + DeliveryHelper public deliveryHelperImpl; + + function setUp() public { + setUpDeliveryHelper(); + } + + function testAddressResolverSlot() public { + // Test AddressResolver version at slot 59 + bytes32 versionSlot = vm.load(address(addressResolver), bytes32(uint256(59))); + assertEq(uint64(uint256(versionSlot)), 1); + } + + function testWatcherPrecompileSlot() public { + // Test AddressResolver address at slot 109 in WatcherPrecompile + bytes32 slotValue = vm.load(address(watcherPrecompile), bytes32(uint256(52))); + assertEq(uint256(slotValue), evmxSlug); + + slotValue = vm.load(address(watcherPrecompile), bytes32(uint256(220))); + assertEq(address(uint160(uint256(slotValue))), address(addressResolver)); + } + + function testFeesManagerSlot() public { + bytes32 slotValue = vm.load(address(feesManager), bytes32(uint256(51))); + assertEq(uint32(uint256(slotValue)), evmxSlug); + + slotValue = vm.load(address(feesManager), bytes32(uint256(106))); + assertEq(address(uint160(uint256(slotValue))), address(addressResolver)); + } + + function testAuctionManagerSlot() public { + bytes32 slotValue = vm.load(address(auctionManager), bytes32(uint256(50))); + assertEq(uint32(uint256(slotValue)), evmxSlug); + + slotValue = vm.load(address(auctionManager), bytes32(uint256(105))); + assertEq(address(uint160(uint256(slotValue))), address(addressResolver)); + } + + function testForwarderSlot() public { + address forwarder = addressResolver.getOrDeployForwarderContract( + address(this), + address(this), + evmxSlug + ); + + bytes32 slotValue = vm.load(address(forwarder), bytes32(uint256(50))); + assertEq(uint32(uint256(slotValue)), evmxSlug); + + slotValue = vm.load(address(forwarder), bytes32(uint256(53))); + assertEq(address(uint160(uint256(slotValue))), address(0)); + } + + function testAsyncPromiseSlot() public { + address asyncPromise = addressResolver.deployAsyncPromiseContract(address(this)); + + bytes32 slotValue = vm.load(address(asyncPromise), bytes32(uint256(51))); + assertEq(address(uint160(uint256(slotValue))), address(this)); + + slotValue = vm.load(address(asyncPromise), bytes32(uint256(103))); + assertEq(address(uint160(uint256(slotValue))), address(addressResolver)); + } + + function testDeliveryHelperSlot() public { + bytes32 slotValue = vm.load(address(deliveryHelper), bytes32(uint256(50))); + assertEq(uint256(uint256(slotValue)), 0); + + slotValue = vm.load(address(deliveryHelper), bytes32(uint256(109))); + assertEq(address(uint160(uint256(slotValue))), address(addressResolver)); + } +} diff --git a/test/mock/MockSocket.sol b/test/mock/MockSocket.sol new file mode 100644 index 00000000..98c87f09 --- /dev/null +++ b/test/mock/MockSocket.sol @@ -0,0 +1,181 @@ +// SPDX-License-Identifier: GPL-3.0-only +pragma solidity ^0.8.21; + +import {PlugDisconnected, InvalidAppGateway} from "../../contracts/protocol/utils/common/Errors.sol"; +import "../../contracts/interfaces/ISwitchboard.sol"; +import "../../contracts/interfaces/ISocket.sol"; + +/** + * @title SocketDst + * @dev SocketDst is an abstract contract that inherits from SocketUtils and + * provides functionality for payload execution, verification. + * It manages the mapping of payload execution status + * timestamps + * It also includes functions for payload execution and verification + */ +contract MockSocket is ISocket { + struct PlugConfig { + // address of the sibling plug on the remote chain + address appGateway; + // switchboard instance for the plug connection + ISwitchboard switchboard__; + } + + // plug => (appGateway, switchboard__) + mapping(address => PlugConfig) internal _plugConfigs; + + function getPlugConfig( + address plugAddress_ + ) external view returns (address appGateway, address switchboard__) { + PlugConfig memory _plugConfig = _plugConfigs[plugAddress_]; + return (_plugConfig.appGateway, address(_plugConfig.switchboard__)); + } + + function connect(address appGateway_, address switchboard_) external override {} + + function registerSwitchboard() external override {} + + //////////////////////////////////////////////////////// + ////////////////////// ERRORS ////////////////////////// + //////////////////////////////////////////////////////// + /** + * @dev Error emitted when proof is invalid + */ + + /** + * @dev Error emitted when a payload has already been executed + */ + error PayloadAlreadyExecuted(); + /** + * @dev Error emitted when the executor is not valid + */ + /** + * @dev Error emitted when verification fails + */ + error VerificationFailed(); + /** + * @dev Error emitted when source slugs deduced from packet id and msg id don't match + */ + /** + * @dev Error emitted when less gas limit is provided for execution than expected + */ + error LowGasLimit(); + error InvalidSlug(); + + //////////////////////////////////////////////////////////// + ////////////////////// State Vars ////////////////////////// + //////////////////////////////////////////////////////////// + uint64 public callCounter; + uint32 public chainSlug; + + enum ExecutionStatus { + NotExecuted, + Executed, + Reverted + } + + /** + * @dev keeps track of whether a payload has been executed or not using payload id + */ + mapping(bytes32 => ExecutionStatus) public payloadExecuted; + + constructor(uint32 chainSlug_, address, address, address, string memory) { + chainSlug = chainSlug_; + } + + //////////////////////////////////////////////////////// + ////////////////////// OPERATIONS ////////////////////////// + //////////////////////////////////////////////////////// + + /** + * @notice To send message to a connected remote chain. Should only be called by a plug. + * @param payload bytes to be delivered to the Plug on the siblingChainSlug_ + * @param params a 32 bytes param to add details for execution, for eg: fees to be paid for execution + */ + function callAppGateway( + bytes calldata payload, + bytes32 params + ) external returns (bytes32 callId) { + PlugConfig memory plugConfig = _plugConfigs[msg.sender]; + // creates a unique ID for the message + callId = _encodeCallId(plugConfig.appGateway); + emit AppGatewayCallRequested( + callId, + chainSlug, + msg.sender, + plugConfig.appGateway, + params, + payload + ); + } + + /** + * @notice Executes a payload that has been delivered by transmitters and authenticated by switchboards + */ + function execute( + address, + ExecuteParams memory params_, + bytes memory + ) external payable returns (bytes memory) { + // execute payload + return + _execute(params_.target, params_.payloadId, params_.executionGasLimit, params_.payload); + } + + //////////////////////////////////////////////////////// + ////////////////// INTERNAL FUNCS ////////////////////// + //////////////////////////////////////////////////////// + + function _verify( + bytes32 digest_, + bytes32 payloadId_, + ISwitchboard switchboard__ + ) internal view { + // NOTE: is the the first un-trusted call in the system, another one is Plug.call + if (!switchboard__.allowPacket(digest_, payloadId_)) revert VerificationFailed(); + } + + /** + * This function assumes localPlug_ will have code while executing. As the payload + * execution failure is not blocking the system, it is not necessary to check if + * code exists in the given address. + */ + function _execute( + address, + bytes32 payloadId_, + uint256, + bytes memory + ) internal returns (bytes memory) { + bytes memory returnData = hex"00010203"; + emit ExecutionSuccess(payloadId_, returnData); + return returnData; + } + + /** + * @dev Decodes the switchboard address from a given payload id. + * @param id_ The ID of the msg to decode the switchboard from. + * @return switchboard_ The address of switchboard decoded from the payload ID. + */ + function _decodeSwitchboard(bytes32 id_) internal pure returns (address switchboard_) { + switchboard_ = address(uint160(uint256(id_) >> 64)); + } + + /** + * @dev Decodes the chain ID from a given packet/payload ID. + * @param id_ The ID of the packet/msg to decode the chain slug from. + * @return chainSlug_ The chain slug decoded from the packet/payload ID. + */ + function _decodeChainSlug(bytes32 id_) internal pure returns (uint32 chainSlug_) { + chainSlug_ = uint32(uint256(id_) >> 224); + } + + // Packs the local plug, local chain slug, remote chain slug and nonce + // callCount++ will take care of call id overflow as well + // callId(256) = localChainSlug(32) | appGateway_(160) | nonce(64) + function _encodeCallId(address appGateway_) internal returns (bytes32) { + return + bytes32( + (uint256(chainSlug) << 224) | (uint256(uint160(appGateway_)) << 64) | callCounter++ + ); + } +} diff --git a/test/mock/MockWatcherPrecompile.sol b/test/mock/MockWatcherPrecompile.sol new file mode 100644 index 00000000..d1886b89 --- /dev/null +++ b/test/mock/MockWatcherPrecompile.sol @@ -0,0 +1,245 @@ +// SPDX-License-Identifier: Unlicense +pragma solidity ^0.8.21; + +import "../../contracts/interfaces/IAppGateway.sol"; +import "../../contracts/interfaces/IWatcherPrecompile.sol"; +import "../../contracts/interfaces/IPromise.sol"; + +import {PayloadDigestParams, AsyncRequest, FinalizeParams, TimeoutRequest, CallFromChainParams, PlugConfig, ResolvedPromises, AppGatewayConfig} from "../../contracts/protocol/utils/common/Structs.sol"; +import {QUERY, FINALIZE, SCHEDULE} from "../../contracts/protocol/utils/common/Constants.sol"; +import {TimeoutDelayTooLarge, TimeoutAlreadyResolved, InvalidInboxCaller, ResolvingTimeoutTooEarly, CallFailed, AppGatewayAlreadyCalled} from "../../contracts/protocol/utils/common/Errors.sol"; +import "solady/utils/ERC1967Factory.sol"; + +/// @title WatcherPrecompile +/// @notice Contract that handles payload verification, execution and app configurations +contract MockWatcherPrecompile { + uint256 public maxTimeoutDelayInSeconds = 24 * 60 * 60; // 24 hours + /// @notice Counter for tracking query requests + uint256 public queryCounter; + /// @notice Counter for tracking payload execution requests + uint256 public payloadCounter; + /// @notice Counter for tracking timeout requests + uint256 public timeoutCounter; + /// @notice Mapping to store async requests + /// @dev payloadId => AsyncRequest struct + mapping(bytes32 => AsyncRequest) public asyncRequests; + /// @notice Mapping to store timeout requests + /// @dev timeoutId => TimeoutRequest struct + mapping(bytes32 => TimeoutRequest) public timeoutRequests; + + mapping(uint32 => mapping(address => PlugConfig)) internal _plugConfigs; + + /// @notice Error thrown when an invalid chain slug is provided + error InvalidChainSlug(); + error InvalidTransmitter(); + + event CalledAppGateway( + bytes32 callId, + uint32 chainSlug, + address plug, + address appGateway, + bytes32 params, + bytes payload + ); + + /// @notice Emitted when a new query is requested + /// @param chainSlug The identifier of the destination chain + /// @param targetAddress The address of the target contract + /// @param payloadId The unique identifier for the query + /// @param payload The query data + event QueryRequested(uint32 chainSlug, address targetAddress, bytes32 payloadId, bytes payload); + + /// @notice Emitted when a finalize request is made + /// @param payloadId The unique identifier for the request + /// @param asyncRequest The async request details + event FinalizeRequested(bytes32 indexed payloadId, AsyncRequest asyncRequest); + + /// @notice Emitted when a request is finalized + /// @param payloadId The unique identifier for the request + /// @param asyncRequest The async request details + /// @param proof The proof from the watcher + event Finalized(bytes32 indexed payloadId, AsyncRequest asyncRequest, bytes proof); + + /// @notice Emitted when a promise is resolved + /// @param payloadId The unique identifier for the resolved promise + event PromiseResolved(bytes32 indexed payloadId); + + event TimeoutRequested( + bytes32 timeoutId, + address target, + bytes payload, + uint256 executeAt // Epoch time when the task should execute + ); + + /// @notice Emitted when a timeout is resolved + /// @param timeoutId The unique identifier for the timeout + /// @param target The target address for the timeout + /// @param payload The payload data + /// @param executedAt The epoch time when the task was executed + event TimeoutResolved(bytes32 timeoutId, address target, bytes payload, uint256 executedAt); + + /// @notice Contract constructor + /// @param _owner Address of the contract owner + constructor(address _owner, address addressResolver_) {} + + // ================== Timeout functions ================== + + /// @notice Sets a timeout for a payload execution on app gateway + /// @param payload_ The payload data + /// @param delayInSeconds_ The delay in seconds + function setTimeout(bytes calldata payload_, uint256 delayInSeconds_) external { + uint256 executeAt = block.timestamp + delayInSeconds_; + bytes32 timeoutId = _encodeTimeoutId(timeoutCounter++); + timeoutRequests[timeoutId] = TimeoutRequest( + timeoutId, + msg.sender, + delayInSeconds_, + executeAt, + 0, + false, + payload_ + ); + emit TimeoutRequested(timeoutId, msg.sender, payload_, executeAt); + } + + /// @notice Ends the timeouts and calls the target address with the callback payload + /// @param timeoutId The unique identifier for the timeout + /// @dev Only callable by the contract owner + function resolveTimeout(bytes32 timeoutId) external { + TimeoutRequest storage timeoutRequest = timeoutRequests[timeoutId]; + + (bool success, ) = address(timeoutRequest.target).call(timeoutRequest.payload); + if (!success) revert CallFailed(); + emit TimeoutResolved( + timeoutId, + timeoutRequest.target, + timeoutRequest.payload, + block.timestamp + ); + } + + // ================== Finalize functions ================== + + /// @notice Finalizes a payload request, requests the watcher to release the proofs to execute on chain + /// @param params_ The finalization parameters + /// @return payloadId The unique identifier for the finalized request + /// @return digest The digest of the payload parameters + function finalize( + FinalizeParams memory params_ + ) external returns (bytes32 payloadId, bytes32 digest) { + digest = keccak256(abi.encode(block.timestamp)); + // Generate a unique payload ID by combining chain, target, and counter + payloadId = encodePayloadId( + params_.payloadDetails.chainSlug, + params_.payloadDetails.target, + payloadCounter++ + ); + address[] memory next = new address[](1); + emit FinalizeRequested( + payloadId, + AsyncRequest( + msg.sender, + address(0), + address(0), + params_.payloadDetails.target, + address(0), + 0, + block.timestamp + 1000, + params_.asyncId, + bytes32(0), + bytes(""), + next + ) + ); + } + + // ================== Query functions ================== + /// @notice Creates a new query request + /// @param chainSlug The identifier of the destination chain + /// @param targetAddress The address of the target contract + /// @param payload The query payload data + /// @return payloadId The unique identifier for the query + function query( + uint32 chainSlug, + address targetAddress, + address[] memory, + bytes memory payload + ) public returns (bytes32 payloadId) { + payloadId = bytes32(queryCounter++); + emit QueryRequested(chainSlug, targetAddress, payloadId, payload); + } + + /// @notice Marks a request as finalized with a proof + /// @param payloadId_ The unique identifier of the request + /// @param proof_ The watcher's proof + /// @dev Only callable by the contract owner + function finalized(bytes32 payloadId_, bytes calldata proof_) external { + emit Finalized(payloadId_, asyncRequests[payloadId_], proof_); + } + + /// @notice Resolves multiple promises with their return data + /// @param resolvedPromises_ Array of resolved promises and their return data + /// @dev Only callable by the contract owner + function resolvePromises(ResolvedPromises[] calldata resolvedPromises_) external { + for (uint256 i = 0; i < resolvedPromises_.length; i++) { + emit PromiseResolved(resolvedPromises_[i].payloadId); + } + } + + // ================== On-Chain Inbox ================== + + function callAppGateways(CallFromChainParams[] calldata params_) external { + for (uint256 i = 0; i < params_.length; i++) { + emit CalledAppGateway( + params_[i].callId, + params_[i].chainSlug, + params_[i].plug, + params_[i].appGateway, + params_[i].params, + params_[i].payload + ); + } + } + + /// @notice Encodes a unique payload ID from chain slug, plug address, and counter + /// @param chainSlug_ The identifier of the chain + /// @param plug_ The plug address + /// @param counter_ The current counter value + /// @return The encoded payload ID as bytes32 + /// @dev Reverts if chainSlug is 0 + function encodePayloadId( + uint32 chainSlug_, + address plug_, + uint256 counter_ + ) internal view returns (bytes32) { + if (chainSlug_ == 0) revert InvalidChainSlug(); + (, address switchboard) = getPlugConfigs(chainSlug_, plug_); + // Encode payload ID by bit-shifting and combining: + // chainSlug (32 bits) | switchboard address (160 bits) | counter (64 bits) + + return + bytes32( + (uint256(chainSlug_) << 224) | (uint256(uint160(switchboard)) << 64) | counter_ + ); + } + + function _encodeTimeoutId(uint256 timeoutCounter_) internal view returns (bytes32) { + // watcher address (160 bits) | counter (64 bits) + return bytes32((uint256(uint160(address(this))) << 64) | timeoutCounter_); + } + + /// @notice Retrieves the configuration for a specific plug on a network + /// @param chainSlug_ The identifier of the network + /// @param plug_ The address of the plug + /// @return The app gateway address and switchboard address for the plug + /// @dev Returns zero addresses if configuration doesn't exist + function getPlugConfigs( + uint32 chainSlug_, + address plug_ + ) public view returns (address, address) { + return ( + _plugConfigs[chainSlug_][plug_].appGateway, + _plugConfigs[chainSlug_][plug_].switchboard + ); + } +}