diff --git a/Errors.md b/Errors.md index 018c142e..e374a824 100644 --- a/Errors.md +++ b/Errors.md @@ -8,28 +8,21 @@ ## interfaces/IWatcherPrecompile.sol -| Error | Signature | -| --------------------------- | ------------ | -| `InvalidChainSlug()` | `0xbff6b106` | -| `InvalidConnection()` | `0x63228f29` | -| `InvalidTransmitter()` | `0x58a70a0a` | -| `InvalidTimeoutRequest()` | `0x600ca372` | -| `InvalidPayloadId()` | `0xfa0b8c86` | -| `InvalidCaller()` | `0x48f5c3ed` | -| `InvalidGateway()` | `0xfc9dfe85` | -| `InvalidSwitchboard()` | `0xf63c9e4d` | -| `RequestAlreadyCancelled()` | `0xc70f47d8` | -| `RequestCancelled()` | `0xe3cf2258` | -| `AlreadyStarted()` | `0x1fbde445` | -| `InvalidLevelNumber()` | `0x5022f14b` | - -## interfaces/IWatcherPrecompileLimits.sol - -| Error | Signature | -| ------------------------------------------------ | ------------ | -| `ActionNotSupported(address,bytes32)` | `0xa219158f` | -| `NotDeliveryHelper()` | `0x29029c67` | -| `LimitExceeded(address,bytes32,uint256,uint256)` | `0x80bb2621` | +| Error | Signature | +| ------------------------------------- | ------------ | +| `InvalidChainSlug()` | `0xbff6b106` | +| `InvalidConnection()` | `0x63228f29` | +| `InvalidTimeoutRequest()` | `0x600ca372` | +| `InvalidPayloadId()` | `0xfa0b8c86` | +| `InvalidCaller()` | `0x48f5c3ed` | +| `InvalidGateway()` | `0xfc9dfe85` | +| `InvalidSwitchboard()` | `0xf63c9e4d` | +| `RequestAlreadyCancelled()` | `0xc70f47d8` | +| `RequestCancelled()` | `0xe3cf2258` | +| `AlreadyStarted()` | `0x1fbde445` | +| `RequestNotProcessing()` | `0x07ba8aaa` | +| `InvalidLevelNumber()` | `0x5022f14b` | +| `DeadlineNotPassedForOnChainRevert()` | `0x7006aa10` | ## protocol/AddressResolver.sol @@ -46,12 +39,6 @@ | `PromiseAlreadySetUp()` | `0x927c53d5` | | `PromiseRevertFailed()` | `0x0175b9de` | -## protocol/Forwarder.sol - -| Error | Signature | -| ------------------------ | ------------ | -| `AsyncModifierNotUsed()` | `0xb9521e1a` | - ## protocol/payload-delivery/AuctionManager.sol | Error | Signature | @@ -61,44 +48,50 @@ ## protocol/payload-delivery/ContractFactoryPlug.sol -| Error | Signature | -| ------------------------- | ------------ | -| `DeploymentFailed()` | `0x30116425` | -| `ExecutionFailed()` | `0xacfdb444` | -| `information(bool,bytes)` | `0x1a5c6d63` | +| Error | Signature | +| -------------------------- | ------------ | +| `DeploymentFailed()` | `0x30116425` | +| `ExecutionFailed()` | `0xacfdb444` | +| `information(bool,,bytes)` | `0x3a82a1f3` | ## protocol/payload-delivery/FeesManager.sol -| Error | Signature | -| ----------------------------- | ------------ | -| `InsufficientFeesAvailable()` | `0x51488f54` | -| `NoFeesForTransmitter()` | `0x248bac55` | -| `NoFeesBlocked()` | `0x116d68f9` | -| `InvalidWatcherSignature()` | `0x5029f14f` | -| `NonceUsed()` | `0x1f6d5aef` | -| `InvalidCaller()` | `0x48f5c3ed` | +| Error | Signature | +| -------------------------------- | ------------ | +| `InsufficientCreditsAvailable()` | `0xe61dc0aa` | +| `NoFeesForTransmitter()` | `0x248bac55` | +| `NoCreditsBlocked()` | `0xada9eb4c` | +| `InvalidCaller()` | `0x48f5c3ed` | +| `InvalidUserSignature()` | `0xe3fb657c` | +| `AppGatewayNotWhitelisted()` | `0x84e5309f` | +| `InvalidAmount()` | `0x2c5211c6` | +| `InsufficientBalance()` | `0xf4d678b8` | ## protocol/payload-delivery/FeesPlug.sol -| Error | Signature | -| ----------------------------------- | ------------ | -| `FeesAlreadyPaid()` | `0xd3b1ad69` | -| `InsufficientTokenBalance(address)` | `0x642faafa` | -| `InvalidDepositAmount()` | `0xfe9ba5cd` | -| `TokenNotWhitelisted(address)` | `0xea3bff2e` | +| Error | Signature | +| --------------------------------------------------- | ------------ | +| `InsufficientTokenBalance(address,uint256,uint256)` | `0xebd6ced9` | +| `InvalidDepositAmount()` | `0xfe9ba5cd` | +| `TokenNotWhitelisted(address)` | `0xea3bff2e` | ## protocol/payload-delivery/app-gateway/DeliveryUtils.sol -| Error | Signature | -| ----------------------- | ------------ | -| `AllPayloadsExecuted()` | `0x6bc43bfe` | -| `NotFromForwarder()` | `0xe83aa6bd` | -| `CallFailed(bytes32)` | `0xe22e3683` | -| `PayloadTooLarge()` | `0x492f620d` | -| `OnlyAppGateway()` | `0xfec944ea` | -| `WinningBidExists()` | `0xe8733654` | -| `InsufficientFees()` | `0x8d53e553` | -| `ReadOnlyRequests()` | `0x5f16b0e6` | +| Error | Signature | +| ------------------------------------ | ------------ | +| `PayloadTooLarge()` | `0x492f620d` | +| `OnlyAppGateway()` | `0xfec944ea` | +| `WinningBidExists()` | `0xe8733654` | +| `InsufficientFees()` | `0x8d53e553` | +| `ReadOnlyRequests()` | `0x5f16b0e6` | +| `RequestPayloadCountLimitExceeded()` | `0xcbef144b` | +| `MaxMsgValueLimitExceeded()` | `0x97b4e8ce` | + +## protocol/payload-delivery/app-gateway/FeesHelpers.sol + +| Error | Signature | +| --------------------------------------------- | ------------ | +| `NewMaxFeesLowerThanCurrent(uint256,uint256)` | `0x1345dda1` | ## protocol/socket/Socket.sol @@ -109,6 +102,8 @@ | `LowGasLimit()` | `0xd38edae0` | | `InvalidSlug()` | `0x290a8315` | | `DeadlinePassed()` | `0x70f65caa` | +| `InsufficientMsgValue()` | `0x78f38f76` | +| `ReadOnlyCall()` | `0xcf8fd6f1` | ## protocol/socket/SocketConfig.sol @@ -119,11 +114,12 @@ | `SwitchboardExists()` | `0x2dff8555` | | `SwitchboardExistsOrDisabled()` | `0x1c7d2487` | -## protocol/socket/SocketUtils.sol +## protocol/socket/SocketFeeManager.sol -| Error | Signature | -| ---------------------- | ------------ | -| `InvalidTransmitter()` | `0x58a70a0a` | +| Error | Signature | +| -------------------- | ------------ | +| `InsufficientFees()` | `0x8d53e553` | +| `FeeTooLow()` | `0x732f9413` | ## protocol/socket/switchboard/FastSwitchboard.sol @@ -132,12 +128,6 @@ | `AlreadyAttested()` | `0x35d90805` | | `WatcherNotFound()` | `0xa278e4ad` | -## protocol/socket/switchboard/SwitchboardBase.sol - -| Error | Signature | -| ---------------- | ------------ | -| `InvalidNonce()` | `0x756688fe` | - ## protocol/utils/AccessControl.sol | Error | Signature | @@ -156,17 +146,8 @@ | Error | Signature | | ---------------------------- | ------------ | -| `NotAuthorized()` | `0xea8e4eb5` | -| `NotBridge()` | `0x7fea9dc5` | | `NotSocket()` | `0xc59f8f7c` | -| `ConnectorUnavailable()` | `0xb1efb84a` | -| `InvalidTokenContract()` | `0x29bdfb34` | -| `ZeroAddressReceiver()` | `0x96bbcf1e` | | `ZeroAddress()` | `0xd92e233d` | -| `ZeroAmount()` | `0x1f2a2005` | -| `InsufficientFunds()` | `0x356680b7` | -| `InvalidSigner()` | `0x815e1d64` | -| `InvalidFunction()` | `0xdb2079c3` | | `TimeoutDelayTooLarge()` | `0xc10bfe64` | | `TimeoutAlreadyResolved()` | `0x7dc8be06` | | `ResolvingTimeoutTooEarly()` | `0x28fd4c50` | @@ -174,13 +155,13 @@ | `FeesAlreadyPaid()` | `0xd3b1ad69` | | `NotAuctionManager()` | `0x87944c26` | | `CallFailed()` | `0x3204506f` | -| `PlugDisconnected()` | `0xe741bafb` | +| `PlugNotFound()` | `0x5f1ac76a` | | `InvalidAppGateway()` | `0x82ded261` | | `AppGatewayAlreadyCalled()` | `0xb224683f` | | `InvalidInboxCaller()` | `0x4f1aa61e` | +| `InvalidCallerTriggered()` | `0x3292d247` | | `PromisesNotResolved()` | `0xb91dbe7d` | | `InvalidPromise()` | `0x45f2d176` | -| `InvalidIndex()` | `0x63df8171` | | `InvalidTransmitter()` | `0x58a70a0a` | | `FeesNotSet()` | `0x2a831034` | | `InvalidTokenAddress()` | `0x1eb00b06` | @@ -191,12 +172,22 @@ | `BidExceedsMaxFees()` | `0x4c923f3c` | | `LowerBidAlreadyExists()` | `0xaaa1f709` | | `AsyncModifierNotUsed()` | `0xb9521e1a` | +| `InvalidIndex()` | `0x63df8171` | +| `RequestAlreadyExecuted()` | `0xd6f1f946` | +| `NoAsyncPromiseFound()` | `0xa2928f68` | +| `PromiseCallerMismatch()` | `0x2b87f115` | +| `RequestCountMismatch()` | `0x98bbcbff` | +| `DeliveryHelperNotSet()` | `0x07e6c946` | ## protocol/watcherPrecompile/WatcherPrecompileConfig.sol -| Error | Signature | -| --------------------------- | ------------ | -| `InvalidGateway()` | `0xfc9dfe85` | -| `InvalidSwitchboard()` | `0xf63c9e4d` | -| `NonceUsed()` | `0x1f6d5aef` | -| `InvalidWatcherSignature()` | `0x5029f14f` | +| Error | Signature | +| ---------------------- | ------------ | +| `InvalidGateway()` | `0xfc9dfe85` | +| `InvalidSwitchboard()` | `0xf63c9e4d` | + +## protocol/watcherPrecompile/WatcherPrecompileLimits.sol + +| Error | Signature | +| ---------------------------- | ------------ | +| `WatcherFeesNotSet(bytes32)` | `0x1ce1de3f` | diff --git a/EventTopics.md b/EventTopics.md index 28b83f5a..cb976877 100644 --- a/EventTopics.md +++ b/EventTopics.md @@ -8,19 +8,31 @@ | `Deployed` | `(proxy: address, implementation: address, admin: address)` | `0xc95935a66d15e0da5e412aca0ad27ae891d20b2fb91cf3994b6a3bf2b8178082` | | `Upgraded` | `(proxy: address, implementation: address)` | `0x5d611f318680d00598bb735d61bacf0c514c6b50e1e5ad30040a4df2b12791c7` | +## TestUSDC + +| Event | Arguments | Topic | +| ---------- | ----------------------------------------------------- | -------------------------------------------------------------------- | +| `Approval` | `(owner: address, spender: address, amount: uint256)` | `0x8c5be1e5ebec7d5bd14f71427d1e84f3dd0314c0f7b2291e5b200ac8c7c3b925` | +| `Transfer` | `(from: address, to: address, amount: uint256)` | `0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef` | + ## AddressResolver -| Event | Arguments | Topic | -| ---------------------------- | ----------------------------------------------------------- | -------------------------------------------------------------------- | -| `AddressSet` | `(name: bytes32, oldAddress: address, newAddress: address)` | `0x9ef0e8c8e52743bb38b83b17d9429141d494b8041ca6d616a6c77cebae9cd8b7` | -| `AsyncPromiseDeployed` | `(newAsyncPromise: address, salt: bytes32)` | `0xb6c5491cf83e09749b1a4dd6a9f07b0e925fcb0a915ac8c2b40e8ab28191c270` | -| `ForwarderDeployed` | `(newForwarder: address, salt: bytes32)` | `0x4dbbecb9cf9c8b93da9743a2b48ea52efe68d69230ab1c1b711891d9d223b29f` | -| `ImplementationUpdated` | `(contractName: string, newImplementation: address)` | `0xa1e41aa2c2f3f20d9b63ac06b634d2788768d6034f3d9192cdf7d07374bb16f4` | -| `Initialized` | `(version: uint64)` | `0xc7f505b2f371ae2175ee4913f4499e1f2633a7b5936321eed1cdaeb6115181d2` | -| `OwnershipHandoverCanceled` | `(pendingOwner: address)` | `0xfa7b8eab7da67f412cc9575ed43464468f9bfbae89d1675917346ca6d8fe3c92` | -| `OwnershipHandoverRequested` | `(pendingOwner: address)` | `0xdbf36a107da19e49527a7176a1babf963b4b0ff8cde35ee35d6cd8f1f9ac7e1d` | -| `OwnershipTransferred` | `(oldOwner: address, newOwner: address)` | `0x8be0079c531659141344cd1fd0a4f28419497f9722a3daafe3b4186f6b6457e0` | -| `PlugAdded` | `(appGateway: address, chainSlug: uint32, plug: address)` | `0x2cb8d865028f9abf3dc064724043264907615fadc8615a3699a85edb66472273` | +| Event | Arguments | Topic | +| ------------------------------ | ----------------------------------------------------------- | -------------------------------------------------------------------- | +| `AddressSet` | `(name: bytes32, oldAddress: address, newAddress: address)` | `0x9ef0e8c8e52743bb38b83b17d9429141d494b8041ca6d616a6c77cebae9cd8b7` | +| `AsyncPromiseDeployed` | `(newAsyncPromise: address, salt: bytes32)` | `0xb6c5491cf83e09749b1a4dd6a9f07b0e925fcb0a915ac8c2b40e8ab28191c270` | +| `ContractsToGatewaysUpdated` | `(contractAddress_: address, appGateway_: address)` | `0xb870bb0c6b5ea24214ae6c653af6c2a8b6240d5838f82132703ee5c069b14b4c` | +| `DefaultAuctionManagerUpdated` | `(defaultAuctionManager_: address)` | `0x60f296739208a505ead7fb622df0f76b7791b824481b120a2300bdaf85e3e3d6` | +| `DeliveryHelperUpdated` | `(deliveryHelper_: address)` | `0xc792471d30bbabcf9dc9fdba5bfa74f8872ff3c28f6e65e122bdb82a71b83c1c` | +| `FeesManagerUpdated` | `(feesManager_: address)` | `0x94e67aa1341a65767dfde81e62fd265bfbade1f5744bfd3cd73f99a6eca0572a` | +| `ForwarderDeployed` | `(newForwarder: address, salt: bytes32)` | `0x4dbbecb9cf9c8b93da9743a2b48ea52efe68d69230ab1c1b711891d9d223b29f` | +| `ImplementationUpdated` | `(contractName: string, newImplementation: address)` | `0xa1e41aa2c2f3f20d9b63ac06b634d2788768d6034f3d9192cdf7d07374bb16f4` | +| `Initialized` | `(version: uint64)` | `0xc7f505b2f371ae2175ee4913f4499e1f2633a7b5936321eed1cdaeb6115181d2` | +| `OwnershipHandoverCanceled` | `(pendingOwner: address)` | `0xfa7b8eab7da67f412cc9575ed43464468f9bfbae89d1675917346ca6d8fe3c92` | +| `OwnershipHandoverRequested` | `(pendingOwner: address)` | `0xdbf36a107da19e49527a7176a1babf963b4b0ff8cde35ee35d6cd8f1f9ac7e1d` | +| `OwnershipTransferred` | `(oldOwner: address, newOwner: address)` | `0x8be0079c531659141344cd1fd0a4f28419497f9722a3daafe3b4186f6b6457e0` | +| `PlugAdded` | `(appGateway: address, chainSlug: uint32, plug: address)` | `0x2cb8d865028f9abf3dc064724043264907615fadc8615a3699a85edb66472273` | +| `WatcherPrecompileUpdated` | `(watcherPrecompile_: address)` | `0xb00972c0b5c3d3d9ddc6d6a6db612abeb109653a3424d5d972510fa20bff4972` | ## AsyncPromise @@ -38,10 +50,11 @@ | Event | Arguments | Topic | | ---------------------------- | ------------------------------------------- | -------------------------------------------------------------------- | -| `AuctionEnded` | `(requestCount: uint40, winningBid: tuple)` | `0x9cc96c8b9e588c26f8beae57fe7fbb59113b82865578b54ff3f025317dcd6895` | +| `AuctionEndDelaySecondsSet` | `(auctionEndDelaySeconds: uint256)` | `0xf38f0d9dc8459cf5426728c250d115196a4c065ebc1a6c29da24764a8c0da722` | +| `AuctionEnded` | `(requestCount: uint40, winningBid: tuple)` | `0xede4ec1efc469fac10dcb4930f70be4cd21f3700ed61c91967c19a7cd7c0d86e` | | `AuctionRestarted` | `(requestCount: uint40)` | `0x071867b21946ec4655665f0d4515d3757a5a52f144c762ecfdfb11e1da542b82` | | `AuctionStarted` | `(requestCount: uint40)` | `0xcd040613cf8ef0cfcaa3af0d711783e827a275fc647c116b74595bf17cb9364f` | -| `BidPlaced` | `(requestCount: uint40, bid: tuple)` | `0xd3dc2f289bc8a88faaaf6a3f4f800dd0eac760a653b067ef749771252a1343b3` | +| `BidPlaced` | `(requestCount: uint40, bid: tuple)` | `0x7f79485e4c9aeea5d4899bc6f7c63b22ac1f4c01d2d28c801e94732fee657b5d` | | `Initialized` | `(version: uint64)` | `0xc7f505b2f371ae2175ee4913f4499e1f2633a7b5936321eed1cdaeb6115181d2` | | `OwnershipHandoverCanceled` | `(pendingOwner: address)` | `0xfa7b8eab7da67f412cc9575ed43464468f9bfbae89d1675917346ca6d8fe3c92` | | `OwnershipHandoverRequested` | `(pendingOwner: address)` | `0xdbf36a107da19e49527a7176a1babf963b4b0ff8cde35ee35d6cd8f1f9ac7e1d` | @@ -63,48 +76,54 @@ ## FeesManager -| Event | Arguments | Topic | -| ---------------------------- | ---------------------------------------------------------------------------- | -------------------------------------------------------------------- | -| `FeesBlocked` | `(requestCount: uint40, chainSlug: uint32, token: address, amount: uint256)` | `0xbb23ad39130b455188189b8de52b55fa41a7ea8ee8413dc28ced31e543d0df0c` | -| `FeesDepositedUpdated` | `(chainSlug: uint32, appGateway: address, token: address, amount: uint256)` | `0xe82dece33ef85114446a366b7d94538d641968e3ec87bf9f2f5a957ace1086e7` | -| `FeesUnblocked` | `(requestCount: uint40, appGateway: address)` | `0xc8b27128d97a92b6664c696ac891afaa87c9fc7d7c7cda17d892237589ebd4fc` | -| `FeesUnblockedAndAssigned` | `(requestCount: uint40, transmitter: address, amount: uint256)` | `0x04d2986fb321499f6bc8263ff6e65d823570e186dcdc16c04c6b388ccd0f29a8` | -| `Initialized` | `(version: uint64)` | `0xc7f505b2f371ae2175ee4913f4499e1f2633a7b5936321eed1cdaeb6115181d2` | -| `OwnershipHandoverCanceled` | `(pendingOwner: address)` | `0xfa7b8eab7da67f412cc9575ed43464468f9bfbae89d1675917346ca6d8fe3c92` | -| `OwnershipHandoverRequested` | `(pendingOwner: address)` | `0xdbf36a107da19e49527a7176a1babf963b4b0ff8cde35ee35d6cd8f1f9ac7e1d` | -| `OwnershipTransferred` | `(oldOwner: address, newOwner: address)` | `0x8be0079c531659141344cd1fd0a4f28419497f9722a3daafe3b4186f6b6457e0` | -| `TransmitterFeesUpdated` | `(requestCount: uint40, transmitter: address, amount: uint256)` | `0x9839a0f8408a769f0f3bb89025b64a6cff279673c77d2de3ab8d59b1841fcd5f` | +| Event | Arguments | Topic | +| ----------------------------------------------- | --------------------------------------------------------------------------- | -------------------------------------------------------------------- | +| `CreditsBlocked` | `(requestCount: uint40, consumeFrom: address, amount: uint256)` | `0xf037c15aef41440aa823cf1fdeaea332105d8b23d52557f6670189b5d76f1eed` | +| `CreditsDeposited` | `(chainSlug: uint32, appGateway: address, token: address, amount: uint256)` | `0x7254d040844de2dac4225a23f81bb54acb13d1eadb6e8b369dd251d36a9e8552` | +| `CreditsUnblocked` | `(requestCount: uint40, appGateway: address)` | `0x45db29ef2701319155cac058aa2f56ce1f73e0e238161d3db9f8c9a47655210d` | +| `CreditsUnblockedAndAssigned` | `(requestCount: uint40, transmitter: address, amount: uint256)` | `0x6f3d11270d1df9aff1aa04d1ea7797a3a572586a31437acc415ac853f625050c` | +| `CreditsUnwrapped` | `(consumeFrom: address, amount: uint256)` | `0xdcc9473b722b4c953617ab373840b365298a520bc7f20ce94fa7314f4a857774` | +| `CreditsWrapped` | `(consumeFrom: address, amount: uint256)` | `0x40246503613721eb4acf4020c6c56b6a16e5d08713316db0bea5210e8819c592` | +| `Initialized` | `(version: uint64)` | `0xc7f505b2f371ae2175ee4913f4499e1f2633a7b5936321eed1cdaeb6115181d2` | +| `InsufficientWatcherPrecompileCreditsAvailable` | `(chainSlug: uint32, token: address, consumeFrom: address)` | `0xd50bc02f94b9ef4a8aff7438da15a69e443956f56b6aa007cf2c584215e87493` | +| `OwnershipHandoverCanceled` | `(pendingOwner: address)` | `0xfa7b8eab7da67f412cc9575ed43464468f9bfbae89d1675917346ca6d8fe3c92` | +| `OwnershipHandoverRequested` | `(pendingOwner: address)` | `0xdbf36a107da19e49527a7176a1babf963b4b0ff8cde35ee35d6cd8f1f9ac7e1d` | +| `OwnershipTransferred` | `(oldOwner: address, newOwner: address)` | `0x8be0079c531659141344cd1fd0a4f28419497f9722a3daafe3b4186f6b6457e0` | +| `TransmitterCreditsUpdated` | `(requestCount: uint40, transmitter: address, amount: uint256)` | `0x24790626bfbe84d1358ce3e8cb0ff6cfc9eb7ea16e597f43ab607107baf889e3` | +| `WatcherPrecompileCreditsAssigned` | `(amount: uint256, consumeFrom: address)` | `0x87eddb69736f41b812366535a59efc79b1997f2d237240d7176d210397012e1b` | ## FeesPlug -| Event | Arguments | Topic | -| ---------------------------- | -------------------------------------------------------- | -------------------------------------------------------------------- | -| `ConnectorPlugDisconnected` | `()` | `0xc2af098c82dba3c4b00be8bda596d62d13b98a87b42626fefa67e0bb0e198fdd` | -| `FeesDeposited` | `(appGateway: address, token: address, amount: uint256)` | `0x0fd38537e815732117cfdab41ba9b6d3eb2c5799d44039c100c05fc9c112f235` | -| `FeesWithdrawn` | `(token: address, amount: uint256, receiver: address)` | `0x87044da2612407bc001bb0985725dcc651a0dc71eaabfd1d7e8617ca85a8c19c` | -| `OwnershipHandoverCanceled` | `(pendingOwner: address)` | `0xfa7b8eab7da67f412cc9575ed43464468f9bfbae89d1675917346ca6d8fe3c92` | -| `OwnershipHandoverRequested` | `(pendingOwner: address)` | `0xdbf36a107da19e49527a7176a1babf963b4b0ff8cde35ee35d6cd8f1f9ac7e1d` | -| `OwnershipTransferred` | `(oldOwner: address, newOwner: address)` | `0x8be0079c531659141344cd1fd0a4f28419497f9722a3daafe3b4186f6b6457e0` | -| `RoleGranted` | `(role: bytes32, grantee: address)` | `0x2ae6a113c0ed5b78a53413ffbb7679881f11145ccfba4fb92e863dfcd5a1d2f3` | -| `RoleRevoked` | `(role: bytes32, revokee: address)` | `0x155aaafb6329a2098580462df33ec4b7441b19729b9601c5fc17ae1cf99a8a52` | -| `TokenRemovedFromWhitelist` | `(token: address)` | `0xdd2e6d9f52cbe8f695939d018b7d4a216dc613a669876163ac548b916489d917` | -| `TokenWhitelisted` | `(token: address)` | `0x6a65f90b1a644d2faac467a21e07e50e3f8fa5846e26231d30ae79a417d3d262` | +| Event | Arguments | Topic | +| ---------------------------- | ----------------------------------------------------------------------------------- | -------------------------------------------------------------------- | +| `ConnectorPlugDisconnected` | `()` | `0xc2af098c82dba3c4b00be8bda596d62d13b98a87b42626fefa67e0bb0e198fdd` | +| `FeesDeposited` | `(token: address, receiver: address, creditAmount: uint256, nativeAmount: uint256)` | `0xeb4e1b24b7fe377de69f80f7380bda5ba4b43176c6a4d300a3be9009c49f4228` | +| `FeesWithdrawn` | `(token: address, receiver: address, amount: uint256)` | `0x5e110f8bc8a20b65dcc87f224bdf1cc039346e267118bae2739847f07321ffa8` | +| `OwnershipHandoverCanceled` | `(pendingOwner: address)` | `0xfa7b8eab7da67f412cc9575ed43464468f9bfbae89d1675917346ca6d8fe3c92` | +| `OwnershipHandoverRequested` | `(pendingOwner: address)` | `0xdbf36a107da19e49527a7176a1babf963b4b0ff8cde35ee35d6cd8f1f9ac7e1d` | +| `OwnershipTransferred` | `(oldOwner: address, newOwner: address)` | `0x8be0079c531659141344cd1fd0a4f28419497f9722a3daafe3b4186f6b6457e0` | +| `RoleGranted` | `(role: bytes32, grantee: address)` | `0x2ae6a113c0ed5b78a53413ffbb7679881f11145ccfba4fb92e863dfcd5a1d2f3` | +| `RoleRevoked` | `(role: bytes32, revokee: address)` | `0x155aaafb6329a2098580462df33ec4b7441b19729b9601c5fc17ae1cf99a8a52` | +| `TokenRemovedFromWhitelist` | `(token: address)` | `0xdd2e6d9f52cbe8f695939d018b7d4a216dc613a669876163ac548b916489d917` | +| `TokenWhitelisted` | `(token: address)` | `0x6a65f90b1a644d2faac467a21e07e50e3f8fa5846e26231d30ae79a417d3d262` | ## Socket -| Event | Arguments | Topic | -| ---------------------------- | ----------------------------------------------------------------------------------------------------------- | -------------------------------------------------------------------- | -| `AppGatewayCallRequested` | `(callId: bytes32, chainSlug: uint32, plug: address, appGateway: address, params: bytes32, payload: bytes)` | `0x392cb36fae7bd0470268c65b15c32a745b37168c4ccd13348c59bd9170f3b3e8` | -| `ExecutionFailed` | `(payloadId: bytes32, returnData: bytes)` | `0xd255d8a333980d77af4f9179384057def133983cb02db3e1fdb70c4dc14102e8` | -| `ExecutionSuccess` | `(payloadId: bytes32, returnData: bytes)` | `0xc54787fbe087097b182e713f16d3443ad2e67cbe6732628451dd3695a11814c2` | -| `OwnershipHandoverCanceled` | `(pendingOwner: address)` | `0xfa7b8eab7da67f412cc9575ed43464468f9bfbae89d1675917346ca6d8fe3c92` | -| `OwnershipHandoverRequested` | `(pendingOwner: address)` | `0xdbf36a107da19e49527a7176a1babf963b4b0ff8cde35ee35d6cd8f1f9ac7e1d` | -| `OwnershipTransferred` | `(oldOwner: address, newOwner: address)` | `0x8be0079c531659141344cd1fd0a4f28419497f9722a3daafe3b4186f6b6457e0` | -| `PlugConnected` | `(plug: address, appGateway: address, switchboard: address)` | `0x99c37c6da3bd69c6d59967915f8339f11a0a17fed28c615efb19457fdec0d7db` | -| `RoleGranted` | `(role: bytes32, grantee: address)` | `0x2ae6a113c0ed5b78a53413ffbb7679881f11145ccfba4fb92e863dfcd5a1d2f3` | -| `RoleRevoked` | `(role: bytes32, revokee: address)` | `0x155aaafb6329a2098580462df33ec4b7441b19729b9601c5fc17ae1cf99a8a52` | -| `SwitchboardAdded` | `(switchboard: address)` | `0x1595852923edfbbf906f09fc8523e4cfb022a194773c4d1509446b614146ee88` | -| `SwitchboardDisabled` | `(switchboard: address)` | `0x1b4ee41596b4e754e5665f01ed6122b356f7b36ea0a02030804fac7fa0fdddfc` | +| Event | Arguments | Topic | +| ---------------------------- | -------------------------------------------------------------------------------------------------------------------- | -------------------------------------------------------------------- | +| `AppGatewayCallRequested` | `(triggerId: bytes32, appGatewayId: bytes32, switchboard: address, plug: address, overrides: bytes, payload: bytes)` | `0x5c88d65ab8ba22a57e582bd8ddfa9801cc0ca6be6cb3182baaedc705a612419e` | +| `ExecutionFailed` | `(payloadId: bytes32, exceededMaxCopy: bool, returnData: bytes)` | `0x385334bc68a32c4d164625189adc7633e6074eb1b837fb4d11d768245151e4ce` | +| `ExecutionSuccess` | `(payloadId: bytes32, exceededMaxCopy: bool, returnData: bytes)` | `0x324d63a433b21a12b90e79cd2ba736b2a5238be6165e03b750fa4a7d5193d5d9` | +| `OwnershipHandoverCanceled` | `(pendingOwner: address)` | `0xfa7b8eab7da67f412cc9575ed43464468f9bfbae89d1675917346ca6d8fe3c92` | +| `OwnershipHandoverRequested` | `(pendingOwner: address)` | `0xdbf36a107da19e49527a7176a1babf963b4b0ff8cde35ee35d6cd8f1f9ac7e1d` | +| `OwnershipTransferred` | `(oldOwner: address, newOwner: address)` | `0x8be0079c531659141344cd1fd0a4f28419497f9722a3daafe3b4186f6b6457e0` | +| `PlugConnected` | `(plug: address, appGatewayId: bytes32, switchboard: address)` | `0x90c5924e27cfb6e3a688e729083681f30494ae2615ae14aac3bc807a0c436a88` | +| `RoleGranted` | `(role: bytes32, grantee: address)` | `0x2ae6a113c0ed5b78a53413ffbb7679881f11145ccfba4fb92e863dfcd5a1d2f3` | +| `RoleRevoked` | `(role: bytes32, revokee: address)` | `0x155aaafb6329a2098580462df33ec4b7441b19729b9601c5fc17ae1cf99a8a52` | +| `SocketFeeManagerUpdated` | `(oldSocketFeeManager: address, newSocketFeeManager: address)` | `0xdcb02e10d5220346a4638aa2826eaab1897306623bc40a427049e4ebd12255b4` | +| `SwitchboardAdded` | `(switchboard: address)` | `0x1595852923edfbbf906f09fc8523e4cfb022a194773c4d1509446b614146ee88` | +| `SwitchboardDisabled` | `(switchboard: address)` | `0x1b4ee41596b4e754e5665f01ed6122b356f7b36ea0a02030804fac7fa0fdddfc` | +| `SwitchboardEnabled` | `(switchboard: address)` | `0x6909a9974e3eec619bc479ba882d30a5ef1219b72ab1ce6a354516e91be317b8` | ## SocketBatcher @@ -114,76 +133,92 @@ | `OwnershipHandoverRequested` | `(pendingOwner: address)` | `0xdbf36a107da19e49527a7176a1babf963b4b0ff8cde35ee35d6cd8f1f9ac7e1d` | | `OwnershipTransferred` | `(oldOwner: address, newOwner: address)` | `0x8be0079c531659141344cd1fd0a4f28419497f9722a3daafe3b4186f6b6457e0` | -## WatcherPrecompile +## SocketFeeManager -| Event | Arguments | Topic | -| ---------------------------- | ----------------------------------------------------------------------------------------------------------- | -------------------------------------------------------------------- | -| `CalledAppGateway` | `(callId: bytes32, chainSlug: uint32, plug: address, appGateway: address, params: bytes32, payload: bytes)` | `0x255bcf22d238fe60f6611670cd7919d2bc890283be2fdaf6d2ad3411e777e33c` | -| `FinalizeRequested` | `(digest: bytes32, params: tuple)` | `0x5bc623895e2e50e307b4c3ba21df61ddfe68de0e084bb85eb1d42d4596532589` | -| `Finalized` | `(payloadId: bytes32, proof: bytes)` | `0x7e6e3e411317567fb9eabe3eb86768c3e33c46e38a50790726e916939b4918d6` | -| `Initialized` | `(version: uint64)` | `0xc7f505b2f371ae2175ee4913f4499e1f2633a7b5936321eed1cdaeb6115181d2` | -| `MarkedRevert` | `(payloadId: bytes32, isRevertingOnchain: bool)` | `0xcf1fd844cb4d32cbebb5ca6ce4ac834fe98da3ddac44deb77fffd22ad933824c` | -| `OwnershipHandoverCanceled` | `(pendingOwner: address)` | `0xfa7b8eab7da67f412cc9575ed43464468f9bfbae89d1675917346ca6d8fe3c92` | -| `OwnershipHandoverRequested` | `(pendingOwner: address)` | `0xdbf36a107da19e49527a7176a1babf963b4b0ff8cde35ee35d6cd8f1f9ac7e1d` | -| `OwnershipTransferred` | `(oldOwner: address, newOwner: address)` | `0x8be0079c531659141344cd1fd0a4f28419497f9722a3daafe3b4186f6b6457e0` | -| `PromiseNotResolved` | `(payloadId: bytes32, asyncPromise: address)` | `0xbcf0d0c678940566e9e64f0c871439395bd5fb5c39bca3547b126fe6ee467937` | -| `PromiseResolved` | `(payloadId: bytes32, asyncPromise: address)` | `0x1b1b5810494fb3e17f7c46547e6e67cd6ad3e6001ea6fb7d12ea0241ba13c4ba` | -| `QueryRequested` | `(params: tuple)` | `0xca81bf0029a549d7e6e3a9c668a717472f4330a6a5ec4350304a9e79bf437345` | -| `RequestSubmitted` | `(middleware: address, requestCount: uint40, payloadParamsArray: tuple[])` | `0xb856562fcff2119ba754f0486f47c06087ebc1842bff464faf1b2a1f8d273b1d` | -| `RoleGranted` | `(role: bytes32, grantee: address)` | `0x2ae6a113c0ed5b78a53413ffbb7679881f11145ccfba4fb92e863dfcd5a1d2f3` | -| `RoleRevoked` | `(role: bytes32, revokee: address)` | `0x155aaafb6329a2098580462df33ec4b7441b19729b9601c5fc17ae1cf99a8a52` | -| `TimeoutRequested` | `(timeoutId: bytes32, target: address, payload: bytes, executeAt: uint256)` | `0xdf94fed77e41734b8a17815476bbbf88e2db15d762f42a30ddb9d7870f2fb858` | -| `TimeoutResolved` | `(timeoutId: bytes32, target: address, payload: bytes, executedAt: uint256)` | `0x221462ec065e22637f794ec3a7edb17b2f04bec88f0546dda308bc37a83801b8` | +| Event | Arguments | Topic | +| ---------------------------- | ---------------------------------------- | -------------------------------------------------------------------- | +| `OwnershipHandoverCanceled` | `(pendingOwner: address)` | `0xfa7b8eab7da67f412cc9575ed43464468f9bfbae89d1675917346ca6d8fe3c92` | +| `OwnershipHandoverRequested` | `(pendingOwner: address)` | `0xdbf36a107da19e49527a7176a1babf963b4b0ff8cde35ee35d6cd8f1f9ac7e1d` | +| `OwnershipTransferred` | `(oldOwner: address, newOwner: address)` | `0x8be0079c531659141344cd1fd0a4f28419497f9722a3daafe3b4186f6b6457e0` | +| `RoleGranted` | `(role: bytes32, grantee: address)` | `0x2ae6a113c0ed5b78a53413ffbb7679881f11145ccfba4fb92e863dfcd5a1d2f3` | +| `RoleRevoked` | `(role: bytes32, revokee: address)` | `0x155aaafb6329a2098580462df33ec4b7441b19729b9601c5fc17ae1cf99a8a52` | +| `SocketFeesUpdated` | `(oldFees: uint256, newFees: uint256)` | `0xcbd4d756fb6198bbcc2e4013cce929f504ad46e9d97c543ef9a8dfea3e407053` | ## WatcherPrecompileConfig | Event | Arguments | Topic | | ---------------------------- | --------------------------------------------------------------------------------------- | -------------------------------------------------------------------- | | `Initialized` | `(version: uint64)` | `0xc7f505b2f371ae2175ee4913f4499e1f2633a7b5936321eed1cdaeb6115181d2` | +| `IsValidPlugSet` | `(appGateway: address, chainSlug: uint32, plug: address, isValid: bool)` | `0x61cccc7387868fc741379c7acd9dd346e0ca2e5c067dc5b156fbbc55b1c2fcf5` | | `OnChainContractSet` | `(chainSlug: uint32, socket: address, contractFactoryPlug: address, feesPlug: address)` | `0xd24cf816377e3c571e7bc798dd43d3d5fc78c32f7fc94b42898b0d37c5301a4e` | | `OwnershipHandoverCanceled` | `(pendingOwner: address)` | `0xfa7b8eab7da67f412cc9575ed43464468f9bfbae89d1675917346ca6d8fe3c92` | | `OwnershipHandoverRequested` | `(pendingOwner: address)` | `0xdbf36a107da19e49527a7176a1babf963b4b0ff8cde35ee35d6cd8f1f9ac7e1d` | | `OwnershipTransferred` | `(oldOwner: address, newOwner: address)` | `0x8be0079c531659141344cd1fd0a4f28419497f9722a3daafe3b4186f6b6457e0` | -| `PlugAdded` | `(appGateway: address, chainSlug: uint32, plug: address)` | `0x2cb8d865028f9abf3dc064724043264907615fadc8615a3699a85edb66472273` | -| `RoleGranted` | `(role: bytes32, grantee: address)` | `0x2ae6a113c0ed5b78a53413ffbb7679881f11145ccfba4fb92e863dfcd5a1d2f3` | -| `RoleRevoked` | `(role: bytes32, revokee: address)` | `0x155aaafb6329a2098580462df33ec4b7441b19729b9601c5fc17ae1cf99a8a52` | +| `PlugAdded` | `(appGatewayId: bytes32, chainSlug: uint32, plug: address)` | `0x7b3e14230a721c4737d275f9a63b92c44cb657bcfddbe6fe9b4d9cd9bd8d4a95` | | `SwitchboardSet` | `(chainSlug: uint32, sbType: bytes32, switchboard: address)` | `0x6273f161f4a795e66ef3585d9b4442ef3796b32337157fdfb420b5281e4cf2e3` | ## WatcherPrecompileLimits -| Event | Arguments | Topic | -| ---------------------------- | ------------------------------------------------------------------ | -------------------------------------------------------------------- | -| `AppGatewayActivated` | `(appGateway: address, maxLimit: uint256, ratePerSecond: uint256)` | `0x44628d7d5628b9fbc2c84ea9bf3bd3987fa9cde8d2b28e2d5ceb451f916cb8b9` | -| `Initialized` | `(version: uint64)` | `0xc7f505b2f371ae2175ee4913f4499e1f2633a7b5936321eed1cdaeb6115181d2` | -| `LimitParamsUpdated` | `(updates: tuple[])` | `0x81576b12f4d507fd0543afd25a86785573a595334c2c7eb8ca8ec1b0a56a55b3` | -| `OwnershipHandoverCanceled` | `(pendingOwner: address)` | `0xfa7b8eab7da67f412cc9575ed43464468f9bfbae89d1675917346ca6d8fe3c92` | -| `OwnershipHandoverRequested` | `(pendingOwner: address)` | `0xdbf36a107da19e49527a7176a1babf963b4b0ff8cde35ee35d6cd8f1f9ac7e1d` | -| `OwnershipTransferred` | `(oldOwner: address, newOwner: address)` | `0x8be0079c531659141344cd1fd0a4f28419497f9722a3daafe3b4186f6b6457e0` | -| `RoleGranted` | `(role: bytes32, grantee: address)` | `0x2ae6a113c0ed5b78a53413ffbb7679881f11145ccfba4fb92e863dfcd5a1d2f3` | -| `RoleRevoked` | `(role: bytes32, revokee: address)` | `0x155aaafb6329a2098580462df33ec4b7441b19729b9601c5fc17ae1cf99a8a52` | +| Event | Arguments | Topic | +| --------------------------------- | ------------------------------------------------------------------ | -------------------------------------------------------------------- | +| `AppGatewayActivated` | `(appGateway: address, maxLimit: uint256, ratePerSecond: uint256)` | `0x44628d7d5628b9fbc2c84ea9bf3bd3987fa9cde8d2b28e2d5ceb451f916cb8b9` | +| `CallBackFeesSet` | `(callBackFees: uint256)` | `0x667c97afffb32265f3b4e026d31b81dc223275ff8bb9819e67012197f5799faf` | +| `DefaultLimitAndRatePerSecondSet` | `(defaultLimit: uint256, defaultRatePerSecond: uint256)` | `0x39def16be1ce80876ad0b0936cfdf88b8be7a1790b6c1da16ba8bdee53367e8e` | +| `FinalizeFeesSet` | `(finalizeFees: uint256)` | `0x0b710f92aabbdda2e8c347f802353f34ef27845d79db79efb4884e8790a0d5fb` | +| `Initialized` | `(version: uint64)` | `0xc7f505b2f371ae2175ee4913f4499e1f2633a7b5936321eed1cdaeb6115181d2` | +| `LimitParamsUpdated` | `(updates: tuple[])` | `0x81576b12f4d507fd0543afd25a86785573a595334c2c7eb8ca8ec1b0a56a55b3` | +| `OwnershipHandoverCanceled` | `(pendingOwner: address)` | `0xfa7b8eab7da67f412cc9575ed43464468f9bfbae89d1675917346ca6d8fe3c92` | +| `OwnershipHandoverRequested` | `(pendingOwner: address)` | `0xdbf36a107da19e49527a7176a1babf963b4b0ff8cde35ee35d6cd8f1f9ac7e1d` | +| `OwnershipTransferred` | `(oldOwner: address, newOwner: address)` | `0x8be0079c531659141344cd1fd0a4f28419497f9722a3daafe3b4186f6b6457e0` | +| `QueryFeesSet` | `(queryFees: uint256)` | `0x19569faa0df733d4b0806372423e828b05a5257eb7652da812b90f662bed5cfb` | +| `TimeoutFeesSet` | `(timeoutFees: uint256)` | `0xe8a5b23529bc11019d6df86a1ee0d043571d464902a3fa98e7e3e67dbd5981ca` | ## DeliveryHelper -| Event | Arguments | Topic | -| ---------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------- | -------------------------------------------------------------------- | -| `AuctionEnded` | `(requestCount: uint40, winningBid: tuple)` | `0x9cc96c8b9e588c26f8beae57fe7fbb59113b82865578b54ff3f025317dcd6895` | -| `BidTimeoutUpdated` | `(newBidTimeout: uint256)` | `0xd4552e666d0e4e343fb2b13682972a8f0c7f1a86e252d6433b356f0c0e817c3d` | -| `CallBackReverted` | `(requestCount_: uint40, payloadId_: bytes32)` | `0xcecb2641ea89470f68bf9f852d731e123505424e4dcfd770c7ea9e2e25326b1b` | -| `FeesIncreased` | `(appGateway: address, requestCount: uint40, newMaxFees: uint256)` | `0x63ee9e9e84d216b804cb18f51b7f7511254b0c1f11304b7a3aa34d57511aa6dc` | -| `Initialized` | `(version: uint64)` | `0xc7f505b2f371ae2175ee4913f4499e1f2633a7b5936321eed1cdaeb6115181d2` | -| `OwnershipHandoverCanceled` | `(pendingOwner: address)` | `0xfa7b8eab7da67f412cc9575ed43464468f9bfbae89d1675917346ca6d8fe3c92` | -| `OwnershipHandoverRequested` | `(pendingOwner: address)` | `0xdbf36a107da19e49527a7176a1babf963b4b0ff8cde35ee35d6cd8f1f9ac7e1d` | -| `OwnershipTransferred` | `(oldOwner: address, newOwner: address)` | `0x8be0079c531659141344cd1fd0a4f28419497f9722a3daafe3b4186f6b6457e0` | -| `PayloadSubmitted` | `(requestCount: uint40, appGateway: address, payloadSubmitParams: tuple[], fees: tuple, auctionManager: address, onlyReadRequests: bool)` | `0x204c4de167e7a12fc9ad8231fa3d877639ed95a66bd19e1a55d1f68088d4c784` | -| `RequestCancelled` | `(requestCount: uint40)` | `0xff191657769be72fc08def44c645014c60d18cb24b9ca05c9a33406a28253245` | +| Event | Arguments | Topic | +| ------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------- | -------------------------------------------------------------------- | +| `BidTimeoutUpdated` | `(newBidTimeout: uint256)` | `0xd4552e666d0e4e343fb2b13682972a8f0c7f1a86e252d6433b356f0c0e817c3d` | +| `ChainMaxMsgValueLimitsUpdated` | `(chainSlugs: uint32[], maxMsgValueLimits: uint256[])` | `0x17e47f6f0fa0e79831bee11b7c29adc45d9a7bd25acd70b91e4b2bad0f544352` | +| `FeesIncreased` | `(appGateway: address, requestCount: uint40, newMaxFees: uint256)` | `0x63ee9e9e84d216b804cb18f51b7f7511254b0c1f11304b7a3aa34d57511aa6dc` | +| `Initialized` | `(version: uint64)` | `0xc7f505b2f371ae2175ee4913f4499e1f2633a7b5936321eed1cdaeb6115181d2` | +| `OwnershipHandoverCanceled` | `(pendingOwner: address)` | `0xfa7b8eab7da67f412cc9575ed43464468f9bfbae89d1675917346ca6d8fe3c92` | +| `OwnershipHandoverRequested` | `(pendingOwner: address)` | `0xdbf36a107da19e49527a7176a1babf963b4b0ff8cde35ee35d6cd8f1f9ac7e1d` | +| `OwnershipTransferred` | `(oldOwner: address, newOwner: address)` | `0x8be0079c531659141344cd1fd0a4f28419497f9722a3daafe3b4186f6b6457e0` | +| `PayloadSubmitted` | `(requestCount: uint40, appGateway: address, payloadSubmitParams: tuple[], fees: uint256, auctionManager: address, onlyReadRequests: bool)` | `0xc6455dba7c07a5e75c7189040ae9e3478162f333a96365b283b434fd0e32c6b3` | +| `RequestCancelled` | `(requestCount: uint40)` | `0xff191657769be72fc08def44c645014c60d18cb24b9ca05c9a33406a28253245` | ## FastSwitchboard -| Event | Arguments | Topic | -| ---------------------------- | ---------------------------------------- | -------------------------------------------------------------------- | -| `Attested` | `(digest_: bytes32, watcher: address)` | `0x3d83c7bc55c269e0bc853ddc0d7b9fca30216ecc43779acb4e36b7e0ad1c71e4` | -| `OwnershipHandoverCanceled` | `(pendingOwner: address)` | `0xfa7b8eab7da67f412cc9575ed43464468f9bfbae89d1675917346ca6d8fe3c92` | -| `OwnershipHandoverRequested` | `(pendingOwner: address)` | `0xdbf36a107da19e49527a7176a1babf963b4b0ff8cde35ee35d6cd8f1f9ac7e1d` | -| `OwnershipTransferred` | `(oldOwner: address, newOwner: address)` | `0x8be0079c531659141344cd1fd0a4f28419497f9722a3daafe3b4186f6b6457e0` | -| `RoleGranted` | `(role: bytes32, grantee: address)` | `0x2ae6a113c0ed5b78a53413ffbb7679881f11145ccfba4fb92e863dfcd5a1d2f3` | -| `RoleRevoked` | `(role: bytes32, revokee: address)` | `0x155aaafb6329a2098580462df33ec4b7441b19729b9601c5fc17ae1cf99a8a52` | +| Event | Arguments | Topic | +| ---------------------------- | ----------------------------------------- | -------------------------------------------------------------------- | +| `Attested` | `(payloadId_: bytes32, watcher: address)` | `0x3d83c7bc55c269e0bc853ddc0d7b9fca30216ecc43779acb4e36b7e0ad1c71e4` | +| `OwnershipHandoverCanceled` | `(pendingOwner: address)` | `0xfa7b8eab7da67f412cc9575ed43464468f9bfbae89d1675917346ca6d8fe3c92` | +| `OwnershipHandoverRequested` | `(pendingOwner: address)` | `0xdbf36a107da19e49527a7176a1babf963b4b0ff8cde35ee35d6cd8f1f9ac7e1d` | +| `OwnershipTransferred` | `(oldOwner: address, newOwner: address)` | `0x8be0079c531659141344cd1fd0a4f28419497f9722a3daafe3b4186f6b6457e0` | +| `RoleGranted` | `(role: bytes32, grantee: address)` | `0x2ae6a113c0ed5b78a53413ffbb7679881f11145ccfba4fb92e863dfcd5a1d2f3` | +| `RoleRevoked` | `(role: bytes32, revokee: address)` | `0x155aaafb6329a2098580462df33ec4b7441b19729b9601c5fc17ae1cf99a8a52` | + +## WatcherPrecompile + +| Event | Arguments | Topic | +| ----------------------------- | ----------------------------------------------------------------------------------------------- | -------------------------------------------------------------------- | +| `AppGatewayCallFailed` | `(triggerId: bytes32)` | `0xcaf8475fdade8465ea31672463949e6cf1797fdcdd11eeddbbaf857e1e5907b7` | +| `CalledAppGateway` | `(triggerId: bytes32)` | `0xf659ffb3875368f54fb4ab8f5412ac4518af79701a48076f7a58d4448e4bdd0b` | +| `ExpiryTimeSet` | `(expiryTime: uint256)` | `0x07e837e13ad9a34715a6bd45f49bbf12de19f06df79cb0be12b3a7d7f2397fa9` | +| `FinalizeRequested` | `(digest: bytes32, params: tuple)` | `0x5bc623895e2e50e307b4c3ba21df61ddfe68de0e084bb85eb1d42d4596532589` | +| `Finalized` | `(payloadId: bytes32, proof: bytes)` | `0x7e6e3e411317567fb9eabe3eb86768c3e33c46e38a50790726e916939b4918d6` | +| `Initialized` | `(version: uint64)` | `0xc7f505b2f371ae2175ee4913f4499e1f2633a7b5936321eed1cdaeb6115181d2` | +| `MarkedRevert` | `(payloadId: bytes32, isRevertingOnchain: bool)` | `0xcf1fd844cb4d32cbebb5ca6ce4ac834fe98da3ddac44deb77fffd22ad933824c` | +| `MaxTimeoutDelayInSecondsSet` | `(maxTimeoutDelayInSeconds: uint256)` | `0x3564638b089495c19e7359a040be083841e11da34c22a29ea8d602c8a9805fec` | +| `OwnershipHandoverCanceled` | `(pendingOwner: address)` | `0xfa7b8eab7da67f412cc9575ed43464468f9bfbae89d1675917346ca6d8fe3c92` | +| `OwnershipHandoverRequested` | `(pendingOwner: address)` | `0xdbf36a107da19e49527a7176a1babf963b4b0ff8cde35ee35d6cd8f1f9ac7e1d` | +| `OwnershipTransferred` | `(oldOwner: address, newOwner: address)` | `0x8be0079c531659141344cd1fd0a4f28419497f9722a3daafe3b4186f6b6457e0` | +| `PromiseNotResolved` | `(payloadId: bytes32, asyncPromise: address)` | `0xbcf0d0c678940566e9e64f0c871439395bd5fb5c39bca3547b126fe6ee467937` | +| `PromiseResolved` | `(payloadId: bytes32, asyncPromise: address)` | `0x1b1b5810494fb3e17f7c46547e6e67cd6ad3e6001ea6fb7d12ea0241ba13c4ba` | +| `QueryRequested` | `(params: tuple)` | `0xca81bf0029a549d7e6e3a9c668a717472f4330a6a5ec4350304a9e79bf437345` | +| `RequestCancelledFromGateway` | `(requestCount: uint40)` | `0x333619ca4a2a9c4ee292aafa3c37215d88afe358afee4a575cfed21d743091c6` | +| `RequestSubmitted` | `(middleware: address, requestCount: uint40, payloadParamsArray: tuple[])` | `0xb856562fcff2119ba754f0486f47c06087ebc1842bff464faf1b2a1f8d273b1d` | +| `TimeoutRequested` | `(timeoutId: bytes32, target: address, payload: bytes, executeAt: uint256)` | `0xdf94fed77e41734b8a17815476bbbf88e2db15d762f42a30ddb9d7870f2fb858` | +| `TimeoutResolved` | `(timeoutId: bytes32, target: address, payload: bytes, executedAt: uint256, returnData: bytes)` | `0x61122416680ac7038ca053afc2c26983f2c524e5003b1f4d9dea095fbc8f6905` | +| `WatcherPrecompileConfigSet` | `(watcherPrecompileConfig: address)` | `0xdc19bca647582b3fbf69a6ffacabf56b4f7a4551d2d0944843712f2d0987a8e5` | +| `WatcherPrecompileLimitsSet` | `(watcherPrecompileLimits: address)` | `0xcec7ba89301793a37efb418279f17f8dd77e5959e9f3fbcbc54e40615a14bd8e` | diff --git a/FunctionSignatures.md b/FunctionSignatures.md index 6f9cdf76..e3ef596c 100644 --- a/FunctionSignatures.md +++ b/FunctionSignatures.md @@ -15,6 +15,25 @@ | `upgrade` | `0x99a88ec4` | | `upgradeAndCall` | `0x9623609d` | +## TestUSDC + +| Function | Signature | +| ------------------ | ------------ | +| `DOMAIN_SEPARATOR` | `0x3644e515` | +| `allowance` | `0xdd62ed3e` | +| `approve` | `0x095ea7b3` | +| `balanceOf` | `0x70a08231` | +| `decimals` | `0x313ce567` | +| `mint` | `0x40c10f19` | +| `name` | `0x06fdde03` | +| `nonces` | `0x7ecebe00` | +| `owner` | `0x8da5cb5b` | +| `permit` | `0xd505accf` | +| `symbol` | `0x95d89b41` | +| `totalSupply` | `0x18160ddd` | +| `transfer` | `0xa9059cbb` | +| `transferFrom` | `0x23b872dd` | + ## AddressResolver | Function | Signature | @@ -83,6 +102,8 @@ | `getOnChainAddress` | `0x9da48789` | | `initialize` | `0x647c576c` | | `latestAsyncPromise` | `0xb8a8ba52` | +| `latestPromiseCaller` | `0xdfe580a8` | +| `latestRequestCount` | `0x198b9a47` | | `onChainAddress` | `0x8bd0b363` | | `then` | `0x0bf2ba15` | | `watcherPrecompileConfig` | `0x8618a912` | @@ -91,51 +112,52 @@ ## AuctionManager -| Function | Signature | -| ---------------------------- | ------------ | -| `addressResolver__` | `0x6a750469` | -| `auctionClosed` | `0x6862ebb0` | -| `auctionEndDelaySeconds` | `0x9087dfdb` | -| `auctionStarted` | `0x7c9c5bb8` | -| `bid` | `0xfcdf49c2` | -| `cancelOwnershipHandover` | `0x54d1f13d` | -| `completeOwnershipHandover` | `0xf04e283e` | -| `deliveryHelper__` | `0xc031dfb4` | -| `endAuction` | `0x1212e653` | -| `evmxSlug` | `0x8bae77c2` | -| `expireBid` | `0x1dd5022c` | -| `grantRole` | `0x2f2ff15d` | -| `hasRole` | `0x91d14854` | -| `initialize` | `0x5f24043b` | -| `maxReAuctionCount` | `0xc367b376` | -| `owner` | `0x8da5cb5b` | -| `ownershipHandoverExpiresAt` | `0xfee81cf4` | -| `reAuctionCount` | `0x9b4b22d3` | -| `renounceOwnership` | `0x715018a6` | -| `requestOwnershipHandover` | `0x25692962` | -| `revokeRole` | `0xd547741f` | -| `setAuctionEndDelaySeconds` | `0x88606b1a` | -| `transferOwnership` | `0xf2fde38b` | -| `watcherPrecompileConfig` | `0x8618a912` | -| `watcherPrecompileLimits` | `0xa71cd97d` | -| `watcherPrecompile__` | `0x1de360c3` | -| `whitelistedTransmitters` | `0xc2f1bf5d` | -| `winningBids` | `0x9133f232` | +| Function | Signature | +| -------------------------------- | ------------ | +| `addressResolver__` | `0x6a750469` | +| `auctionClosed` | `0x6862ebb0` | +| `auctionEndDelaySeconds` | `0x9087dfdb` | +| `auctionStarted` | `0x7c9c5bb8` | +| `bid` | `0xfcdf49c2` | +| `cancelOwnershipHandover` | `0x54d1f13d` | +| `completeOwnershipHandover` | `0xf04e283e` | +| `deliveryHelper__` | `0xc031dfb4` | +| `endAuction` | `0x1212e653` | +| `evmxSlug` | `0x8bae77c2` | +| `expireBid` | `0x1dd5022c` | +| `getTransmitterMaxFeesAvailable` | `0xa70f18ea` | +| `grantRole` | `0x2f2ff15d` | +| `hasRole` | `0x91d14854` | +| `initialize` | `0x5f24043b` | +| `maxReAuctionCount` | `0xc367b376` | +| `owner` | `0x8da5cb5b` | +| `ownershipHandoverExpiresAt` | `0xfee81cf4` | +| `reAuctionCount` | `0x9b4b22d3` | +| `renounceOwnership` | `0x715018a6` | +| `requestOwnershipHandover` | `0x25692962` | +| `revokeRole` | `0xd547741f` | +| `setAuctionEndDelaySeconds` | `0x88606b1a` | +| `transferOwnership` | `0xf2fde38b` | +| `watcherPrecompileConfig` | `0x8618a912` | +| `watcherPrecompileLimits` | `0xa71cd97d` | +| `watcherPrecompile__` | `0x1de360c3` | +| `winningBids` | `0x9133f232` | ## ContractFactoryPlug | Function | Signature | | ---------------------------- | ------------ | -| `appGateway` | `0xb82bb881` | +| `appGatewayId` | `0x1c335f49` | | `cancelOwnershipHandover` | `0x54d1f13d` | | `completeOwnershipHandover` | `0xf04e283e` | -| `connectSocket` | `0x052615a6` | -| `deployContract` | `0x35041492` | +| `connectSocket` | `0x258d19c8` | +| `deployContract` | `0xa0695389` | | `getAddress` | `0x94ca2cb5` | | `grantRole` | `0x2f2ff15d` | | `hasRole` | `0x91d14854` | -| `initSocket` | `0x59c92b64` | +| `initSocket` | `0xa07d8545` | | `isSocketInitialized` | `0x9a7d9a9b` | +| `overrides` | `0x4a85f041` | | `owner` | `0x8da5cb5b` | | `ownershipHandoverExpiresAt` | `0xfee81cf4` | | `renounceOwnership` | `0x715018a6` | @@ -147,53 +169,64 @@ ## FeesManager -| Function | Signature | -| ---------------------------- | ------------ | -| `addressResolver__` | `0x6a750469` | -| `appGatewayFeeBalances` | `0x46a312be` | -| `blockFees` | `0x1c0ac675` | -| `cancelOwnershipHandover` | `0x54d1f13d` | -| `completeOwnershipHandover` | `0xf04e283e` | -| `deliveryHelper__` | `0xc031dfb4` | -| `evmxSlug` | `0x8bae77c2` | -| `feesCounter` | `0xb94f4778` | -| `getAvailableFees` | `0xe3d07506` | -| `incrementFeesDeposited` | `0x4f88fe32` | -| `initialize` | `0x6f6186bd` | -| `isFeesEnough` | `0x7d274c6a` | -| `isNonceUsed` | `0x5d00bb12` | -| `owner` | `0x8da5cb5b` | -| `ownershipHandoverExpiresAt` | `0xfee81cf4` | -| `renounceOwnership` | `0x715018a6` | -| `requestCountBlockedFees` | `0xd09604ed` | -| `requestOwnershipHandover` | `0x25692962` | -| `sbType` | `0x745de344` | -| `transferOwnership` | `0xf2fde38b` | -| `transmitterFees` | `0xefb4cdea` | -| `unblockAndAssignFees` | `0x3c5366a2` | -| `unblockFees` | `0xc1867a4b` | -| `watcherPrecompileConfig` | `0x8618a912` | -| `watcherPrecompileLimits` | `0xa71cd97d` | -| `watcherPrecompile__` | `0x1de360c3` | -| `withdrawFees` | `0xe1a69364` | -| `withdrawTransmitterFees` | `0x8c047bbd` | +| Function | Signature | +| ------------------------------------------------ | ------------ | +| `addressResolver__` | `0x6a750469` | +| `assignWatcherPrecompileCreditsFromAddress` | `0xd699b6c8` | +| `assignWatcherPrecompileCreditsFromRequestCount` | `0x7a483022` | +| `blockCredits` | `0x2d64fc91` | +| `cancelOwnershipHandover` | `0x54d1f13d` | +| `completeOwnershipHandover` | `0xf04e283e` | +| `deliveryHelper__` | `0xc031dfb4` | +| `depositCredits` | `0x211998c8` | +| `evmxSlug` | `0x8bae77c2` | +| `feesCounter` | `0xb94f4778` | +| `getAvailableCredits` | `0xb065a8e5` | +| `getMaxCreditsAvailableForWithdraw` | `0x6ef9efe2` | +| `getWithdrawTransmitterCreditsPayloadParams` | `0x27be4536` | +| `initialize` | `0x6f6186bd` | +| `isAppGatewayWhitelisted` | `0x2a83b813` | +| `isNonceUsed` | `0x5d00bb12` | +| `isUserCreditsEnough` | `0x5bee3a67` | +| `onRequestComplete` | `0x5ed1f959` | +| `owner` | `0x8da5cb5b` | +| `ownershipHandoverExpiresAt` | `0xfee81cf4` | +| `renounceOwnership` | `0x715018a6` | +| `requestCountCredits` | `0x0c9b8a49` | +| `requestOwnershipHandover` | `0x25692962` | +| `sbType` | `0x745de344` | +| `tokenPoolBalances` | `0x2eda3bfd` | +| `transferOwnership` | `0xf2fde38b` | +| `unblockAndAssignCredits` | `0x01958181` | +| `unblockCredits` | `0xa0b32314` | +| `unwrap` | `0xde0e9a3e` | +| `userCredits` | `0x20babb92` | +| `userNonce` | `0x2e04b8e7` | +| `watcherPrecompileConfig` | `0x8618a912` | +| `watcherPrecompileCredits` | `0x052e1f6a` | +| `watcherPrecompileLimits` | `0xa71cd97d` | +| `watcherPrecompile__` | `0x1de360c3` | +| `whitelistAppGatewayWithSignature` | `0x28cbf83d` | +| `whitelistAppGateways` | `0x6c2499e3` | +| `withdrawCredits` | `0xc22f8cf3` | +| `wrap` | `0xd46eb119` | ## FeesPlug | Function | Signature | | ---------------------------- | ------------ | -| `appGateway` | `0xb82bb881` | -| `balanceOf` | `0x70a08231` | +| `appGatewayId` | `0x1c335f49` | | `cancelOwnershipHandover` | `0x54d1f13d` | | `completeOwnershipHandover` | `0xf04e283e` | -| `connectSocket` | `0x052615a6` | -| `deposit` | `0x8340f549` | -| `distributeFee` | `0x7aeee972` | -| `feesRedeemed` | `0x58f8782b` | +| `connectSocket` | `0x258d19c8` | +| `depositToFee` | `0xef0db49f` | +| `depositToFeeAndNative` | `0x5f952be8` | +| `depositToNative` | `0xe2665889` | | `grantRole` | `0x2f2ff15d` | | `hasRole` | `0x91d14854` | -| `initSocket` | `0x59c92b64` | +| `initSocket` | `0xa07d8545` | | `isSocketInitialized` | `0x9a7d9a9b` | +| `overrides` | `0x4a85f041` | | `owner` | `0x8da5cb5b` | | `ownershipHandoverExpiresAt` | `0xfee81cf4` | | `removeTokenFromWhitelist` | `0x306275be` | @@ -205,40 +238,45 @@ | `transferOwnership` | `0xf2fde38b` | | `whitelistToken` | `0x6247f6f2` | | `whitelistedTokens` | `0xdaf9c210` | -| `withdrawFees` | `0x9ba372c2` | +| `withdrawFees` | `0xe55dc4e6` | ## Socket | Function | Signature | | ---------------------------- | ------------ | -| `callAppGateway` | `0x31ed7099` | -| `callCounter` | `0xc0f9882e` | | `cancelOwnershipHandover` | `0x54d1f13d` | | `chainSlug` | `0xb349ba65` | | `completeOwnershipHandover` | `0xf04e283e` | -| `connect` | `0x295058ef` | +| `connect` | `0xb3bde1aa` | | `disableSwitchboard` | `0xe545b261` | -| `execute` | `0x2c6571a9` | +| `enableSwitchboard` | `0xf97a498a` | +| `execute` | `0x68ef086b` | | `getPlugConfig` | `0xf9778ee0` | | `grantRole` | `0x2f2ff15d` | | `hasRole` | `0x91d14854` | | `isValidSwitchboard` | `0xb2d67675` | +| `maxCopyBytes` | `0x212249d4` | | `owner` | `0x8da5cb5b` | | `ownershipHandoverExpiresAt` | `0xfee81cf4` | | `payloadExecuted` | `0x3eaeac3d` | +| `payloadIdToDigest` | `0x7c8552b2` | | `registerSwitchboard` | `0x74f5b1fc` | | `renounceOwnership` | `0x715018a6` | | `requestOwnershipHandover` | `0x25692962` | | `rescueFunds` | `0x6ccae054` | | `revokeRole` | `0xd547741f` | +| `setMaxCopyBytes` | `0x4fc7d6e9` | +| `setSocketFeeManager` | `0x25bd97e5` | +| `socketFeeManager` | `0xde5b8838` | | `transferOwnership` | `0xf2fde38b` | +| `triggerCounter` | `0x8b0021de` | | `version` | `0x54fd4d50` | ## SocketBatcher | Function | Signature | | ---------------------------- | ------------ | -| `attestAndExecute` | `0x841f0228` | +| `attestAndExecute` | `0xa11d3bdc` | | `cancelOwnershipHandover` | `0x54d1f13d` | | `completeOwnershipHandover` | `0xf04e283e` | | `owner` | `0x8da5cb5b` | @@ -249,64 +287,25 @@ | `socket__` | `0xc6a261d2` | | `transferOwnership` | `0xf2fde38b` | -## WatcherPrecompile +## SocketFeeManager -| Function | Signature | -| ----------------------------- | ------------ | -| `addressResolver__` | `0x6a750469` | -| `appGatewayCalled` | `0xc6767cf1` | -| `batchPayloadIds` | `0x02b74f98` | -| `callAppGateways` | `0xdede3465` | -| `cancelOwnershipHandover` | `0x54d1f13d` | -| `cancelRequest` | `0x50ad0779` | -| `completeOwnershipHandover` | `0xf04e283e` | -| `deliveryHelper__` | `0xc031dfb4` | -| `evmxSlug` | `0x8bae77c2` | -| `expiryTime` | `0x99bc0aea` | -| `finalize` | `0x7ffecf2e` | -| `finalized` | `0x81c051de` | -| `getBatchPayloadIds` | `0xfd83cd1f` | -| `getBatches` | `0xcb95b7b3` | -| `getCurrentRequestCount` | `0x5715abbb` | -| `getDigest` | `0xeba9500e` | -| `getPayloadParams` | `0xae5eeb77` | -| `getRequestParams` | `0x71263d0d` | -| `grantRole` | `0x2f2ff15d` | -| `hasRole` | `0x91d14854` | -| `initialize` | `0xb7dc6b77` | -| `isNonceUsed` | `0x5d00bb12` | -| `isPromiseExecuted` | `0x17a2cdf0` | -| `markRevert` | `0x1c75dad5` | -| `maxTimeoutDelayInSeconds` | `0x46fbc9d7` | -| `nextBatchCount` | `0x333a3963` | -| `nextRequestCount` | `0xfef72893` | -| `owner` | `0x8da5cb5b` | -| `ownershipHandoverExpiresAt` | `0xfee81cf4` | -| `payloadCounter` | `0x550ce1d5` | -| `payloads` | `0x58722672` | -| `query` | `0x16ad71bc` | -| `renounceOwnership` | `0x715018a6` | -| `requestBatchIds` | `0xf865c4a7` | -| `requestOwnershipHandover` | `0x25692962` | -| `requestParams` | `0x5ce2d853` | -| `resolvePromises` | `0xccb1caff` | -| `resolveTimeout` | `0xa67c0781` | -| `revokeRole` | `0xd547741f` | -| `setExpiryTime` | `0x30fc4cff` | -| `setMaxTimeoutDelayInSeconds` | `0x65d480fc` | -| `setTimeout` | `0x9c29ec74` | -| `startProcessingRequest` | `0x77290f24` | -| `submitRequest` | `0x16b47482` | -| `timeoutCounter` | `0x94f6522e` | -| `timeoutRequests` | `0xcdf85751` | -| `transferOwnership` | `0xf2fde38b` | -| `updateTransmitter` | `0xb228a22c` | -| `watcherPrecompileConfig` | `0x8618a912` | -| `watcherPrecompileConfig__` | `0xa816cbd9` | -| `watcherPrecompileLimits` | `0xa71cd97d` | -| `watcherPrecompileLimits__` | `0xb2ad6c48` | -| `watcherPrecompile__` | `0x1de360c3` | -| `watcherProofs` | `0x3fa3166b` | +| Function | Signature | +| ---------------------------- | ------------ | +| `cancelOwnershipHandover` | `0x54d1f13d` | +| `completeOwnershipHandover` | `0xf04e283e` | +| `getMinSocketFees` | `0xd383b688` | +| `grantRole` | `0x2f2ff15d` | +| `hasRole` | `0x91d14854` | +| `owner` | `0x8da5cb5b` | +| `ownershipHandoverExpiresAt` | `0xfee81cf4` | +| `payAndCheckFees` | `0xbaa56229` | +| `renounceOwnership` | `0x715018a6` | +| `requestOwnershipHandover` | `0x25692962` | +| `rescueFunds` | `0x6ccae054` | +| `revokeRole` | `0xd547741f` | +| `setSocketFees` | `0x47a406f6` | +| `socketFees` | `0xab1b33a8` | +| `transferOwnership` | `0xf2fde38b` | ## WatcherPrecompileConfig @@ -320,8 +319,6 @@ | `evmxSlug` | `0x8bae77c2` | | `feesPlug` | `0xd1ba159d` | | `getPlugConfigs` | `0x8a028c38` | -| `grantRole` | `0x2f2ff15d` | -| `hasRole` | `0x91d14854` | | `initialize` | `0x6ecf2b22` | | `isNonceUsed` | `0x5d00bb12` | | `isValidPlug` | `0xec8aef74` | @@ -329,62 +326,66 @@ | `ownershipHandoverExpiresAt` | `0xfee81cf4` | | `renounceOwnership` | `0x715018a6` | | `requestOwnershipHandover` | `0x25692962` | -| `revokeRole` | `0xd547741f` | -| `setAppGateways` | `0xbdf0b455` | +| `setAppGateways` | `0xdd8539d4` | | `setIsValidPlug` | `0xb3a6bbcf` | | `setOnChainContracts` | `0x33fa78c2` | | `setSwitchboard` | `0x61706f1e` | | `sockets` | `0xb44a23ab` | | `switchboards` | `0xaa539546` | | `transferOwnership` | `0xf2fde38b` | -| `verifyConnections` | `0xe283ce7b` | +| `verifyConnections` | `0xf269ab50` | | `watcherPrecompileConfig` | `0x8618a912` | | `watcherPrecompileLimits` | `0xa71cd97d` | | `watcherPrecompile__` | `0x1de360c3` | ## WatcherPrecompileLimits -| Function | Signature | -| ---------------------------- | ------------ | -| `LIMIT_DECIMALS` | `0x1e65497d` | -| `addressResolver__` | `0x6a750469` | -| `cancelOwnershipHandover` | `0x54d1f13d` | -| `completeOwnershipHandover` | `0xf04e283e` | -| `consumeLimit` | `0xc22f5a13` | -| `defaultLimit` | `0xe26b013b` | -| `defaultRatePerSecond` | `0x16d7acdf` | -| `deliveryHelper__` | `0xc031dfb4` | -| `getCurrentLimit` | `0x1a065507` | -| `getLimitParams` | `0x2ff81ee0` | -| `grantRole` | `0x2f2ff15d` | -| `hasRole` | `0x91d14854` | -| `initialize` | `0x1794bb3c` | -| `owner` | `0x8da5cb5b` | -| `ownershipHandoverExpiresAt` | `0xfee81cf4` | -| `renounceOwnership` | `0x715018a6` | -| `requestOwnershipHandover` | `0x25692962` | -| `revokeRole` | `0xd547741f` | -| `setDefaultLimit` | `0x995284b1` | -| `setDefaultRatePerSecond` | `0xa44df657` | -| `transferOwnership` | `0xf2fde38b` | -| `updateLimitParams` | `0x01b2a5a0` | -| `watcherPrecompileConfig` | `0x8618a912` | -| `watcherPrecompileLimits` | `0xa71cd97d` | -| `watcherPrecompile__` | `0x1de360c3` | +| Function | Signature | +| --------------------------------- | ------------ | +| `addressResolver__` | `0x6a750469` | +| `callBackFees` | `0xf9554ecc` | +| `cancelOwnershipHandover` | `0x54d1f13d` | +| `completeOwnershipHandover` | `0xf04e283e` | +| `consumeLimit` | `0xc22f5a13` | +| `defaultLimit` | `0xe26b013b` | +| `defaultRatePerSecond` | `0x16d7acdf` | +| `deliveryHelper__` | `0xc031dfb4` | +| `finalizeFees` | `0x09207879` | +| `getCurrentLimit` | `0x1a065507` | +| `getLimitParams` | `0x2ff81ee0` | +| `getTotalFeesRequired` | `0x964500b5` | +| `initialize` | `0x1794bb3c` | +| `limitDecimals` | `0xee185533` | +| `owner` | `0x8da5cb5b` | +| `ownershipHandoverExpiresAt` | `0xfee81cf4` | +| `queryFees` | `0xcfcbafb6` | +| `renounceOwnership` | `0x715018a6` | +| `requestOwnershipHandover` | `0x25692962` | +| `setCallBackFees` | `0x622be814` | +| `setDefaultLimitAndRatePerSecond` | `0x7e434156` | +| `setFinalizeFees` | `0xbce0a88c` | +| `setQueryFees` | `0x877135d7` | +| `setTimeoutFees` | `0x571db4f9` | +| `timeoutFees` | `0xeab12f7e` | +| `transferOwnership` | `0xf2fde38b` | +| `updateLimitParams` | `0x01b2a5a0` | +| `watcherPrecompileConfig` | `0x8618a912` | +| `watcherPrecompileLimits` | `0xa71cd97d` | +| `watcherPrecompile__` | `0x1de360c3` | ## DeliveryHelper | Function | Signature | | ------------------------------ | ------------ | | `addressResolver__` | `0x6a750469` | -| `batch` | `0x039cc50a` | +| `batch` | `0xd9307dd8` | | `bidTimeout` | `0x94090d0b` | | `cancelOwnershipHandover` | `0x54d1f13d` | | `cancelRequest` | `0x50ad0779` | +| `chainMaxMsgValueLimit` | `0x01d1e126` | | `clearQueue` | `0xf22cb874` | | `completeOwnershipHandover` | `0xf04e283e` | | `deliveryHelper__` | `0xc031dfb4` | -| `endTimeout` | `0x9c3bb867` | | `finishRequest` | `0xeab148c0` | | `getDeliveryHelperPlugAddress` | `0xb709bd9f` | | `getFees` | `0xfbf4ec4b` | @@ -400,28 +401,28 @@ | `requestOwnershipHandover` | `0x25692962` | | `requests` | `0xb71a5e58` | | `saltCounter` | `0xa04c6809` | -| `startRequestProcessing` | `0xf61474a9` | +| `startRequestProcessing` | `0x5ca2100f` | | `transferOwnership` | `0xf2fde38b` | | `updateBidTimeout` | `0xa29f83d1` | +| `updateChainMaxMsgValueLimits` | `0x0b32de76` | | `watcherPrecompileConfig` | `0x8618a912` | | `watcherPrecompileLimits` | `0xa71cd97d` | | `watcherPrecompile__` | `0x1de360c3` | -| `withdrawTo` | `0x74c33667` | +| `withdrawTo` | `0x2ba9d5bb` | +| `withdrawTransmitterFees` | `0x38ff6dd2` | ## FastSwitchboard | Function | Signature | | ---------------------------- | ------------ | -| `allowPacket` | `0x21e9ec80` | +| `allowPayload` | `0x31c23f66` | | `attest` | `0x63671b60` | | `cancelOwnershipHandover` | `0x54d1f13d` | | `chainSlug` | `0xb349ba65` | | `completeOwnershipHandover` | `0xf04e283e` | | `grantRole` | `0x2f2ff15d` | | `hasRole` | `0x91d14854` | -| `initialPacketCount` | `0x7c138814` | | `isAttested` | `0xc13c2396` | -| `nextNonce` | `0x0cd55abf` | | `owner` | `0x8da5cb5b` | | `ownershipHandoverExpiresAt` | `0xfee81cf4` | | `registerSwitchboard` | `0x74f5b1fc` | @@ -431,3 +432,62 @@ | `revokeRole` | `0xd547741f` | | `socket__` | `0xc6a261d2` | | `transferOwnership` | `0xf2fde38b` | + +## WatcherPrecompile + +| Function | Signature | +| ----------------------------- | ------------ | +| `addressResolver__` | `0x6a750469` | +| `appGatewayCalled` | `0xc6767cf1` | +| `appGatewayCaller` | `0x712b193a` | +| `batchPayloadIds` | `0x02b74f98` | +| `callAppGateways` | `0x5c38ded5` | +| `cancelOwnershipHandover` | `0x54d1f13d` | +| `cancelRequest` | `0x50ad0779` | +| `completeOwnershipHandover` | `0xf04e283e` | +| `deliveryHelper__` | `0xc031dfb4` | +| `evmxSlug` | `0x8bae77c2` | +| `expiryTime` | `0x99bc0aea` | +| `finalized` | `0x81c051de` | +| `getBatchPayloadIds` | `0xfd83cd1f` | +| `getBatches` | `0xcb95b7b3` | +| `getCurrentRequestCount` | `0x5715abbb` | +| `getDigest` | `0xa7993154` | +| `getPayloadParams` | `0xae5eeb77` | +| `getRequestParams` | `0x71263d0d` | +| `initialize` | `0xb7dc6b77` | +| `isNonceUsed` | `0x5d00bb12` | +| `isPromiseExecuted` | `0x17a2cdf0` | +| `markRevert` | `0x1c75dad5` | +| `maxTimeoutDelayInSeconds` | `0x46fbc9d7` | +| `nextBatchCount` | `0x333a3963` | +| `nextRequestCount` | `0xfef72893` | +| `owner` | `0x8da5cb5b` | +| `ownershipHandoverExpiresAt` | `0xfee81cf4` | +| `payloadCounter` | `0x550ce1d5` | +| `payloads` | `0x58722672` | +| `query` | `0x16ad71bc` | +| `renounceOwnership` | `0x715018a6` | +| `requestBatchIds` | `0xf865c4a7` | +| `requestMetadata` | `0x875b3f7e` | +| `requestOwnershipHandover` | `0x25692962` | +| `requestParams` | `0x5ce2d853` | +| `resolvePromises` | `0xccb1caff` | +| `resolveTimeout` | `0xa67c0781` | +| `setExpiryTime` | `0x30fc4cff` | +| `setMaxTimeoutDelayInSeconds` | `0x65d480fc` | +| `setTimeout` | `0x9c29ec74` | +| `setWatcherPrecompileConfig` | `0x794edeb4` | +| `setWatcherPrecompileLimits` | `0x712a6f07` | +| `startProcessingRequest` | `0x77290f24` | +| `submitRequest` | `0x16b47482` | +| `timeoutIdPrefix` | `0x96ec119f` | +| `timeoutRequests` | `0xcdf85751` | +| `transferOwnership` | `0xf2fde38b` | +| `updateTransmitter` | `0xb228a22c` | +| `watcherPrecompileConfig` | `0x8618a912` | +| `watcherPrecompileConfig__` | `0xa816cbd9` | +| `watcherPrecompileLimits` | `0xa71cd97d` | +| `watcherPrecompileLimits__` | `0xb2ad6c48` | +| `watcherPrecompile__` | `0x1de360c3` | +| `watcherProofs` | `0x3fa3166b` | diff --git a/contracts/base/AppGatewayBase.sol b/contracts/base/AppGatewayBase.sol index 2b509aab..f1a936f4 100644 --- a/contracts/base/AppGatewayBase.sol +++ b/contracts/base/AppGatewayBase.sol @@ -1,5 +1,5 @@ -// SPDX-License-Identifier: MIT -pragma solidity ^0.8.3; +// SPDX-License-Identifier: GPL-3.0-only +pragma solidity ^0.8.21; import "../protocol/utils/AddressResolverUtil.sol"; import "../interfaces/IAppGateway.sol"; @@ -7,37 +7,66 @@ import "../interfaces/IForwarder.sol"; import "../interfaces/IMiddleware.sol"; import "../interfaces/IPromise.sol"; -import {FeesPlugin} from "../protocol/utils/FeesPlugin.sol"; import {InvalidPromise, FeesNotSet, AsyncModifierNotUsed} from "../protocol/utils/common/Errors.sol"; import {FAST} from "../protocol/utils/common/Constants.sol"; /// @title AppGatewayBase /// @notice Abstract contract for the app gateway -abstract contract AppGatewayBase is AddressResolverUtil, IAppGateway, FeesPlugin { +/// @dev This contract contains helpers for contract deployment, overrides, hooks and request processing +abstract contract AppGatewayBase is AddressResolverUtil, IAppGateway { OverrideParams public overrideParams; + bool public isAsyncModifierSet; address public auctionManager; - bytes public onCompleteData; bytes32 public sbType; - bool public isAsyncModifierSet; + bytes public onCompleteData; + uint256 public maxFees; mapping(address => bool) public isValidPromise; mapping(bytes32 => mapping(uint32 => address)) public override forwarderAddresses; mapping(bytes32 => bytes) public creationCodeWithArgs; + address public consumeFrom; + /// @notice Modifier to treat functions async - modifier async() { - if (fees.feePoolChain == 0) revert FeesNotSet(); - isAsyncModifierSet = true; - deliveryHelper__().clearQueue(); - addressResolver__.clearPromises(); - _clearOverrides(); + modifier async(bytes memory feesApprovalData_) { + _preAsync(feesApprovalData_); + _; + _postAsync(); + } + + // todo: can't overload modifier with same name, can rename later + /// @notice Modifier to treat functions async with consume from address + modifier asyncWithConsume(address consumeFrom_) { + _preAsync(new bytes(0)); + consumeFrom = consumeFrom_; _; + _postAsync(); + } + + function _postAsync() internal { isAsyncModifierSet = false; - deliveryHelper__().batch(fees, auctionManager, onCompleteData); + + deliveryHelper__().batch(maxFees, auctionManager, consumeFrom, onCompleteData); _markValidPromises(); onCompleteData = bytes(""); } + function _preAsync(bytes memory feesApprovalData_) internal { + isAsyncModifierSet = true; + _clearOverrides(); + deliveryHelper__().clearQueue(); + addressResolver__.clearPromises(); + + _handleFeesApproval(feesApprovalData_); + } + + function _handleFeesApproval(bytes memory feesApprovalData_) internal { + if (feesApprovalData_.length > 0) { + (consumeFrom, , ) = IFeesManager(addressResolver__.feesManager()) + .whitelistAppGatewayWithSignature(feesApprovalData_); + } else consumeFrom = address(this); + } + /// @notice Modifier to ensure only valid promises can call the function /// @dev only valid promises can call the function modifier onlyPromises() { @@ -87,13 +116,6 @@ abstract contract AppGatewayBase is AddressResolverUtil, IAppGateway, FeesPlugin } } - /// @notice Gets the socket address - /// @param chainSlug_ The chain slug - /// @return socketAddress_ The socket address - function getSocketAddress(uint32 chainSlug_) public view returns (address) { - return watcherPrecompileConfig().sockets(chainSlug_); - } - /// @notice Sets the validity of an onchain contract (plug) to authorize it to send information to a specific AppGateway /// @param chainSlug_ The unique identifier of the chain where the contract resides /// @param contractId The bytes32 identifier of the contract to be validated @@ -127,9 +149,9 @@ abstract contract AppGatewayBase is AddressResolverUtil, IAppGateway, FeesPlugin if (!isAsyncModifierSet) revert AsyncModifierNotUsed(); address asyncPromise = addressResolver__.deployAsyncPromiseContract(address(this)); - isValidPromise[asyncPromise] = true; IPromise(asyncPromise).then(this.setAddress.selector, abi.encode(chainSlug_, contractId_)); + isValidPromise[asyncPromise] = true; onCompleteData = abi.encode(chainSlug_, true); QueuePayloadParams memory queuePayloadParams = QueuePayloadParams({ @@ -143,7 +165,7 @@ abstract contract AppGatewayBase is AddressResolverUtil, IAppGateway, FeesPlugin target: address(0), appGateway: address(this), gasLimit: overrideParams.gasLimit, - value: 0, + value: overrideParams.value, readAt: overrideParams.readAt, payload: creationCodeWithArgs[contractId_], initCallData: initCallData_ @@ -156,7 +178,6 @@ abstract contract AppGatewayBase is AddressResolverUtil, IAppGateway, FeesPlugin /// @param returnData_ The return data function setAddress(bytes memory data_, bytes memory returnData_) external onlyPromises { (uint32 chainSlug, bytes32 contractId) = abi.decode(data_, (uint32, bytes32)); - address forwarderContractAddress = addressResolver__.getOrDeployForwarderContract( address(this), abi.decode(returnData_, (address)), @@ -166,6 +187,13 @@ abstract contract AppGatewayBase is AddressResolverUtil, IAppGateway, FeesPlugin forwarderAddresses[contractId][chainSlug] = forwarderContractAddress; } + /// @notice Gets the socket address + /// @param chainSlug_ The chain slug + /// @return socketAddress_ The socket address + function getSocketAddress(uint32 chainSlug_) public view returns (address) { + return watcherPrecompileConfig().sockets(chainSlug_); + } + /// @notice Gets the on-chain address /// @param contractId_ The contract ID /// @param chainSlug_ The chain slug @@ -188,30 +216,31 @@ abstract contract AppGatewayBase is AddressResolverUtil, IAppGateway, FeesPlugin /// @notice Sets multiple overrides in one call /// @param isReadCall_ The read call flag - /// @param fees_ The fees configuration + /// @param fees_ The maxFees configuration /// @param gasLimit_ The gas limit /// @param isParallelCall_ The sequential call flag function _setOverrides( Read isReadCall_, Parallel isParallelCall_, uint256 gasLimit_, - Fees memory fees_ + uint256 fees_ ) internal { overrideParams.isReadCall = isReadCall_; overrideParams.isParallelCall = isParallelCall_; overrideParams.gasLimit = gasLimit_; - fees = fees_; + maxFees = fees_; } function _clearOverrides() internal { overrideParams.isReadCall = Read.OFF; overrideParams.isParallelCall = Parallel.OFF; overrideParams.gasLimit = 0; + overrideParams.value = 0; overrideParams.readAt = 0; overrideParams.writeFinality = WriteFinality.LOW; } - /// @notice Sets isReadCall, fees and gasLimit overrides + /// @notice Sets isReadCall, maxFees and gasLimit overrides /// @param isReadCall_ The read call flag /// @param isParallelCall_ The sequential call flag /// @param gasLimit_ The gas limit @@ -269,16 +298,20 @@ abstract contract AppGatewayBase is AddressResolverUtil, IAppGateway, FeesPlugin overrideParams.gasLimit = gasLimit_; } - /// @notice Sets fees overrides - /// @param fees_ The fees configuration - function _setOverrides(Fees memory fees_) internal { - fees = fees_; + function _setMsgValue(uint256 value_) internal { + overrideParams.value = value_; + } + + /// @notice Sets maxFees overrides + /// @param fees_ The maxFees configuration + function _setMaxFees(uint256 fees_) internal { + maxFees = fees_; } function getOverrideParams() public view - returns (Read, Parallel, WriteFinality, uint256, uint256, bytes32) + returns (Read, Parallel, WriteFinality, uint256, uint256, uint256, bytes32) { return ( overrideParams.isReadCall, @@ -286,6 +319,7 @@ abstract contract AppGatewayBase is AddressResolverUtil, IAppGateway, FeesPlugin overrideParams.writeFinality, overrideParams.readAt, overrideParams.gasLimit, + overrideParams.value, sbType ); } @@ -300,7 +334,7 @@ abstract contract AppGatewayBase is AddressResolverUtil, IAppGateway, FeesPlugin deliveryHelper__().cancelRequest(requestCount_); } - /// @notice increases the transaction fees + /// @notice increases the transaction maxFees /// @param requestCount_ The async ID function _increaseFees(uint40 requestCount_, uint256 newMaxFees_) internal { deliveryHelper__().increaseFees(requestCount_, newMaxFees_); @@ -324,7 +358,7 @@ abstract contract AppGatewayBase is AddressResolverUtil, IAppGateway, FeesPlugin amount_, receiver_, auctionManager, - fees + maxFees ); } @@ -336,7 +370,10 @@ abstract contract AppGatewayBase is AddressResolverUtil, IAppGateway, FeesPlugin /// @param onCompleteData_ The on complete data /// @dev only payload delivery can call this /// @dev callback in pd promise to be called after all contracts are deployed - function onRequestComplete(uint40, bytes calldata onCompleteData_) external override { + function onRequestComplete( + uint40, + bytes calldata onCompleteData_ + ) external override onlyDeliveryHelper { if (onCompleteData_.length == 0) return; (uint32 chainSlug, bool isDeploy) = abi.decode(onCompleteData_, (uint32, bool)); if (isDeploy) { @@ -344,14 +381,8 @@ abstract contract AppGatewayBase is AddressResolverUtil, IAppGateway, FeesPlugin } } - function callFromChain( - uint32 chainSlug_, - address plug_, - bytes32 params_, - bytes calldata payload_ - ) external virtual onlyWatcherPrecompile {} - - /// @notice Initializes the contract + /// @notice Initializes the contract after deployment + /// @dev can be overridden by the app gateway to add custom logic /// @param chainSlug_ The chain slug function initialize(uint32 chainSlug_) public virtual {} diff --git a/contracts/base/PlugBase.sol b/contracts/base/PlugBase.sol index 4707cff4..1ef146bc 100644 --- a/contracts/base/PlugBase.sol +++ b/contracts/base/PlugBase.sol @@ -1,4 +1,4 @@ -// SPDX-License-Identifier: MIT +// SPDX-License-Identifier: GPL-3.0-only pragma solidity ^0.8.21; import {ISocket} from "../interfaces/ISocket.sol"; @@ -7,10 +7,12 @@ import {NotSocket} from "../protocol/utils/common/Errors.sol"; /// @title PlugBase /// @notice Abstract contract for plugs +/// @dev This contract contains helpers for socket connection, disconnection, and overrides abstract contract PlugBase is IPlug { ISocket public socket__; - address public appGateway; + bytes32 public appGatewayId; uint256 public isSocketInitialized; + bytes public overrides; error SocketAlreadyInitialized(); event ConnectorPlugDisconnected(); @@ -22,7 +24,7 @@ abstract contract PlugBase is IPlug { _; } - /// @notice Modifier to ensure the socket is initialized + /// @notice Modifier to ensure the socket is initialized and if not already initialized, it will be initialized modifier socketInitializer() { if (isSocketInitialized == 1) revert SocketAlreadyInitialized(); isSocketInitialized = 1; @@ -30,19 +32,20 @@ abstract contract PlugBase is IPlug { } /// @notice Connects the plug to the app gateway and switchboard - /// @param appGateway_ The app gateway address + /// @param appGatewayId_ The app gateway id + /// @param socket_ The socket address /// @param switchboard_ The switchboard address - function _connectSocket(address appGateway_, address socket_, address switchboard_) internal { + function _connectSocket(bytes32 appGatewayId_, address socket_, address switchboard_) internal { _setSocket(socket_); - appGateway = appGateway_; + appGatewayId = appGatewayId_; - socket__.connect(appGateway_, switchboard_); + socket__.connect(appGatewayId_, switchboard_); } /// @notice Disconnects the plug from the socket function _disconnectSocket() internal { (, address switchboard) = socket__.getPlugConfig(address(this)); - socket__.connect(address(0), switchboard); + socket__.connect(bytes32(0), switchboard); emit ConnectorPlugDisconnected(); } @@ -52,15 +55,17 @@ abstract contract PlugBase is IPlug { socket__ = ISocket(socket_); } - function _callAppGateway(bytes memory payload_, bytes32 params_) internal returns (bytes32) { - return socket__.callAppGateway(payload_, params_); + /// @notice Sets the overrides needed for the trigger + /// @param overrides_ The overrides + function _setOverrides(bytes memory overrides_) internal { + overrides = overrides_; } function initSocket( - address appGateway_, + bytes32 appGatewayId_, address socket_, address switchboard_ ) external virtual socketInitializer { - _connectSocket(appGateway_, socket_, switchboard_); + _connectSocket(appGatewayId_, socket_, switchboard_); } } diff --git a/contracts/base/ProxyFactory.sol b/contracts/base/ProxyFactory.sol index 0f903337..a49a9af3 100644 --- a/contracts/base/ProxyFactory.sol +++ b/contracts/base/ProxyFactory.sol @@ -1,7 +1,6 @@ -pragma solidity ^0.8.0; +// SPDX-License-Identifier: GPL-3.0-only +pragma solidity ^0.8.21; import {ERC1967Factory} from "solady/utils/ERC1967Factory.sol"; -contract ProxyFactory is ERC1967Factory { - constructor() {} -} +contract ProxyFactory is ERC1967Factory {} diff --git a/contracts/helpers/TestUSDC.sol b/contracts/helpers/TestUSDC.sol new file mode 100644 index 00000000..02528a07 --- /dev/null +++ b/contracts/helpers/TestUSDC.sol @@ -0,0 +1,41 @@ +// SPDX-License-Identifier: GPL-3.0-only +pragma solidity ^0.8.21; + +import "solady/tokens/ERC20.sol"; + +contract TestUSDC is ERC20 { + address public immutable owner; + string private _name; + string private _symbol; + uint8 private _decimals; + + constructor( + string memory name_, + string memory symbol_, + uint8 decimals_, + address owner_, + uint256 initialSupply_ + ) { + owner = owner_; + _name = name_; + _symbol = symbol_; + _decimals = decimals_; + _mint(owner_, initialSupply_); + } + + function name() public view override returns (string memory) { + return _name; + } + + function symbol() public view override returns (string memory) { + return _symbol; + } + + function decimals() public view override returns (uint8) { + return _decimals; + } + + function mint(address to, uint256 amount) external { + _mint(to, amount); + } +} diff --git a/contracts/interfaces/IAddressResolver.sol b/contracts/interfaces/IAddressResolver.sol index 7a34fcbd..1e8684bc 100644 --- a/contracts/interfaces/IAddressResolver.sol +++ b/contracts/interfaces/IAddressResolver.sol @@ -1,4 +1,4 @@ -// SPDX-License-Identifier: Unlicense +// SPDX-License-Identifier: GPL-3.0-only pragma solidity ^0.8.21; import "./IWatcherPrecompile.sol"; @@ -12,24 +12,45 @@ interface IAddressResolver { /// @param newAddress The new address of the contract event AddressSet(bytes32 indexed name, address oldAddress, address newAddress); + /// @notice Emitted when a new plug is added to the resolver + /// @param appGateway The address of the app gateway + /// @param chainSlug The chain slug + /// @param plug The address of the plug + event PlugAdded(address appGateway, uint32 chainSlug, address plug); + + /// @notice Emitted when a new forwarder is deployed + /// @param newForwarder The address of the new forwarder + /// @param salt The salt used to deploy the forwarder + event ForwarderDeployed(address newForwarder, bytes32 salt); + + /// @notice Emitted when a new async promise is deployed + /// @param newAsyncPromise The address of the new async promise + /// @param salt The salt used to deploy the async promise + event AsyncPromiseDeployed(address newAsyncPromise, bytes32 salt); + + /// @notice Emitted when an implementation is updated + /// @param contractName The name of the contract + /// @param newImplementation The new implementation address + event ImplementationUpdated(string contractName, address newImplementation); + /// @notice Gets the address of the delivery helper contract - /// @return IMiddleware The delivery helper interface - /// @dev Returns interface pointing to zero address if not configured + /// @return The delivery helper contract address + /// @dev Returns zero address if not configured function deliveryHelper() external view returns (address); /// @notice Gets the address of the fees manager contract - /// @return IFeesManager The fees manager interface - /// @dev Returns interface pointing to zero address if not configured + /// @return The fees manager contract address + /// @dev Returns zero address if not configured function feesManager() external view returns (address); /// @notice Gets the address of the default auction manager contract - /// @return IAuctionManager The auction manager interface - /// @dev Returns interface pointing to zero address if not configured + /// @return The auction manager contract address + /// @dev Returns zero address if not configured function defaultAuctionManager() external view returns (address); - /// @notice Gets the watcher precompile contract interface - /// @return IWatcherPrecompile The watcher precompile interface - /// @dev Returns interface pointing to zero address if not configured + /// @notice Gets the watcher precompile contract instance + /// @return The watcher precompile contract instance + /// @dev Returns instance with zero address if not configured function watcherPrecompile__() external view returns (IWatcherPrecompile); /// @notice Maps contract addresses to their corresponding gateway addresses @@ -41,29 +62,17 @@ interface IAddressResolver { /// @return Array of async promise contract addresses function getPromises() external view returns (address[] memory); - // State-changing functions - /// @notice Sets the auction house contract address - /// @param deliveryHelper_ The new delivery helper contract address - /// @dev Only callable by contract owner - function setDeliveryHelper(address deliveryHelper_) external; - - /// @notice Sets the watcher precompile contract address - /// @param watcherPrecompile_ The new watcher precompile contract address - /// @dev Only callable by contract owner - function setWatcherPrecompile(address watcherPrecompile_) external; - /// @notice Maps a contract address to its gateway /// @param contractAddress_ The contract address to map /// @dev Creates bidirectional mapping between contract and gateway function setContractsToGateways(address contractAddress_) external; - /// @notice Clears the list of deployed async promise contracts - /// @dev Only callable by contract owner + /// @notice Clears the list of deployed async promise contracts array function clearPromises() external; - /// @notice Deploys a new forwarder contract if not already deployed - /// @param chainContractAddress_ The contract address on the destination chain - /// @param chainSlug_ The identifier of the destination chain + /// @notice Deploys or returns the address of a new forwarder contract if not already deployed + /// @param chainContractAddress_ The contract address on the `chainSlug_` + /// @param chainSlug_ The identifier of the chain /// @return The address of the newly deployed forwarder contract function getOrDeployForwarderContract( address appGateway_, diff --git a/contracts/interfaces/IAppGateway.sol b/contracts/interfaces/IAppGateway.sol index d62d647f..886ebec7 100644 --- a/contracts/interfaces/IAppGateway.sol +++ b/contracts/interfaces/IAppGateway.sol @@ -1,37 +1,55 @@ -// SPDX-License-Identifier: Unlicense +// SPDX-License-Identifier: GPL-3.0-only pragma solidity ^0.8.21; -import {Fees, Read, Parallel, QueuePayloadParams, OverrideParams, CallType, WriteFinality, PayloadParams} from "../protocol/utils/common/Structs.sol"; +import {Read, Parallel, QueuePayloadParams, OverrideParams, CallType, WriteFinality, PayloadParams} from "../protocol/utils/common/Structs.sol"; +/// @title IAppGateway +/// @notice Interface for the app gateway interface IAppGateway { + /// @notice Checks if the async modifier is set + /// @return isAsyncModifierSet_ True if the async modifier is set, false otherwise function isAsyncModifierSet() external view returns (bool); + /// @notice Gets the override parameters + /// @return read_ The read parameters + /// @return parallel_ The parallel parameters + /// @return writeFinality_ The write finality parameters + /// @return readTimeout_ The read timeout + /// @return writeTimeout_ The write timeout + /// @return writeFinalityTimeout_ The write finality timeout + /// @return sbType_ The switchboard type function getOverrideParams() external view - returns (Read, Parallel, WriteFinality, uint256, uint256, bytes32); + returns (Read, Parallel, WriteFinality, uint256, uint256, uint256, bytes32); + /// @notice Handles the request complete event + /// @param requestCount_ The request count + /// @param onCompleteData_ The on complete data function onRequestComplete(uint40 requestCount_, bytes calldata onCompleteData_) external; - function callFromChain( - uint32 chainSlug_, - address plug_, - bytes32 params_, - bytes calldata payload_ - ) external; - + /// @notice Handles the revert event + /// @param requestCount_ The request count + /// @param payloadId_ The payload id function handleRevert(uint40 requestCount_, bytes32 payloadId_) external; /// @notice initialize the contracts on chain + /// @param chainSlug_ The chain slug function initialize(uint32 chainSlug_) external; /// @notice get the on-chain address of a contract + /// @param contractId_ The contract id + /// @param chainSlug_ The chain slug + /// @return onChainAddress The on-chain address function getOnChainAddress( bytes32 contractId_, uint32 chainSlug_ ) external view returns (address onChainAddress); /// @notice get the forwarder address of a contract + /// @param contractId_ The contract id + /// @param chainSlug_ The chain slug + /// @return forwarderAddress The forwarder address function forwarderAddresses( bytes32 contractId_, uint32 chainSlug_ diff --git a/contracts/interfaces/IAuctionManager.sol b/contracts/interfaces/IAuctionManager.sol index bee93dc1..a67f0200 100644 --- a/contracts/interfaces/IAuctionManager.sol +++ b/contracts/interfaces/IAuctionManager.sol @@ -1,9 +1,14 @@ -// SPDX-License-Identifier: MIT -pragma solidity ^0.8.3; +// SPDX-License-Identifier: GPL-3.0-only +pragma solidity ^0.8.21; -import {Bid, Fees} from "../protocol/utils/common/Structs.sol"; +import {Bid, RequestMetadata, RequestParams} from "../protocol/utils/common/Structs.sol"; interface IAuctionManager { + /// @notice Bids for an auction + /// @param requestCount_ The request count + /// @param fee_ The fee + /// @param transmitterSignature_ The transmitter signature + /// @param extraData_ The extra data function bid( uint40 requestCount_, uint256 fee_, @@ -11,9 +16,17 @@ interface IAuctionManager { bytes memory extraData_ ) external; + /// @notice Ends an auction + /// @param requestCount_ The request count function endAuction(uint40 requestCount_) external; + /// @notice Checks if an auction is closed + /// @param requestCount_ The request count + /// @return isClosed_ Whether the auction is closed function auctionClosed(uint40 requestCount_) external view returns (bool); + /// @notice Checks if an auction is started + /// @param requestCount_ The request count + /// @return isStarted_ Whether the auction is started function auctionStarted(uint40 requestCount_) external view returns (bool); } diff --git a/contracts/interfaces/IContractFactoryPlug.sol b/contracts/interfaces/IContractFactoryPlug.sol index a3b5bac0..7a020a6a 100644 --- a/contracts/interfaces/IContractFactoryPlug.sol +++ b/contracts/interfaces/IContractFactoryPlug.sol @@ -1,4 +1,4 @@ -// SPDX-License-Identifier: MIT +// SPDX-License-Identifier: GPL-3.0-only pragma solidity ^0.8.21; import {IsPlug} from "../protocol/utils/common/Structs.sol"; @@ -14,7 +14,7 @@ interface IContractFactoryPlug { function deployContract( IsPlug isPlug_, bytes32 salt_, - address appGateway_, + bytes32 appGatewayId_, address switchboard_, bytes memory creationCode_, bytes memory initCallData_ diff --git a/contracts/interfaces/IFeesManager.sol b/contracts/interfaces/IFeesManager.sol index a76d6d43..02701602 100644 --- a/contracts/interfaces/IFeesManager.sol +++ b/contracts/interfaces/IFeesManager.sol @@ -1,31 +1,66 @@ -// SPDX-License-Identifier: MIT -pragma solidity ^0.8.3; +// SPDX-License-Identifier: GPL-3.0-only +pragma solidity ^0.8.21; -import {Fees, Bid, QueuePayloadParams} from "../protocol/utils/common/Structs.sol"; +import {Bid, QueuePayloadParams, PayloadSubmitParams, AppGatewayWhitelistParams} from "../protocol/utils/common/Structs.sol"; interface IFeesManager { - function blockFees( + function blockCredits( + address consumeFrom_, + uint256 transmitterCredits_, + uint40 requestCount_ + ) external; + + function unblockCredits(uint40 requestCount_) external; + + function isUserCreditsEnough( + address consumeFrom_, address appGateway_, - Fees memory fees_, - Bid memory winningBid_, + uint256 amount_ + ) external view returns (bool); + + function unblockAndAssignCredits(uint40 requestCount_, address transmitter_) external; + + function assignWatcherPrecompileCreditsFromRequestCount( + uint256 fees_, uint40 requestCount_ ) external; - function unblockFees(uint40 requestCount_) external; + function assignWatcherPrecompileCreditsFromAddress( + uint256 fees_, + address consumeFrom_ + ) external; + + function whitelistAppGatewayWithSignature( + bytes memory feeApprovalData_ + ) external returns (address consumeFrom, address appGateway, bool isApproved); - function isFeesEnough(address appGateway_, Fees memory fees_) external view returns (bool); + function whitelistAppGateways(AppGatewayWhitelistParams[] calldata params_) external; - function unblockAndAssignFees( - uint40 requestCount_, + function getWithdrawTransmitterCreditsPayloadParams( address transmitter_, - address appGateway_ - ) external; + uint32 chainSlug_, + address token_, + address receiver_, + uint256 amount_ + ) external returns (PayloadSubmitParams[] memory); - function withdrawFees( - address appGateway_, + function getMaxCreditsAvailableForWithdraw( + address transmitter_ + ) external view returns (uint256); + + function withdrawCredits( + address originAppGatewayOrUser_, uint32 chainSlug_, address token_, uint256 amount_, address receiver_ ) external; + + function depositCredits( + address depositTo_, + uint32 chainSlug_, + address token_, + uint256 signatureNonce_, + bytes memory signature_ + ) external payable; } diff --git a/contracts/interfaces/IFeesPlug.sol b/contracts/interfaces/IFeesPlug.sol index 9515ee81..46797d62 100644 --- a/contracts/interfaces/IFeesPlug.sol +++ b/contracts/interfaces/IFeesPlug.sol @@ -1,21 +1,12 @@ -// SPDX-License-Identifier: MIT +// SPDX-License-Identifier: GPL-3.0-only pragma solidity ^0.8.21; interface IFeesPlug { - function balanceOf(address appGateway_, address token_) external view returns (uint256); + function depositToFee(address token_, address receiver_, uint256 amount_) external; - function feesRedeemed(uint256 feesCounter_) external view returns (bool); + function depositToFeeAndNative(address token_, address receiver_, uint256 amount_) external; - function deposit(address token_, address appGateway_, uint256 amount_) external payable; + function depositToNative(address token_, address receiver_, uint256 amount_) external; - function connect(address appGateway_, address switchboard_) external; - - function distributeFee( - address feeToken_, - uint256 fee_, - address transmitter_, - bytes32 feesCounter_ - ) external; - - function withdrawFees(address token_, uint256 amount_, address receiver_) external; + function withdrawFees(address token_, address receiver_, uint256 amount_) external; } diff --git a/contracts/interfaces/IForwarder.sol b/contracts/interfaces/IForwarder.sol index 9372cc5f..b609fc31 100644 --- a/contracts/interfaces/IForwarder.sol +++ b/contracts/interfaces/IForwarder.sol @@ -1,9 +1,14 @@ -// SPDX-License-Identifier: MIT +// SPDX-License-Identifier: GPL-3.0-only pragma solidity ^0.8.21; +/// @title IForwarder +/// @notice Interface for the Forwarder contract that allows contracts to call async promises interface IForwarder { - // View functions + /// @notice Returns the on-chain address of the contract being referenced + /// @return The on-chain address function getOnChainAddress() external view returns (address); + /// @notice Returns the chain slug of the on chain contract + /// @return The chain slug function getChainSlug() external view returns (uint32); } diff --git a/contracts/interfaces/IMiddleware.sol b/contracts/interfaces/IMiddleware.sol index 3509457d..6bbd5a3d 100644 --- a/contracts/interfaces/IMiddleware.sol +++ b/contracts/interfaces/IMiddleware.sol @@ -1,47 +1,77 @@ -// SPDX-License-Identifier: UNLICENSED -pragma solidity ^0.8.3; -import {QueuePayloadParams, Bid, Fees, WriteFinality, CallType, Parallel, IsPlug, RequestMetadata} from "../protocol/utils/common/Structs.sol"; +// SPDX-License-Identifier: GPL-3.0-only +pragma solidity ^0.8.21; +import {PayloadSubmitParams, QueuePayloadParams, Bid, WriteFinality, BatchParams, CallType, Parallel, IsPlug, RequestMetadata} from "../protocol/utils/common/Structs.sol"; +/// @title IMiddleware +/// @notice Interface for the Middleware contract interface IMiddleware { - event AuctionEnded( - uint40 indexed requestCount, - Bid winningBid // Replaced winningTransmitter and winningBid with Bid struct - ); - + /// @notice Returns the timeout after which a bid expires function bidTimeout() external view returns (uint128); + /// @notice Returns the metadata for a request + /// @param requestCount_ The request id + /// @return requestMetadata The metadata for the request function getRequestMetadata( uint40 requestCount_ ) external view returns (RequestMetadata memory); + /// @notice Clears the temporary queue used to store payloads for a request function clearQueue() external; + /// @notice Queues a payload for a request + /// @param queuePayloadParams_ The parameters for the payload function queue(QueuePayloadParams memory queuePayloadParams_) external; + /// @notice Batches a request + /// @param fees_ The fees for the request + /// @param auctionManager_ The address of the auction manager + /// @param onCompleteData_ The data to be passed to the onComplete callback + /// @return requestCount The request id function batch( - Fees memory fees_, + uint256 fees_, address auctionManager_, + address consumeFrom_, bytes memory onCompleteData_ ) external returns (uint40 requestCount); + /// @notice Withdraws funds to a receiver + /// @param chainSlug_ The chain slug + /// @param token_ The token address + /// @param amount_ The amount to withdraw + /// @param receiver_ The receiver address + /// @param auctionManager_ The address of the auction manager + /// @param fees_ The fees for the request function withdrawTo( uint32 chainSlug_, address token_, uint256 amount_, address receiver_, address auctionManager_, - Fees memory fees_ + uint256 fees_ ) external returns (uint40); + /// @notice Cancels a request + /// @param requestCount_ The request id function cancelRequest(uint40 requestCount_) external; + /// @notice Increases the fees for a request + /// @param requestCount_ The request id + /// @param fees_ The new fees function increaseFees(uint40 requestCount_, uint256 fees_) external; + /// @notice Starts the request processing + /// @param requestCount_ The request id + /// @param winningBid_ The winning bid function startRequestProcessing(uint40 requestCount_, Bid memory winningBid_) external; - function getFees(uint40 requestCount_) external view returns (Fees memory); + /// @notice Returns the fees for a request + function getFees(uint40 requestCount_) external view returns (uint256); + /// @notice Finishes a request by assigning fees and calling the onComplete callback + /// @param requestCount_ The request id function finishRequest(uint40 requestCount_) external; + /// @notice Handles request reverts by unblocking the fees and calling the onRevert callback + /// @param requestCount_ The request id function handleRequestReverts(uint40 requestCount_) external; } diff --git a/contracts/interfaces/IPlug.sol b/contracts/interfaces/IPlug.sol index 1e738f09..be18836e 100644 --- a/contracts/interfaces/IPlug.sol +++ b/contracts/interfaces/IPlug.sol @@ -6,5 +6,13 @@ pragma solidity ^0.8.21; * @notice Interface for a plug contract that executes the payload received from a source chain. */ interface IPlug { - function initSocket(address appGateway_, address socket_, address switchboard_) external; + /// @notice Initializes the socket + /// @param appGatewayId_ The app gateway id + /// @param socket_ The socket address + /// @param switchboard_ The switchboard address + function initSocket(bytes32 appGatewayId_, address socket_, address switchboard_) external; + + /// @notice Gets the overrides + /// @return overrides_ The overrides + function overrides() external view returns (bytes memory overrides_); } diff --git a/contracts/interfaces/IPromise.sol b/contracts/interfaces/IPromise.sol index 13318ac0..c43d6627 100644 --- a/contracts/interfaces/IPromise.sol +++ b/contracts/interfaces/IPromise.sol @@ -1,16 +1,27 @@ -// SPDX-License-Identifier: Unlicense +// SPDX-License-Identifier: GPL-3.0-only pragma solidity ^0.8.21; +/// @title IPromise interface IPromise { - function then(bytes4 selector_, bytes memory data_) external returns (address _promise); + /// @notice Sets the callback selector and data for the promise. + /// @param selector_ The function selector for the callback. + /// @param data_ The data to be passed to the callback. + /// @return promise_ The address of the current promise + function then(bytes4 selector_, bytes memory data_) external returns (address promise_); + /// @notice Marks the promise as resolved and executes the callback if set. + /// @dev Only callable by the watcher precompile. + /// @param returnData_ The data returned from the async payload execution. function markResolved( uint40 requestCount_, bytes32 payloadId_, bytes memory returnData_ ) external returns (bool success); + /// @notice Marks the promise as onchain reverting. + /// @dev Only callable by the watcher precompile. function markOnchainRevert(uint40 requestCount_, bytes32 payloadId_) external; + /// @notice Indicates whether the promise has been resolved. function resolved() external view returns (bool); } diff --git a/contracts/interfaces/ISocket.sol b/contracts/interfaces/ISocket.sol index 1d7e1a1e..dc5b999e 100644 --- a/contracts/interfaces/ISocket.sol +++ b/contracts/interfaces/ISocket.sol @@ -1,7 +1,8 @@ // SPDX-License-Identifier: GPL-3.0-only pragma solidity ^0.8.21; -import {ExecuteParams} from "../protocol/utils/common/Structs.sol"; +import {ExecuteParams, TransmissionParams} from "../protocol/utils/common/Structs.sol"; + /** * @title ISocket * @notice An interface for a Chain Abstraction contract @@ -13,67 +14,59 @@ import {ExecuteParams} from "../protocol/utils/common/Structs.sol"; interface ISocket { /** * @notice emits the status of payload after external call - * @param payloadId msg id which is executed + * @param payloadId payload id which is executed */ - event ExecutionSuccess(bytes32 payloadId, bytes returnData); + event ExecutionSuccess(bytes32 payloadId, bool exceededMaxCopy, bytes returnData); /** * @notice emits the status of payload after external call - * @param payloadId msg id which is executed + * @param payloadId payload id which is executed */ - event ExecutionFailed(bytes32 payloadId, bytes returnData); + event ExecutionFailed(bytes32 payloadId, bool exceededMaxCopy, bytes returnData); /** * @notice emits the config set by a plug for a remoteChainSlug * @param plug address of plug on current chain - * @param appGateway address of plug on sibling chain + * @param appGatewayId address of plug on sibling chain * @param switchboard outbound switchboard (select from registered options) */ - event PlugConnected(address plug, address appGateway, address switchboard); + event PlugConnected(address plug, bytes32 appGatewayId, address switchboard); /** - * @notice emits the message details when a new message arrives at outbound - * @param callId call id - * @param chainSlug local chain slug + * @notice emits the payload details when a new payload arrives at outbound + * @param triggerId trigger id + * @param switchboard switchboard address * @param plug local plug address - * @param appGateway appGateway address to trigger the call - * @param params params, for specifying details like fee pool chain, fee pool token and max fees if required + * @param overrides params, for specifying details like fee pool chain, fee pool token and max fees if required * @param payload the data which will be used by contracts on chain */ event AppGatewayCallRequested( - bytes32 callId, - uint32 chainSlug, + bytes32 triggerId, + bytes32 appGatewayId, + address switchboard, address plug, - address appGateway, - bytes32 params, + bytes overrides, bytes payload ); - /** - * @notice To call the appGateway on EVMx. Should only be called by a plug. - * @param payload_ bytes to be delivered to the Plug on EVMx - * @param params_ a 32 bytes param to add details for execution. - */ - function callAppGateway( - bytes calldata payload_, - bytes32 params_ - ) external returns (bytes32 callId); - /** * @notice executes a payload */ function execute( - ExecuteParams memory executeParams_, - bytes memory transmitterSignature_ - ) external payable returns (bytes memory); + ExecuteParams calldata executeParams_, + TransmissionParams calldata transmissionParams_ + ) external payable returns (bool, bytes memory); /** * @notice sets the config specific to the plug - * @param appGateway_ address of plug present at sibling chain + * @param appGatewayId_ address of plug present at sibling chain * @param switchboard_ the address of switchboard to use for executing payloads */ - function connect(address appGateway_, address switchboard_) external; + function connect(bytes32 appGatewayId_, address switchboard_) external; + /** + * @notice registers a switchboard for the socket + */ function registerSwitchboard() external; /** @@ -82,5 +75,5 @@ interface ISocket { */ function getPlugConfig( address plugAddress_ - ) external view returns (address appGateway, address switchboard); + ) external view returns (bytes32 appGatewayId, address switchboard); } diff --git a/contracts/interfaces/ISocketBatcher.sol b/contracts/interfaces/ISocketBatcher.sol index ab753866..b8cb2270 100644 --- a/contracts/interfaces/ISocketBatcher.sol +++ b/contracts/interfaces/ISocketBatcher.sol @@ -3,11 +3,25 @@ pragma solidity ^0.8.21; import {ExecuteParams} from "../protocol/utils/common/Structs.sol"; +/** + * @title ISocketBatcher + * @notice Interface for a helper contract for socket which batches attest (on sb) and execute calls (on socket). + */ interface ISocketBatcher { + /** + * @notice Attests a payload and executes it + * @param executeParams_ The execution parameters + * @param digest_ The digest of the payload + * @param proof_ The proof of the payload + * @param transmitterSignature_ The signature of the transmitter + * @return The return data after execution + */ function attestAndExecute( ExecuteParams calldata executeParams_, + address switchboard_, bytes32 digest_, bytes calldata proof_, - bytes calldata transmitterSignature_ - ) external payable returns (bytes memory); + bytes calldata transmitterSignature_, + address refundAddress_ + ) external payable returns (bool, bytes memory); } diff --git a/contracts/interfaces/ISocketFeeManager.sol b/contracts/interfaces/ISocketFeeManager.sol new file mode 100644 index 00000000..1d7958c0 --- /dev/null +++ b/contracts/interfaces/ISocketFeeManager.sol @@ -0,0 +1,34 @@ +// SPDX-License-Identifier: GPL-3.0-only +pragma solidity ^0.8.21; + +import {ExecuteParams, TransmissionParams} from "../protocol/utils/common/Structs.sol"; + +interface ISocketFeeManager { + /** + * @notice Pays and validates fees for execution + * @param executeParams_ Execute params + * @param transmissionParams_ Transmission params + */ + function payAndCheckFees( + ExecuteParams memory executeParams_, + TransmissionParams memory transmissionParams_ + ) external payable; + + /** + * @notice Gets minimum fees required for execution + * @return nativeFees Minimum native token fees required + */ + function getMinSocketFees() external view returns (uint256 nativeFees); + + /** + * @notice Sets socket fees + * @param socketFees_ New socket fees amount + */ + function setSocketFees(uint256 socketFees_) external; + + /** + * @notice Gets current socket fees + * @return Current socket fees amount + */ + function socketFees() external view returns (uint256); +} diff --git a/contracts/interfaces/ISwitchboard.sol b/contracts/interfaces/ISwitchboard.sol index c6998445..4f1095ea 100644 --- a/contracts/interfaces/ISwitchboard.sol +++ b/contracts/interfaces/ISwitchboard.sol @@ -3,8 +3,8 @@ pragma solidity ^0.8.21; /** * @title ISwitchboard - * @dev The interface for a switchboard contract that is responsible for verification of payloadss between - * different blockchain networks. + * @dev The interface for a switchboard contract that is responsible for verification of payloads if the correct + * digest is executed. */ interface ISwitchboard { /** @@ -13,7 +13,12 @@ interface ISwitchboard { * @param payloadId_ The unique identifier for the payloads. * @return A boolean indicating whether the payloads is allowed to go through the switchboard or not. */ - function allowPacket(bytes32 digest_, bytes32 payloadId_) external view returns (bool); + function allowPayload(bytes32 digest_, bytes32 payloadId_) external view returns (bool); + /** + * @notice Attests a payload + * @param digest_ The digest of the payload + * @param proof_ The proof of the payload + */ function attest(bytes32 digest_, bytes calldata proof_) external; } diff --git a/contracts/interfaces/IWatcherPrecompile.sol b/contracts/interfaces/IWatcherPrecompile.sol index 5ce28d64..0262a220 100644 --- a/contracts/interfaces/IWatcherPrecompile.sol +++ b/contracts/interfaces/IWatcherPrecompile.sol @@ -1,7 +1,7 @@ -// SPDX-License-Identifier: Unlicense +// SPDX-License-Identifier: GPL-3.0-only pragma solidity ^0.8.21; -import {DigestParams, ResolvedPromises, PayloadParams, CallFromChainParams, PayloadSubmitParams, RequestParams} from "../protocol/utils/common/Structs.sol"; +import {DigestParams, ResolvedPromises, PayloadParams, TriggerParams, PayloadSubmitParams, Bid, RequestParams, RequestMetadata} from "../protocol/utils/common/Structs.sol"; import {IWatcherPrecompileLimits} from "./IWatcherPrecompileLimits.sol"; import {IWatcherPrecompileConfig} from "./IWatcherPrecompileConfig.sol"; @@ -9,14 +9,13 @@ import {IWatcherPrecompileConfig} from "./IWatcherPrecompileConfig.sol"; /// @notice Interface for the Watcher Precompile system that handles payload verification and execution /// @dev Defines core functionality for payload processing and promise resolution interface IWatcherPrecompile { - event CalledAppGateway( - bytes32 callId, - uint32 chainSlug, - address plug, - address appGateway, - bytes32 params, - bytes payload - ); + /// @notice Emitted when a new call is made to an app gateway + /// @param triggerId The unique identifier for the trigger + event CalledAppGateway(bytes32 triggerId); + + /// @notice Emitted when a call to an app gateway fails + /// @param triggerId The unique identifier for the trigger + event AppGatewayCallFailed(bytes32 triggerId); /// @notice Emitted when a new query is requested event QueryRequested(PayloadParams params); @@ -37,20 +36,31 @@ interface IWatcherPrecompile { /// @param payloadId The unique identifier for the not resolved promise event PromiseNotResolved(bytes32 indexed payloadId, address asyncPromise); + /// @notice Emitted when a payload is marked as revert + /// @param payloadId The unique identifier for the payload + /// @param isRevertingOnchain Whether the payload is reverting onchain event MarkedRevert(bytes32 indexed payloadId, bool isRevertingOnchain); - event TimeoutRequested( - bytes32 timeoutId, - address target, - bytes payload, - uint256 executeAt // Epoch time when the task should execute - ); + + /// @notice Emitted when a timeout is requested + /// @param timeoutId The unique identifier for the timeout + /// @param target The target address for the timeout callback + /// @param payload The payload data + /// @param executeAt The epoch time when the task should execute + event TimeoutRequested(bytes32 timeoutId, address target, bytes payload, uint256 executeAt); /// @notice Emitted when a timeout is resolved /// @param timeoutId The unique identifier for the timeout - /// @param target The target address for the timeout + /// @param target The target address for the callback /// @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); + /// @param returnData The return data from the callback + event TimeoutResolved( + bytes32 timeoutId, + address target, + bytes payload, + uint256 executedAt, + bytes returnData + ); event RequestSubmitted( address middleware, @@ -58,12 +68,20 @@ interface IWatcherPrecompile { PayloadParams[] payloadParamsArray ); + event MaxTimeoutDelayInSecondsSet(uint256 maxTimeoutDelayInSeconds); + + event ExpiryTimeSet(uint256 expiryTime); + + event WatcherPrecompileLimitsSet(address watcherPrecompileLimits); + + event WatcherPrecompileConfigSet(address watcherPrecompileConfig); + + event RequestCancelledFromGateway(uint40 requestCount); + /// @notice Error thrown when an invalid chain slug is provided error InvalidChainSlug(); /// @notice Error thrown when an invalid app gateway reaches a plug error InvalidConnection(); - /// @notice Error thrown if winning bid is assigned to an invalid transmitter - error InvalidTransmitter(); /// @notice Error thrown when a timeout request is invalid error InvalidTimeoutRequest(); /// @notice Error thrown when a payload id is invalid @@ -79,7 +97,9 @@ interface IWatcherPrecompile { error RequestCancelled(); error AlreadyStarted(); + error RequestNotProcessing(); error InvalidLevelNumber(); + error DeadlineNotPassedForOnChainRevert(); /// @notice Calculates the digest hash of payload parameters /// @param params_ The payload parameters @@ -112,11 +132,6 @@ interface IWatcherPrecompile { bytes calldata signature_ ) external; - function finalize( - PayloadParams memory params_, - address transmitter_ - ) external returns (bytes32 digest); - function query(PayloadParams memory params_) external; function finalized( @@ -146,7 +161,7 @@ interface IWatcherPrecompile { function setMaxTimeoutDelayInSeconds(uint256 maxTimeoutDelayInSeconds_) external; function callAppGateways( - CallFromChainParams[] calldata params_, + TriggerParams[] calldata params_, uint256 signatureNonce_, bytes calldata signature_ ) external; @@ -166,4 +181,6 @@ interface IWatcherPrecompile { function watcherPrecompileLimits__() external view returns (IWatcherPrecompileLimits); function getRequestParams(uint40 requestCount) external view returns (RequestParams memory); + + function nextRequestCount() external view returns (uint40); } diff --git a/contracts/interfaces/IWatcherPrecompileConfig.sol b/contracts/interfaces/IWatcherPrecompileConfig.sol index d42c7294..3cf44e00 100644 --- a/contracts/interfaces/IWatcherPrecompileConfig.sol +++ b/contracts/interfaces/IWatcherPrecompileConfig.sol @@ -1,4 +1,4 @@ -// SPDX-License-Identifier: Unlicense +// SPDX-License-Identifier: GPL-3.0-only pragma solidity ^0.8.21; import {AppGatewayConfig, PlugConfig} from "../protocol/utils/common/Structs.sol"; @@ -36,20 +36,22 @@ interface IWatcherPrecompileConfig { function setSwitchboard(uint32 chainSlug_, bytes32 sbType_, address switchboard_) external; /// @notice Sets valid plugs for each chain slug + /// @dev This function is used to verify if a plug deployed on a chain slug is valid connection to the app gateway function setIsValidPlug(uint32 chainSlug_, address plug_, bool isValid_) external; /// @notice Retrieves the configuration for a specific plug on a network function getPlugConfigs( uint32 chainSlug_, address plug_ - ) external view returns (address, address); + ) external view returns (bytes32, address); /// @notice Verifies connections between components function verifyConnections( uint32 chainSlug_, address target_, address appGateway_, - address switchboard_ + address switchboard_, + address middleware_ ) external view; function setAppGateways( diff --git a/contracts/interfaces/IWatcherPrecompileLimits.sol b/contracts/interfaces/IWatcherPrecompileLimits.sol index 70a0c01a..02899a72 100644 --- a/contracts/interfaces/IWatcherPrecompileLimits.sol +++ b/contracts/interfaces/IWatcherPrecompileLimits.sol @@ -1,4 +1,4 @@ -// SPDX-License-Identifier: Unlicense +// SPDX-License-Identifier: GPL-3.0-only pragma solidity ^0.8.21; import {LimitParams, UpdateLimitParams} from "../protocol/utils/common/Structs.sol"; @@ -31,14 +31,10 @@ interface IWatcherPrecompileLimits { /// @notice Set the default limit value /// @param defaultLimit_ The new default limit value - function setDefaultLimit(uint256 defaultLimit_) external; - - /// @notice Set the rate at which limit replenishes - /// @param defaultRatePerSecond_ The new rate per second - function setDefaultRatePerSecond(uint256 defaultRatePerSecond_) external; + function setDefaultLimitAndRatePerSecond(uint256 defaultLimit_) external; /// @notice Number of decimals used in limit calculations - function LIMIT_DECIMALS() external view returns (uint256); + function limitDecimals() external view returns (uint256); /// @notice Default limit value for any app gateway function defaultLimit() external view returns (uint256); @@ -46,20 +42,27 @@ interface IWatcherPrecompileLimits { /// @notice Rate at which limit replenishes per second function defaultRatePerSecond() external view returns (uint256); + /// @notice Consumes a limit for an app gateway + /// @param appGateway_ The app gateway address + /// @param limitType_ The type of limit to consume + /// @param consumeLimit_ The amount of limit to consume function consumeLimit(address appGateway_, bytes32 limitType_, uint256 consumeLimit_) external; + function getTotalFeesRequired( + uint256 queryCount_, + uint256 finalizeCount_, + uint256 scheduleCount_, + uint256 callbackCount_ + ) external view returns (uint256); + + function queryFees() external view returns (uint256); + function finalizeFees() external view returns (uint256); + function timeoutFees() external view returns (uint256); + function callBackFees() external view returns (uint256); + /// @notice Emitted when limit parameters are updated event LimitParamsUpdated(UpdateLimitParams[] updates); /// @notice Emitted when an app gateway is activated with default limits event AppGatewayActivated(address indexed appGateway, uint256 maxLimit, uint256 ratePerSecond); - - error ActionNotSupported(address appGateway_, bytes32 limitType_); - error NotDeliveryHelper(); - error LimitExceeded( - address appGateway, - bytes32 limitType, - uint256 requested, - uint256 available - ); } diff --git a/contracts/protocol/AddressResolver.sol b/contracts/protocol/AddressResolver.sol index df9bf3b6..e6d0a401 100644 --- a/contracts/protocol/AddressResolver.sol +++ b/contracts/protocol/AddressResolver.sol @@ -1,4 +1,4 @@ -// SPDX-License-Identifier: Unlicense +// SPDX-License-Identifier: GPL-3.0-only pragma solidity ^0.8.21; import {Ownable} from "solady/auth/Ownable.sol"; @@ -17,16 +17,16 @@ abstract contract AddressResolverStorage is IAddressResolver { IWatcherPrecompile public override watcherPrecompile__; // slot 51 - address public override deliveryHelper; + UpgradeableBeacon public forwarderBeacon; // slot 52 - address public override feesManager; + UpgradeableBeacon public asyncPromiseBeacon; // slot 53 - UpgradeableBeacon public forwarderBeacon; + address public override deliveryHelper; // slot 54 - UpgradeableBeacon public asyncPromiseBeacon; + address public override feesManager; // slot 55 address public forwarderImplementation; @@ -42,15 +42,13 @@ abstract contract AddressResolverStorage is IAddressResolver { // slot 59 uint64 public version; + address public override defaultAuctionManager; // slot 60 mapping(address => address) public override contractsToGateways; - // slot 61 - address public override defaultAuctionManager; - - // slots [62-110] reserved for gap - uint256[49] _gap_after; + // slots [61-110] reserved for gap + uint256[50] _gap_after; } /// @title AddressResolver Contract @@ -60,16 +58,24 @@ contract AddressResolver is AddressResolverStorage, Initializable, Ownable { /// @notice Error thrown if AppGateway contract was already set by a different address error InvalidAppGateway(address contractAddress_); - event PlugAdded(address appGateway, uint32 chainSlug, address plug); - event ForwarderDeployed(address newForwarder, bytes32 salt); - event AsyncPromiseDeployed(address newAsyncPromise, bytes32 salt); - event ImplementationUpdated(string contractName, address newImplementation); + /// @notice Event emitted when the delivery helper is updated + event DeliveryHelperUpdated(address deliveryHelper_); + /// @notice Event emitted when the fees manager is updated + event FeesManagerUpdated(address feesManager_); + /// @notice Event emitted when the default auction manager is updated + event DefaultAuctionManagerUpdated(address defaultAuctionManager_); + /// @notice Event emitted when the watcher precompile is updated + event WatcherPrecompileUpdated(address watcherPrecompile_); + /// @notice Event emitted when the contracts to gateways mapping is updated + event ContractsToGatewaysUpdated(address contractAddress_, address appGateway_); constructor() { _disableInitializers(); // disable for implementation } /// @notice Initializer to replace constructor for upgradeable contracts + /// @dev it deploys the forwarder and async promise implementations and beacons for them + /// @dev this contract is owner of the beacons for upgrading later /// @param owner_ The address of the contract owner function initialize(address owner_) public reinitializer(1) { version = 1; @@ -84,6 +90,8 @@ contract AddressResolver is AddressResolverStorage, Initializable, Ownable { } /// @notice Gets or deploys a Forwarder proxy contract + /// @dev it checks if the forwarder is already deployed, if yes, it returns the address + /// @dev it maps the forwarder with the app gateway which is used for verifying if they are linked /// @param chainContractAddress_ The address of the chain contract /// @param chainSlug_ The chain slug /// @return newForwarder The address of the deployed Forwarder proxy contract @@ -94,18 +102,25 @@ contract AddressResolver is AddressResolverStorage, Initializable, Ownable { ) public returns (address newForwarder) { // predict address address forwarderAddress = getForwarderAddress(chainContractAddress_, chainSlug_); + // check if addr has code, if yes, return if (forwarderAddress.code.length > 0) { return forwarderAddress; } + // creates init data and salt (bytes32 salt, bytes memory initData) = _createForwarderParams( chainContractAddress_, chainSlug_ ); + // deploys the proxy newForwarder = _deployProxy(salt, address(forwarderBeacon), initData); + + // sets the config _setConfig(appGateway_, newForwarder); + + // emits the event emit ForwarderDeployed(newForwarder, salt); } @@ -120,6 +135,8 @@ contract AddressResolver is AddressResolverStorage, Initializable, Ownable { chainContractAddress_, address(this) ); + + // creates salt with constructor args salt = keccak256(constructorArgs); } @@ -127,6 +144,8 @@ contract AddressResolver is AddressResolverStorage, Initializable, Ownable { address invoker_ ) internal view returns (bytes32 salt, bytes memory initData) { bytes memory constructorArgs = abi.encode(invoker_, msg.sender, address(this)); + + // creates init data initData = abi.encodeWithSelector( AsyncPromise.initialize.selector, invoker_, @@ -134,6 +153,7 @@ contract AddressResolver is AddressResolverStorage, Initializable, Ownable { address(this) ); + // creates salt with a counter salt = keccak256(abi.encodePacked(constructorArgs, asyncPromiseCounter)); } @@ -143,9 +163,11 @@ contract AddressResolver is AddressResolverStorage, Initializable, Ownable { function deployAsyncPromiseContract( address invoker_ ) external returns (address newAsyncPromise) { + // creates init data and salt (bytes32 salt, bytes memory initData) = _createAsyncPromiseParams(invoker_); asyncPromiseCounter++; + // deploys the proxy newAsyncPromise = _deployProxy(salt, address(asyncPromiseBeacon), initData); _promises.push(newAsyncPromise); @@ -157,10 +179,10 @@ contract AddressResolver is AddressResolverStorage, Initializable, Ownable { address beacon_, bytes memory initData_ ) internal returns (address) { - // 1. Deploy proxy without initialization args + // Deploy proxy without initialization args address proxy = LibClone.deployDeterministicERC1967BeaconProxy(beacon_, salt_); - // 2. Explicitly initialize after deployment + // Explicitly initialize after deployment (bool success, ) = proxy.call(initData_); require(success, "Initialization failed"); @@ -189,6 +211,7 @@ contract AddressResolver is AddressResolverStorage, Initializable, Ownable { revert InvalidAppGateway(contractAddress_); } contractsToGateways[contractAddress_] = msg.sender; + emit ContractsToGatewaysUpdated(contractAddress_, msg.sender); } /// @notice Gets the predicted address of a Forwarder proxy contract @@ -243,23 +266,27 @@ contract AddressResolver is AddressResolverStorage, Initializable, Ownable { /// @param deliveryHelper_ The address of the delivery helper function setDeliveryHelper(address deliveryHelper_) external onlyOwner { deliveryHelper = deliveryHelper_; + emit DeliveryHelperUpdated(deliveryHelper_); } /// @notice Updates the address of the fees manager /// @param feesManager_ The address of the fees manager function setFeesManager(address feesManager_) external onlyOwner { feesManager = feesManager_; + emit FeesManagerUpdated(feesManager_); } /// @notice Updates the address of the default auction manager /// @param defaultAuctionManager_ The address of the default auction manager function setDefaultAuctionManager(address defaultAuctionManager_) external onlyOwner { defaultAuctionManager = defaultAuctionManager_; + emit DefaultAuctionManagerUpdated(defaultAuctionManager_); } /// @notice Updates the address of the watcher precompile contract /// @param watcherPrecompile_ The address of the watcher precompile contract function setWatcherPrecompile(address watcherPrecompile_) external onlyOwner { watcherPrecompile__ = IWatcherPrecompile(watcherPrecompile_); + emit WatcherPrecompileUpdated(watcherPrecompile_); } } diff --git a/contracts/protocol/AsyncPromise.sol b/contracts/protocol/AsyncPromise.sol index ff81e4b7..261c9d56 100644 --- a/contracts/protocol/AsyncPromise.sol +++ b/contracts/protocol/AsyncPromise.sol @@ -1,11 +1,13 @@ -// SPDX-License-Identifier: MIT +// SPDX-License-Identifier: GPL-3.0-only pragma solidity ^0.8.21; import {AddressResolverUtil} from "./utils/AddressResolverUtil.sol"; import {IPromise} from "../interfaces/IPromise.sol"; import {IAppGateway} from "../interfaces/IAppGateway.sol"; import {Initializable} from "solady/utils/Initializable.sol"; -import {AsyncPromiseState} from "../protocol/utils/common/Structs.sol"; +import {AsyncPromiseState} from "./utils/common/Structs.sol"; +import {MAX_COPY_BYTES} from "./utils/common/Constants.sol"; +import {LibCall} from "solady/utils/LibCall.sol"; abstract contract AsyncPromiseStorage is IPromise { // slots [0-49] reserved for gap @@ -27,7 +29,7 @@ abstract contract AsyncPromiseStorage is IPromise { address public localInvoker; // slot 51 - /// @notice The forwarder address which can call the callback + /// @notice The forwarder address which can set the callback selector and data address public forwarder; // slot 52 @@ -37,13 +39,14 @@ abstract contract AsyncPromiseStorage is IPromise { // slots [53-102] reserved for gap uint256[50] _gap_after; - // slots 103-154 reserved for addr resolver util + // slots 103-154 (51) 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 +/// @notice this contract stores the callback selector and data to be executed once the on-chain call is executed /// This promise expires once the callback is executed contract AsyncPromise is AsyncPromiseStorage, Initializable, AddressResolverUtil { + using LibCall for address; /// @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 @@ -57,25 +60,24 @@ contract AsyncPromise is AsyncPromiseStorage, Initializable, AddressResolverUtil _disableInitializers(); // disable for implementation } - /// @notice Initializer to replace constructor for upgradeable contracts - /// @param invoker_ The address of the local invoker. - /// @param forwarder_ The address of the forwarder. - /// @param addressResolver_ The address resolver contract address. + /// @notice Initialize promise states + /// @param invoker_ The address of the local invoker + /// @param forwarder_ The address of the forwarder + /// @param addressResolver_ The address resolver contract address function initialize( address invoker_, address forwarder_, address addressResolver_ ) public initializer { - _setAddressResolver(addressResolver_); localInvoker = invoker_; forwarder = forwarder_; - state = AsyncPromiseState.WAITING_FOR_SET_CALLBACK_SELECTOR; - resolved = false; + + _setAddressResolver(addressResolver_); } /// @notice Marks the promise as resolved and executes the callback if set. - /// @param returnData_ The data returned from the async payload execution. /// @dev Only callable by the watcher precompile. + /// @param returnData_ The data returned from the async payload execution. function markResolved( uint40 requestCount_, bytes32 payloadId_, @@ -88,11 +90,14 @@ contract AsyncPromise is AsyncPromiseStorage, Initializable, AddressResolverUtil // Call callback to app gateway if (callbackSelector == bytes4(0)) return true; + bytes memory combinedCalldata = abi.encodePacked( callbackSelector, abi.encode(callbackData, returnData_) ); - (success, ) = localInvoker.call(combinedCalldata); + + // setting max_copy_bytes to 0 as not using returnData right now + (success, , ) = localInvoker.tryCall(0, gasleft(), 0, combinedCalldata); if (success) return success; _handleRevert(requestCount_, payloadId_, AsyncPromiseState.CALLBACK_REVERTING); @@ -113,13 +118,13 @@ contract AsyncPromise is AsyncPromiseStorage, Initializable, AddressResolverUtil AsyncPromiseState state_ ) internal { // to update the state in case selector is bytes(0) but reverting onchain - resolved = false; + resolved = true; state = state_; - - (bool success, ) = localInvoker.call( - abi.encodeWithSelector(IAppGateway.handleRevert.selector, requestCount_, payloadId_) - ); - if (!success) revert PromiseRevertFailed(); + try IAppGateway(localInvoker).handleRevert(requestCount_, payloadId_) { + // Successfully handled revert + } catch { + revert PromiseRevertFailed(); + } } /// @notice Sets the callback selector and data for the promise. @@ -130,14 +135,17 @@ contract AsyncPromise is AsyncPromiseStorage, Initializable, AddressResolverUtil bytes4 selector_, bytes memory data_ ) external override returns (address promise_) { + // allows forwarder or local invoker to set the callback selector and data if (msg.sender != forwarder && msg.sender != localInvoker) { revert OnlyForwarderOrLocalInvoker(); } + // if the promise is already set up, revert if (state == AsyncPromiseState.WAITING_FOR_CALLBACK_EXECUTION) { revert PromiseAlreadySetUp(); } + // if the promise is waiting for the callback selector, set it and update the state if (state == AsyncPromiseState.WAITING_FOR_SET_CALLBACK_SELECTOR) { callbackSelector = selector_; callbackData = data_; diff --git a/contracts/protocol/Forwarder.sol b/contracts/protocol/Forwarder.sol index d9231e1a..17bb2b6d 100644 --- a/contracts/protocol/Forwarder.sol +++ b/contracts/protocol/Forwarder.sol @@ -1,4 +1,4 @@ -// SPDX-License-Identifier: MIT +// SPDX-License-Identifier: GPL-3.0-only pragma solidity ^0.8.21; import "../interfaces/IAddressResolver.sol"; @@ -6,18 +6,19 @@ import "../interfaces/IMiddleware.sol"; import "../interfaces/IAppGateway.sol"; import "../interfaces/IPromise.sol"; import "../interfaces/IForwarder.sol"; - import {AddressResolverUtil} from "./utils/AddressResolverUtil.sol"; +import {AsyncModifierNotUsed, NoAsyncPromiseFound, PromiseCallerMismatch, RequestCountMismatch, DeliveryHelperNotSet} from "./utils/common/Errors.sol"; import "solady/utils/Initializable.sol"; +/// @title Forwarder Storage +/// @notice Storage contract for the Forwarder contract that contains the state variables abstract contract ForwarderStorage is IForwarder { // slots [0-49] reserved for gap uint256[50] _gap_before; // slot 50 - /// @notice chain id + /// @notice chain slug on which the contract is deployed uint32 public chainSlug; - /// @notice on-chain address associated with this forwarder address public onChainAddress; @@ -25,23 +26,29 @@ abstract contract ForwarderStorage is IForwarder { /// @notice caches the latest async promise address for the last call address public latestAsyncPromise; - // slots [52-101] reserved for gap + // slot 52 + /// @notice the address of the contract that called the latest async promise + address public latestPromiseCaller; + /// @notice the request count of the latest async promise + uint40 public latestRequestCount; + + // slots [53-102] reserved for gap uint256[50] _gap_after; + + // slots 103-154 (51) reserved for addr resolver util } /// @title Forwarder Contract /// @notice This contract acts as a forwarder for async calls to the on-chain contracts. contract Forwarder is ForwarderStorage, Initializable, AddressResolverUtil { - error AsyncModifierNotUsed(); - constructor() { _disableInitializers(); // disable for implementation } /// @notice Initializer to replace constructor for upgradeable contracts - /// @param chainSlug_ chain id - /// @param onChainAddress_ on-chain address - /// @param addressResolver_ address resolver contract address + /// @param chainSlug_ chain slug on which the contract is deployed + /// @param onChainAddress_ on-chain address associated with this forwarder + /// @param addressResolver_ address resolver contract function initialize( uint32 chainSlug_, address onChainAddress_, @@ -54,13 +61,20 @@ contract Forwarder is ForwarderStorage, Initializable, AddressResolverUtil { /// @notice Stores the callback address and data to be executed once the promise is resolved. /// @dev This function should not be called before the fallback function. + /// @dev It resets the latest async promise address /// @param selector_ The function selector for callback /// @param data_ The data to be passed to callback /// @return promise_ The address of the new promise function then(bytes4 selector_, bytes memory data_) external returns (address promise_) { - if (latestAsyncPromise == address(0)) revert("Forwarder: no async promise found"); - promise_ = IPromise(latestAsyncPromise).then(selector_, data_); + if (latestAsyncPromise == address(0)) revert NoAsyncPromiseFound(); + if (latestPromiseCaller != msg.sender) revert PromiseCallerMismatch(); + if (latestRequestCount != watcherPrecompile__().nextRequestCount()) + revert RequestCountMismatch(); + + address latestAsyncPromise_ = latestAsyncPromise; latestAsyncPromise = address(0); + + promise_ = IPromise(latestAsyncPromise_).then(selector_, data_); } /// @notice Returns the on-chain address associated with this forwarder. @@ -69,38 +83,45 @@ contract Forwarder is ForwarderStorage, Initializable, AddressResolverUtil { return onChainAddress; } - /// @notice Returns the chain id - /// @return chain id + /// @notice Returns the chain slug on which the contract is deployed. + /// @return chain slug function getChainSlug() external view returns (uint32) { return chainSlug; } /// @notice Fallback function to process the contract calls to onChainAddress - /// @dev It queues the calls in the auction house and deploys the promise contract - fallback() external payable { - // Retrieve the auction house address from the address resolver. + /// @dev It queues the calls in the middleware and deploys the promise contract + fallback() external { if (address(deliveryHelper__()) == address(0)) { - revert("Forwarder: deliveryHelper not found"); + revert DeliveryHelperNotSet(); } + // validates if the async modifier is set bool isAsyncModifierSet = IAppGateway(msg.sender).isAsyncModifierSet(); if (!isAsyncModifierSet) revert AsyncModifierNotUsed(); // Deploy a new async promise contract. latestAsyncPromise = addressResolver__.deployAsyncPromiseContract(msg.sender); - // Determine if the call is a read or write operation. + // set the latest promise caller and request count for validating if the future .then call is valid + latestPromiseCaller = msg.sender; + latestRequestCount = watcherPrecompile__().nextRequestCount(); + + // fetch the override params from app gateway ( Read isReadCall, Parallel isParallelCall, WriteFinality writeFinality, uint256 readAt, uint256 gasLimit, + uint256 value, bytes32 sbType ) = IAppGateway(msg.sender).getOverrideParams(); + + // get the switchboard address from the watcher precompile config address switchboard = watcherPrecompileConfig().switchboards(chainSlug, sbType); - // Queue the call in the auction house. + // Queue the call in the middleware. deliveryHelper__().queue( QueuePayloadParams({ chainSlug: chainSlug, @@ -113,13 +134,11 @@ contract Forwarder is ForwarderStorage, Initializable, AddressResolverUtil { target: onChainAddress, appGateway: msg.sender, gasLimit: gasLimit, - value: 0, + value: value, readAt: readAt, payload: msg.data, initCallData: bytes("") }) ); } - - receive() external payable {} } diff --git a/contracts/protocol/payload-delivery/AuctionManager.sol b/contracts/protocol/payload-delivery/AuctionManager.sol index 860d527e..40f1d38d 100644 --- a/contracts/protocol/payload-delivery/AuctionManager.sol +++ b/contracts/protocol/payload-delivery/AuctionManager.sol @@ -1,19 +1,18 @@ -// SPDX-License-Identifier: MIT -pragma solidity ^0.8.0; +// SPDX-License-Identifier: GPL-3.0-only +pragma solidity ^0.8.21; import {ECDSA} from "solady/utils/ECDSA.sol"; import "solady/utils/Initializable.sol"; import "../utils/AccessControl.sol"; - +import "../../interfaces/IAuctionManager.sol"; import {IMiddleware} from "../../interfaces/IMiddleware.sol"; import {IFeesManager} from "../../interfaces/IFeesManager.sol"; -import {IAuctionManager} from "../../interfaces/IAuctionManager.sol"; - import {AddressResolverUtil} from "../utils/AddressResolverUtil.sol"; -import {Fees, Bid, RequestMetadata, RequestParams} from "../utils/common/Structs.sol"; import {AuctionClosed, AuctionAlreadyStarted, BidExceedsMaxFees, LowerBidAlreadyExists, InvalidTransmitter} from "../utils/common/Errors.sol"; import {TRANSMITTER_ROLE} from "../utils/common/AccessRoles.sol"; +/// @title AuctionManagerStorage +/// @notice Storage for the AuctionManager contract abstract contract AuctionManagerStorage is IAuctionManager { // slots [0-49] reserved for gap uint256[50] _gap_before; @@ -22,31 +21,29 @@ abstract contract AuctionManagerStorage is IAuctionManager { uint32 public evmxSlug; // slot 51 - mapping(uint40 => Bid) public winningBids; + uint256 public maxReAuctionCount; // slot 52 - // requestCount => auction status - mapping(uint40 => bool) public override auctionClosed; + uint256 public auctionEndDelaySeconds; // slot 53 - mapping(uint40 => bool) public override auctionStarted; + mapping(uint40 => Bid) public winningBids; // slot 54 - uint256 public auctionEndDelaySeconds; + // requestCount => auction status + mapping(uint40 => bool) public override auctionClosed; // slot 55 - mapping(address => bool) public whitelistedTransmitters; + mapping(uint40 => bool) public override auctionStarted; // slot 56 mapping(uint40 => uint256) public reAuctionCount; - // slot 57 - uint256 public maxReAuctionCount; - - // slots [57-104] reserved for gap - uint256[48] _gap_after; + // slots [57-106] reserved for gap + uint256[50] _gap_after; - // slots 105-155 reserved for addr resolver util + // slots 107-157 (51) reserved for access control + // slots 158-208 (51) reserved for addr resolver util } /// @title AuctionManager @@ -61,6 +58,7 @@ contract AuctionManager is event AuctionStarted(uint40 requestCount); event AuctionEnded(uint40 requestCount, Bid winningBid); event BidPlaced(uint40 requestCount, Bid bid); + event AuctionEndDelaySecondsSet(uint256 auctionEndDelaySeconds); error InvalidBid(); error MaxReAuctionCountReached(); @@ -84,6 +82,7 @@ contract AuctionManager is ) public reinitializer(1) { _setAddressResolver(addressResolver_); _initializeOwner(owner_); + evmxSlug = evmxSlug_; auctionEndDelaySeconds = auctionEndDelaySeconds_; maxReAuctionCount = maxReAuctionCount_; @@ -91,56 +90,48 @@ contract AuctionManager is function setAuctionEndDelaySeconds(uint256 auctionEndDelaySeconds_) external onlyOwner { auctionEndDelaySeconds = auctionEndDelaySeconds_; - } - - function startAuction(uint40 requestCount_) internal { - if (auctionClosed[requestCount_]) revert AuctionClosed(); - if (auctionStarted[requestCount_]) revert AuctionAlreadyStarted(); - - auctionStarted[requestCount_] = true; - emit AuctionStarted(requestCount_); + emit AuctionEndDelaySecondsSet(auctionEndDelaySeconds_); } /// @notice Places a bid for an auction /// @param requestCount_ The ID of the auction - /// @param fee The bid amount + /// @param bidFees The bid amount /// @param transmitterSignature The signature of the transmitter function bid( uint40 requestCount_, - uint256 fee, + uint256 bidFees, bytes memory transmitterSignature, bytes memory extraData ) external { if (auctionClosed[requestCount_]) revert AuctionClosed(); + // check if the transmitter is valid address transmitter = _recoverSigner( - keccak256(abi.encode(address(this), evmxSlug, requestCount_, fee, extraData)), + keccak256(abi.encode(address(this), evmxSlug, requestCount_, bidFees, extraData)), transmitterSignature ); if (!_hasRole(TRANSMITTER_ROLE, transmitter)) revert InvalidTransmitter(); - Bid memory newBid = Bid({fee: fee, transmitter: transmitter, extraData: extraData}); - RequestMetadata memory requestMetadata = IMiddleware(addressResolver__.deliveryHelper()) - .getRequestMetadata(requestCount_); - if (fee > requestMetadata.fees.amount) revert BidExceedsMaxFees(); - if (requestMetadata.auctionManager != address(this)) revert InvalidBid(); + uint256 transmitterCredits = getTransmitterMaxFeesAvailable(requestCount_); + + // check if the bid exceeds the max fees quoted by app gateway subtracting the watcher fees + if (bidFees > transmitterCredits) revert BidExceedsMaxFees(); + // check if the bid is lower than the existing bid if ( winningBids[requestCount_].transmitter != address(0) && - fee >= winningBids[requestCount_].fee + bidFees >= winningBids[requestCount_].fee ) revert LowerBidAlreadyExists(); - winningBids[requestCount_] = newBid; + // create a new bid + Bid memory newBid = Bid({fee: bidFees, transmitter: transmitter, extraData: extraData}); - IFeesManager(addressResolver__.feesManager()).blockFees( - requestMetadata.appGateway, - requestMetadata.fees, - newBid, - requestCount_ - ); + // update the winning bid + winningBids[requestCount_] = newBid; + // end the auction if the no auction end delay if (auctionEndDelaySeconds > 0) { - startAuction(requestCount_); + _startAuction(requestCount_); watcherPrecompile__().setTimeout( auctionEndDelaySeconds, abi.encodeWithSelector(this.endAuction.selector, requestCount_) @@ -150,31 +141,65 @@ contract AuctionManager is } emit BidPlaced(requestCount_, newBid); - auctionClosed[requestCount_] = true; + } + + function getTransmitterMaxFeesAvailable(uint40 requestCount_) public view returns (uint256) { + RequestMetadata memory requestMetadata = _getRequestMetadata(requestCount_); + + // check if the bid is for this auction manager + if (requestMetadata.auctionManager != address(this)) revert InvalidBid(); + + // get the total fees required for the watcher precompile ops + uint256 watcherFees = watcherPrecompileLimits().getTotalFeesRequired( + requestMetadata.queryCount, + requestMetadata.finalizeCount, + 0, + 0 + ); + return requestMetadata.maxFees - watcherFees; } /// @notice Ends an auction /// @param requestCount_ The ID of the auction function endAuction(uint40 requestCount_) external onlyWatcherPrecompile { + if (auctionClosed[requestCount_]) return; _endAuction(requestCount_); } function _endAuction(uint40 requestCount_) internal { - auctionClosed[requestCount_] = true; + // get the winning bid, if no transmitter is set, revert Bid memory winningBid = winningBids[requestCount_]; if (winningBid.transmitter == address(0)) revert InvalidTransmitter(); + auctionClosed[requestCount_] = true; + RequestMetadata memory requestMetadata = _getRequestMetadata(requestCount_); + // block the fees + IFeesManager(addressResolver__.feesManager()).blockCredits( + requestMetadata.consumeFrom, + winningBid.fee, + requestCount_ + ); + + // set the timeout for the bid expiration + // useful in case a transmitter did bid but did not execute payloads watcherPrecompile__().setTimeout( IMiddleware(addressResolver__.deliveryHelper()).bidTimeout(), abi.encodeWithSelector(this.expireBid.selector, requestCount_) ); + + // start the request processing, it will finalize the request IMiddleware(addressResolver__.deliveryHelper()).startRequestProcessing( requestCount_, winningBid ); + emit AuctionEnded(requestCount_, winningBid); } + /// @notice Expires a bid and restarts an auction in case a request is not fully executed. + /// @dev Auction can be restarted only for `maxReAuctionCount` times. + /// @dev It also unblocks the fees from last transmitter to be assigned to the new winner. + /// @param requestCount_ The request id function expireBid(uint40 requestCount_) external onlyWatcherPrecompile { if (reAuctionCount[requestCount_] >= maxReAuctionCount) revert MaxReAuctionCountReached(); RequestParams memory requestParams = watcherPrecompile__().getRequestParams(requestCount_); @@ -185,10 +210,18 @@ contract AuctionManager is auctionClosed[requestCount_] = false; reAuctionCount[requestCount_]++; - IFeesManager(addressResolver__.feesManager()).unblockFees(requestCount_); + IFeesManager(addressResolver__.feesManager()).unblockCredits(requestCount_); emit AuctionRestarted(requestCount_); } + function _startAuction(uint40 requestCount_) internal { + if (auctionClosed[requestCount_]) revert AuctionClosed(); + if (auctionStarted[requestCount_]) revert AuctionAlreadyStarted(); + + auctionStarted[requestCount_] = true; + emit AuctionStarted(requestCount_); + } + function _recoverSigner( bytes32 digest_, bytes memory signature_ @@ -197,4 +230,10 @@ contract AuctionManager is // recovered signer is checked for the valid roles later signer = ECDSA.recover(digest, signature_); } + + function _getRequestMetadata( + uint40 requestCount_ + ) internal view returns (RequestMetadata memory) { + return IMiddleware(addressResolver__.deliveryHelper()).getRequestMetadata(requestCount_); + } } diff --git a/contracts/protocol/payload-delivery/ContractFactoryPlug.sol b/contracts/protocol/payload-delivery/ContractFactoryPlug.sol index 6e137162..24518bd6 100644 --- a/contracts/protocol/payload-delivery/ContractFactoryPlug.sol +++ b/contracts/protocol/payload-delivery/ContractFactoryPlug.sol @@ -1,4 +1,4 @@ -// SPDX-License-Identifier: MIT +// SPDX-License-Identifier: GPL-3.0-only pragma solidity ^0.8.21; import "../utils/AccessControl.sol"; @@ -7,34 +7,44 @@ import "../utils/RescueFundsLib.sol"; import {NotSocket} from "../utils/common/Errors.sol"; import "../../base/PlugBase.sol"; import "../../interfaces/IContractFactoryPlug.sol"; +import {LibCall} from "solady/utils/LibCall.sol"; +import {MAX_COPY_BYTES} from "../utils/common/Constants.sol"; /// @title ContractFactory /// @notice Abstract contract for deploying contracts contract ContractFactoryPlug is PlugBase, AccessControl, IContractFactoryPlug { + using LibCall for address; + event Deployed(address addr, bytes32 salt, bytes returnData); /// @notice Error thrown if it failed to deploy the create2 contract error DeploymentFailed(); error ExecutionFailed(); + /// @notice Constructor for the ContractFactoryPlug + /// @param socket_ The socket address + /// @param owner_ The owner address constructor(address socket_, address owner_) { _initializeOwner(owner_); _setSocket(socket_); } + /// @notice Deploys a contract + /// @param isPlug_ Whether the contract to be deployed is a plug + /// @param salt_ The salt used for create 2 + /// @param appGatewayId_ The app gateway id + /// @param switchboard_ The switchboard address + /// @param creationCode_ The creation code + /// @param initCallData_ The init call data + /// @return addr The address of the deployed contract function deployContract( IsPlug isPlug_, bytes32 salt_, - address appGateway_, + bytes32 appGatewayId_, address switchboard_, bytes memory creationCode_, bytes memory initCallData_ - ) public override returns (address) { - if (msg.sender != address(socket__)) { - revert NotSocket(); - } - - address addr; + ) public override onlySocket returns (address addr) { assembly { addr := create2(callvalue(), add(creationCode_, 0x20), mload(creationCode_), salt_) if iszero(addr) { @@ -43,12 +53,17 @@ contract ContractFactoryPlug is PlugBase, AccessControl, IContractFactoryPlug { } } - if (isPlug_ == IsPlug.YES) IPlug(addr).initSocket(appGateway_, msg.sender, switchboard_); + if (isPlug_ == IsPlug.YES) IPlug(addr).initSocket(appGatewayId_, msg.sender, switchboard_); bytes memory returnData; if (initCallData_.length > 0) { // Capture more detailed error information - (bool success, bytes memory returnData_) = addr.call(initCallData_); + (bool success, , bytes memory returnData_) = addr.tryCall( + 0, + gasleft(), + MAX_COPY_BYTES, + initCallData_ + ); if (!success) { // Additional error logging @@ -64,7 +79,6 @@ contract ContractFactoryPlug is PlugBase, AccessControl, IContractFactoryPlug { } emit Deployed(addr, salt_, returnData); - return addr; } /// @notice Gets the address for a deployed contract @@ -80,11 +94,11 @@ contract ContractFactoryPlug is PlugBase, AccessControl, IContractFactoryPlug { } function connectSocket( - address appGateway_, + bytes32 appGatewayId_, address socket_, address switchboard_ ) external onlyOwner { - _connectSocket(appGateway_, socket_, switchboard_); + _connectSocket(appGatewayId_, socket_, switchboard_); } /** diff --git a/contracts/protocol/payload-delivery/FeesManager.sol b/contracts/protocol/payload-delivery/FeesManager.sol index 7b9804b5..22a6f70b 100644 --- a/contracts/protocol/payload-delivery/FeesManager.sol +++ b/contracts/protocol/payload-delivery/FeesManager.sol @@ -1,16 +1,14 @@ -// SPDX-License-Identifier: MIT -pragma solidity ^0.8.0; +// SPDX-License-Identifier: GPL-3.0-only +pragma solidity ^0.8.21; import {Ownable} from "solady/auth/Ownable.sol"; import "solady/utils/Initializable.sol"; import "solady/utils/ECDSA.sol"; - import {IFeesPlug} from "../../interfaces/IFeesPlug.sol"; -import {IFeesManager} from "../../interfaces/IFeesManager.sol"; - +import "../../interfaces/IFeesManager.sol"; import {AddressResolverUtil} from "../utils/AddressResolverUtil.sol"; -import {NotAuctionManager} from "../utils/common/Errors.sol"; -import {Bid, Fees, CallType, Parallel, WriteFinality, TokenBalance, QueuePayloadParams, IsPlug, PayloadSubmitParams, RequestParams, RequestMetadata} from "../utils/common/Structs.sol"; +import {NotAuctionManager, InvalidWatcherSignature, NonceUsed} from "../utils/common/Errors.sol"; +import {Bid, CallType, Parallel, WriteFinality, QueuePayloadParams, IsPlug, PayloadSubmitParams, RequestMetadata, UserCredits} from "../utils/common/Structs.sol"; abstract contract FeesManagerStorage is IFeesManager { // slots [0-49] reserved for gap @@ -25,21 +23,27 @@ abstract contract FeesManagerStorage is IFeesManager { // slot 52 bytes32 public sbType; - // slot 53 - /// @notice Master mapping tracking all fee information - /// @dev appGateway => chainSlug => token => TokenBalance - mapping(address => mapping(uint32 => mapping(address => TokenBalance))) - public appGatewayFeeBalances; + // user credits + mapping(address => UserCredits) public userCredits; + + // user nonce + mapping(address => uint256) public userNonce; + + // token pool balances + // chainSlug => token address => amount + mapping(uint32 => mapping(address => uint256)) public tokenPoolBalances; + + // user approved app gateways + // userAddress => appGateway => isWhitelisted + mapping(address => mapping(address => bool)) public isAppGatewayWhitelisted; // slot 54 - /// @notice Mapping to track blocked fees for each async id - /// @dev requestCount => Fees - mapping(uint40 => Fees) public requestCountBlockedFees; + /// @notice Mapping to track request credits details for each request count + /// @dev requestCount => RequestFee + mapping(uint40 => uint256) public requestCountCredits; - // slot 55 - /// @notice Mapping to track fees to be distributed to transmitters - /// @dev transmitter => chainSlug => token => amount - mapping(address => mapping(uint32 => mapping(address => uint256))) public transmitterFees; + // @dev amount + uint256 public watcherPrecompileCredits; // slot 56 /// @notice Mapping to track nonce to whether it has been used @@ -49,7 +53,7 @@ abstract contract FeesManagerStorage is IFeesManager { // slots [57-106] reserved for gap uint256[50] _gap_after; - // slots 107-157 reserved for addr resolver util + // slots 107-157 (51) reserved for addr resolver util } /// @title FeesManager @@ -57,32 +61,26 @@ abstract contract FeesManagerStorage is IFeesManager { contract FeesManager is FeesManagerStorage, Initializable, Ownable, AddressResolverUtil { /// @notice Emitted when fees are blocked for a batch /// @param requestCount The batch identifier - /// @param chainSlug The chain identifier - /// @param token The token address + /// @param consumeFrom The consume from address /// @param amount The blocked amount - event FeesBlocked( - uint40 indexed requestCount, - uint32 indexed chainSlug, - address indexed token, - uint256 amount - ); + event CreditsBlocked(uint40 indexed requestCount, address indexed consumeFrom, uint256 amount); /// @notice Emitted when transmitter fees are updated /// @param requestCount The batch identifier /// @param transmitter The transmitter address /// @param amount The new amount deposited - event TransmitterFeesUpdated( + event TransmitterCreditsUpdated( uint40 indexed requestCount, address indexed transmitter, uint256 amount ); - + event WatcherPrecompileCreditsAssigned(uint256 amount, address consumeFrom); /// @notice Emitted when fees deposited are updated /// @param chainSlug The chain identifier /// @param appGateway The app gateway address /// @param token The token address /// @param amount The new amount deposited - event FeesDepositedUpdated( + event CreditsDeposited( uint32 indexed chainSlug, address indexed appGateway, address indexed token, @@ -93,7 +91,7 @@ contract FeesManager is FeesManagerStorage, Initializable, Ownable, AddressResol /// @param requestCount The batch identifier /// @param transmitter The transmitter address /// @param amount The unblocked amount - event FeesUnblockedAndAssigned( + event CreditsUnblockedAndAssigned( uint40 indexed requestCount, address indexed transmitter, uint256 amount @@ -102,20 +100,36 @@ contract FeesManager is FeesManagerStorage, Initializable, Ownable, AddressResol /// @notice Emitted when fees are unblocked /// @param requestCount The batch identifier /// @param appGateway The app gateway address - event FeesUnblocked(uint40 indexed requestCount, address indexed appGateway); + event CreditsUnblocked(uint40 indexed requestCount, address indexed appGateway); + + /// @notice Emitted when insufficient watcher precompile fees are available + event InsufficientWatcherPrecompileCreditsAvailable( + uint32 chainSlug, + address token, + address consumeFrom + ); + + /// @notice Emitted when credits are wrapped + event CreditsWrapped(address indexed consumeFrom, uint256 amount); + + /// @notice Emitted when credits are unwrapped + event CreditsUnwrapped(address indexed consumeFrom, uint256 amount); /// @notice Error thrown when insufficient fees are available - error InsufficientFeesAvailable(); + error InsufficientCreditsAvailable(); /// @notice Error thrown when no fees are available for a transmitter error NoFeesForTransmitter(); /// @notice Error thrown when no fees was blocked - error NoFeesBlocked(); - /// @notice Error thrown when watcher signature is invalid - error InvalidWatcherSignature(); - /// @notice Error thrown when nonce is used - error NonceUsed(); + error NoCreditsBlocked(); /// @notice Error thrown when caller is invalid error InvalidCaller(); + /// @notice Error thrown when user signature is invalid + error InvalidUserSignature(); + /// @notice Error thrown when app gateway is not whitelisted + error AppGatewayNotWhitelisted(); + + error InvalidAmount(); + error InsufficientBalance(); constructor() { _disableInitializers(); // disable for implementation @@ -138,137 +152,207 @@ contract FeesManager is FeesManagerStorage, Initializable, Ownable, AddressResol } /// @notice Returns available (unblocked) fees for a gateway - /// @param chainSlug_ The chain identifier - /// @param appGateway_ The app gateway address - /// @param token_ The token address + /// @param consumeFrom_ The app gateway address /// @return The available fee amount - function getAvailableFees( - uint32 chainSlug_, - address appGateway_, - address token_ - ) public view returns (uint256) { - TokenBalance memory tokenBalance = appGatewayFeeBalances[appGateway_][chainSlug_][token_]; - if (tokenBalance.deposited == 0 || tokenBalance.deposited <= tokenBalance.blocked) return 0; - return tokenBalance.deposited - tokenBalance.blocked; + function getAvailableCredits(address consumeFrom_) public view returns (uint256) { + UserCredits memory userCredit = userCredits[consumeFrom_]; + if (userCredit.totalCredits == 0 || userCredit.totalCredits <= userCredit.blockedCredits) + return 0; + return userCredit.totalCredits - userCredit.blockedCredits; } /// @notice Adds the fees deposited for an app gateway on a chain - /// @param chainSlug_ The chain identifier - /// @param originAppGateway_ The app gateway address - /// @param token_ The token address - /// @param amount_ The amount deposited - function incrementFeesDeposited( + /// @param depositTo_ The app gateway address + // @dev only callable by watcher precompile + // @dev will need tokenAmount_ and creditAmount_ when introduce tokens except stables + function depositCredits( + address depositTo_, uint32 chainSlug_, - address originAppGateway_, address token_, - uint256 amount_, uint256 signatureNonce_, bytes memory signature_ - ) external { - _isWatcherSignatureValid( - abi.encode(chainSlug_, originAppGateway_, token_, amount_), - signatureNonce_, - signature_ + ) external payable { + if (isNonceUsed[signatureNonce_]) revert NonceUsed(); + isNonceUsed[signatureNonce_] = true; + + uint256 amount = msg.value; + + // check signature + bytes32 digest = keccak256( + abi.encode(depositTo_, chainSlug_, token_, amount, address(this), evmxSlug) ); - address appGateway = _getCoreAppGateway(originAppGateway_); + if (_recoverSigner(digest, signature_) != owner()) revert InvalidWatcherSignature(); + + UserCredits storage userCredit = userCredits[depositTo_]; + userCredit.totalCredits += amount; + tokenPoolBalances[chainSlug_][token_] += amount; + emit CreditsDeposited(chainSlug_, depositTo_, token_, amount); + } - TokenBalance storage tokenBalance = appGatewayFeeBalances[appGateway][chainSlug_][token_]; - tokenBalance.deposited += amount_; - emit FeesDepositedUpdated(chainSlug_, appGateway, token_, amount_); + function wrap() external payable { + UserCredits storage userCredit = userCredits[msg.sender]; + userCredit.totalCredits += msg.value; + emit CreditsWrapped(msg.sender, msg.value); } - function isFeesEnough( - address originAppGateway_, - Fees memory fees_ + function unwrap(uint256 amount_) external { + UserCredits storage userCredit = userCredits[msg.sender]; + if (userCredit.totalCredits < amount_) revert InsufficientCreditsAvailable(); + userCredit.totalCredits -= amount_; + + // todo: if contract balance not enough, take from our pool? + if (address(this).balance < amount_) revert InsufficientBalance(); + payable(msg.sender).transfer(amount_); + emit CreditsUnwrapped(msg.sender, amount_); + } + + function isUserCreditsEnough( + address consumeFrom_, + address appGateway_, + uint256 amount_ ) external view returns (bool) { - address appGateway = _getCoreAppGateway(originAppGateway_); - uint256 availableFees = getAvailableFees( - fees_.feePoolChain, - appGateway, - fees_.feePoolToken + // If consumeFrom is not appGateway, check if it is whitelisted + if (consumeFrom_ != appGateway_ && !isAppGatewayWhitelisted[consumeFrom_][appGateway_]) + revert AppGatewayNotWhitelisted(); + return getAvailableCredits(consumeFrom_) >= amount_; + } + + function _processFeeApprovalData( + bytes memory feeApprovalData_ + ) internal returns (address, address, bool) { + (address consumeFrom, address appGateway, bool isApproved, bytes memory signature_) = abi + .decode(feeApprovalData_, (address, address, bool, bytes)); + if (signature_.length == 0) { + // If no signature, consumeFrom is appGateway + return (appGateway, appGateway, isApproved); + } + bytes32 digest = keccak256( + abi.encode( + address(this), + evmxSlug, + consumeFrom, + appGateway, + userNonce[consumeFrom], + isApproved + ) ); - return availableFees >= fees_.amount; + if (_recoverSigner(digest, signature_) != consumeFrom) revert InvalidUserSignature(); + isAppGatewayWhitelisted[consumeFrom][appGateway] = isApproved; + userNonce[consumeFrom]++; + + return (consumeFrom, appGateway, isApproved); } - /// @notice Blocks fees for transmitter - /// @param originAppGateway_ The app gateway address - /// @param feesGivenByApp_ The fees data struct given by the app gateway - /// @param requestCount_ The batch identifier - /// @dev Only callable by delivery helper - function blockFees( - address originAppGateway_, - Fees memory feesGivenByApp_, - Bid memory winningBid_, - uint40 requestCount_ - ) external { + function whitelistAppGatewayWithSignature( + bytes memory feeApprovalData_ + ) external returns (address consumeFrom, address appGateway, bool isApproved) { + return _processFeeApprovalData(feeApprovalData_); + } + + /// @notice Whitelists multiple app gateways for the caller + /// @param params_ Array of app gateway addresses to whitelist + function whitelistAppGateways(AppGatewayWhitelistParams[] calldata params_) external { + for (uint256 i = 0; i < params_.length; i++) { + isAppGatewayWhitelisted[msg.sender][params_[i].appGateway] = params_[i].isApproved; + } + } + + modifier onlyAuctionManager(uint40 requestCount_) { if (msg.sender != deliveryHelper__().getRequestMetadata(requestCount_).auctionManager) revert NotAuctionManager(); + _; + } - address appGateway = _getCoreAppGateway(originAppGateway_); + /// @notice Blocks fees for a request count + /// @param consumeFrom_ The fees payer address + /// @param transmitterCredits_ The total fees to block + /// @param requestCount_ The batch identifier + /// @dev Only callable by delivery helper + function blockCredits( + address consumeFrom_, + uint256 transmitterCredits_, + uint40 requestCount_ + ) external onlyAuctionManager(requestCount_) { // Block fees - uint256 availableFees = getAvailableFees( - feesGivenByApp_.feePoolChain, - appGateway, - feesGivenByApp_.feePoolToken - ); - - if (requestCountBlockedFees[requestCount_].amount > 0) - availableFees += requestCountBlockedFees[requestCount_].amount; + if (getAvailableCredits(consumeFrom_) < transmitterCredits_) + revert InsufficientCreditsAvailable(); - if (availableFees < winningBid_.fee) revert InsufficientFeesAvailable(); - TokenBalance storage tokenBalance = appGatewayFeeBalances[appGateway][ - feesGivenByApp_.feePoolChain - ][feesGivenByApp_.feePoolToken]; + UserCredits storage userCredit = userCredits[consumeFrom_]; + userCredit.blockedCredits += transmitterCredits_; - tokenBalance.blocked = - tokenBalance.blocked + - winningBid_.fee - - requestCountBlockedFees[requestCount_].amount; - - requestCountBlockedFees[requestCount_] = Fees({ - feePoolChain: feesGivenByApp_.feePoolChain, - feePoolToken: feesGivenByApp_.feePoolToken, - amount: winningBid_.fee - }); + requestCountCredits[requestCount_] = transmitterCredits_; - emit FeesBlocked( - requestCount_, - feesGivenByApp_.feePoolChain, - feesGivenByApp_.feePoolToken, - winningBid_.fee - ); + emit CreditsBlocked(requestCount_, consumeFrom_, transmitterCredits_); } /// @notice Unblocks fees after successful execution and assigns them to the transmitter /// @param requestCount_ The async ID of the executed batch /// @param transmitter_ The address of the transmitter who executed the batch - function unblockAndAssignFees( + function unblockAndAssignCredits( uint40 requestCount_, - address transmitter_, - address originAppGateway_ + address transmitter_ ) external override onlyDeliveryHelper { - Fees memory fees = requestCountBlockedFees[requestCount_]; - if (fees.amount == 0) return; - - address appGateway = _getCoreAppGateway(originAppGateway_); - TokenBalance storage tokenBalance = appGatewayFeeBalances[appGateway][fees.feePoolChain][ - fees.feePoolToken - ]; + uint256 blockedCredits = requestCountCredits[requestCount_]; + if (blockedCredits == 0) return; + RequestMetadata memory requestMetadata = deliveryHelper__().getRequestMetadata( + requestCount_ + ); + uint256 fees = requestMetadata.winningBid.fee; // Unblock fees from deposit - tokenBalance.blocked -= fees.amount; - tokenBalance.deposited -= fees.amount; + _useBlockedUserCredits(requestMetadata.consumeFrom, blockedCredits, fees); // Assign fees to transmitter - transmitterFees[transmitter_][fees.feePoolChain][fees.feePoolToken] += fees.amount; + userCredits[transmitter_].totalCredits += fees; // Clean up storage - delete requestCountBlockedFees[requestCount_]; - emit FeesUnblockedAndAssigned(requestCount_, transmitter_, fees.amount); + delete requestCountCredits[requestCount_]; + emit CreditsUnblockedAndAssigned(requestCount_, transmitter_, fees); + } + + function _useBlockedUserCredits( + address consumeFrom_, + uint256 toConsumeFromBlocked_, + uint256 toConsumeFromTotal_ + ) internal { + UserCredits storage userCredit = userCredits[consumeFrom_]; + userCredit.blockedCredits -= toConsumeFromBlocked_; + userCredit.totalCredits -= toConsumeFromTotal_; + } + + function _useAvailableUserCredits(address consumeFrom_, uint256 toConsume_) internal { + UserCredits storage userCredit = userCredits[consumeFrom_]; + if (userCredit.totalCredits < toConsume_) revert InsufficientCreditsAvailable(); + userCredit.totalCredits -= toConsume_; + } + + function assignWatcherPrecompileCreditsFromRequestCount( + uint256 amount_, + uint40 requestCount_ + ) external onlyWatcherPrecompile { + RequestMetadata memory requestMetadata = deliveryHelper__().getRequestMetadata( + requestCount_ + ); + _assignWatcherPrecompileCredits(amount_, requestMetadata.consumeFrom); + } + + function assignWatcherPrecompileCreditsFromAddress( + uint256 amount_, + address consumeFrom_ + ) external onlyWatcherPrecompile { + _assignWatcherPrecompileCredits(amount_, consumeFrom_); + } + + function _assignWatcherPrecompileCredits(uint256 amount_, address consumeFrom_) internal { + // deduct the fees from the user + _useAvailableUserCredits(consumeFrom_, amount_); + // add the fees to the watcher precompile + watcherPrecompileCredits += amount_; + emit WatcherPrecompileCreditsAssigned(amount_, consumeFrom_); } - function unblockFees(uint40 requestCount_) external { + function unblockCredits(uint40 requestCount_) external { RequestMetadata memory requestMetadata = deliveryHelper__().getRequestMetadata( requestCount_ ); @@ -278,81 +362,64 @@ contract FeesManager is FeesManagerStorage, Initializable, Ownable, AddressResol msg.sender != address(deliveryHelper__()) ) revert InvalidCaller(); - Fees memory fees = requestCountBlockedFees[requestCount_]; - if (fees.amount == 0) return; - - TokenBalance storage tokenBalance = appGatewayFeeBalances[requestMetadata.appGateway][ - fees.feePoolChain - ][fees.feePoolToken]; + uint256 blockedCredits = requestCountCredits[requestCount_]; + if (blockedCredits == 0) return; // Unblock fees from deposit - tokenBalance.blocked -= fees.amount; - tokenBalance.deposited += fees.amount; - - delete requestCountBlockedFees[requestCount_]; - emit FeesUnblocked(requestCount_, requestMetadata.appGateway); - } - - /// @notice Withdraws fees to a specified receiver - /// @param chainSlug_ The chain identifier - /// @param token_ The token address - /// @param receiver_ The address of the receiver - function withdrawTransmitterFees( - uint32 chainSlug_, - address token_, - address receiver_ - ) external returns (uint40 requestCount) { - address transmitter = msg.sender; - // Get total fees for the transmitter in given chain and token - uint256 totalFees = transmitterFees[transmitter][chainSlug_][token_]; - if (totalFees == 0) revert NoFeesForTransmitter(); - - // Clean up storage - transmitterFees[transmitter][chainSlug_][token_] = 0; - - // Create fee distribution payload - bytes32 feesId = _encodeFeesId(feesCounter++); - bytes memory payload = abi.encodeCall( - IFeesPlug.distributeFee, - (token_, totalFees, receiver_, feesId) - ); + UserCredits storage userCredit = userCredits[requestMetadata.consumeFrom]; + userCredit.blockedCredits -= blockedCredits; - // finalize for plug contract - return _submitAndStartProcessing(chainSlug_, payload, transmitter); + delete requestCountCredits[requestCount_]; + emit CreditsUnblocked(requestCount_, requestMetadata.consumeFrom); } /// @notice Withdraws funds to a specified receiver /// @dev This function is used to withdraw fees from the fees plug - /// @param originAppGateway_ The address of the app gateway + /// @param originAppGatewayOrUser_ The address of the app gateway /// @param chainSlug_ The chain identifier /// @param token_ The address of the token /// @param amount_ The amount of tokens to withdraw /// @param receiver_ The address of the receiver - function withdrawFees( - address originAppGateway_, + function withdrawCredits( + address originAppGatewayOrUser_, uint32 chainSlug_, address token_, uint256 amount_, address receiver_ ) public { - address appGateway = _getCoreAppGateway(originAppGateway_); + if (msg.sender != address(deliveryHelper__())) originAppGatewayOrUser_ = msg.sender; + address source = _getCoreAppGateway(originAppGatewayOrUser_); // Check if amount is available in fees plug - uint256 availableAmount = getAvailableFees(chainSlug_, appGateway, token_); - if (availableAmount < amount_) revert InsufficientFeesAvailable(); + uint256 availableAmount = getAvailableCredits(source); + if (availableAmount < amount_) revert InsufficientCreditsAvailable(); - TokenBalance storage tokenBalance = appGatewayFeeBalances[appGateway][chainSlug_][token_]; - tokenBalance.deposited -= amount_; + _useAvailableUserCredits(source, amount_); + tokenPoolBalances[chainSlug_][token_] -= amount_; // Add it to the queue and submit request - _queue(chainSlug_, abi.encodeCall(IFeesPlug.withdrawFees, (token_, amount_, receiver_))); + _queue(chainSlug_, abi.encodeCall(IFeesPlug.withdrawFees, (token_, receiver_, amount_))); } - function _submitAndStartProcessing( + /// @notice Withdraws fees to a specified receiver + /// @param chainSlug_ The chain identifier + /// @param token_ The token address + /// @param receiver_ The address of the receiver + function getWithdrawTransmitterCreditsPayloadParams( + address transmitter_, uint32 chainSlug_, - bytes memory payload_, - address transmitter_ - ) internal returns (uint40 requestCount) { + address token_, + address receiver_, + uint256 amount_ + ) external onlyDeliveryHelper returns (PayloadSubmitParams[] memory) { + uint256 maxCreditsAvailableForWithdraw = getMaxCreditsAvailableForWithdraw(transmitter_); + if (amount_ > maxCreditsAvailableForWithdraw) revert InsufficientCreditsAvailable(); + + // Clean up storage + _useAvailableUserCredits(transmitter_, amount_); + tokenPoolBalances[chainSlug_][token_] -= amount_; + + bytes memory payload = abi.encodeCall(IFeesPlug.withdrawFees, (token_, receiver_, amount_)); PayloadSubmitParams[] memory payloadSubmitParamsArray = new PayloadSubmitParams[](1); payloadSubmitParamsArray[0] = PayloadSubmitParams({ levelNumber: 0, @@ -367,10 +434,15 @@ contract FeesManager is FeesManagerStorage, Initializable, Ownable, AddressResol gasLimit: 10000000, value: 0, readAt: 0, - payload: payload_ + payload: payload }); - requestCount = watcherPrecompile__().submitRequest(payloadSubmitParamsArray); - watcherPrecompile__().startProcessingRequest(requestCount, transmitter_); + return payloadSubmitParamsArray; + } + + function getMaxCreditsAvailableForWithdraw(address transmitter_) public view returns (uint256) { + uint256 watcherFees = watcherPrecompileLimits().getTotalFeesRequired(0, 1, 0, 1); + uint256 transmitterCredits = userCredits[transmitter_].totalCredits; + return transmitterCredits > watcherFees ? transmitterCredits - watcherFees : 0; } function _getSwitchboard(uint32 chainSlug_) internal view returns (address) { @@ -400,6 +472,9 @@ contract FeesManager is FeesManagerStorage, Initializable, Ownable, AddressResol }); } + /// @notice hook called by watcher precompile when request is finished + function onRequestComplete(uint40 requestCount_, bytes memory) external {} + function _queue(uint32 chainSlug_, bytes memory payload_) internal { QueuePayloadParams memory queuePayloadParams = _createQueuePayloadParams( chainSlug_, @@ -408,27 +483,16 @@ contract FeesManager is FeesManagerStorage, Initializable, Ownable, AddressResol deliveryHelper__().queue(queuePayloadParams); } - function _encodeFeesId(uint256 feesCounter_) internal view returns (bytes32) { - // watcher address (160 bits) | counter (64 bits) - return bytes32((uint256(uint160(address(this))) << 64) | feesCounter_); - } - function _getFeesPlugAddress(uint32 chainSlug_) internal view returns (address) { return watcherPrecompileConfig().feesPlug(chainSlug_); } - function _isWatcherSignatureValid( - bytes memory digest_, - uint256 signatureNonce_, + function _recoverSigner( + bytes32 digest_, bytes memory signature_ - ) internal { - if (isNonceUsed[signatureNonce_]) revert NonceUsed(); - isNonceUsed[signatureNonce_] = true; - - bytes32 digest = keccak256(abi.encode(address(this), evmxSlug, signatureNonce_, digest_)); - digest = keccak256(abi.encodePacked("\x19Ethereum Signed Message:\n32", digest)); + ) internal view returns (address signer) { + bytes32 digest = keccak256(abi.encodePacked("\x19Ethereum Signed Message:\n32", digest_)); // recovered signer is checked for the valid roles later - address signer = ECDSA.recover(digest, signature_); - if (signer != owner()) revert InvalidWatcherSignature(); + signer = ECDSA.recover(digest, signature_); } } diff --git a/contracts/protocol/payload-delivery/FeesPlug.sol b/contracts/protocol/payload-delivery/FeesPlug.sol index 77778f05..4935afa9 100644 --- a/contracts/protocol/payload-delivery/FeesPlug.sol +++ b/contracts/protocol/payload-delivery/FeesPlug.sol @@ -1,118 +1,112 @@ -// SPDX-License-Identifier: MIT +// SPDX-License-Identifier: GPL-3.0-only pragma solidity ^0.8.21; import "solady/utils/SafeTransferLib.sol"; +import "solady/tokens/ERC20.sol"; import "../../base/PlugBase.sol"; import "../utils/AccessControl.sol"; import {RESCUE_ROLE} from "../utils/common/AccessRoles.sol"; +import {IFeesPlug} from "../../interfaces/IFeesPlug.sol"; import "../utils/RescueFundsLib.sol"; import {ETH_ADDRESS} from "../utils/common/Constants.sol"; -import {InvalidTokenAddress} from "../utils/common/Errors.sol"; +import {InvalidTokenAddress, FeesAlreadyPaid} from "../utils/common/Errors.sol"; /// @title FeesManager -/// @notice Abstract contract for managing fees -contract FeesPlug is PlugBase, AccessControl { - mapping(address => uint256) public balanceOf; - mapping(bytes32 => bool) public feesRedeemed; +/// @notice Contract for managing fees on a network +/// @dev The amount deposited here is locked and updated in the EVMx for an app gateway +/// @dev The fees are redeemed by the transmitters executing request or can be withdrawn by the owner +contract FeesPlug is IFeesPlug, PlugBase, AccessControl { + /// @notice Mapping to store if a token is whitelisted mapping(address => bool) public whitelistedTokens; - /// @notice Error thrown when attempting to pay fees again - error FeesAlreadyPaid(); /// @notice Error thrown when balance is not enough to cover fees - error InsufficientTokenBalance(address token_); + error InsufficientTokenBalance(address token_, uint256 balance_, uint256 fee_); /// @notice Error thrown when deposit amount does not match msg.value error InvalidDepositAmount(); + /// @notice Error thrown when token is not whitelisted error TokenNotWhitelisted(address token_); /// @notice Event emitted when fees are deposited - event FeesDeposited(address appGateway, address token, uint256 amount); + event FeesDeposited( + address token, + address receiver, + uint256 creditAmount, + uint256 nativeAmount + ); /// @notice Event emitted when fees are withdrawn - event FeesWithdrawn(address token, uint256 amount, address receiver); + event FeesWithdrawn(address token, address receiver, uint256 amount); /// @notice Event emitted when a token is whitelisted event TokenWhitelisted(address token); /// @notice Event emitted when a token is removed from whitelist event TokenRemovedFromWhitelist(address token); - modifier isFeesEnough(uint256 fee_, address feeToken_) { - if (balanceOf[feeToken_] < fee_) revert InsufficientTokenBalance(feeToken_); + /// @notice Modifier to check if the balance of a token is enough to withdraw + modifier isUserCreditsEnough(address feeToken_, uint256 fee_) { + uint balance_ = ERC20(feeToken_).balanceOf(address(this)); + if (balance_ < fee_) revert InsufficientTokenBalance(feeToken_, balance_, fee_); _; } + /// @notice Constructor for the FeesPlug contract + /// @param socket_ The socket address + /// @param owner_ The owner address constructor(address socket_, address owner_) { _setSocket(socket_); _initializeOwner(owner_); - whitelistedTokens[ETH_ADDRESS] = true; // ETH is whitelisted by default - } - - function distributeFee( - address feeToken_, - uint256 fee_, - address transmitter_, - bytes32 feesId_ - ) external onlySocket isFeesEnough(fee_, feeToken_) { - if (feesRedeemed[feesId_]) revert FeesAlreadyPaid(); - feesRedeemed[feesId_] = true; - - balanceOf[feeToken_] -= fee_; - _transferTokens(feeToken_, fee_, transmitter_); } + /// @notice Withdraws fees + /// @param token_ The token address + /// @param amount_ The amount + /// @param receiver_ The receiver address function withdrawFees( address token_, - uint256 amount_, - address receiver_ - ) external onlySocket isFeesEnough(amount_, token_) { - balanceOf[token_] -= amount_; - _transferTokens(token_, amount_, receiver_); - - emit FeesWithdrawn(token_, amount_, receiver_); + address receiver_, + uint256 amount_ + ) external override onlySocket isUserCreditsEnough(token_, amount_) { + SafeTransferLib.safeTransfer(token_, receiver_, amount_); + emit FeesWithdrawn(token_, receiver_, amount_); } - /// @notice Deposits funds - /// @param token_ The token address - /// @param amount_ The amount - /// @param appGateway_ The app gateway address - function deposit(address token_, address appGateway_, uint256 amount_) external payable { - if (!whitelistedTokens[token_]) revert TokenNotWhitelisted(token_); - - if (token_ == ETH_ADDRESS) { - if (msg.value != amount_) revert InvalidDepositAmount(); - } else { - if (token_.code.length == 0) revert InvalidTokenAddress(); - } - - balanceOf[token_] += amount_; + function depositToFee(address token_, address receiver_, uint256 amount_) external override { + _deposit(token_, receiver_, amount_, 0); + } - if (token_ != ETH_ADDRESS) { - SafeTransferLib.safeTransferFrom(token_, msg.sender, address(this), amount_); - } + function depositToFeeAndNative( + address token_, + address receiver_, + uint256 amount_ + ) external override { + uint256 nativeAmount_ = amount_ / 10; + uint256 creditAmount_ = amount_ - nativeAmount_; + _deposit(token_, receiver_, creditAmount_, nativeAmount_); + } - emit FeesDeposited(appGateway_, token_, amount_); + function depositToNative(address token_, address receiver_, uint256 amount_) external override { + _deposit(token_, receiver_, 0, amount_); } - /// @notice Transfers tokens + /// @notice Deposits funds /// @param token_ The token address - /// @param amount_ The amount + /// @param creditAmount_ The amount of fees + /// @param nativeAmount_ The amount of native tokens /// @param receiver_ The receiver address - function _transferTokens(address token_, uint256 amount_, address receiver_) internal { - if (token_ == ETH_ADDRESS) { - SafeTransferLib.forceSafeTransferETH(receiver_, amount_); - } else { - SafeTransferLib.safeTransfer(token_, receiver_, amount_); - } - } - - function connectSocket( - address appGateway_, - address socket_, - address switchboard_ - ) external onlyOwner { - _connectSocket(appGateway_, socket_, switchboard_); + function _deposit( + address token_, + address receiver_, + uint256 creditAmount_, + uint256 nativeAmount_ + ) internal { + uint256 totalAmount_ = creditAmount_ + nativeAmount_; + if (!whitelistedTokens[token_]) revert TokenNotWhitelisted(token_); + SafeTransferLib.safeTransferFrom(token_, msg.sender, address(this), totalAmount_); + emit FeesDeposited(token_, receiver_, creditAmount_, nativeAmount_); } /// @notice Adds a token to the whitelist /// @param token_ The token address to whitelist function whitelistToken(address token_) external onlyOwner { + if (token_.code.length == 0) revert InvalidTokenAddress(); whitelistedTokens[token_] = true; emit TokenWhitelisted(token_); } @@ -120,11 +114,17 @@ contract FeesPlug is PlugBase, AccessControl { /// @notice Removes a token from the whitelist /// @param token_ The token address to remove function removeTokenFromWhitelist(address token_) external onlyOwner { - if (token_ == ETH_ADDRESS) revert(); // Cannot remove ETH from whitelist whitelistedTokens[token_] = false; emit TokenRemovedFromWhitelist(token_); } + function connectSocket( + bytes32 appGatewayId_, + address socket_, + address switchboard_ + ) external onlyOwner { + _connectSocket(appGatewayId_, socket_, switchboard_); + } /** * @notice Rescues funds from the contract if they are locked by mistake. This contract does not * theoretically need this function but it is added for safety. @@ -139,8 +139,4 @@ contract FeesPlug is PlugBase, AccessControl { ) external onlyRole(RESCUE_ROLE) { RescueFundsLib._rescueFunds(token_, rescueTo_, amount_); } - - fallback() external payable {} - - receive() external payable {} } diff --git a/contracts/protocol/payload-delivery/app-gateway/DeliveryHelper.sol b/contracts/protocol/payload-delivery/app-gateway/DeliveryHelper.sol index 81d9e7b3..24a78e3b 100644 --- a/contracts/protocol/payload-delivery/app-gateway/DeliveryHelper.sol +++ b/contracts/protocol/payload-delivery/app-gateway/DeliveryHelper.sol @@ -1,8 +1,10 @@ -// SPDX-License-Identifier: MIT -pragma solidity ^0.8.0; +// SPDX-License-Identifier: GPL-3.0-only +pragma solidity ^0.8.21; import "./FeesHelpers.sol"; +/// @title DeliveryHelper +/// @notice Contract for managing payload delivery contract DeliveryHelper is FeesHelpers { constructor() { _disableInitializers(); // disable for implementation @@ -17,14 +19,15 @@ contract DeliveryHelper is FeesHelpers { uint128 bidTimeout_ ) public reinitializer(1) { _setAddressResolver(addressResolver_); - bidTimeout = bidTimeout_; _initializeOwner(owner_); - } - function endTimeout(uint40 requestCount_) external onlyWatcherPrecompile { - IAuctionManager(requests[requestCount_].auctionManager).endAuction(requestCount_); + bidTimeout = bidTimeout_; } + /// @notice Calls the watcher precompile to start processing a request + /// @dev If a transmitter was already assigned, it updates the transmitter in watcher precompile too + /// @param requestCount_ The ID of the request + /// @param winningBid_ The winning bid function startRequestProcessing( uint40 requestCount_, Bid memory winningBid_ @@ -33,9 +36,9 @@ contract DeliveryHelper is FeesHelpers { if (winningBid_.transmitter == address(0)) revert InvalidTransmitter(); RequestMetadata storage requestMetadata_ = requests[requestCount_]; + // if a transmitter was already assigned, it means the request was restarted bool isRestarted = requestMetadata_.winningBid.transmitter != address(0); - - requestMetadata_.winningBid.transmitter = winningBid_.transmitter; + requestMetadata_.winningBid = winningBid_; if (!isRestarted) { watcherPrecompile__().startProcessingRequest(requestCount_, winningBid_.transmitter); @@ -44,59 +47,66 @@ contract DeliveryHelper is FeesHelpers { } } + /// @notice Finishes the request processing by assigning fees and calling the on complete hook on app gateway + /// @param requestCount_ The ID of the request function finishRequest(uint40 requestCount_) external onlyWatcherPrecompile { RequestMetadata storage requestMetadata_ = requests[requestCount_]; + // todo: move it to watcher precompile if (requestMetadata_.winningBid.transmitter != address(0)) - IFeesManager(addressResolver__.feesManager()).unblockAndAssignFees( + IFeesManager(addressResolver__.feesManager()).unblockAndAssignCredits( requestCount_, - requestMetadata_.winningBid.transmitter, - requestMetadata_.appGateway + requestMetadata_.winningBid.transmitter ); - IAppGateway(requestMetadata_.appGateway).onRequestComplete( - requestCount_, - requestMetadata_.onCompleteData - ); + if (requestMetadata_.appGateway.code.length > 0) { + IAppGateway(requestMetadata_.appGateway).onRequestComplete( + requestCount_, + requestMetadata_.onCompleteData + ); + } } - /// @notice Cancels a request + /// @notice Cancels a request and settles the fees + /// @dev if no transmitter was assigned, fees is unblocked to app gateway + /// @dev Only app gateway can call this function /// @param requestCount_ The ID of the request function cancelRequest(uint40 requestCount_) external { if (msg.sender != requests[requestCount_].appGateway) { revert OnlyAppGateway(); } - // If the request has a winning bid, ie. transmitter already assigned, unblock and assign fees - if (requests[requestCount_].winningBid.transmitter != address(0)) { - IFeesManager(addressResolver__.feesManager()).unblockAndAssignFees( - requestCount_, - requests[requestCount_].winningBid.transmitter, - requests[requestCount_].appGateway - ); - } else { - // If the request has no winning bid, ie. transmitter not assigned, unblock fees - IFeesManager(addressResolver__.feesManager()).unblockFees(requestCount_); - } + _settleFees(requestCount_); watcherPrecompile__().cancelRequest(requestCount_); emit RequestCancelled(requestCount_); } - /// @notice Handles request reverts + /// @notice For request reverts, settles the fees /// @param requestCount_ The ID of the request function handleRequestReverts(uint40 requestCount_) external onlyWatcherPrecompile { - // assign fees after expiry time + _settleFees(requestCount_); + } + + /// @notice Settles the fees for a request + /// @dev If a transmitter was already assigned, it unblocks and assigns fees to the transmitter + /// @dev If no transmitter was assigned, it unblocks fees to the app gateway + /// @param requestCount_ The ID of the request + function _settleFees(uint40 requestCount_) internal { + // If the request has a winning bid, ie. transmitter already assigned, unblock and assign fees if (requests[requestCount_].winningBid.transmitter != address(0)) { - IFeesManager(addressResolver__.feesManager()).unblockAndAssignFees( + IFeesManager(addressResolver__.feesManager()).unblockAndAssignCredits( requestCount_, - requests[requestCount_].winningBid.transmitter, - requests[requestCount_].appGateway + requests[requestCount_].winningBid.transmitter ); } else { - IFeesManager(addressResolver__.feesManager()).unblockFees(requestCount_); + // If the request has no winning bid, ie. transmitter not assigned, unblock fees + IFeesManager(addressResolver__.feesManager()).unblockCredits(requestCount_); } } + /// @notice Returns the request metadata + /// @param requestCount_ The ID of the request + /// @return requestMetadata The request metadata function getRequestMetadata( uint40 requestCount_ ) external view returns (RequestMetadata memory) { diff --git a/contracts/protocol/payload-delivery/app-gateway/DeliveryHelperStorage.sol b/contracts/protocol/payload-delivery/app-gateway/DeliveryHelperStorage.sol index d6daf1f4..73c89be1 100644 --- a/contracts/protocol/payload-delivery/app-gateway/DeliveryHelperStorage.sol +++ b/contracts/protocol/payload-delivery/app-gateway/DeliveryHelperStorage.sol @@ -1,19 +1,15 @@ -// SPDX-License-Identifier: Unlicense +// SPDX-License-Identifier: GPL-3.0-only pragma solidity ^0.8.21; -import {IMiddleware} from "../../../interfaces/IMiddleware.sol"; -import {IPromise} from "../../../interfaces/IPromise.sol"; +import "../../../interfaces/IMiddleware.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 {QueuePayloadParams, Fees, CallType, Bid, Parallel, IsPlug, WriteFinality, RequestMetadata} from "../../utils/common/Structs.sol"; -import {NotAuctionManager, InvalidPromise, InvalidIndex, PromisesNotResolved, InvalidTransmitter} from "../../utils/common/Errors.sol"; -import {FORWARD_CALL, DISTRIBUTE_FEE, DEPLOY, QUERY, FINALIZE} from "../../utils/common/Constants.sol"; +import {NotAuctionManager, InvalidTransmitter, InvalidIndex} from "../../utils/common/Errors.sol"; +import {DEPLOY, PAYLOAD_SIZE_LIMIT, REQUEST_PAYLOAD_COUNT_LIMIT} from "../../utils/common/Constants.sol"; /// @title DeliveryHelperStorage /// @notice Storage contract for DeliveryHelper @@ -21,14 +17,28 @@ abstract contract DeliveryHelperStorage is IMiddleware { // slots [0-49] reserved for gap uint256[50] _gap_before; + // slot 50 + /// @notice The timeout after which a bid expires uint128 public bidTimeout; + + // slot 51 + /// @notice The counter for the salt used to generate/deploy the contract address uint256 public saltCounter; - /// @notice The call parameters array + // slot 52 + /// @notice The parameters array used to store payloads for a request QueuePayloadParams[] public queuePayloadParams; + // slot 53 + /// @notice The metadata for a request mapping(uint40 => RequestMetadata) public requests; - // slots [59-108] reserved for gap + // slot 54 + /// @notice The maximum message value limit for a chain + mapping(uint32 => uint256) public chainMaxMsgValueLimit; + + // slots [55-104] reserved for gap uint256[50] _gap_after; + + // slots 105-155 (51) reserved for addr resolver utils } diff --git a/contracts/protocol/payload-delivery/app-gateway/DeliveryUtils.sol b/contracts/protocol/payload-delivery/app-gateway/DeliveryUtils.sol index 6532aae7..38fcecc9 100644 --- a/contracts/protocol/payload-delivery/app-gateway/DeliveryUtils.sol +++ b/contracts/protocol/payload-delivery/app-gateway/DeliveryUtils.sol @@ -1,11 +1,10 @@ -// SPDX-License-Identifier: Unlicense +// SPDX-License-Identifier: GPL-3.0-only pragma solidity ^0.8.21; -import {Ownable} from "solady/auth/Ownable.sol"; import "solady/utils/Initializable.sol"; +import {Ownable} from "solady/auth/Ownable.sol"; import {AddressResolverUtil} from "../../utils/AddressResolverUtil.sol"; import "./DeliveryHelperStorage.sol"; -import {PayloadSubmitParams} from "../../utils/common/Structs.sol"; /// @notice Abstract contract for managing asynchronous payloads abstract contract DeliveryUtils is @@ -14,16 +13,9 @@ abstract contract DeliveryUtils is Ownable, AddressResolverUtil { - // slots [0-108] reserved for delivery helper storage and [109-159] reserved for addr resolver util - // slots [160-209] reserved for gap + // slots [156-206] reserved for gap uint256[50] _gap_delivery_utils; - /// @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 - error NotFromForwarder(); - /// @notice Error thrown when a payload call fails - error CallFailed(bytes32 payloadId); /// @notice Error thrown if payload is too large error PayloadTooLarge(); /// @notice Error thrown if trying to cancel a batch without being the application gateway @@ -35,23 +27,33 @@ abstract contract DeliveryUtils is /// @notice Error thrown when a request contains only reads error ReadOnlyRequests(); - event CallBackReverted(uint40 requestCount_, bytes32 payloadId_); - event RequestCancelled(uint40 indexed requestCount); + /// @notice Error thrown when a request contains more than 10 payloads + error RequestPayloadCountLimitExceeded(); + /// @notice Error thrown when a maximum message value limit is exceeded + error MaxMsgValueLimitExceeded(); + event BidTimeoutUpdated(uint256 newBidTimeout); + + /// @notice Emitted when a payload is submitted event PayloadSubmitted( uint40 indexed requestCount, address indexed appGateway, PayloadSubmitParams[] payloadSubmitParams, - Fees fees, + uint256 fees, address auctionManager, bool onlyReadRequests ); + /// @notice Emitted when fees are increased event FeesIncreased( address indexed appGateway, uint40 indexed requestCount, uint256 newMaxFees ); + /// @notice Emitted when chain max message value limits are updated + event ChainMaxMsgValueLimitsUpdated(uint32[] chainSlugs, uint256[] maxMsgValueLimits); + /// @notice Emitted when a request is cancelled + event RequestCancelled(uint40 indexed requestCount); modifier onlyAuctionManager(uint40 requestCount_) { if (msg.sender != requests[requestCount_].auctionManager) revert NotAuctionManager(); @@ -71,4 +73,20 @@ abstract contract DeliveryUtils is bidTimeout = newBidTimeout_; emit BidTimeoutUpdated(newBidTimeout_); } + + /// @notice Updates the maximum message value limit for multiple chains + /// @param chainSlugs_ Array of chain identifiers + /// @param maxMsgValueLimits_ Array of corresponding maximum message value limits + function updateChainMaxMsgValueLimits( + uint32[] calldata chainSlugs_, + uint256[] calldata maxMsgValueLimits_ + ) external onlyOwner { + if (chainSlugs_.length != maxMsgValueLimits_.length) revert InvalidIndex(); + + for (uint256 i = 0; i < chainSlugs_.length; i++) { + chainMaxMsgValueLimit[chainSlugs_[i]] = maxMsgValueLimits_[i]; + } + + emit ChainMaxMsgValueLimitsUpdated(chainSlugs_, maxMsgValueLimits_); + } } diff --git a/contracts/protocol/payload-delivery/app-gateway/FeesHelpers.sol b/contracts/protocol/payload-delivery/app-gateway/FeesHelpers.sol index 4655c8b8..f1845865 100644 --- a/contracts/protocol/payload-delivery/app-gateway/FeesHelpers.sol +++ b/contracts/protocol/payload-delivery/app-gateway/FeesHelpers.sol @@ -1,4 +1,4 @@ -// SPDX-License-Identifier: Unlicense +// SPDX-License-Identifier: GPL-3.0-only pragma solidity ^0.8.21; import "./RequestQueue.sol"; @@ -6,17 +6,24 @@ import "./RequestQueue.sol"; /// @title RequestAsync /// @notice Abstract contract for managing asynchronous payload batches abstract contract FeesHelpers is RequestQueue { - // slots [210-259] reserved for gap + // slots [258-308] reserved for gap uint256[50] _gap_batch_async; + error NewMaxFeesLowerThanCurrent(uint256 current, uint256 new_); + + /// @notice Increases the fees for a request if no bid is placed + /// @param requestCount_ The ID of the request + /// @param newMaxFees_ The new maximum fees function increaseFees(uint40 requestCount_, uint256 newMaxFees_) external override { address appGateway = _getCoreAppGateway(msg.sender); + // todo: should we allow core app gateway too? if (appGateway != requests[requestCount_].appGateway) { revert OnlyAppGateway(); } - if (requests[requestCount_].winningBid.transmitter != address(0)) revert WinningBidExists(); - requests[requestCount_].fees.amount = newMaxFees_; + if (requests[requestCount_].maxFees >= newMaxFees_) + revert NewMaxFeesLowerThanCurrent(requests[requestCount_].maxFees, newMaxFees_); + requests[requestCount_].maxFees = newMaxFees_; emit FeesIncreased(appGateway, requestCount_, newMaxFees_); } @@ -32,20 +39,61 @@ abstract contract FeesHelpers is RequestQueue { uint256 amount_, address receiver_, address auctionManager_, - Fees memory fees_ + uint256 fees_ ) external returns (uint40) { - IFeesManager(addressResolver__.feesManager()).withdrawFees( + IFeesManager(addressResolver__.feesManager()).withdrawCredits( msg.sender, chainSlug_, token_, amount_, receiver_ ); + return _batch(msg.sender, auctionManager_, msg.sender, fees_, bytes("")); + } + + /// @notice Withdraws fees to a specified receiver + /// @param chainSlug_ The chain identifier + /// @param token_ The token address + /// @param receiver_ The address of the receiver + function withdrawTransmitterFees( + uint32 chainSlug_, + address token_, + address receiver_, + uint256 amount_ + ) external returns (uint40 requestCount) { + address transmitter = msg.sender; + + PayloadSubmitParams[] memory payloadSubmitParamsArray = IFeesManager( + addressResolver__.feesManager() + ).getWithdrawTransmitterCreditsPayloadParams( + transmitter, + chainSlug_, + token_, + receiver_, + amount_ + ); - return _batch(msg.sender, auctionManager_, fees_, bytes("")); + RequestMetadata memory requestMetadata = RequestMetadata({ + appGateway: addressResolver__.feesManager(), + auctionManager: address(0), + maxFees: 0, + winningBid: Bid({transmitter: transmitter, fee: 0, extraData: new bytes(0)}), + onCompleteData: bytes(""), + onlyReadRequests: false, + consumeFrom: transmitter, + queryCount: 0, + finalizeCount: 1 + }); // finalize for plug contract + requestCount = watcherPrecompile__().submitRequest(payloadSubmitParamsArray); + requests[requestCount] = requestMetadata; + // same transmitter can execute requests without auction + watcherPrecompile__().startProcessingRequest(requestCount, transmitter); } - function getFees(uint40 requestCount_) external view returns (Fees memory) { - return requests[requestCount_].fees; + /// @notice Returns the fees for a request + /// @param requestCount_ The ID of the request + /// @return fees The fees data + function getFees(uint40 requestCount_) external view returns (uint256) { + return requests[requestCount_].maxFees; } } diff --git a/contracts/protocol/payload-delivery/app-gateway/RequestQueue.sol b/contracts/protocol/payload-delivery/app-gateway/RequestQueue.sol index de8e7f36..73a7b809 100644 --- a/contracts/protocol/payload-delivery/app-gateway/RequestQueue.sol +++ b/contracts/protocol/payload-delivery/app-gateway/RequestQueue.sol @@ -1,15 +1,11 @@ -// SPDX-License-Identifier: Unlicense +// SPDX-License-Identifier: GPL-3.0-only pragma solidity ^0.8.21; -import {Ownable} from "solady/auth/Ownable.sol"; -import "solady/utils/Initializable.sol"; -import {AddressResolverUtil} from "../../utils/AddressResolverUtil.sol"; import "./DeliveryUtils.sol"; /// @notice Abstract contract for managing asynchronous payloads abstract contract RequestQueue is DeliveryUtils { - // slots [0-108] reserved for delivery helper storage and [109-159] reserved for addr resolver util - // slots [160-209] reserved for gap + // slots [207-257] reserved for gap uint256[50] _gap_queue_async; /// @notice Clears the call parameters array @@ -24,96 +20,182 @@ abstract contract RequestQueue is DeliveryUtils { } /// @notice Initiates a batch of payloads - /// @param fees_ The fees data + /// @param maxFees_ The fees data /// @param auctionManager_ The auction manager address /// @return requestCount The ID of the batch function batch( - Fees memory fees_, + uint256 maxFees_, address auctionManager_, + address consumeFrom_, bytes memory onCompleteData_ ) external returns (uint40 requestCount) { address appGateway = _getCoreAppGateway(msg.sender); - return _batch(appGateway, auctionManager_, fees_, onCompleteData_); + return _batch(appGateway, auctionManager_, consumeFrom_, maxFees_, onCompleteData_); } + /// @notice Initiates a batch of payloads + /// @dev it checks fees, payload limits and creates the payload submit params array after assigning proper levels + /// @dev It also modifies the deploy payloads as needed by contract factory plug + /// @dev Stores request metadata and submits the request to watcher precompile function _batch( address appGateway_, address auctionManager_, - Fees memory fees_, + address consumeFrom_, + uint256 maxFees_, bytes memory onCompleteData_ ) internal returns (uint40 requestCount) { if (queuePayloadParams.length == 0) return 0; - if (!IFeesManager(addressResolver__.feesManager()).isFeesEnough(appGateway_, fees_)) - revert InsufficientFees(); + BatchParams memory params = BatchParams({ + appGateway: appGateway_, + auctionManager: _getAuctionManager(auctionManager_), + maxFees: maxFees_, + onCompleteData: onCompleteData_, + onlyReadRequests: false, + queryCount: 0, + finalizeCount: 0 + }); + // Split the function into smaller parts ( PayloadSubmitParams[] memory payloadSubmitParamsArray, - , - bool onlyReadRequests + bool onlyReadRequests, + uint256 queryCount, + uint256 finalizeCount ) = _createPayloadSubmitParamsArray(); - if (auctionManager_ == address(0)) - auctionManager_ = IAddressResolver(addressResolver__).defaultAuctionManager(); + params.onlyReadRequests = onlyReadRequests; + params.queryCount = queryCount; + params.finalizeCount = finalizeCount; + + _checkBatch(consumeFrom_, params.appGateway, params.maxFees); + + return _submitBatchRequest(payloadSubmitParamsArray, consumeFrom_, params); + } + + function _submitBatchRequest( + PayloadSubmitParams[] memory payloadSubmitParamsArray, + address consumeFrom_, + BatchParams memory params + ) internal returns (uint40 requestCount) { RequestMetadata memory requestMetadata = RequestMetadata({ - appGateway: appGateway_, - auctionManager: auctionManager_, - fees: fees_, + appGateway: params.appGateway, + auctionManager: params.auctionManager, + maxFees: params.maxFees, winningBid: Bid({fee: 0, transmitter: address(0), extraData: new bytes(0)}), - onCompleteData: onCompleteData_, - onlyReadRequests: onlyReadRequests + onCompleteData: params.onCompleteData, + onlyReadRequests: params.onlyReadRequests, + consumeFrom: consumeFrom_, + queryCount: params.queryCount, + finalizeCount: params.finalizeCount }); requestCount = watcherPrecompile__().submitRequest(payloadSubmitParamsArray); requests[requestCount] = requestMetadata; - // send query directly if request contains only reads - // transmitter should ignore the batch for auction, the transaction will also revert if someone bids - if (onlyReadRequests) + if (params.onlyReadRequests) { watcherPrecompile__().startProcessingRequest(requestCount, address(0)); + } + + uint256 watcherFees = watcherPrecompileLimits().getTotalFeesRequired( + params.queryCount, + params.finalizeCount, + 0, + 0 + ); + if (watcherFees > params.maxFees) revert InsufficientFees(); + uint256 maxTransmitterFees = params.maxFees - watcherFees; emit PayloadSubmitted( requestCount, - appGateway_, + params.appGateway, payloadSubmitParamsArray, - fees_, - auctionManager_, - onlyReadRequests + maxTransmitterFees, + params.auctionManager, + params.onlyReadRequests ); } + function _getAuctionManager(address auctionManager_) internal view returns (address) { + return + auctionManager_ == address(0) + ? IAddressResolver(addressResolver__).defaultAuctionManager() + : auctionManager_; + } + + function _checkBatch( + address consumeFrom_, + address appGateway_, + uint256 maxFees_ + ) internal view { + if (queuePayloadParams.length > REQUEST_PAYLOAD_COUNT_LIMIT) + revert RequestPayloadCountLimitExceeded(); + + if ( + !IFeesManager(addressResolver__.feesManager()).isUserCreditsEnough( + consumeFrom_, + appGateway_, + maxFees_ + ) + ) revert InsufficientFees(); + } + /// @notice Creates an array of payload details /// @return payloadDetailsArray An array of payload details function _createPayloadSubmitParamsArray() internal returns ( PayloadSubmitParams[] memory payloadDetailsArray, - uint256 totalLevels, - bool onlyReadRequests + bool onlyReadRequests, + uint256 queryCount, + uint256 finalizeCount ) { - if (queuePayloadParams.length == 0) - return (payloadDetailsArray, totalLevels, onlyReadRequests); payloadDetailsArray = new PayloadSubmitParams[](queuePayloadParams.length); - - totalLevels = 0; onlyReadRequests = queuePayloadParams[0].callType == CallType.READ; + + uint256 currentLevel = 0; for (uint256 i = 0; i < queuePayloadParams.length; i++) { - if (queuePayloadParams[i].callType != CallType.READ) { + if (queuePayloadParams[i].callType == CallType.READ) { + queryCount++; + } else { onlyReadRequests = false; + finalizeCount++; } - // Update level for sequential calls + // Update level for calls if (i > 0 && queuePayloadParams[i].isParallel != Parallel.ON) { - totalLevels = totalLevels + 1; + currentLevel = currentLevel + 1; } - payloadDetailsArray[i] = _createPayloadDetails(totalLevels, queuePayloadParams[i]); + payloadDetailsArray[i] = _createPayloadDetails(currentLevel, queuePayloadParams[i]); } clearQueue(); } + function _createDeployPayloadDetails( + QueuePayloadParams memory queuePayloadParams_ + ) internal returns (bytes memory payload, address target) { + bytes32 salt = keccak256( + abi.encode(queuePayloadParams_.appGateway, queuePayloadParams_.chainSlug, saltCounter++) + ); + + // app gateway is set in the plug deployed on chain + payload = abi.encodeWithSelector( + IContractFactoryPlug.deployContract.selector, + queuePayloadParams_.isPlug, + salt, + bytes32(uint256(uint160(queuePayloadParams_.appGateway))), + queuePayloadParams_.switchboard, + queuePayloadParams_.payload, + queuePayloadParams_.initCallData + ); + + // getting app gateway for deployer as the plug is connected to the app gateway + target = getDeliveryHelperPlugAddress(queuePayloadParams_.chainSlug); + } + /// @notice Creates the payload details for a given call parameters /// @param queuePayloadParams_ The call parameters /// @return payloadDetails The payload details @@ -121,33 +203,16 @@ abstract contract RequestQueue is DeliveryUtils { uint256 level_, QueuePayloadParams memory queuePayloadParams_ ) internal returns (PayloadSubmitParams memory) { - bytes memory payload_ = queuePayloadParams_.payload; + bytes memory payload = queuePayloadParams_.payload; address target = queuePayloadParams_.target; if (queuePayloadParams_.callType == CallType.DEPLOY) { - // getting app gateway for deployer as the plug is connected to the app gateway - bytes32 salt_ = keccak256( - abi.encode( - queuePayloadParams_.appGateway, - queuePayloadParams_.chainSlug, - saltCounter++ - ) - ); - - // app gateway is set in the plug deployed on chain - payload_ = abi.encodeWithSelector( - IContractFactoryPlug.deployContract.selector, - queuePayloadParams_.isPlug, - salt_, - queuePayloadParams_.appGateway, - queuePayloadParams_.switchboard, - payload_, - queuePayloadParams_.initCallData - ); - - if (payload_.length > 24.5 * 1024) revert PayloadTooLarge(); - target = getDeliveryHelperPlugAddress(queuePayloadParams_.chainSlug); + (payload, target) = _createDeployPayloadDetails(queuePayloadParams_); } + if (payload.length > PAYLOAD_SIZE_LIMIT) revert PayloadTooLarge(); + if (queuePayloadParams_.value > chainMaxMsgValueLimit[queuePayloadParams_.chainSlug]) + revert MaxMsgValueLimitExceeded(); + return PayloadSubmitParams({ levelNumber: level_, @@ -164,7 +229,7 @@ abstract contract RequestQueue is DeliveryUtils { : queuePayloadParams_.gasLimit, value: queuePayloadParams_.value, readAt: queuePayloadParams_.readAt, - payload: payload_ + payload: payload }); } } diff --git a/contracts/protocol/socket/Socket.sol b/contracts/protocol/socket/Socket.sol index 84b588af..2b39f518 100644 --- a/contracts/protocol/socket/Socket.sol +++ b/contracts/protocol/socket/Socket.sol @@ -1,18 +1,26 @@ // SPDX-License-Identifier: GPL-3.0-only pragma solidity ^0.8.21; +import {LibCall} from "solady/utils/LibCall.sol"; import "./SocketUtils.sol"; -import {PlugDisconnected, InvalidAppGateway} from "../utils/common/Errors.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 + * @title Socket + * @dev Socket is an abstract contract that inherits from SocketUtils and SocketConfig and + * provides functionality for payload execution, verification, and management of payload execution status */ contract Socket is SocketUtils { + using LibCall for address; + + // @notice mapping of payload id to execution status + mapping(bytes32 => ExecutionStatus) public payloadExecuted; + + // @notice mapping of payload id to execution status + mapping(bytes32 => bytes32) public payloadIdToDigest; + + // @notice buffer to account for gas used by current contract execution + uint256 private constant GAS_LIMIT_BUFFER = 105; + //////////////////////////////////////////////////////// ////////////////////// ERRORS ////////////////////////// //////////////////////////////////////////////////////// @@ -24,84 +32,95 @@ contract Socket is SocketUtils { * @dev Error emitted when verification fails */ error VerificationFailed(); - /** * @dev Error emitted when less gas limit is provided for execution than expected */ error LowGasLimit(); + /** + * @dev Error emitted when the chain slug is invalid + */ error InvalidSlug(); + /** + * @dev Error emitted when the deadline has passed + */ error DeadlinePassed(); + /** + * @dev Error emitted when the message value is insufficient + */ + error InsufficientMsgValue(); + /** + * @dev Error emitted when the call type is read + */ + error ReadOnlyCall(); + /** + * @notice Constructor for the Socket contract + * @param chainSlug_ The chain slug + * @param owner_ The owner of the contract + * @param version_ The version of the contract + */ constructor( uint32 chainSlug_, address owner_, string memory version_ ) SocketUtils(chainSlug_, owner_, version_) {} - //////////////////////////////////////////////////////// - ////////////////////// 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]; - - // if no sibling plug is found for the given chain slug, revert - if (plugConfig.appGateway == address(0)) revert PlugDisconnected(); - - // 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( - ExecuteParams memory executeParams_, - bytes memory transmitterSignature_ - ) external payable returns (bytes memory) { + ExecuteParams calldata executeParams_, + TransmissionParams calldata transmissionParams_ + ) external payable returns (bool, bytes memory) { + // check if the deadline has passed if (executeParams_.deadline < block.timestamp) revert DeadlinePassed(); + // check if the call type is valid + if (executeParams_.callType == CallType.READ) revert ReadOnlyCall(); + PlugConfig memory plugConfig = _plugConfigs[executeParams_.target]; - if (plugConfig.appGateway == address(0)) revert PlugDisconnected(); + // check if the plug is disconnected + if (plugConfig.appGatewayId == bytes32(0)) revert PlugNotFound(); + + if (msg.value < executeParams_.value + transmissionParams_.socketFees) + revert InsufficientMsgValue(); bytes32 payloadId = _createPayloadId(plugConfig.switchboard, executeParams_); + + // validate the execution status _validateExecutionStatus(payloadId); - address transmitter = _recoverSigner( - keccak256(abi.encode(address(this), payloadId)), - transmitterSignature_ - ); + address transmitter = transmissionParams_.transmitterSignature.length > 0 + ? _recoverSigner( + keccak256(abi.encode(address(this), payloadId)), + transmissionParams_.transmitterSignature + ) + : address(0); + // create the digest + // transmitter, payloadId, appGateway, executeParams_ and there contents are validated using digest verification from switchboard bytes32 digest = _createDigest( transmitter, payloadId, - plugConfig.appGateway, + plugConfig.appGatewayId, executeParams_ ); + payloadIdToDigest[payloadId] = digest; + + // verify the digest _verify(digest, payloadId, plugConfig.switchboard); - return _execute(payloadId, executeParams_); + + return _execute(payloadId, executeParams_, transmissionParams_); } //////////////////////////////////////////////////////// ////////////////// INTERNAL FUNCS ////////////////////// //////////////////////////////////////////////////////// function _verify(bytes32 digest_, bytes32 payloadId_, address switchboard_) internal view { + if (isValidSwitchboard[switchboard_] != SwitchboardStatus.REGISTERED) + revert InvalidSwitchboard(); + // NOTE: is the the first un-trusted call in the system, another one is Plug.call - if (!ISwitchboard(switchboard_).allowPacket(digest_, payloadId_)) + if (!ISwitchboard(switchboard_).allowPayload(digest_, payloadId_)) revert VerificationFailed(); } @@ -112,29 +131,81 @@ contract Socket is SocketUtils { */ function _execute( bytes32 payloadId_, - ExecuteParams memory executeParams_ - ) internal returns (bytes memory) { - if (gasleft() < executeParams_.gasLimit) revert LowGasLimit(); + ExecuteParams calldata executeParams_, + TransmissionParams calldata transmissionParams_ + ) internal returns (bool success, bytes memory returnData) { + // check if the gas limit is sufficient + // bump by 5% to account for gas used by current contract execution + if (gasleft() < (executeParams_.gasLimit * GAS_LIMIT_BUFFER) / 100) revert LowGasLimit(); // NOTE: external un-trusted call - (bool success, bytes memory returnData) = executeParams_.target.call{ - gas: executeParams_.gasLimit, - value: msg.value - }(executeParams_.payload); + bool exceededMaxCopy; + (success, exceededMaxCopy, returnData) = executeParams_.target.tryCall( + executeParams_.value, + executeParams_.gasLimit, + maxCopyBytes, + executeParams_.payload + ); - if (!success) { - payloadExecuted[payloadId_] = ExecutionStatus.Reverted; - emit ExecutionFailed(payloadId_, returnData); + if (success) { + emit ExecutionSuccess(payloadId_, exceededMaxCopy, returnData); + + if (address(socketFeeManager) != address(0)) { + socketFeeManager.payAndCheckFees{value: transmissionParams_.socketFees}( + executeParams_, + transmissionParams_ + ); + } } else { - emit ExecutionSuccess(payloadId_, returnData); - } + payloadExecuted[payloadId_] = ExecutionStatus.Reverted; - return returnData; + address receiver = transmissionParams_.refundAddress == address(0) + ? msg.sender + : transmissionParams_.refundAddress; + SafeTransferLib.forceSafeTransferETH(receiver, msg.value); + emit ExecutionFailed(payloadId_, exceededMaxCopy, returnData); + } + return (success, returnData); } function _validateExecutionStatus(bytes32 payloadId_) internal { - if (payloadExecuted[payloadId_] != ExecutionStatus.NotExecuted) + if (payloadExecuted[payloadId_] == ExecutionStatus.Executed) revert PayloadAlreadyExecuted(payloadExecuted[payloadId_]); + payloadExecuted[payloadId_] = ExecutionStatus.Executed; } + + //////////////////////////////////////////////////////// + ////////////////////// Trigger ////////////////////// + //////////////////////////////////////////////////////// + /** + * @notice To trigger to a connected remote chain. Should only be called by a plug. + */ + function _triggerAppGateway(address plug_) internal returns (bytes32 triggerId) { + PlugConfig memory plugConfig = _plugConfigs[plug_]; + + // if no sibling plug is found for the given chain slug, revert + // sends the trigger to connected app gateway + if (plugConfig.appGatewayId == bytes32(0)) revert PlugNotFound(); + + // creates a unique ID for the message + triggerId = _encodeTriggerId(); + emit AppGatewayCallRequested( + triggerId, + plugConfig.appGatewayId, + plugConfig.switchboard, + plug_, + // gets the overrides from the plug + IPlug(plug_).overrides(), + msg.data + ); + } + + /// @notice Fallback function that forwards all calls to Socket's callAppGateway + /// @dev The calldata is passed as-is to the gateways + /// @dev if ETH sent with the call, it will revert + fallback(bytes calldata) external returns (bytes memory) { + // return the trigger id + return abi.encode(_triggerAppGateway(msg.sender)); + } } diff --git a/contracts/protocol/socket/SocketBatcher.sol b/contracts/protocol/socket/SocketBatcher.sol index 49eb1966..d996a722 100644 --- a/contracts/protocol/socket/SocketBatcher.sol +++ b/contracts/protocol/socket/SocketBatcher.sol @@ -5,7 +5,7 @@ import "solady/auth/Ownable.sol"; import "../../interfaces/ISocket.sol"; import "../../interfaces/ISwitchboard.sol"; import "../utils/RescueFundsLib.sol"; -import {ExecuteParams} from "../../protocol/utils/common/Structs.sol"; +import {ExecuteParams, TransmissionParams} from "../../protocol/utils/common/Structs.sol"; import "../../interfaces/ISocketBatcher.sol"; /** @@ -17,25 +17,50 @@ contract SocketBatcher is ISocketBatcher, Ownable { ISocket public immutable socket__; /** - * @notice Initializes the TransmitManager contract - * @param socket_ The address of socket contract + * @notice Initializes the SocketBatcher contract * @param owner_ The owner of the contract with GOVERNANCE_ROLE + * @param socket_ The address of socket contract */ constructor(address owner_, ISocket socket_) { socket__ = socket_; _initializeOwner(owner_); } + /** + * @notice Attests a payload and executes it + * @param executeParams_ The execution parameters + * @param digest_ The digest of the payload + * @param proof_ The proof of the payload + * @param transmitterSignature_ The signature of the transmitter + * @return The return data after execution + */ function attestAndExecute( ExecuteParams calldata executeParams_, + address switchboard_, bytes32 digest_, bytes calldata proof_, - bytes calldata transmitterSignature_ - ) external payable returns (bytes memory) { - ISwitchboard(executeParams_.switchboard).attest(digest_, proof_); - return socket__.execute{value: msg.value}(executeParams_, transmitterSignature_); + bytes calldata transmitterSignature_, + address refundAddress_ + ) external payable returns (bool, bytes memory) { + ISwitchboard(switchboard_).attest(digest_, proof_); + return + socket__.execute{value: msg.value}( + executeParams_, + TransmissionParams({ + transmitterSignature: transmitterSignature_, + socketFees: 0, + extraData: "", + refundAddress: refundAddress_ + }) + ); } + /** + * @notice Rescues funds from the contract + * @param token_ The address of the token to rescue + * @param to_ The address to rescue the funds to + * @param amount_ The amount of funds to rescue + */ function rescueFunds(address token_, address to_, uint256 amount_) external onlyOwner { RescueFundsLib._rescueFunds(token_, to_, amount_); } diff --git a/contracts/protocol/socket/SocketConfig.sol b/contracts/protocol/socket/SocketConfig.sol index 346a6da9..96aebd12 100644 --- a/contracts/protocol/socket/SocketConfig.sol +++ b/contracts/protocol/socket/SocketConfig.sol @@ -3,33 +3,53 @@ pragma solidity ^0.8.21; import "../../interfaces/ISocket.sol"; import "../../interfaces/ISwitchboard.sol"; +import {IPlug} from "../../interfaces/IPlug.sol"; + import "../utils/AccessControl.sol"; -import {GOVERNANCE_ROLE, RESCUE_ROLE} from "../utils/common/AccessRoles.sol"; -import {PlugConfig, SwitchboardStatus, ExecutionStatus} from "../utils/common/Structs.sol"; +import {GOVERNANCE_ROLE, RESCUE_ROLE, SWITCHBOARD_DISABLER_ROLE} from "../utils/common/AccessRoles.sol"; +import {CallType, PlugConfig, SwitchboardStatus, ExecutionStatus} from "../utils/common/Structs.sol"; +import {PlugNotFound, InvalidAppGateway, InvalidTransmitter} from "../utils/common/Errors.sol"; +import "../../interfaces/ISocketFeeManager.sol"; +import {MAX_COPY_BYTES} from "../utils/common/Constants.sol"; /** * @title SocketConfig - * @notice An abstract contract for configuring socket connections for plugs between different chains, + * @notice An abstract contract for configuring socket connections for plugs, * manages plug configs and switchboard registrations * @dev This contract is meant to be inherited by other contracts that require socket configuration functionality */ abstract contract SocketConfig is ISocket, AccessControl { - // Error triggered when a switchboard already exists + // socket fee manager + ISocketFeeManager public socketFeeManager; + + // @notice mapping of switchboard address to its status, helps socket to block invalid switchboards mapping(address => SwitchboardStatus) public isValidSwitchboard; - // plug => (appGateway, switchboard__) + // @notice mapping of plug address to its config mapping(address => PlugConfig) internal _plugConfigs; - // Error triggered when a connection is invalid + // @notice max copy bytes for socket + uint16 public maxCopyBytes = 2048; // 2KB + + // @notice error triggered when a connection is invalid error InvalidConnection(); + // @notice error triggered when a switchboard is invalid error InvalidSwitchboard(); + // @notice error triggered when a switchboard already exists error SwitchboardExists(); + // @notice error triggered when a switchboard already exists or is disabled error SwitchboardExistsOrDisabled(); - // Event triggered when a new switchboard is added + // @notice event triggered when a new switchboard is added event SwitchboardAdded(address switchboard); + // @notice event triggered when a switchboard is disabled event SwitchboardDisabled(address switchboard); + // @notice event triggered when a switchboard is enabled + event SwitchboardEnabled(address switchboard); + event SocketFeeManagerUpdated(address oldSocketFeeManager, address newSocketFeeManager); + // @notice function to register a switchboard + // @dev only callable by switchboards function registerSwitchboard() external { if (isValidSwitchboard[msg.sender] != SwitchboardStatus.NOT_REGISTERED) revert SwitchboardExistsOrDisabled(); @@ -38,34 +58,57 @@ abstract contract SocketConfig is ISocket, AccessControl { emit SwitchboardAdded(msg.sender); } - function disableSwitchboard() external onlyRole(GOVERNANCE_ROLE) { + // @notice function to disable a switchboard + // @dev only callable by governance role + function disableSwitchboard() external onlyRole(SWITCHBOARD_DISABLER_ROLE) { isValidSwitchboard[msg.sender] = SwitchboardStatus.DISABLED; emit SwitchboardDisabled(msg.sender); } + // @notice function to enable a switchboard + // @dev only callable by governance role + function enableSwitchboard() external onlyRole(GOVERNANCE_ROLE) { + isValidSwitchboard[msg.sender] = SwitchboardStatus.REGISTERED; + emit SwitchboardEnabled(msg.sender); + } + + function setSocketFeeManager(address socketFeeManager_) external onlyRole(GOVERNANCE_ROLE) { + emit SocketFeeManagerUpdated(address(socketFeeManager), socketFeeManager_); + socketFeeManager = ISocketFeeManager(socketFeeManager_); + } + /** * @notice connects Plug to Socket and sets the config for given `siblingChainSlug_` */ - function connect(address appGateway_, address switchboard_) external override { + function connect(bytes32 appGatewayId_, address switchboard_) external override { if (isValidSwitchboard[switchboard_] != SwitchboardStatus.REGISTERED) revert InvalidSwitchboard(); PlugConfig storage _plugConfig = _plugConfigs[msg.sender]; - _plugConfig.appGateway = appGateway_; + _plugConfig.appGatewayId = appGatewayId_; _plugConfig.switchboard = switchboard_; - emit PlugConnected(msg.sender, appGateway_, switchboard_); + emit PlugConnected(msg.sender, appGatewayId_, switchboard_); + } + + // @notice function to set the max copy bytes for socket + // @dev only callable by governance role + // @param maxCopyBytes_ max copy bytes for socket + function setMaxCopyBytes(uint16 maxCopyBytes_) external onlyRole(GOVERNANCE_ROLE) { + maxCopyBytes = maxCopyBytes_; } /** - * @notice returns the config for given `plugAddress_` and `siblingChainSlug_` + * @notice returns the config for given `plugAddress_` * @param plugAddress_ address of plug present at current chain + * @return appGatewayId The app gateway id + * @return switchboard The switchboard address */ function getPlugConfig( address plugAddress_ - ) external view returns (address appGateway, address switchboard) { + ) external view returns (bytes32 appGatewayId, address switchboard) { PlugConfig memory _plugConfig = _plugConfigs[plugAddress_]; - return (_plugConfig.appGateway, _plugConfig.switchboard); + return (_plugConfig.appGatewayId, _plugConfig.switchboard); } } diff --git a/contracts/protocol/socket/SocketFeeManager.sol b/contracts/protocol/socket/SocketFeeManager.sol new file mode 100644 index 00000000..1bfb66f4 --- /dev/null +++ b/contracts/protocol/socket/SocketFeeManager.sol @@ -0,0 +1,72 @@ +// SPDX-License-Identifier: GPL-3.0-only +pragma solidity ^0.8.21; + +import "../utils/AccessControl.sol"; +import {GOVERNANCE_ROLE, RESCUE_ROLE} from "../utils/common/AccessRoles.sol"; +import {ExecuteParams, TransmissionParams} from "../../protocol/utils/common/Structs.sol"; +import "../../interfaces/ISocketFeeManager.sol"; +import "../utils/RescueFundsLib.sol"; + +/** + * @title SocketFeeManager + * @notice The SocketFeeManager contract is responsible for managing socket fees + */ +contract SocketFeeManager is ISocketFeeManager, AccessControl { + // Current socket fees in native tokens + uint256 public socketFees; + + error InsufficientFees(); + error FeeTooLow(); + + event SocketFeesUpdated(uint256 oldFees, uint256 newFees); + + /** + * @notice Initializes the SocketFeeManager contract + * @param owner_ The owner of the contract with GOVERNANCE_ROLE + * @param socketFees_ Initial socket fees amount + */ + constructor(address owner_, uint256 socketFees_) { + emit SocketFeesUpdated(0, socketFees_); + socketFees = socketFees_; + _grantRole(GOVERNANCE_ROLE, owner_); + _grantRole(RESCUE_ROLE, owner_); + } + + /** + * @notice Pays and validates fees for execution + */ + function payAndCheckFees(ExecuteParams memory, TransmissionParams memory) external payable { + if (msg.value < socketFees) revert InsufficientFees(); + } + + /** + * @notice Gets minimum fees required for execution + * @return nativeFees Minimum native token fees required + */ + function getMinSocketFees() external view returns (uint256 nativeFees) { + return socketFees; + } + + /** + * @notice Sets socket fees + * @param socketFees_ New socket fees amount + */ + function setSocketFees(uint256 socketFees_) external onlyRole(GOVERNANCE_ROLE) { + emit SocketFeesUpdated(socketFees, socketFees_); + socketFees = socketFees_; + } + + /** + * @notice Allows owner to rescue stuck funds + * @param token_ Token address (address(0) for native tokens) + * @param to_ Address to send funds to + * @param amount_ Amount of tokens to rescue + */ + function rescueFunds( + address token_, + address to_, + uint256 amount_ + ) external onlyRole(RESCUE_ROLE) { + RescueFundsLib._rescueFunds(token_, to_, amount_); + } +} diff --git a/contracts/protocol/socket/SocketUtils.sol b/contracts/protocol/socket/SocketUtils.sol index 97aa46ba..ccd8db5d 100644 --- a/contracts/protocol/socket/SocketUtils.sol +++ b/contracts/protocol/socket/SocketUtils.sol @@ -1,14 +1,13 @@ // SPDX-License-Identifier: GPL-3.0-only pragma solidity ^0.8.21; +import {ECDSA} from "solady/utils/ECDSA.sol"; import "../utils/RescueFundsLib.sol"; import "./SocketConfig.sol"; -import {ECDSA} from "solady/utils/ECDSA.sol"; /** * @title SocketUtils - * @notice A contract that is responsible for common storage for src and dest contracts, governance - * setters and inherits SocketConfig + * @notice Utility functions for socket */ abstract contract SocketUtils is SocketConfig { //////////////////////////////////////////////////////////// @@ -19,13 +18,11 @@ abstract contract SocketUtils is SocketConfig { bytes32 public immutable version; // ChainSlug for this deployed socket instance uint32 public immutable chainSlug; + // Prefix for trigger ID containing chain slug and address bits + uint256 private immutable triggerPrefix; - uint64 public callCounter; - - /** - * @dev keeps track of whether a payload has been executed or not using payload id - */ - mapping(bytes32 => ExecutionStatus) public payloadExecuted; + // @notice counter for trigger id + uint64 public triggerCounter; /* * @notice constructor for creating a new Socket contract instance. @@ -36,47 +33,40 @@ abstract contract SocketUtils is SocketConfig { constructor(uint32 chainSlug_, address owner_, string memory version_) { chainSlug = chainSlug_; version = keccak256(bytes(version_)); + triggerPrefix = (uint256(chainSlug_) << 224) | (uint256(uint160(address(this))) << 64); + _initializeOwner(owner_); } - //////////////////////////////////////////////////////// - ////////////////////// ERRORS ////////////////////////// - //////////////////////////////////////////////////////// - - /** - * @dev Error thrown when non-transmitter tries to execute - */ - error InvalidTransmitter(); - /** * @notice creates the digest for the payload * @param transmitter_ The address of the transmitter * @param payloadId_ The ID of the payload - * @param appGateway_ The address of the app gateway + * @param appGatewayId_ The id of the app gateway * @param executeParams_ The parameters of the payload * @return The packed payload as a bytes32 hash */ function _createDigest( address transmitter_, bytes32 payloadId_, - address appGateway_, - ExecuteParams memory executeParams_ + bytes32 appGatewayId_, + ExecuteParams calldata executeParams_ ) internal view returns (bytes32) { return keccak256( abi.encode( + address(this), transmitter_, payloadId_, executeParams_.deadline, executeParams_.callType, - executeParams_.writeFinality, executeParams_.gasLimit, - msg.value, - executeParams_.readAt, + executeParams_.value, executeParams_.payload, executeParams_.target, - appGateway_, - executeParams_.prevDigestsHash + appGatewayId_, + executeParams_.prevDigestsHash, + executeParams_.extraData ) ); } @@ -88,38 +78,41 @@ abstract contract SocketUtils is SocketConfig { */ function _createPayloadId( address switchboard_, - ExecuteParams memory executeParams_ + ExecuteParams calldata executeParams_ ) internal view returns (bytes32) { - // todo: match with watcher return keccak256( abi.encode( executeParams_.requestCount, executeParams_.batchCount, executeParams_.payloadCount, - switchboard_, - chainSlug + chainSlug, + switchboard_ ) ); } + /** + * @notice recovers the signer from the signature + * @param digest_ The digest of the payload + * @param signature_ The signature of the payload + * @return signer The address of the signer + */ function _recoverSigner( bytes32 digest_, - bytes memory signature_ + bytes calldata signature_ ) internal view returns (address signer) { bytes32 digest = keccak256(abi.encodePacked("\x19Ethereum Signed Message:\n32", digest_)); // recovered signer is checked for the valid roles later signer = ECDSA.recover(digest, signature_); } - // 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++ - ); + /** + * @notice Encodes the trigger ID with the chain slug, socket address and nonce + * @return The trigger ID + */ + function _encodeTriggerId() internal returns (bytes32) { + return bytes32(triggerPrefix | triggerCounter++); } ////////////////////////////////////////////// diff --git a/contracts/protocol/socket/switchboard/FastSwitchboard.sol b/contracts/protocol/socket/switchboard/FastSwitchboard.sol index 308e07f6..ee0679a4 100644 --- a/contracts/protocol/socket/switchboard/FastSwitchboard.sol +++ b/contracts/protocol/socket/switchboard/FastSwitchboard.sol @@ -2,24 +2,24 @@ pragma solidity ^0.8.21; import "./SwitchboardBase.sol"; +import {WATCHER_ROLE} from "../../utils/common/AccessRoles.sol"; /** * @title FastSwitchboard contract * @dev This contract implements a fast version of the SwitchboardBase contract - * that enables packet attestations + * that enables payload attestations from watchers */ contract FastSwitchboard is SwitchboardBase { - // used to track if watcher have attested a digest - // digest => isAttested + // used to track if watcher have attested a payload + // payloadId => isAttested mapping(bytes32 => bool) public isAttested; - // Error emitted when a digest is already attested by watcher. + // Error emitted when a payload is already attested by watcher. error AlreadyAttested(); // Error emitted when watcher is not valid error WatcherNotFound(); - - // Event emitted when watcher attests a digest - event Attested(bytes32 digest_, address watcher); + // Event emitted when watcher attests a payload + event Attested(bytes32 payloadId_, address watcher); /** * @dev Constructor function for the FastSwitchboard contract @@ -34,25 +34,28 @@ contract FastSwitchboard is SwitchboardBase { ) SwitchboardBase(chainSlug_, socket_, owner_) {} /** - * @dev Function to attest a packet + * @dev Function to attest a payload * @param digest_ digest of the payload to be executed * @param proof_ proof from watcher - * @notice we are attesting a digest uniquely identified with payloadId. + * @notice we are attesting a payload uniquely identified with digest. */ function attest(bytes32 digest_, bytes calldata proof_) external { - address watcher = _recoverSigner(keccak256(abi.encode(address(this), digest_)), proof_); - if (isAttested[digest_]) revert AlreadyAttested(); + + address watcher = _recoverSigner( + keccak256(abi.encode(address(this), chainSlug, digest_)), + proof_ + ); if (!_hasRole(WATCHER_ROLE, watcher)) revert WatcherNotFound(); - isAttested[digest_] = true; + isAttested[digest_] = true; emit Attested(digest_, watcher); } /** * @inheritdoc ISwitchboard */ - function allowPacket(bytes32 digest_, bytes32) external view returns (bool) { + function allowPayload(bytes32 digest_, bytes32) external view returns (bool) { // digest has enough attestations return isAttested[digest_]; } diff --git a/contracts/protocol/socket/switchboard/SwitchboardBase.sol b/contracts/protocol/socket/switchboard/SwitchboardBase.sol index e628bb20..0c99eaac 100644 --- a/contracts/protocol/socket/switchboard/SwitchboardBase.sol +++ b/contracts/protocol/socket/switchboard/SwitchboardBase.sol @@ -1,32 +1,21 @@ // SPDX-License-Identifier: GPL-3.0-only pragma solidity ^0.8.21; +import {ECDSA} from "solady/utils/ECDSA.sol"; import "../../../interfaces/ISwitchboard.sol"; import "../../../interfaces/ISocket.sol"; import "../../utils/AccessControl.sol"; -import {RESCUE_ROLE} from "../../utils/common/AccessRoles.sol"; import "../../utils/RescueFundsLib.sol"; -import {ECDSA} from "solady/utils/ECDSA.sol"; +import {RESCUE_ROLE} from "../../utils/common/AccessRoles.sol"; +/// @title SwitchboardBase +/// @notice Base contract for switchboards, contains common and util functions for all switchboards abstract contract SwitchboardBase is ISwitchboard, AccessControl { ISocket public immutable socket__; // chain slug of deployed chain uint32 public immutable chainSlug; - // incrementing nonce for each signer - // watcher => nextNonce - mapping(address => uint256) public nextNonce; - - // destinationChainSlug => initialPacketCount - packets with packetCount after this will be accepted at the switchboard. - // This is to prevent attacks with sending payloads for chain slugs before the switchboard is registered for them. - mapping(uint32 => uint256) public initialPacketCount; - - // Error hit when a signature with unexpected nonce is received - error InvalidNonce(); - - bytes32 constant WATCHER_ROLE = keccak256("WATCHER_ROLE"); - /** * @dev Constructor of SwitchboardBase * @param chainSlug_ Chain slug of deployment chain @@ -38,6 +27,10 @@ abstract contract SwitchboardBase is ISwitchboard, AccessControl { _initializeOwner(owner_); } + /// @notice Recovers the signer from the signature + /// @param digest_ The digest of the payload + /// @param signature_ The signature of the watcher + /// @return signer The address of the signer function _recoverSigner( bytes32 digest_, bytes memory signature_ diff --git a/contracts/protocol/utils/AddressResolverUtil.sol b/contracts/protocol/utils/AddressResolverUtil.sol index f168022c..21f24611 100644 --- a/contracts/protocol/utils/AddressResolverUtil.sol +++ b/contracts/protocol/utils/AddressResolverUtil.sol @@ -1,4 +1,4 @@ -// SPDX-License-Identifier: Unlicense +// SPDX-License-Identifier: GPL-3.0-only pragma solidity ^0.8.21; import "../../interfaces/IAddressResolver.sol"; @@ -6,6 +6,7 @@ import "../../interfaces/IMiddleware.sol"; import "../../interfaces/IWatcherPrecompile.sol"; import "../../interfaces/IWatcherPrecompileConfig.sol"; import "../../interfaces/IWatcherPrecompileLimits.sol"; +import "../../interfaces/IFeesManager.sol"; /// @title AddressResolverUtil /// @notice Utility contract for resolving system contract addresses @@ -26,8 +27,8 @@ abstract contract AddressResolverUtil { /// @notice Error thrown when an invalid address attempts to call the Watcher precompile or delivery helper error OnlyWatcherPrecompileOrDeliveryHelper(); - /// @notice Restricts function access to the auction house contract - /// @dev Validates that msg.sender matches the registered auction house address + /// @notice Restricts function access to the delivery helper contract + /// @dev Validates that msg.sender matches the registered delivery helper address modifier onlyDeliveryHelper() { if (msg.sender != addressResolver__.deliveryHelper()) { revert OnlyPayloadDelivery(); @@ -59,9 +60,9 @@ abstract contract AddressResolverUtil { _; } - /// @notice Gets the auction house contract interface - /// @return IMiddleware interface of the registered auction house - /// @dev Resolves and returns the auction house contract for interaction + /// @notice Gets the delivery helper contract interface + /// @return IMiddleware interface of the registered delivery helper + /// @dev Resolves and returns the delivery helper contract for interaction function deliveryHelper__() public view returns (IMiddleware) { return IMiddleware(addressResolver__.deliveryHelper()); } diff --git a/contracts/protocol/utils/FeesPlugin.sol b/contracts/protocol/utils/FeesPlugin.sol deleted file mode 100644 index a5320f38..00000000 --- a/contracts/protocol/utils/FeesPlugin.sol +++ /dev/null @@ -1,21 +0,0 @@ -// SPDX-License-Identifier: MIT -pragma solidity ^0.8.21; - -import {Fees} from "../utils/common/Structs.sol"; - -/// @title FeesPlugin -/// @notice Abstract contract for managing fee configurations -/// @dev Provides base functionality for fee management in the system -abstract contract FeesPlugin { - /// @notice Storage for the current fee configuration - /// @dev Contains fee parameters like rates, limits, and recipient addresses - Fees public fees; - - /// @notice Retrieves the current fee configuration - /// @return Current fee configuration struct - /// @dev Public view function accessible to any caller - /// @dev Used by external contracts to verify fee parameters - function getFees() public view returns (Fees memory) { - return fees; - } -} diff --git a/contracts/protocol/utils/Gauge.sol b/contracts/protocol/utils/Gauge.sol index 0a0d7b59..fa2aba78 100644 --- a/contracts/protocol/utils/Gauge.sol +++ b/contracts/protocol/utils/Gauge.sol @@ -1,4 +1,4 @@ -// SPDX-License-Identifier: MIT +// SPDX-License-Identifier: GPL-3.0-only pragma solidity ^0.8.21; import {LimitParams} from "../utils/common/Structs.sol"; import {LimitReached} from "../utils/common/Errors.sol"; diff --git a/contracts/protocol/utils/RescueFundsLib.sol b/contracts/protocol/utils/RescueFundsLib.sol index 189f2a7b..738ec796 100644 --- a/contracts/protocol/utils/RescueFundsLib.sol +++ b/contracts/protocol/utils/RescueFundsLib.sol @@ -9,7 +9,6 @@ import {ETH_ADDRESS} from "./common/Constants.sol"; * @title RescueFundsLib * @dev A library that provides a function to rescue funds from a contract. */ - library RescueFundsLib { /** * @dev Rescues funds from a contract. diff --git a/contracts/protocol/utils/common/AccessRoles.sol b/contracts/protocol/utils/common/AccessRoles.sol index 04a7d1b7..e1406a49 100644 --- a/contracts/protocol/utils/common/AccessRoles.sol +++ b/contracts/protocol/utils/common/AccessRoles.sol @@ -1,13 +1,14 @@ // SPDX-License-Identifier: GPL-3.0-only pragma solidity ^0.8.21; -// contains role hashes used in socket for various different operations - +// contains role hashes used in socket for various different operation // used to rescue funds bytes32 constant RESCUE_ROLE = keccak256("RESCUE_ROLE"); // used by governance bytes32 constant GOVERNANCE_ROLE = keccak256("GOVERNANCE_ROLE"); -// used by transmitters who seal and propose packets in socket +// used by transmitters who execute payloads in socket bytes32 constant TRANSMITTER_ROLE = keccak256("TRANSMITTER_ROLE"); // used by switchboard watchers who work against transmitters bytes32 constant WATCHER_ROLE = keccak256("WATCHER_ROLE"); +// used to disable switchboard +bytes32 constant SWITCHBOARD_DISABLER_ROLE = keccak256("SWITCHBOARD_DISABLER_ROLE"); diff --git a/contracts/protocol/utils/common/Constants.sol b/contracts/protocol/utils/common/Constants.sol index 082dcf62..7b17f483 100644 --- a/contracts/protocol/utils/common/Constants.sol +++ b/contracts/protocol/utils/common/Constants.sol @@ -1,19 +1,16 @@ -// SPDX-License-Identifier: MIT +// SPDX-License-Identifier: GPL-3.0-only pragma solidity ^0.8.21; address constant ETH_ADDRESS = address(0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE); -address constant ZERO_ADDRESS = address(0); - bytes32 constant FORWARD_CALL = keccak256("FORWARD_CALL"); bytes32 constant DISTRIBUTE_FEE = keccak256("DISTRIBUTE_FEE"); bytes32 constant DEPLOY = keccak256("DEPLOY"); -bytes32 constant CONFIGURE = keccak256("CONFIGURE"); -bytes32 constant CONNECT = keccak256("CONNECT"); bytes32 constant QUERY = keccak256("QUERY"); bytes32 constant FINALIZE = keccak256("FINALIZE"); bytes32 constant SCHEDULE = keccak256("SCHEDULE"); +bytes32 constant CALLBACK = keccak256("CALLBACK"); bytes32 constant FAST = keccak256("FAST"); - -uint256 constant DEPLOY_GAS_LIMIT = 5_000_000; -uint256 constant CONFIGURE_GAS_LIMIT = 1_000_000; +uint256 constant REQUEST_PAYLOAD_COUNT_LIMIT = 10; +uint256 constant PAYLOAD_SIZE_LIMIT = 24_500; +uint16 constant MAX_COPY_BYTES = 2048; // 2KB diff --git a/contracts/protocol/utils/common/Errors.sol b/contracts/protocol/utils/common/Errors.sol index bdebefd1..ebfe9dc6 100644 --- a/contracts/protocol/utils/common/Errors.sol +++ b/contracts/protocol/utils/common/Errors.sol @@ -1,17 +1,8 @@ -// SPDX-License-Identifier: MIT +// SPDX-License-Identifier: GPL-3.0-only pragma solidity ^0.8.21; -error NotAuthorized(); -error NotBridge(); error NotSocket(); -error ConnectorUnavailable(); -error InvalidTokenContract(); -error ZeroAddressReceiver(); error ZeroAddress(); -error ZeroAmount(); -error InsufficientFunds(); -error InvalidSigner(); -error InvalidFunction(); error TimeoutDelayTooLarge(); error TimeoutAlreadyResolved(); error ResolvingTimeoutTooEarly(); @@ -19,13 +10,13 @@ error LimitReached(); error FeesAlreadyPaid(); error NotAuctionManager(); error CallFailed(); -error PlugDisconnected(); +error PlugNotFound(); error InvalidAppGateway(); error AppGatewayAlreadyCalled(); error InvalidInboxCaller(); +error InvalidCallerTriggered(); error PromisesNotResolved(); error InvalidPromise(); -error InvalidIndex(); error InvalidTransmitter(); error FeesNotSet(); error InvalidTokenAddress(); @@ -40,3 +31,13 @@ error BidExceedsMaxFees(); /// @notice Error thrown if a lower bid already exists error LowerBidAlreadyExists(); error AsyncModifierNotUsed(); +error InvalidIndex(); +error RequestAlreadyExecuted(); +/// @notice Error thrown when no async promise is found +error NoAsyncPromiseFound(); +/// @notice Error thrown when promise caller mismatch +error PromiseCallerMismatch(); +/// @notice Error thrown when request count mismatch +error RequestCountMismatch(); +/// @notice Error thrown when delivery helper is not set +error DeliveryHelperNotSet(); diff --git a/contracts/protocol/utils/common/Structs.sol b/contracts/protocol/utils/common/Structs.sol index 2b7c1f55..2f93bf17 100644 --- a/contracts/protocol/utils/common/Structs.sol +++ b/contracts/protocol/utils/common/Structs.sol @@ -1,4 +1,4 @@ -// SPDX-License-Identifier: MIT +// SPDX-License-Identifier: GPL-3.0-only pragma solidity ^0.8.21; //// ENUMS //// @@ -50,6 +50,22 @@ enum ExecutionStatus { Reverted } +/// @notice Creates a struct to hold batch parameters +struct BatchParams { + address appGateway; + address auctionManager; + uint256 maxFees; + bytes onCompleteData; + bool onlyReadRequests; + uint256 queryCount; + uint256 finalizeCount; +} + +struct AppGatewayWhitelistParams { + address appGateway; + bool isApproved; +} + //// STRUCTS //// // plug: struct LimitParams { @@ -64,29 +80,29 @@ struct UpdateLimitParams { uint256 maxLimit; uint256 ratePerSecond; } + struct AppGatewayConfig { address plug; - address appGateway; + bytes32 appGatewayId; address switchboard; uint32 chainSlug; } // Plug config: struct PlugConfig { - address appGateway; + bytes32 appGatewayId; address switchboard; } -//inbox: -struct CallFromChainParams { - bytes32 callId; - bytes32 params; +//trigger: +struct TriggerParams { + bytes32 triggerId; address plug; - address appGateway; + bytes32 appGatewayId; uint32 chainSlug; + bytes overrides; bytes payload; } // timeout: struct TimeoutRequest { - bytes32 timeoutId; address target; uint256 delayInSeconds; uint256 executeAt; @@ -94,13 +110,7 @@ struct TimeoutRequest { bool isResolved; bytes payload; } -struct QueryResults { - address target; - uint256 queryCounter; - bytes functionSelector; - bytes returnData; - bytes callback; -} + struct ResolvedPromises { bytes32 payloadId; bytes returnData; @@ -108,41 +118,45 @@ struct ResolvedPromises { // AM struct Bid { - address transmitter; uint256 fee; + address transmitter; bytes extraData; } +struct OnChainFees { + uint32 chainSlug; + address token; + uint256 amount; +} + // App gateway base: struct OverrideParams { Read isReadCall; Parallel isParallelCall; WriteFinality writeFinality; uint256 gasLimit; + uint256 value; uint256 readAt; } -// FM: -struct Fees { - uint32 feePoolChain; - address feePoolToken; - uint256 amount; +struct UserCredits { + uint256 totalCredits; + uint256 blockedCredits; } // digest: struct DigestParams { + address socket; address transmitter; bytes32 payloadId; uint256 deadline; CallType callType; - WriteFinality writeFinality; uint256 gasLimit; uint256 value; - uint256 readAt; bytes payload; address target; - address appGateway; - bytes32 prevDigestsHash; // should be id? hash of hashes + bytes32 appGatewayId; + bytes32 prevDigestsHash; } struct QueuePayloadParams { @@ -181,7 +195,7 @@ struct PayloadSubmitParams { struct PayloadParams { // uint40 requestCount + uint40 batchCount + uint40 payloadCount + uint32 chainSlug // CallType callType + Parallel isParallel + WriteFinality writeFinality - bytes32 dump; + bytes32 payloadHeader; // uint40 requestCount; // uint40 batchCount; // uint40 payloadCount; @@ -206,47 +220,55 @@ struct PayloadParams { struct RequestParams { bool isRequestCancelled; uint40 currentBatch; + // updated while processing request uint256 currentBatchPayloadsLeft; uint256 payloadsRemaining; + uint256 queryCount; + uint256 finalizeCount; + uint256 scheduleCount; address middleware; + // updated after auction address transmitter; PayloadParams[] payloadParamsArray; } struct RequestMetadata { + bool onlyReadRequests; + address consumeFrom; address appGateway; address auctionManager; - Fees fees; + uint256 maxFees; + uint256 queryCount; + uint256 finalizeCount; Bid winningBid; bytes onCompleteData; - bool onlyReadRequests; } struct ExecuteParams { - uint256 deadline; CallType callType; - WriteFinality writeFinality; - uint256 gasLimit; - uint256 readAt; - bytes payload; - address target; uint40 requestCount; uint40 batchCount; uint40 payloadCount; - bytes32 prevDigestsHash; // should be id? hash of hashes - address switchboard; + uint256 deadline; + uint256 gasLimit; + uint256 value; + bytes32 prevDigestsHash; + address target; + bytes payload; + bytes extraData; +} + +struct TransmissionParams { + uint256 socketFees; + address refundAddress; + bytes extraData; + bytes transmitterSignature; } struct PayloadIdParams { uint40 requestCount; uint40 batchCount; uint40 payloadCount; - address switchboard; uint32 chainSlug; -} - -/// @notice Struct containing fee amounts and status -struct TokenBalance { - uint256 deposited; // Amount deposited - uint256 blocked; // Amount blocked + address switchboard; } diff --git a/contracts/protocol/watcherPrecompile/DumpDecoder.sol b/contracts/protocol/watcherPrecompile/PayloadHeaderDecoder.sol similarity index 56% rename from contracts/protocol/watcherPrecompile/DumpDecoder.sol rename to contracts/protocol/watcherPrecompile/PayloadHeaderDecoder.sol index fb98c56c..a7f17356 100644 --- a/contracts/protocol/watcherPrecompile/DumpDecoder.sol +++ b/contracts/protocol/watcherPrecompile/PayloadHeaderDecoder.sol @@ -1,9 +1,9 @@ -// SPDX-License-Identifier: Unlicense +// SPDX-License-Identifier: GPL-3.0-only pragma solidity ^0.8.21; import {CallType, Parallel, WriteFinality} from "../utils/common/Structs.sol"; -library DumpDecoder { +library PayloadHeaderDecoder { // Corrected mapping (most significant bits on the left): // [256.....................................................................80][79.............................................0] // ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ @@ -21,102 +21,120 @@ library DumpDecoder { // ------------------------------------------------------------------------- // GETTERS // ------------------------------------------------------------------------- - function getRequestCount(bytes32 dump_) internal pure returns (uint40) { + function getRequestCount(bytes32 payloadHeader_) internal pure returns (uint40) { // Top 40 bits => shift right by 216 - return uint40(uint256(dump_) >> 216); + return uint40(uint256(payloadHeader_) >> 216); } - function getBatchCount(bytes32 dump_) internal pure returns (uint40) { - return uint40((uint256(dump_) >> 176) & 0xFFFFFFFFFF); + function getBatchCount(bytes32 payloadHeader_) internal pure returns (uint40) { + return uint40((uint256(payloadHeader_) >> 176) & 0xFFFFFFFFFF); } - function getPayloadCount(bytes32 dump_) internal pure returns (uint40) { - return uint40((uint256(dump_) >> 136) & 0xFFFFFFFFFF); + function getPayloadCount(bytes32 payloadHeader_) internal pure returns (uint40) { + return uint40((uint256(payloadHeader_) >> 136) & 0xFFFFFFFFFF); } - function getChainSlug(bytes32 dump_) internal pure returns (uint32) { - return uint32((uint256(dump_) >> 104) & 0xFFFFFFFF); + function getChainSlug(bytes32 payloadHeader_) internal pure returns (uint32) { + return uint32((uint256(payloadHeader_) >> 104) & 0xFFFFFFFF); } - function getCallType(bytes32 dump_) internal pure returns (CallType) { - return CallType(uint8((uint256(dump_) >> 96) & 0xFF)); + function getCallType(bytes32 payloadHeader_) internal pure returns (CallType) { + return CallType(uint8((uint256(payloadHeader_) >> 96) & 0xFF)); } - function getIsParallel(bytes32 dump_) internal pure returns (Parallel) { - return Parallel(uint8((uint256(dump_) >> 88) & 0xFF)); + function getIsParallel(bytes32 payloadHeader_) internal pure returns (Parallel) { + return Parallel(uint8((uint256(payloadHeader_) >> 88) & 0xFF)); } - function getWriteFinality(bytes32 dump_) internal pure returns (WriteFinality) { - return WriteFinality(uint8((uint256(dump_) >> 80) & 0xFF)); + function getWriteFinality(bytes32 payloadHeader_) internal pure returns (WriteFinality) { + return WriteFinality(uint8((uint256(payloadHeader_) >> 80) & 0xFF)); } // ------------------------------------------------------------------------- // SETTERS // ------------------------------------------------------------------------- - /// @notice Sets the request count in a dump (top 40 bits) - function setRequestCount(bytes32 dump_, uint40 requestCount_) internal pure returns (bytes32) { + /// @notice Sets the request count in a payloadHeader (top 40 bits) + function setRequestCount( + bytes32 payloadHeader_, + uint40 requestCount_ + ) internal pure returns (bytes32) { // Clear bits [216..255], then OR in the new requestCount << 216 return bytes32( - (uint256(dump_) & ~((uint256(0xFFFFFFFFFF)) << 216)) | + (uint256(payloadHeader_) & ~((uint256(0xFFFFFFFFFF)) << 216)) | (uint256(requestCount_) << 216) ); } - /// @notice Sets the batch count in a dump [176..215] - function setBatchCount(bytes32 dump_, uint40 batchCount_) internal pure returns (bytes32) { + /// @notice Sets the batch count in a payloadHeader [176..215] + function setBatchCount( + bytes32 payloadHeader_, + uint40 batchCount_ + ) internal pure returns (bytes32) { return bytes32( - (uint256(dump_) & ~((uint256(0xFFFFFFFFFF)) << 176)) | + (uint256(payloadHeader_) & ~((uint256(0xFFFFFFFFFF)) << 176)) | ((uint256(batchCount_) & 0xFFFFFFFFFF) << 176) ); } /// @notice Sets the payload count [136..175] - function setPayloadCount(bytes32 dump_, uint40 payloadCount_) internal pure returns (bytes32) { + function setPayloadCount( + bytes32 payloadHeader_, + uint40 payloadCount_ + ) internal pure returns (bytes32) { return bytes32( - (uint256(dump_) & ~((uint256(0xFFFFFFFFFF)) << 136)) | + (uint256(payloadHeader_) & ~((uint256(0xFFFFFFFFFF)) << 136)) | ((uint256(payloadCount_) & 0xFFFFFFFFFF) << 136) ); } /// @notice Sets the chain slug [104..135] - function setChainSlug(bytes32 dump_, uint32 chainSlug_) internal pure returns (bytes32) { + function setChainSlug( + bytes32 payloadHeader_, + uint32 chainSlug_ + ) internal pure returns (bytes32) { return bytes32( - (uint256(dump_) & ~((uint256(0xFFFFFFFF)) << 104)) | + (uint256(payloadHeader_) & ~((uint256(0xFFFFFFFF)) << 104)) | ((uint256(chainSlug_) & 0xFFFFFFFF) << 104) ); } /// @notice Sets the call type [96..103] - function setCallType(bytes32 dump_, CallType callType_) internal pure returns (bytes32) { + function setCallType( + bytes32 payloadHeader_, + CallType callType_ + ) internal pure returns (bytes32) { return bytes32( - (uint256(dump_) & ~((uint256(0xFF)) << 96)) | + (uint256(payloadHeader_) & ~((uint256(0xFF)) << 96)) | ((uint256(uint8(callType_)) & 0xFF) << 96) ); } /// @notice Sets the parallel flag [88..95] - function setIsParallel(bytes32 dump_, Parallel isParallel_) internal pure returns (bytes32) { + function setIsParallel( + bytes32 payloadHeader_, + Parallel isParallel_ + ) internal pure returns (bytes32) { return bytes32( - (uint256(dump_) & ~((uint256(0xFF)) << 88)) | + (uint256(payloadHeader_) & ~((uint256(0xFF)) << 88)) | ((uint256(uint8(isParallel_)) & 0xFF) << 88) ); } /// @notice Sets the write finality [80..87] function setWriteFinality( - bytes32 dump_, + bytes32 payloadHeader_, WriteFinality writeFinality_ ) internal pure returns (bytes32) { return bytes32( - (uint256(dump_) & ~((uint256(0xFF)) << 80)) | + (uint256(payloadHeader_) & ~((uint256(0xFF)) << 80)) | ((uint256(uint8(writeFinality_)) & 0xFF) << 80) ); } @@ -124,8 +142,8 @@ library DumpDecoder { // ------------------------------------------------------------------------- // CREATE // ------------------------------------------------------------------------- - /// @notice Creates a new dump with all fields set - function createDump( + /// @notice Creates a new payloadHeader with all fields set + function createPayloadHeader( uint40 requestCount_, uint40 batchCount_, uint40 payloadCount_, diff --git a/contracts/protocol/watcherPrecompile/RequestHandler.sol b/contracts/protocol/watcherPrecompile/RequestHandler.sol deleted file mode 100644 index da161179..00000000 --- a/contracts/protocol/watcherPrecompile/RequestHandler.sol +++ /dev/null @@ -1,146 +0,0 @@ -// SPDX-License-Identifier: Unlicense -pragma solidity ^0.8.21; - -import "./WatcherPrecompileCore.sol"; - -abstract contract RequestHandler is WatcherPrecompileCore { - using DumpDecoder for bytes32; - - function submitRequest( - PayloadSubmitParams[] calldata payloadSubmitParams - ) public returns (uint40 requestCount) { - requestCount = nextRequestCount++; - uint40 batchCount = nextBatchCount; - uint40 currentBatch = batchCount; - - address appGateway = _checkAppGateways(payloadSubmitParams); - - uint256 readCount; - uint256 writeCount; - PayloadSubmitParams memory lastP; - - for (uint256 i = 0; i < payloadSubmitParams.length; i++) { - PayloadSubmitParams memory p = payloadSubmitParams[i]; - - // Count reads and writes - if (p.callType == CallType.READ) { - readCount++; - } else writeCount++; - - if (i > 0) { - if (p.levelNumber != lastP.levelNumber && p.levelNumber != lastP.levelNumber + 1) - revert InvalidLevelNumber(); - if (p.levelNumber == lastP.levelNumber + 1) { - requestBatchIds[requestCount].push(batchCount); - batchCount = ++nextBatchCount; - } - } - - uint40 localPayloadCount = payloadCounter++; - bytes32 payloadId = _createPayloadId(p, requestCount, batchCount, localPayloadCount); - batchPayloadIds[batchCount].push(payloadId); - - bytes32 dump; - dump = dump.setRequestCount(requestCount); - dump = dump.setBatchCount(batchCount); - dump = dump.setPayloadCount(localPayloadCount); - dump = dump.setChainSlug(p.chainSlug); - dump = dump.setCallType(p.callType); - dump = dump.setIsParallel(p.isParallel); - dump = dump.setWriteFinality(p.writeFinality); - - payloads[payloadId].dump = dump; - payloads[payloadId].asyncPromise = p.asyncPromise; - payloads[payloadId].switchboard = p.switchboard; - payloads[payloadId].target = p.target; - payloads[payloadId].appGateway = p.callType == CallType.DEPLOY - ? addressResolver__.deliveryHelper() - : p.appGateway; - payloads[payloadId].payloadId = payloadId; - payloads[payloadId].prevDigestsHash = bytes32(0); - payloads[payloadId].gasLimit = p.gasLimit; - payloads[payloadId].value = p.value; - payloads[payloadId].readAt = p.readAt; - payloads[payloadId].deadline = 0; - payloads[payloadId].payload = p.payload; - payloads[payloadId].finalizedTransmitter = address(0); - - requestParams[requestCount].payloadParamsArray.push(payloads[payloadId]); - lastP = p; - } - - requestBatchIds[requestCount].push(nextBatchCount++); - - watcherPrecompileLimits__.consumeLimit(appGateway, QUERY, readCount); - watcherPrecompileLimits__.consumeLimit(appGateway, FINALIZE, writeCount); - requestParams[requestCount].isRequestCancelled = false; - requestParams[requestCount].currentBatch = currentBatch; - requestParams[requestCount].currentBatchPayloadsLeft = 0; - requestParams[requestCount].payloadsRemaining = payloadSubmitParams.length; - requestParams[requestCount].middleware = msg.sender; - requestParams[requestCount].transmitter = address(0); - - emit RequestSubmitted( - msg.sender, - requestCount, - requestParams[requestCount].payloadParamsArray - ); - } - - function _checkAppGateways( - PayloadSubmitParams[] calldata payloadSubmitParams - ) internal view returns (address appGateway) { - bool isDeliveryHelper = msg.sender == addressResolver__.deliveryHelper(); - address coreAppGateway = isDeliveryHelper - ? payloadSubmitParams[0].appGateway - : _getCoreAppGateway(payloadSubmitParams[0].appGateway); - for (uint256 i = 0; i < payloadSubmitParams.length; i++) { - address callerAppGateway = isDeliveryHelper - ? payloadSubmitParams[i].appGateway - : msg.sender; - appGateway = _getCoreAppGateway(callerAppGateway); - if (appGateway != coreAppGateway) revert InvalidGateway(); - } - } - - function startProcessingRequest(uint40 requestCount, address transmitter) public { - RequestParams storage r = requestParams[requestCount]; - if (r.middleware != msg.sender) revert InvalidCaller(); - if (r.transmitter != address(0)) revert AlreadyStarted(); - if (r.currentBatchPayloadsLeft > 0) revert AlreadyStarted(); - - uint40 batchCount = r.payloadParamsArray[0].dump.getBatchCount(); - r.transmitter = transmitter; - r.currentBatch = batchCount; - - uint256 totalPayloadsLeft = _processBatch(requestCount, batchCount); - // todo: for retry cases - r.currentBatchPayloadsLeft = totalPayloadsLeft; - } - - function _processBatch( - uint40 requestCount_, - uint40 batchCount_ - ) internal returns (uint256 totalPayloadsLeft) { - RequestParams memory r = requestParams[requestCount_]; - PayloadParams[] memory payloadParamsArray = _getBatch(batchCount_); - - if (r.isRequestCancelled) revert RequestCancelled(); - - for (uint40 i = 0; i < payloadParamsArray.length; i++) { - bool executed = isPromiseExecuted[payloadParamsArray[i].payloadId]; - if (executed) continue; - totalPayloadsLeft++; - - if (payloadParamsArray[i].dump.getCallType() != CallType.READ) { - _finalize(payloadParamsArray[i], r.transmitter); - } else { - _query(payloadParamsArray[i]); - } - } - } - - function getCurrentRequestCount() external view returns (uint40) { - return nextRequestCount; - } -} diff --git a/contracts/protocol/watcherPrecompile/WatcherPrecompile.sol b/contracts/protocol/watcherPrecompile/WatcherPrecompile.sol deleted file mode 100644 index c8bc8a76..00000000 --- a/contracts/protocol/watcherPrecompile/WatcherPrecompile.sol +++ /dev/null @@ -1,288 +0,0 @@ -// SPDX-License-Identifier: Unlicense -pragma solidity ^0.8.21; - -import "./RequestHandler.sol"; - -/// @title WatcherPrecompile -/// @notice Contract that handles payload verification, execution and app configurations -contract WatcherPrecompile is RequestHandler { - using DumpDecoder for bytes32; - - constructor() { - _disableInitializers(); // disable for implementation - } - - /// @notice Initial initialization (version 1) - function initialize( - address owner_, - address addressResolver_, - uint256 expiryTime_, - uint32 evmxSlug_, - address watcherPrecompileLimits_, - address watcherPrecompileConfig_ - ) public reinitializer(1) { - _setAddressResolver(addressResolver_); - _initializeOwner(owner_); - - watcherPrecompileLimits__ = IWatcherPrecompileLimits(watcherPrecompileLimits_); - watcherPrecompileConfig__ = IWatcherPrecompileConfig(watcherPrecompileConfig_); - maxTimeoutDelayInSeconds = 24 * 60 * 60; // 24 hours - expiryTime = expiryTime_; - - evmxSlug = evmxSlug_; - } - - // ================== 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( - uint256 delayInSeconds_, - bytes calldata payload_ - ) external returns (bytes32) { - return _setTimeout(payload_, delayInSeconds_); - } - - /// @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_, - uint256 signatureNonce_, - bytes calldata signature_ - ) external { - _isWatcherSignatureValid( - abi.encode(this.resolveTimeout.selector, timeoutId_), - signatureNonce_, - signature_ - ); - - TimeoutRequest storage timeoutRequest_ = timeoutRequests[timeoutId_]; - if (timeoutRequest_.target == address(0)) revert InvalidTimeoutRequest(); - if (timeoutRequest_.isResolved) revert TimeoutAlreadyResolved(); - if (block.timestamp < timeoutRequest_.executeAt) revert ResolvingTimeoutTooEarly(); - - (bool success, ) = address(timeoutRequest_.target).call(timeoutRequest_.payload); - if (!success) revert CallFailed(); - - timeoutRequest_.isResolved = true; - timeoutRequest_.executedAt = block.timestamp; - - 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 payload parameters - /// @param transmitter_ The address of the transmitter - function finalize( - PayloadParams memory params_, - address transmitter_ - ) external returns (bytes32 digest) { - digest = _finalize(params_, transmitter_); - } - - // ================== Query functions ================== - /// @notice Creates a new query request - /// @param params_ The payload parameters - function query(PayloadParams memory params_) external { - _query(params_); - } - - /// @notice Marks a request as finalized with a proof on digest - /// @param payloadId_ The unique identifier of the request - /// @param proof_ The watcher's proof - /// @param signatureNonce_ The nonce of the signature - /// @param signature_ The signature of the watcher - /// @dev Only callable by the contract owner - /// @dev Watcher signs on following digest for validation on switchboard: - /// @dev keccak256(abi.encode(switchboard, digest)) - function finalized( - bytes32 payloadId_, - bytes calldata proof_, - uint256 signatureNonce_, - bytes calldata signature_ - ) external { - _isWatcherSignatureValid( - abi.encode(this.finalized.selector, payloadId_, proof_), - signatureNonce_, - signature_ - ); - - watcherProofs[payloadId_] = proof_; - emit Finalized(payloadId_, proof_); - } - - function updateTransmitter(uint40 requestCount, address transmitter) public { - RequestParams storage r = requestParams[requestCount]; - if (r.isRequestCancelled) revert RequestCancelled(); - if (r.middleware != msg.sender) revert InvalidCaller(); - if (r.transmitter != address(0)) revert AlreadyStarted(); - - r.transmitter = transmitter; - /// todo: recheck limits - _processBatch(requestCount, r.currentBatch); - } - - function cancelRequest(uint40 requestCount) external { - RequestParams storage r = requestParams[requestCount]; - if (r.isRequestCancelled) revert RequestAlreadyCancelled(); - if (r.middleware != msg.sender) revert InvalidCaller(); - - r.isRequestCancelled = true; - } - - /// @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_, - uint256 signatureNonce_, - bytes calldata signature_ - ) external { - _isWatcherSignatureValid( - abi.encode(this.resolvePromises.selector, resolvedPromises_), - signatureNonce_, - signature_ - ); - - 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; - if (asyncPromise == address(0)) continue; - - // Resolve each promise with its corresponding return data - bool success = IPromise(asyncPromise).markResolved( - payloadParams.dump.getRequestCount(), - resolvedPromises_[i].payloadId, - resolvedPromises_[i].returnData - ); - - isPromiseExecuted[resolvedPromises_[i].payloadId] = true; - if (!success) { - emit PromiseNotResolved(resolvedPromises_[i].payloadId, asyncPromise); - continue; - } - - RequestParams storage requestParams_ = requestParams[ - payloadParams.dump.getRequestCount() - ]; - - requestParams_.currentBatchPayloadsLeft--; - requestParams_.payloadsRemaining--; - - if ( - requestParams_.currentBatchPayloadsLeft == 0 && requestParams_.payloadsRemaining > 0 - ) { - uint256 totalPayloadsLeft = _processBatch( - payloadParams.dump.getRequestCount(), - ++requestParams_.currentBatch - ); - requestParams_.currentBatchPayloadsLeft = totalPayloadsLeft; - } - - if (requestParams_.payloadsRemaining == 0) { - IMiddleware(requestParams_.middleware).finishRequest( - payloadParams.dump.getRequestCount() - ); - } - emit PromiseResolved(resolvedPromises_[i].payloadId, asyncPromise); - } - } - - // wait till expiry time to assign fees - function markRevert( - bool isRevertingOnchain_, - bytes32 payloadId_, - uint256 signatureNonce_, - bytes calldata signature_ - ) external { - _isWatcherSignatureValid( - abi.encode(this.markRevert.selector, isRevertingOnchain_, payloadId_), - signatureNonce_, - signature_ - ); - - PayloadParams storage payloadParams = payloads[payloadId_]; - RequestParams storage currentRequestParams = requestParams[ - payloadParams.dump.getRequestCount() - ]; - currentRequestParams.isRequestCancelled = true; - - if (isRevertingOnchain_) - IPromise(payloadParams.asyncPromise).markOnchainRevert( - payloadParams.dump.getRequestCount(), - payloadId_ - ); - - IMiddleware(currentRequestParams.middleware).handleRequestReverts( - payloadParams.dump.getRequestCount() - ); - - emit MarkedRevert(payloadId_, isRevertingOnchain_); - } - - function setMaxTimeoutDelayInSeconds(uint256 maxTimeoutDelayInSeconds_) external onlyOwner { - maxTimeoutDelayInSeconds = maxTimeoutDelayInSeconds_; - } - - // ================== On-Chain Inbox ================== - - function callAppGateways( - CallFromChainParams[] calldata params_, - uint256 signatureNonce_, - bytes calldata signature_ - ) external { - _isWatcherSignatureValid( - abi.encode(this.callAppGateways.selector, params_), - signatureNonce_, - signature_ - ); - - for (uint256 i = 0; i < params_.length; i++) { - if (appGatewayCalled[params_[i].callId]) revert AppGatewayAlreadyCalled(); - if ( - !watcherPrecompileConfig__.isValidPlug( - params_[i].appGateway, - params_[i].chainSlug, - params_[i].plug - ) - ) revert InvalidInboxCaller(); - - appGatewayCalled[params_[i].callId] = true; - IAppGateway(params_[i].appGateway).callFromChain( - params_[i].chainSlug, - params_[i].plug, - params_[i].params, - params_[i].payload - ); - - emit CalledAppGateway( - params_[i].callId, - params_[i].chainSlug, - params_[i].plug, - params_[i].appGateway, - params_[i].params, - params_[i].payload - ); - } - } - - // ================== Helper functions ================== - - function setExpiryTime(uint256 expiryTime_) external onlyOwner { - expiryTime = expiryTime_; - } - - function getRequestParams(uint40 requestCount) external view returns (RequestParams memory) { - return requestParams[requestCount]; - } -} diff --git a/contracts/protocol/watcherPrecompile/WatcherPrecompileConfig.sol b/contracts/protocol/watcherPrecompile/WatcherPrecompileConfig.sol index 881b3706..81083086 100644 --- a/contracts/protocol/watcherPrecompile/WatcherPrecompileConfig.sol +++ b/contracts/protocol/watcherPrecompile/WatcherPrecompileConfig.sol @@ -1,10 +1,13 @@ -// SPDX-License-Identifier: Unlicense +// SPDX-License-Identifier: GPL-3.0-only pragma solidity ^0.8.21; -import "./WatcherPrecompileLimits.sol"; -import {ECDSA} from "solady/utils/ECDSA.sol"; import "solady/utils/Initializable.sol"; +import {ECDSA} from "solady/utils/ECDSA.sol"; +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/WatcherIdUtils.sol"; /// @title WatcherPrecompileConfig /// @notice Configuration contract for the Watcher Precompile system @@ -12,52 +15,57 @@ import "../../interfaces/IWatcherPrecompileConfig.sol"; contract WatcherPrecompileConfig is IWatcherPrecompileConfig, Initializable, - AccessControl, + Ownable, AddressResolverUtil { - // slot 52: evmxSlug + // slots 0-50 (51) reserved for addr resolver util + + // slots [51-100]: gap for future storage variables + uint256[50] _gap_before; + + // slot 101: evmxSlug /// @notice The chain slug of the watcher precompile uint32 public evmxSlug; - // slot 55: _plugConfigs + // slot 102: _plugConfigs /// @notice Maps network and plug to their configuration /// @dev chainSlug => plug => PlugConfig mapping(uint32 => mapping(address => PlugConfig)) internal _plugConfigs; - // slot 56: switchboards + // slot 103: 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 + // slot 104: sockets /// @notice Maps chain slug to their associated socket /// @dev chainSlug => socket address mapping(uint32 => address) public sockets; - // slot 58: contractFactoryPlug + // slot 105: 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 + // slot 106: feesPlug /// @notice Maps chain slug to their associated fees plug /// @dev chainSlug => fees plug address mapping(uint32 => address) public feesPlug; - // slot 60: isNonceUsed + // slot 107: isNonceUsed /// @notice Maps nonce to whether it has been used /// @dev signatureNonce => isValid mapping(uint256 => bool) public isNonceUsed; - // slot 61: isValidPlug + // slot 108: isValidPlug // appGateway => chainSlug => plug => isValid mapping(address => mapping(uint32 => mapping(address => bool))) public isValidPlug; /// @notice Emitted when a new plug is configured for an app gateway - /// @param appGateway The address of the app gateway + /// @param appGatewayId The id of the app gateway /// @param chainSlug The identifier of the destination network /// @param plug The address of the plug - event PlugAdded(address appGateway, uint32 chainSlug, address plug); + event PlugAdded(bytes32 appGatewayId, uint32 chainSlug, address plug); /// @notice Emitted when a switchboard is set for a network /// @param chainSlug The identifier of the network @@ -77,10 +85,15 @@ contract WatcherPrecompileConfig is address feesPlug ); + /// @notice Emitted when a valid plug is set for an app gateway + /// @param appGateway The address of the app gateway + /// @param chainSlug The identifier of the network + /// @param plug The address of the plug + /// @param isValid Whether the plug is valid + event IsValidPlugSet(address appGateway, uint32 chainSlug, address plug, bool isValid); + error InvalidGateway(); error InvalidSwitchboard(); - error NonceUsed(); - error InvalidWatcherSignature(); /// @notice Initial initialization (version 1) function initialize( @@ -94,12 +107,10 @@ contract WatcherPrecompileConfig is evmxSlug = evmxSlug_; } - /// @notice Emitted when a plug is set as valid for an app gateway - /// @notice Configures app gateways with their respective plugs and switchboards - /// @param configs_ Array of configurations containing app gateway, network, plug, and switchboard details - /// @dev Only callable by the contract owner + /// @dev Only callable by the watcher /// @dev This helps in verifying that plugs are called by respective app gateways + /// @param configs_ Array of configurations containing app gateway, network, plug, and switchboard details function setAppGateways( AppGatewayConfig[] calldata configs_, uint256 signatureNonce_, @@ -114,15 +125,15 @@ contract WatcherPrecompileConfig is for (uint256 i = 0; i < configs_.length; i++) { // Store the plug configuration for this network and plug _plugConfigs[configs_[i].chainSlug][configs_[i].plug] = PlugConfig({ - appGateway: configs_[i].appGateway, + appGatewayId: configs_[i].appGatewayId, switchboard: configs_[i].switchboard }); - emit PlugAdded(configs_[i].appGateway, configs_[i].chainSlug, configs_[i].plug); + emit PlugAdded(configs_[i].appGatewayId, configs_[i].chainSlug, configs_[i].plug); } } - /// @notice Sets the switchboard for a network + /// @notice Sets the socket, contract factory plug, and fees plug for a network /// @param chainSlug_ The identifier of the network function setOnChainContracts( uint32 chainSlug_, @@ -139,6 +150,7 @@ contract WatcherPrecompileConfig is /// @notice Sets the switchboard for a network /// @param chainSlug_ The identifier of the network + /// @param sbType_ The type of switchboard, hash of a string /// @param switchboard_ The address of the switchboard function setSwitchboard( uint32 chainSlug_, @@ -149,50 +161,68 @@ contract WatcherPrecompileConfig is emit SwitchboardSet(chainSlug_, sbType_, switchboard_); } - // @dev app gateway can set the valid plugs for each chain slug + /// @notice Sets the valid plugs for an app gateway + /// @dev Only callable by the app gateway + /// @dev This helps in verifying that app gateways are called by respective plugs + /// @param chainSlug_ The identifier of the network + /// @param plug_ The address of the plug + /// @param isValid_ Whether the plug is valid function setIsValidPlug(uint32 chainSlug_, address plug_, bool isValid_) external { isValidPlug[msg.sender][chainSlug_][plug_] = isValid_; + emit IsValidPlugSet(msg.sender, chainSlug_, plug_, isValid_); } /// @notice Retrieves the configuration for a specific plug on a network + /// @dev Returns zero addresses if configuration doesn't exist /// @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 + /// @return The app gateway id 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) { + ) public view returns (bytes32, address) { return ( - _plugConfigs[chainSlug_][plug_].appGateway, + _plugConfigs[chainSlug_][plug_].appGatewayId, _plugConfigs[chainSlug_][plug_].switchboard ); } + /// @notice Verifies the connections between the target, app gateway, and switchboard + /// @dev Only callable by the watcher + /// @param chainSlug_ The identifier of the network + /// @param target_ The address of the target + /// @param appGateway_ The address of the app gateway + /// @param switchboard_ The address of the switchboard function verifyConnections( uint32 chainSlug_, address target_, address appGateway_, - address switchboard_ + address switchboard_, + address middleware_ ) external view { - // todo: revisit this // if target is contractFactoryPlug, return - if (target_ == contractFactoryPlug[chainSlug_]) return; + // as connection is with middleware delivery helper and not app gateway + if ( + middleware_ == address(deliveryHelper__()) && target_ == contractFactoryPlug[chainSlug_] + ) return; - (address appGateway, address switchboard) = getPlugConfigs(chainSlug_, target_); - if (appGateway != appGateway_) revert InvalidGateway(); + (bytes32 appGatewayId, address switchboard) = getPlugConfigs(chainSlug_, target_); + if (appGatewayId != WatcherIdUtils.encodeAppGatewayId(appGateway_)) revert InvalidGateway(); if (switchboard != switchboard_) revert InvalidSwitchboard(); } function _isWatcherSignatureValid( - bytes memory digest_, + bytes memory inputData_, uint256 signatureNonce_, bytes memory signature_ ) internal { if (isNonceUsed[signatureNonce_]) revert NonceUsed(); isNonceUsed[signatureNonce_] = true; - bytes32 digest = keccak256(abi.encode(address(this), evmxSlug, signatureNonce_, digest_)); + bytes32 digest = keccak256( + abi.encode(address(this), evmxSlug, signatureNonce_, inputData_) + ); digest = keccak256(abi.encodePacked("\x19Ethereum Signed Message:\n32", digest)); // recovered signer is checked for the valid roles later diff --git a/contracts/protocol/watcherPrecompile/WatcherPrecompileCore.sol b/contracts/protocol/watcherPrecompile/WatcherPrecompileCore.sol deleted file mode 100644 index 7950823e..00000000 --- a/contracts/protocol/watcherPrecompile/WatcherPrecompileCore.sol +++ /dev/null @@ -1,249 +0,0 @@ -// SPDX-License-Identifier: Unlicense -pragma solidity ^0.8.21; - -import "./WatcherPrecompileStorage.sol"; -import {ECDSA} from "solady/utils/ECDSA.sol"; -import {AccessControl} from "../utils/AccessControl.sol"; -import "solady/utils/Initializable.sol"; -import {AddressResolverUtil} from "../utils/AddressResolverUtil.sol"; - -/// @title WatcherPrecompile -/// @notice Contract that handles payload verification, execution and app configurations -abstract contract WatcherPrecompileCore is - IWatcherPrecompile, - WatcherPrecompileStorage, - Initializable, - AccessControl, - AddressResolverUtil -{ - using DumpDecoder for bytes32; - - // ================== 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_ - ) internal returns (bytes32 timeoutId) { - if (delayInSeconds_ > maxTimeoutDelayInSeconds) revert TimeoutDelayTooLarge(); - - // from auction manager - watcherPrecompileLimits__.consumeLimit(_getCoreAppGateway(msg.sender), SCHEDULE, 1); - uint256 executeAt = block.timestamp + delayInSeconds_; - timeoutId = _encodeId(evmxSlug, address(this)); - timeoutRequests[timeoutId] = TimeoutRequest( - timeoutId, - msg.sender, - delayInSeconds_, - executeAt, - 0, - false, - payload_ - ); - emit TimeoutRequested(timeoutId, msg.sender, payload_, executeAt); - } - - function _finalize( - PayloadParams memory params_, - address transmitter_ - ) internal returns (bytes32 digest) { - // Verify that the app gateway is properly configured for this chain and target - watcherPrecompileConfig__.verifyConnections( - params_.dump.getChainSlug(), - params_.target, - params_.appGateway, - params_.switchboard - ); - - uint256 deadline = block.timestamp + expiryTime; - payloads[params_.payloadId].deadline = deadline; - payloads[params_.payloadId].finalizedTransmitter = transmitter_; - - bytes32 prevDigestsHash = _getPreviousDigestsHash( - params_.dump.getRequestCount(), - params_.dump.getBatchCount() - ); - payloads[params_.payloadId].prevDigestsHash = prevDigestsHash; - - // Construct parameters for digest calculation - DigestParams memory digestParams_ = DigestParams( - transmitter_, - params_.payloadId, - deadline, - params_.dump.getCallType(), - params_.dump.getWriteFinality(), - params_.gasLimit, - params_.value, - params_.readAt, - params_.payload, - params_.target, - params_.appGateway, - prevDigestsHash - ); - - // Calculate digest from payload parameters - digest = getDigest(digestParams_); - emit FinalizeRequested(digest, payloads[params_.payloadId]); - } - - function _getBatch(uint40 batchCount) internal view returns (PayloadParams[] memory) { - bytes32[] memory payloadIds = batchPayloadIds[batchCount]; - PayloadParams[] memory payloadParamsArray = new PayloadParams[](payloadIds.length); - - for (uint40 i = 0; i < payloadIds.length; i++) { - payloadParamsArray[i] = payloads[payloadIds[i]]; - } - return payloadParamsArray; - } - - // ================== Query functions ================== - /// @notice Creates a new query request - /// @param params_ The payload parameters - function _query(PayloadParams memory params_) internal { - bytes32 prevDigestsHash = _getPreviousDigestsHash( - params_.dump.getRequestCount(), - params_.dump.getBatchCount() - ); - payloads[params_.payloadId].prevDigestsHash = prevDigestsHash; - emit QueryRequested(params_); - } - - /// @notice Calculates the digest hash of payload parameters - /// @param params_ The payload parameters - /// @return digest The calculated digest - function getDigest(DigestParams memory params_) public pure returns (bytes32 digest) { - digest = keccak256( - abi.encode( - params_.transmitter, - params_.payloadId, - params_.deadline, - params_.callType, - params_.writeFinality, - params_.gasLimit, - params_.value, - params_.readAt, - params_.payload, - params_.target, - params_.appGateway, - params_.prevDigestsHash - ) - ); - } - - function _getPreviousDigestsHash( - uint40 requestCount_, - uint40 batchCount_ - ) internal view returns (bytes32) { - RequestParams memory r = requestParams[requestCount_]; - - // If this is the first batch of the request, return 0 bytes - if (batchCount_ == r.payloadParamsArray[0].dump.getBatchCount()) { - return bytes32(0); - } - - PayloadParams[] memory previousPayloads = _getBatch(batchCount_ - 1); - bytes32 prevDigestsHash = bytes32(0); - - for (uint40 i = 0; i < previousPayloads.length; i++) { - PayloadParams memory p = payloads[previousPayloads[i].payloadId]; - DigestParams memory digestParams = DigestParams( - p.finalizedTransmitter, - p.payloadId, - p.deadline, - p.dump.getCallType(), - p.dump.getWriteFinality(), - p.gasLimit, - p.value, - p.readAt, - p.payload, - p.target, - p.appGateway, - p.prevDigestsHash - ); - prevDigestsHash = keccak256(abi.encodePacked(prevDigestsHash, getDigest(digestParams))); - } - return prevDigestsHash; - } - - // ================== Helper functions ================== - - /// @notice Verifies the connection between chain slug, target, and app gateway - /// @param chainSlug_ The identifier of the chain - /// @param target_ The target address - /// @param appGateway_ The app gateway address to verify - /// @dev Internal function to validate connections - function _verifyConnections( - uint32 chainSlug_, - address target_, - address appGateway_, - address switchboard_ - ) internal view { - // todo: revisit this - // if target is contractFactoryPlug, return - if (target_ == watcherPrecompileConfig__.contractFactoryPlug(chainSlug_)) return; - - (address appGateway, address switchboard) = watcherPrecompileConfig__.getPlugConfigs( - chainSlug_, - target_ - ); - if (appGateway != appGateway_) revert InvalidGateway(); - if (switchboard != switchboard_) revert InvalidSwitchboard(); - } - - // todo: revisit when we do timeout precompile - function _encodeId( - uint32 chainSlug_, - address switchboardOrWatcher_ - ) internal returns (bytes32) { - // Encode payload ID by bit-shifting and combining: - // chainSlug (32 bits) | switchboard or watcher precompile address (160 bits) | counter (64 bits) - return - bytes32( - (uint256(chainSlug_) << 224) | - (uint256(uint160(switchboardOrWatcher_)) << 64) | - payloadCounter++ - ); - } - - function _createPayloadId( - PayloadSubmitParams memory p_, - uint40 requestCount_, - uint40 batchCount_, - uint40 payloadCount_ - ) internal pure returns (bytes32) { - return - keccak256( - abi.encode(requestCount_, batchCount_, payloadCount_, p_.switchboard, p_.chainSlug) - ); - } - - function _isWatcherSignatureValid( - bytes memory digest_, - uint256 signatureNonce_, - bytes memory signature_ - ) internal { - if (isNonceUsed[signatureNonce_]) revert NonceUsed(); - isNonceUsed[signatureNonce_] = true; - - bytes32 digest = keccak256(abi.encode(address(this), evmxSlug, signatureNonce_, digest_)); - digest = keccak256(abi.encodePacked("\x19Ethereum Signed Message:\n32", digest)); - - // recovered signer is checked for the valid roles later - address signer = ECDSA.recover(digest, signature_); - if (signer != owner()) revert InvalidWatcherSignature(); - } - - function getBatches(uint40 requestCount_) external view returns (uint40[] memory) { - return requestBatchIds[requestCount_]; - } - - function getBatchPayloadIds(uint40 batchCount_) external view returns (bytes32[] memory) { - return batchPayloadIds[batchCount_]; - } - - function getPayloadParams(bytes32 payloadId_) external view returns (PayloadParams memory) { - return payloads[payloadId_]; - } -} diff --git a/contracts/protocol/watcherPrecompile/WatcherPrecompileLimits.sol b/contracts/protocol/watcherPrecompile/WatcherPrecompileLimits.sol index 42f1d2b4..2c01968e 100644 --- a/contracts/protocol/watcherPrecompile/WatcherPrecompileLimits.sol +++ b/contracts/protocol/watcherPrecompile/WatcherPrecompileLimits.sol @@ -1,47 +1,67 @@ -// SPDX-License-Identifier: MIT +// SPDX-License-Identifier: GPL-3.0-only pragma solidity ^0.8.21; -import {AccessControl} from "../utils/AccessControl.sol"; +import "solady/utils/Initializable.sol"; +import {Ownable} from "solady/auth/Ownable.sol"; import {Gauge} from "../utils/Gauge.sol"; import {AddressResolverUtil} from "../utils/AddressResolverUtil.sol"; -import {WATCHER_ROLE} from "../utils/common/AccessRoles.sol"; import "../../interfaces/IWatcherPrecompileLimits.sol"; -import "solady/utils/Initializable.sol"; -import {SCHEDULE, QUERY, FINALIZE} from "../utils/common/Constants.sol"; +import {SCHEDULE, QUERY, FINALIZE, CALLBACK} from "../utils/common/Constants.sol"; +/// @title WatcherPrecompileLimits +/// @notice Contract for managing watcher precompile limits contract WatcherPrecompileLimits is IWatcherPrecompileLimits, Initializable, - AccessControl, + Ownable, Gauge, AddressResolverUtil { - // 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; + // slots 0-49 (50) reserved for gauge + // slots 50-100 (51) reserved for addr resolver util + + // slots [101-150]: gap for future storage variables + uint256[50] _gap_before; + // slot 151: limitDecimals /// @notice Number of decimals used in limit calculations - uint256 public constant LIMIT_DECIMALS = 18; - // slot 50: defaultLimit + uint256 public limitDecimals; + + // slot 152: defaultLimit /// @notice Default limit value for any app gateway uint256 public defaultLimit; - // slot 51: defaultRatePerSecond + + // slot 153: defaultRatePerSecond /// @notice Rate at which limit replenishes per second uint256 public defaultRatePerSecond; - // slot 53: _limitParams + // slot 154: _limitParams // appGateway => limitType => receivingLimitParams mapping(address => mapping(bytes32 => LimitParams)) internal _limitParams; - // slot 54: _activeAppGateways + // slot 155: _activeAppGateways // Mapping to track active app gateways mapping(address => bool) internal _activeAppGateways; + // slot 157: fees + uint256 public queryFees; + uint256 public finalizeFees; + uint256 public timeoutFees; + uint256 public callBackFees; + + /// @notice Emitted when the default limit and rate per second are set + event DefaultLimitAndRatePerSecondSet(uint256 defaultLimit, uint256 defaultRatePerSecond); + /// @notice Emitted when the query fees are set + event QueryFeesSet(uint256 queryFees); + /// @notice Emitted when the finalize fees are set + event FinalizeFeesSet(uint256 finalizeFees); + /// @notice Emitted when the timeout fees are set + event TimeoutFeesSet(uint256 timeoutFees); + /// @notice Emitted when the call back fees are set + event CallBackFeesSet(uint256 callBackFees); + + error WatcherFeesNotSet(bytes32 limitType); + /// @notice Initial initialization (version 1) function initialize( address owner_, @@ -50,9 +70,10 @@ contract WatcherPrecompileLimits is ) public reinitializer(1) { _setAddressResolver(addressResolver_); _initializeOwner(owner_); + limitDecimals = 18; // limit per day - defaultLimit = defaultLimit_ * 10 ** LIMIT_DECIMALS; + defaultLimit = defaultLimit_ * 10 ** limitDecimals; // limit per second defaultRatePerSecond = defaultLimit / (24 * 60 * 60); } @@ -120,7 +141,7 @@ contract WatcherPrecompileLimits is ) external override onlyWatcherPrecompile { LimitParams storage limitParams = _limitParams[appGateway_][limitType_]; - // Initialize limit if not active + // Initialize limit if not active, give default limit and rate per second if (!_activeAppGateways[appGateway_]) { LimitParams memory limitParam = LimitParams({ maxLimit: defaultLimit, @@ -138,22 +159,52 @@ contract WatcherPrecompileLimits is } // Update the limit - _consumeFullLimit(consumeLimit_ * 10 ** LIMIT_DECIMALS, limitParams); + _consumeFullLimit(consumeLimit_ * 10 ** limitDecimals, limitParams); } /** * @notice Set the default limit value * @param defaultLimit_ The new default limit value */ - function setDefaultLimit(uint256 defaultLimit_) external onlyOwner { + function setDefaultLimitAndRatePerSecond(uint256 defaultLimit_) external onlyOwner { defaultLimit = defaultLimit_; + defaultRatePerSecond = defaultLimit / (24 * 60 * 60); + + emit DefaultLimitAndRatePerSecondSet(defaultLimit, defaultRatePerSecond); } - /** - * @notice Set the rate at which limit replenishes - * @param defaultRatePerSecond_ The new rate per second - */ - function setDefaultRatePerSecond(uint256 defaultRatePerSecond_) external onlyOwner { - defaultRatePerSecond = defaultRatePerSecond_; + function setQueryFees(uint256 queryFees_) external onlyOwner { + queryFees = queryFees_; + emit QueryFeesSet(queryFees_); + } + + function setFinalizeFees(uint256 finalizeFees_) external onlyOwner { + finalizeFees = finalizeFees_; + emit FinalizeFeesSet(finalizeFees_); + } + + function setTimeoutFees(uint256 timeoutFees_) external onlyOwner { + timeoutFees = timeoutFees_; + emit TimeoutFeesSet(timeoutFees_); + } + + function setCallBackFees(uint256 callBackFees_) external onlyOwner { + callBackFees = callBackFees_; + emit CallBackFeesSet(callBackFees_); + } + + function getTotalFeesRequired( + uint256 queryCount_, + uint256 finalizeCount_, + uint256 scheduleCount_, + uint256 callbackCount_ + ) external view returns (uint256) { + uint256 totalFees = 0; + totalFees += callbackCount_ * callBackFees; + totalFees += queryCount_ * queryFees; + totalFees += finalizeCount_ * finalizeFees; + totalFees += scheduleCount_ * timeoutFees; + + return totalFees; } } diff --git a/contracts/protocol/watcherPrecompile/WatcherPrecompileStorage.sol b/contracts/protocol/watcherPrecompile/WatcherPrecompileStorage.sol deleted file mode 100644 index 974a0b03..00000000 --- a/contracts/protocol/watcherPrecompile/WatcherPrecompileStorage.sol +++ /dev/null @@ -1,82 +0,0 @@ -// SPDX-License-Identifier: MIT -pragma solidity ^0.8.0; - -import "../../interfaces/IWatcherPrecompile.sol"; -import {IAppGateway} from "../../interfaces/IAppGateway.sol"; -import {IFeesManager} from "../../interfaces/IFeesManager.sol"; -import {IPromise} from "../../interfaces/IPromise.sol"; -import "./DumpDecoder.sol"; - -import {IMiddleware} from "../../interfaces/IMiddleware.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, WriteFinality, UpdateLimitParams, PlugConfig, DigestParams, TimeoutRequest, CallFromChainParams, QueuePayloadParams, PayloadParams, RequestParams} from "../utils/common/Structs.sol"; - -abstract contract WatcherPrecompileStorage is IWatcherPrecompile { - // slots [0-49]: gap for future storage variables - uint256[50] _gap_before; - - // slot 50: evmxSlug - /// @notice The chain slug of the watcher precompile - uint32 public evmxSlug; - - // slot 51: isNonceUsed - /// @notice Maps nonce to whether it has been used - /// @dev signatureNonce => isValid - mapping(uint256 => bool) public isNonceUsed; - - // slot 52: maxTimeoutDelayInSeconds - uint256 public maxTimeoutDelayInSeconds; - - // slot 53: payloadCounter - /// @notice Counter for tracking payload requests - uint40 public payloadCounter; - - // slot 54: timeoutCounter - /// @notice Counter for tracking timeout requests - uint40 public timeoutCounter; - - // slot 55: expiryTime - /// @notice The expiry time for the payload - uint256 public expiryTime; - - // slot 56: timeoutRequests - /// @notice Mapping to store timeout requests - /// @dev timeoutId => TimeoutRequest struct - mapping(bytes32 => TimeoutRequest) public timeoutRequests; - - // slot 57: watcherProofs - /// @notice Mapping to store watcher proofs - /// @dev payloadId => proof bytes - mapping(bytes32 => bytes) public watcherProofs; - - // slot 58: appGatewayCalled - /// @notice Mapping to store if appGateway has been called with trigger from on-chain Inbox - /// @dev callId => bool - mapping(bytes32 => bool) public appGatewayCalled; - - // slot 59: nextRequestCount - uint40 public nextRequestCount; - - // slot 60: nextBatchCount - uint40 public nextBatchCount; - - // slot 61: requestParams - mapping(uint40 => RequestParams) public requestParams; - // slot 62: batchPayloadIds - mapping(uint40 => bytes32[]) public batchPayloadIds; - // slot 63: requestBatchIds - mapping(uint40 => uint40[]) public requestBatchIds; - // slot 64: payloads - mapping(bytes32 => PayloadParams) public payloads; - // slot 65: isPromiseExecuted - mapping(bytes32 => bool) public isPromiseExecuted; - - // slot 66: watcherPrecompileLimits__ - IWatcherPrecompileLimits public watcherPrecompileLimits__; - // slot 67: watcherPrecompileConfig__ - IWatcherPrecompileConfig public watcherPrecompileConfig__; - - // slots [68-117]: gap for future storage variables - uint256[50] _gap_after; -} diff --git a/contracts/protocol/watcherPrecompile/core/RequestHandler.sol b/contracts/protocol/watcherPrecompile/core/RequestHandler.sol new file mode 100644 index 00000000..d6f620eb --- /dev/null +++ b/contracts/protocol/watcherPrecompile/core/RequestHandler.sol @@ -0,0 +1,180 @@ +// SPDX-License-Identifier: GPL-3.0-only +pragma solidity ^0.8.21; + +import "./WatcherPrecompileCore.sol"; + +/// @title RequestHandler +/// @notice Contract that handles request submission and processing +/// @dev This contract extends WatcherPrecompileCore to provide request handling functionality +/// @dev It manages the submission of payload requests and their processing +abstract contract RequestHandler is WatcherPrecompileCore { + using PayloadHeaderDecoder for bytes32; + + // slots [266-315] reserved for gap + uint256[50] _request_handler_gap; + + /// @notice Submits a batch of payload requests from middleware + /// @param payloadSubmitParams_ Array of payload submit parameters + /// @return requestCount The unique identifier for the submitted request + /// @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[] memory payloadSubmitParams_ + ) public returns (uint40 requestCount) { + address appGateway = _checkAppGateways(payloadSubmitParams_); + + requestCount = nextRequestCount++; + uint40 batchCount = nextBatchCount; + uint40 currentBatch = batchCount; + + uint256 readCount; + uint256 writeCount; + PayloadSubmitParams memory lastP; + + for (uint256 i = 0; i < payloadSubmitParams_.length; i++) { + PayloadSubmitParams memory p = payloadSubmitParams_[i]; + + // Count reads and writes for checking limits + if (p.callType == CallType.READ) { + readCount++; + } else writeCount++; + + // checking level number for batching + if (i > 0) { + if (p.levelNumber != lastP.levelNumber && p.levelNumber != lastP.levelNumber + 1) + revert InvalidLevelNumber(); + if (p.levelNumber == lastP.levelNumber + 1) { + requestBatchIds[requestCount].push(batchCount); + batchCount = ++nextBatchCount; + } + } + + uint40 localPayloadCount = payloadCounter++; + bytes32 payloadId = WatcherIdUtils.createPayloadId( + requestCount, + batchCount, + localPayloadCount, + p.switchboard, + p.chainSlug + ); + batchPayloadIds[batchCount].push(payloadId); + + bytes32 payloadHeader = PayloadHeaderDecoder.createPayloadHeader( + requestCount, + batchCount, + localPayloadCount, + p.chainSlug, + p.callType, + p.isParallel, + p.writeFinality + ); + + payloads[payloadId].payloadHeader = payloadHeader; + payloads[payloadId].asyncPromise = p.asyncPromise; + payloads[payloadId].switchboard = p.switchboard; + payloads[payloadId].target = p.target; + payloads[payloadId].appGateway = p.callType == CallType.DEPLOY + ? addressResolver__.deliveryHelper() + : p.appGateway; + payloads[payloadId].payloadId = payloadId; + payloads[payloadId].gasLimit = p.gasLimit; + payloads[payloadId].value = p.value; + payloads[payloadId].readAt = p.readAt; + payloads[payloadId].payload = p.payload; + + requestParams[requestCount].payloadParamsArray.push(payloads[payloadId]); + lastP = p; + } + + // Push the final batch ID to the request's batch list and increment the counter + // 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++); + + requestParams[requestCount].queryCount = readCount; + requestParams[requestCount].finalizeCount = writeCount; + + requestParams[requestCount].currentBatch = currentBatch; + requestParams[requestCount].payloadsRemaining = payloadSubmitParams_.length; + requestParams[requestCount].middleware = msg.sender; + + emit RequestSubmitted( + msg.sender, + requestCount, + requestParams[requestCount].payloadParamsArray + ); + } + + /// @notice Checks if all app gateways in the payload submit parameters are valid and same + /// @dev It also handles special cases for the delivery helper + /// @param payloadSubmitParams Array of payload submit parameters + /// @return appGateway The core app gateway address + function _checkAppGateways( + PayloadSubmitParams[] memory payloadSubmitParams + ) internal view returns (address appGateway) { + bool isDeliveryHelper = msg.sender == addressResolver__.deliveryHelper(); + + // Get first app gateway and use it as reference + address coreAppGateway = isDeliveryHelper + ? _getCoreAppGateway(payloadSubmitParams[0].appGateway) + : _getCoreAppGateway(msg.sender); + + // Skip first element since we already checked it + for (uint256 i = 1; i < payloadSubmitParams.length; i++) { + appGateway = isDeliveryHelper + ? _getCoreAppGateway(payloadSubmitParams[i].appGateway) + : coreAppGateway; + + if (appGateway != coreAppGateway) revert InvalidGateway(); + } + } + + /// @notice Starts processing a request with a specified transmitter + /// @param requestCount The request count to start processing + /// @param transmitter_ The winning bid, contains fees, transmitter and extra data + /// @dev This function initiates the processing of a request by a transmitter + /// @dev It verifies that the caller is the middleware and that the request hasn't been started yet + function startProcessingRequest(uint40 requestCount, address transmitter_) public { + RequestParams storage r = requestParams[requestCount]; + if (r.middleware != msg.sender) revert InvalidCaller(); + if (r.transmitter != address(0)) revert AlreadyStarted(); + if (r.currentBatchPayloadsLeft > 0) revert AlreadyStarted(); + + uint40 batchCount = r.payloadParamsArray[0].payloadHeader.getBatchCount(); + r.transmitter = transmitter_; + r.currentBatch = batchCount; + + _processBatch(requestCount, batchCount); + } + + /// @notice Processes a batch of payloads for a request + /// @param requestCount_ The request count to process + /// @param batchCount_ The batch count to process + /// @dev This function processes all payloads in a batch, either finalizing them or querying them + /// @dev It skips payloads that have already been executed + function _processBatch(uint40 requestCount_, uint40 batchCount_) internal { + RequestParams storage r = requestParams[requestCount_]; + PayloadParams[] memory payloadParamsArray = _getBatch(batchCount_); + if (r.isRequestCancelled) revert RequestCancelled(); + + uint256 totalPayloads = 0; + for (uint40 i = 0; i < payloadParamsArray.length; i++) { + if (isPromiseExecuted[payloadParamsArray[i].payloadId]) continue; + totalPayloads++; + + if (payloadParamsArray[i].payloadHeader.getCallType() != CallType.READ) { + _finalize(payloadParamsArray[i], r.transmitter); + } else { + _query(payloadParamsArray[i]); + } + } + + r.currentBatchPayloadsLeft = totalPayloads; + } + + /// @notice Gets the current request count + /// @return The current request count + /// @dev This function returns the next request count, which is the current request count + function getCurrentRequestCount() external view returns (uint40) { + return nextRequestCount; + } +} diff --git a/contracts/protocol/watcherPrecompile/core/WatcherIdUtils.sol b/contracts/protocol/watcherPrecompile/core/WatcherIdUtils.sol new file mode 100644 index 00000000..1d2ed762 --- /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_, chainSlug_, switchboard_) + ); + } +} diff --git a/contracts/protocol/watcherPrecompile/core/WatcherPrecompile.sol b/contracts/protocol/watcherPrecompile/core/WatcherPrecompile.sol new file mode 100644 index 00000000..878ef213 --- /dev/null +++ b/contracts/protocol/watcherPrecompile/core/WatcherPrecompile.sol @@ -0,0 +1,375 @@ +// SPDX-License-Identifier: GPL-3.0-only +pragma solidity ^0.8.21; + +import "./RequestHandler.sol"; +import {LibCall} from "solady/utils/LibCall.sol"; + +/// @title WatcherPrecompile +/// @notice Contract that handles request submission, iteration and execution +/// @dev This contract extends RequestHandler to provide the main functionality for the WatcherPrecompile system +/// @dev It handles timeout requests, finalization, queries, and promise resolution +contract WatcherPrecompile is RequestHandler { + using PayloadHeaderDecoder for bytes32; + using LibCall for address; + + /// @notice Constructor that disables initializers for the implementation + constructor() { + _disableInitializers(); + } + + /// @notice Initializes the contract with the required parameters + /// @param owner_ The address of the owner + /// @param addressResolver_ The address of the address resolver + /// @param expiryTime_ The expiry time for payload execution + /// @param evmxSlug_ The EVM chain slug + /// @param watcherPrecompileLimits_ The address of the watcher precompile limits contract + /// @param watcherPrecompileConfig_ The address of the watcher precompile config contract + /// @dev This function initializes the contract with the required parameters and sets up the initial state + function initialize( + address owner_, + address addressResolver_, + uint256 expiryTime_, + uint32 evmxSlug_, + address watcherPrecompileLimits_, + address watcherPrecompileConfig_ + ) public reinitializer(1) { + _setAddressResolver(addressResolver_); + _initializeOwner(owner_); + + watcherPrecompileLimits__ = IWatcherPrecompileLimits(watcherPrecompileLimits_); + watcherPrecompileConfig__ = IWatcherPrecompileConfig(watcherPrecompileConfig_); + maxTimeoutDelayInSeconds = 24 * 60 * 60; // 24 hours + expiryTime = expiryTime_; + evmxSlug = evmxSlug_; + + timeoutIdPrefix = (uint256(evmxSlug_) << 224) | (uint256(uint160(address(this))) << 64); + } + + // ================== Timeout functions ================== + + /// @notice Sets a timeout for a payload execution on app gateway + /// @dev This function creates a timeout request that will be executed after the specified `delayInSeconds_` + /// @dev request is executed on msg.sender + /// @dev msg sender needs SCHEDULE precompile limit + /// @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 memory payload_) external returns (bytes32) { + return _setTimeout(delayInSeconds_, payload_); + } + + /// @notice Ends the timeouts and calls the target address with the callback payload + /// @param timeoutId_ The unique identifier for the timeout + /// @param signatureNonce_ The nonce used in the watcher's signature + /// @param signature_ The watcher's signature + /// @dev It verifies if the signature is valid and the timeout hasn't been resolved yet + function resolveTimeout( + bytes32 timeoutId_, + uint256 signatureNonce_, + bytes memory signature_ + ) external { + _isWatcherSignatureValid( + abi.encode(this.resolveTimeout.selector, timeoutId_), + signatureNonce_, + signature_ + ); + + TimeoutRequest storage timeoutRequest_ = timeoutRequests[timeoutId_]; + if (timeoutRequest_.target == address(0)) revert InvalidTimeoutRequest(); + if (timeoutRequest_.isResolved) revert TimeoutAlreadyResolved(); + if (block.timestamp < timeoutRequest_.executeAt) revert ResolvingTimeoutTooEarly(); + + (bool success, , bytes memory returnData) = timeoutRequest_.target.tryCall( + 0, + gasleft(), + 0, // setting max_copy_bytes to 0 as not using returnData right now + timeoutRequest_.payload + ); + if (!success) revert CallFailed(); + + timeoutRequest_.isResolved = true; + timeoutRequest_.executedAt = block.timestamp; + + emit TimeoutResolved( + timeoutId_, + timeoutRequest_.target, + timeoutRequest_.payload, + block.timestamp, + returnData + ); + } + + // ================== Query functions ================== + + /// @notice Creates a new query request + /// @param params_ The payload parameters + /// @dev This function creates a new query request + function query(PayloadParams memory params_) external { + _query(params_); + } + + /// @notice Marks a request as finalized with a proof on digest + /// @param payloadId_ The unique identifier of the request + /// @param proof_ The watcher's proof + /// @param signatureNonce_ The nonce of the signature + /// @param signature_ The signature of the watcher + /// @dev This function marks a request as finalized with a proof + /// @dev It verifies that the signature is valid + /// @dev Watcher signs on following digest for validation on switchboard: + /// @dev keccak256(abi.encode(switchboard, digest)) + function finalized( + bytes32 payloadId_, + bytes memory proof_, + uint256 signatureNonce_, + bytes memory signature_ + ) external { + _isWatcherSignatureValid( + abi.encode(this.finalized.selector, payloadId_, proof_), + signatureNonce_, + signature_ + ); + + watcherProofs[payloadId_] = proof_; + emit Finalized(payloadId_, proof_); + } + + /// @notice Updates the transmitter for a request + /// @param requestCount The request count to update + /// @param transmitter The new transmitter address + /// @dev This function updates the transmitter for a request + /// @dev It verifies that the caller is the middleware and that the request hasn't been started yet + function updateTransmitter(uint40 requestCount, address transmitter) public { + RequestParams storage r = requestParams[requestCount]; + if (r.isRequestCancelled) revert RequestCancelled(); + if (r.payloadsRemaining == 0) revert RequestAlreadyExecuted(); + if (r.middleware != msg.sender) revert InvalidCaller(); + if (r.transmitter != address(0)) revert RequestNotProcessing(); + r.transmitter = transmitter; + + _processBatch(requestCount, r.currentBatch); + } + + /// @notice Cancels a request + /// @param requestCount The request count to cancel + /// @dev This function cancels a request + /// @dev It verifies that the caller is the middleware and that the request hasn't been cancelled yet + function cancelRequest(uint40 requestCount) external { + RequestParams storage r = requestParams[requestCount]; + if (r.isRequestCancelled) revert RequestAlreadyCancelled(); + if (r.middleware != msg.sender) revert InvalidCaller(); + + r.isRequestCancelled = true; + emit RequestCancelledFromGateway(requestCount); + } + + /// @notice Resolves multiple promises with their return data + /// @param resolvedPromises_ Array of resolved promises and their return data + /// @param signatureNonce_ The nonce of the signature + /// @param signature_ The signature of the watcher + /// @dev This function resolves multiple promises with their return data + /// @dev It verifies that the signature is valid + /// @dev It also processes the next batch if the current batch is complete + function resolvePromises( + ResolvedPromises[] memory resolvedPromises_, + uint256 signatureNonce_, + bytes memory signature_ + ) external { + _isWatcherSignatureValid( + abi.encode(this.resolvePromises.selector, resolvedPromises_), + signatureNonce_, + signature_ + ); + + for (uint256 i = 0; i < resolvedPromises_.length; i++) { + uint40 requestCount = payloads[resolvedPromises_[i].payloadId] + .payloadHeader + .getRequestCount(); + RequestParams storage requestParams_ = requestParams[requestCount]; + + _processPromiseResolution(resolvedPromises_[i], requestParams_); + _checkAndProcessBatch(requestParams_, requestCount); + } + } + + /// @notice Marks a request as reverting + /// @param isRevertingOnchain_ Whether the request is reverting onchain + /// @param payloadId_ The unique identifier of the payload + /// @param signatureNonce_ The nonce of the signature + /// @param signature_ The signature of the watcher + /// @dev Only valid watcher can mark a request as reverting + /// @dev This function marks a request as reverting if callback or payload is reverting on chain + /// @dev Request is marked cancelled for both cases. + function markRevert( + bool isRevertingOnchain_, + bytes32 payloadId_, + uint256 signatureNonce_, + bytes memory signature_ + ) external { + _isWatcherSignatureValid( + abi.encode(this.markRevert.selector, isRevertingOnchain_, payloadId_), + signatureNonce_, + signature_ + ); + + PayloadParams memory payloadParams = payloads[payloadId_]; + if (payloadParams.deadline > block.timestamp) revert DeadlineNotPassedForOnChainRevert(); + + RequestParams storage currentRequestParams = requestParams[ + payloadParams.payloadHeader.getRequestCount() + ]; + currentRequestParams.isRequestCancelled = true; + + IMiddleware(currentRequestParams.middleware).handleRequestReverts( + payloadParams.payloadHeader.getRequestCount() + ); + + if (isRevertingOnchain_ && payloadParams.asyncPromise != address(0)) + IPromise(payloadParams.asyncPromise).markOnchainRevert( + payloadParams.payloadHeader.getRequestCount(), + payloadId_ + ); + + emit MarkedRevert(payloadId_, isRevertingOnchain_); + } + + // ================== On-Chain Inbox ================== + + /// @notice Calls app gateways with the specified parameters + /// @param params_ Array of call from chain parameters + /// @param signatureNonce_ The nonce of the signature + /// @param signature_ The signature of the watcher + /// @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[] memory params_, + uint256 signatureNonce_, + bytes memory signature_ + ) external { + _isWatcherSignatureValid( + abi.encode(this.callAppGateways.selector, params_), + signatureNonce_, + signature_ + ); + + for (uint256 i = 0; i < params_.length; i++) { + if (appGatewayCalled[params_[i].triggerId]) revert AppGatewayAlreadyCalled(); + + address appGateway = WatcherIdUtils.decodeAppGatewayId(params_[i].appGatewayId); + if ( + !watcherPrecompileConfig__.isValidPlug( + appGateway, + params_[i].chainSlug, + params_[i].plug + ) + ) revert InvalidCallerTriggered(); + + IFeesManager(addressResolver__.feesManager()).assignWatcherPrecompileCreditsFromAddress( + watcherPrecompileLimits__.callBackFees(), + appGateway + ); + + appGatewayCaller = appGateway; + appGatewayCalled[params_[i].triggerId] = true; + + (bool success, , ) = appGateway.tryCall( + 0, + gasleft(), + 0, // setting max_copy_bytes to 0 as not using returnData right now + params_[i].payload + ); + if (!success) { + emit AppGatewayCallFailed(params_[i].triggerId); + } else { + emit CalledAppGateway(params_[i].triggerId); + } + } + + appGatewayCaller = address(0); + } + + // ================== Helper functions ================== + + /// @notice Sets the maximum timeout delay in seconds + /// @param maxTimeoutDelayInSeconds_ The maximum timeout delay in seconds + /// @dev This function sets the maximum timeout delay in seconds + /// @dev Only callable by the contract owner + function setMaxTimeoutDelayInSeconds(uint256 maxTimeoutDelayInSeconds_) external onlyOwner { + maxTimeoutDelayInSeconds = maxTimeoutDelayInSeconds_; + emit MaxTimeoutDelayInSecondsSet(maxTimeoutDelayInSeconds_); + } + + /// @notice Sets the expiry time for payload execution + /// @param expiryTime_ The expiry time in seconds + /// @dev This function sets the expiry time for payload execution + /// @dev Only callable by the contract owner + function setExpiryTime(uint256 expiryTime_) external onlyOwner { + expiryTime = expiryTime_; + emit ExpiryTimeSet(expiryTime_); + } + + /// @notice Sets the watcher precompile limits contract + /// @param watcherPrecompileLimits_ The address of the watcher precompile limits contract + /// @dev This function sets the watcher precompile limits contract + /// @dev Only callable by the contract owner + function setWatcherPrecompileLimits(address watcherPrecompileLimits_) external onlyOwner { + watcherPrecompileLimits__ = IWatcherPrecompileLimits(watcherPrecompileLimits_); + emit WatcherPrecompileLimitsSet(watcherPrecompileLimits_); + } + + /// @notice Sets the watcher precompile config contract + /// @param watcherPrecompileConfig_ The address of the watcher precompile config contract + /// @dev This function sets the watcher precompile config contract + /// @dev Only callable by the contract owner + function setWatcherPrecompileConfig(address watcherPrecompileConfig_) external onlyOwner { + watcherPrecompileConfig__ = IWatcherPrecompileConfig(watcherPrecompileConfig_); + emit WatcherPrecompileConfigSet(watcherPrecompileConfig_); + } + + /// @notice Gets the request parameters for a request + /// @param requestCount The request count to get the parameters for + /// @return The request parameters for the given request count + 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 new file mode 100644 index 00000000..85fc60f7 --- /dev/null +++ b/contracts/protocol/watcherPrecompile/core/WatcherPrecompileCore.sol @@ -0,0 +1,256 @@ +// SPDX-License-Identifier: GPL-3.0-only +pragma solidity ^0.8.21; + +import "./WatcherPrecompileStorage.sol"; +import {ECDSA} from "solady/utils/ECDSA.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 "./WatcherIdUtils.sol"; + +/// @title WatcherPrecompileCore +/// @notice Core functionality for the WatcherPrecompile system +/// @dev This contract implements the core functionality for payload verification, execution, and app configurations +/// @dev It is inherited by WatcherPrecompile and provides the base implementation for request handling +abstract contract WatcherPrecompileCore is + IWatcherPrecompile, + WatcherPrecompileStorage, + Initializable, + Ownable, + AddressResolverUtil +{ + using PayloadHeaderDecoder for bytes32; + + // slots [216-265] reserved for gap + uint256[50] _core_gap; + + // ================== Timeout functions ================== + + /// @notice Sets a timeout for a payload execution on app gateway + /// @return timeoutId The unique identifier for the timeout request + function _setTimeout( + uint256 delayInSeconds_, + bytes memory payload_ + ) internal returns (bytes32 timeoutId) { + if (delayInSeconds_ > maxTimeoutDelayInSeconds) revert TimeoutDelayTooLarge(); + _consumeCallbackFeesFromAddress(watcherPrecompileLimits__.timeoutFees(), msg.sender); + + uint256 executeAt = block.timestamp + delayInSeconds_; + timeoutId = _encodeTimeoutId(); + + 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); + } + + /// @notice Finalizes a payload request and requests the watcher to release the proofs + /// @param params_ The payload parameters to be finalized + /// @param transmitter_ The address of the transmitter + /// @return digest The digest hash of the finalized payload + /// @dev This function verifies the app gateway configuration and creates a digest for the payload + function _finalize( + PayloadParams memory params_, + address transmitter_ + ) internal returns (bytes32 digest) { + uint32 chainSlug = params_.payloadHeader.getChainSlug(); + + // Verify that the app gateway is properly configured for this chain and target + watcherPrecompileConfig__.verifyConnections( + chainSlug, + params_.target, + params_.appGateway, + params_.switchboard, + requestParams[params_.payloadHeader.getRequestCount()].middleware + ); + + _consumeCallbackFeesFromRequestCount( + watcherPrecompileLimits__.finalizeFees(), + params_.payloadHeader.getRequestCount() + ); + + uint256 deadline = block.timestamp + expiryTime; + payloads[params_.payloadId].deadline = deadline; + payloads[params_.payloadId].finalizedTransmitter = transmitter_; + + bytes32 prevDigestsHash = _getPreviousDigestsHash(params_.payloadHeader.getBatchCount()); + payloads[params_.payloadId].prevDigestsHash = prevDigestsHash; + + // Construct parameters for digest calculation + DigestParams memory digestParams_ = DigestParams( + watcherPrecompileConfig__.sockets(chainSlug), + transmitter_, + params_.payloadId, + deadline, + params_.payloadHeader.getCallType(), + params_.gasLimit, + params_.value, + params_.payload, + params_.target, + WatcherIdUtils.encodeAppGatewayId(params_.appGateway), + prevDigestsHash + ); + + // Calculate digest from payload parameters + digest = getDigest(digestParams_); + emit FinalizeRequested(digest, payloads[params_.payloadId]); + } + + // ================== Query functions ================== + + /// @notice Creates a new query request + /// @param params_ The payload parameters for the query + /// @dev This function sets up a query request and emits a QueryRequested event + function _query(PayloadParams memory params_) internal { + _consumeCallbackFeesFromRequestCount( + watcherPrecompileLimits__.queryFees(), + params_.payloadHeader.getRequestCount() + ); + + payloads[params_.payloadId].prevDigestsHash = _getPreviousDigestsHash( + params_.payloadHeader.getBatchCount() + ); + emit QueryRequested(params_); + } + + // ================== Helper functions ================== + + /// @notice Calculates the digest hash of payload parameters + /// @dev extraData is empty for now, not needed for this EVMx + /// @param params_ The payload parameters to calculate the digest for + /// @return digest The calculated digest hash + /// @dev This function creates a keccak256 hash of the payload parameters + function getDigest(DigestParams memory params_) public pure returns (bytes32 digest) { + digest = keccak256( + abi.encode( + params_.socket, + params_.transmitter, + params_.payloadId, + params_.deadline, + params_.callType, + params_.gasLimit, + params_.value, + params_.payload, + params_.target, + params_.appGatewayId, + params_.prevDigestsHash, + bytes("") + ) + ); + } + + /// @notice Gets the hash of previous batch digests + /// @param batchCount_ The batch count to get the previous digests hash + /// @return The hash of all digests in the previous batch + function _getPreviousDigestsHash(uint40 batchCount_) internal view returns (bytes32) { + bytes32[] memory payloadIds = batchPayloadIds[batchCount_]; + bytes32 prevDigestsHash = bytes32(0); + + for (uint40 i = 0; i < payloadIds.length; i++) { + PayloadParams memory p = payloads[payloadIds[i]]; + DigestParams memory digestParams = DigestParams( + watcherPrecompileConfig__.sockets(p.payloadHeader.getChainSlug()), + p.finalizedTransmitter, + p.payloadId, + p.deadline, + p.payloadHeader.getCallType(), + p.gasLimit, + p.value, + p.payload, + p.target, + WatcherIdUtils.encodeAppGatewayId(p.appGateway), + p.prevDigestsHash + ); + prevDigestsHash = keccak256(abi.encodePacked(prevDigestsHash, getDigest(digestParams))); + } + return prevDigestsHash; + } + + /// @notice Gets the batch of payload parameters for a given batch count + /// @param batchCount The batch count to get the payload parameters for + /// @return An array of PayloadParams for the given batch + /// @dev This function retrieves all payload parameters for a specific batch + function _getBatch(uint40 batchCount) internal view returns (PayloadParams[] memory) { + bytes32[] memory payloadIds = batchPayloadIds[batchCount]; + PayloadParams[] memory payloadParamsArray = new PayloadParams[](payloadIds.length); + + for (uint40 i = 0; i < payloadIds.length; i++) { + payloadParamsArray[i] = payloads[payloadIds[i]]; + } + return payloadParamsArray; + } + + /// @notice Encodes an ID for a timeout or payload + /// @return The encoded ID + /// @dev This function creates a unique ID by combining the chain slug, address, and a counter + 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(timeoutIdPrefix | payloadCounter++); + } + + /// @notice Verifies that a watcher signature is valid + /// @param inputData_ The input data to verify + /// @param signatureNonce_ The nonce of the signature + /// @param signature_ The signature to verify + /// @dev This function verifies that the signature was created by the watcher and that the nonce has not been used before + function _isWatcherSignatureValid( + bytes memory inputData_, + uint256 signatureNonce_, + bytes memory signature_ + ) internal { + if (isNonceUsed[signatureNonce_]) revert NonceUsed(); + isNonceUsed[signatureNonce_] = true; + + bytes32 digest = keccak256( + abi.encode(address(this), evmxSlug, signatureNonce_, inputData_) + ); + digest = keccak256(abi.encodePacked("\x19Ethereum Signed Message:\n32", digest)); + + // recovered signer is checked for the valid roles later + address signer = ECDSA.recover(digest, signature_); + if (signer != owner()) revert InvalidWatcherSignature(); + } + + function _consumeCallbackFeesFromRequestCount(uint256 fees_, uint40 requestCount_) internal { + // for callbacks in all precompiles + uint256 feesToConsume = fees_ + watcherPrecompileLimits__.callBackFees(); + IFeesManager(addressResolver__.feesManager()) + .assignWatcherPrecompileCreditsFromRequestCount(feesToConsume, requestCount_); + } + + function _consumeCallbackFeesFromAddress(uint256 fees_, address consumeFrom_) internal { + // for callbacks in all precompiles + uint256 feesToConsume = fees_ + watcherPrecompileLimits__.callBackFees(); + IFeesManager(addressResolver__.feesManager()).assignWatcherPrecompileCreditsFromAddress( + feesToConsume, + consumeFrom_ + ); + } + + /// @notice Gets the batch IDs for a request + /// @param requestCount_ The request count to get the batch IDs for + /// @return An array of batch IDs for the given request + function getBatches(uint40 requestCount_) external view returns (uint40[] memory) { + return requestBatchIds[requestCount_]; + } + + /// @notice Gets the payload IDs for a batch + /// @param batchCount_ The batch count to get the payload IDs for + /// @return An array of payload IDs for the given batch + function getBatchPayloadIds(uint40 batchCount_) external view returns (bytes32[] memory) { + return batchPayloadIds[batchCount_]; + } + + /// @notice Gets the payload parameters for a payload ID + /// @param payloadId_ The payload ID to get the parameters for + /// @return The payload parameters for the given payload ID + function getPayloadParams(bytes32 payloadId_) external view returns (PayloadParams memory) { + return payloads[payloadId_]; + } +} diff --git a/contracts/protocol/watcherPrecompile/core/WatcherPrecompileStorage.sol b/contracts/protocol/watcherPrecompile/core/WatcherPrecompileStorage.sol new file mode 100644 index 00000000..d90706f9 --- /dev/null +++ b/contracts/protocol/watcherPrecompile/core/WatcherPrecompileStorage.sol @@ -0,0 +1,112 @@ +// SPDX-License-Identifier: GPL-3.0-only +pragma solidity ^0.8.21; + +import "../../../interfaces/IWatcherPrecompile.sol"; +import {IAppGateway} from "../../../interfaces/IAppGateway.sol"; +import {IPromise} from "../../../interfaces/IPromise.sol"; +import "../PayloadHeaderDecoder.sol"; + +import {IMiddleware} from "../../../interfaces/IMiddleware.sol"; +import {QUERY, FINALIZE, SCHEDULE, MAX_COPY_BYTES} from "../../utils/common/Constants.sol"; +import {InvalidCallerTriggered, TimeoutDelayTooLarge, TimeoutAlreadyResolved, InvalidInboxCaller, ResolvingTimeoutTooEarly, CallFailed, AppGatewayAlreadyCalled, InvalidWatcherSignature, NonceUsed, RequestAlreadyExecuted} from "../../utils/common/Errors.sol"; +import {ResolvedPromises, AppGatewayConfig, LimitParams, WriteFinality, UpdateLimitParams, PlugConfig, DigestParams, TimeoutRequest, QueuePayloadParams, PayloadParams, RequestParams} from "../../utils/common/Structs.sol"; + +/// @title WatcherPrecompileStorage +/// @notice Storage contract for the WatcherPrecompile system +/// @dev This contract contains all the storage variables used by the WatcherPrecompile system +/// @dev It is inherited by WatcherPrecompileCore and WatcherPrecompile +abstract contract WatcherPrecompileStorage is IWatcherPrecompile { + // slots [0-49]: gap for future storage variables + uint256[50] _gap_before; + + // slot 50 + /// @notice The chain slug of the watcher precompile + uint32 public evmxSlug; + + /// @notice Counter for tracking payload requests + uint40 public payloadCounter; + + /// @notice Counter for tracking request counts + uint40 public override nextRequestCount; + + /// @notice Counter for tracking batch counts + uint40 public nextBatchCount; + + // slot 51 + /// @notice The time from finalize for the payload to be executed + /// @dev Expiry time in seconds for payload execution + uint256 public expiryTime; + + // slot 52 + /// @notice The maximum delay for a timeout + /// @dev Maximum timeout delay in seconds + uint256 public maxTimeoutDelayInSeconds; + + // slot 53 + /// @notice stores temporary address of the app gateway caller from a chain + 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 + mapping(uint256 => bool) public isNonceUsed; + + // slot 55 + /// @notice Mapping to store timeout requests + /// @dev Maps timeout ID to TimeoutRequest struct + /// @dev timeoutId => TimeoutRequest struct + mapping(bytes32 => TimeoutRequest) public timeoutRequests; + + // slot 56 + /// @notice Mapping to store watcher proofs + /// @dev Maps payload ID to proof bytes + /// @dev payloadId => proof bytes + mapping(bytes32 => bytes) public watcherProofs; + + // slot 57 + /// @notice Mapping to store if appGateway has been called with trigger from on-chain Inbox + /// @dev Maps call ID to boolean indicating if the appGateway has been called + /// @dev callId => bool + mapping(bytes32 => bool) public appGatewayCalled; + + // slot 58 + /// @notice Mapping to store the request parameters for each request count + mapping(uint40 => RequestParams) public requestParams; + + // slot 59 + /// @notice Mapping to store the list of payload IDs for each batch + mapping(uint40 => bytes32[]) public batchPayloadIds; + + // slot 60 + /// @notice Mapping to store the batch IDs for each request + mapping(uint40 => uint40[]) public requestBatchIds; + + // slot 61 + /// @notice Mapping to store the payload parameters for each payload ID + mapping(bytes32 => PayloadParams) public payloads; + + // slot 62 + /// @notice Mapping to store if a promise has been executed + mapping(bytes32 => bool) public isPromiseExecuted; + + // slot 63 + IWatcherPrecompileLimits public watcherPrecompileLimits__; + + // slot 64 + IWatcherPrecompileConfig public watcherPrecompileConfig__; + + // slot 65 + /// @notice Mapping to store the request metadata for each request count + mapping(uint40 => RequestMetadata) public requestMetadata; + + // slots [67-114]: gap for future storage variables + uint256[48] _gap_after; + + // slots 115-165 (51) reserved for access control + // slots 166-216 (51) reserved for addr resolver util +} diff --git a/deployments/dev_addresses.json b/deployments/dev_addresses.json index a5ecf711..72af1b71 100644 --- a/deployments/dev_addresses.json +++ b/deployments/dev_addresses.json @@ -1,52 +1,38 @@ { - "84532": { - "ContractFactoryPlug": "0xFEFdbD9CB5bdC58Aa8B6B455923454275392838c", - "FastSwitchboard": "0xCc9f995DAE2D3Cf3C0763e353986E97ab78801b4", - "FeesPlug": "0xBBff19fBEAd971f39F1B298De985D7ADfb57b784", - "Socket": "0x89e66357C5F101C56b4F9F97cf378Cc32A21438a", - "SocketBatcher": "0x3bABE35428D9E2B87d678289f837F805dAB0db3A", - "startBlock": 23827713 - }, "421614": { - "ContractFactoryPlug": "0x10BFD064258ac890957Ca65A85E7997a923Fb551", - "FastSwitchboard": "0xB0feE9136102B99A5722C693C8eb538C8c8d013B", - "FeesPlug": "0x0Ae8458F80a50a4ad706680605c88e050AaC8A89", - "Socket": "0xA99E6020d973C5956dBD727eDB45cDA5F7C4ca99", - "SocketBatcher": "0x3F95273234164329aec7DE66A037a80eb5a8F393", - "startBlock": 136387501 + "ContractFactoryPlug": "0x9dEa956E863a44a24c1CE068460E6190EA438602", + "FastSwitchboard": "0xb4C5A231614639801F421Ca386388f65576F3c81", + "FeesPlug": "0x2fc70A464588b3f692D5C500Bfe3A2F2165911aD", + "Socket": "0xBd0436A6E0dee11e9359767142Ed6bD7B48ba258", + "SocketBatcher": "0x1683c3AB4954852f71f400AbDeF98112a066ee44", + "startBlock": 148461456, + "TestUSDC": "0x3f134B047620590a0b7a2f98d5F9B07D32d4D927" }, "7625382": { - "AddressResolver": "0x491DE197D50aaa4F59090DBDa4b5Aa6C2E14A10D", - "AddressResolverImpl": "0x2736Fc0AA0fa5Ca595Ae5F27Ec807B912A5b1D0f", - "AuctionManager": "0x7187372e1fF5CB8E8B84Ee02F25033fc42B3D033", - "AuctionManagerImpl": "0xaE4624b006D7730f22De1F7df5b1C0b960262AE3", - "DeliveryHelper": "0x8b4a6cee33f18D757d7F3613476b135E3B889303", - "DeliveryHelperImpl": "0xE693bEc40e39223749AC351156E713b7256541B0", - "ERC1967Factory": "0x122beAFCfc2E99D825322a78EAFD8a11fa2d9E0b", - "FeesManager": "0x7c0922C2AfaBF9098F55710E8D8E3c8F81bCB8ee", - "FeesManagerImpl": "0x2c50B6e519e0705A396E5c8652E5D447F37f9796", - "startBlock": 2491495, - "WatcherPrecompile": "0x6E4c1afEcc1AbFF69dF3bC6bFf81eF7A34a343d5", - "WatcherPrecompileConfig": "0x95f86089D4015e6874031B3583dE8795C3b217Fd", - "WatcherPrecompileConfigImpl": "0xE4a2eBcE3Bdcaa1861c01ddc6465b90311B749e4", - "WatcherPrecompileImpl": "0x233539d6BBf231660652AF00B5d6E850E892946a", - "WatcherPrecompileLimits": "0x34212a2Cf92D2863F35877Ed835B7e28b8287764", - "WatcherPrecompileLimitsImpl": "0xdfeA8cb793b84b3f046d2426259e0eC237Ec9bF3" - }, - "11155111": { - "ContractFactoryPlug": "0xdd7b56968a3505D1A10181F2880cAA65a89E4750", - "FastSwitchboard": "0xb34DB19C7FAeECf14B3D0336C6E34f7dc1336968", - "FeesPlug": "0xfBa932AaE9Ae2777aEC77832FC0C2D9059C8E905", - "Socket": "0xF86B89B5c689c170BfD2734254228D6d2db5a672", - "SocketBatcher": "0x466b51bcc1799A4cb8F629C46640ab3aA2608F75", - "startBlock": 23827730 + "AddressResolver": "0x9EAa5b231c1DF8c5660dDC5fE7cD06a9D86e26f0", + "AddressResolverImpl": "0x4b067469E7865d9959E0E658BA77ec4b0F28FB15", + "AuctionManager": "0x2b1F13479D9D2E53E1487169b9C9073958710170", + "AuctionManagerImpl": "0x23364acdd298EBB3Dfd3c1835C5ACd7f77E3E2bD", + "DeliveryHelper": "0xe557Ba90Dd94Aa0Dd49DD6467f1e7d196C6Bd179", + "DeliveryHelperImpl": "0x915eA695e03265998cd455B4df2A1EAeb1b61e74", + "ERC1967Factory": "0xb30eBbA571721923175751AE7aF5aC90cfb1E892", + "FeesManager": "0xBf9529b5aA4a6a047Ff65CfAE9613A274C479143", + "FeesManagerImpl": "0xB577c29F7Cbb1bBB314dD8E74059Aa5BF72838b0", + "startBlock": 5537530, + "WatcherPrecompile": "0xB0CC4C4a6706E265306daCa279Ce60D1052b2782", + "WatcherPrecompileConfig": "0xD0F77272a5F0208f20c836bB4eeddbCE1e4aef9d", + "WatcherPrecompileConfigImpl": "0x060b4a50EcCC9Cf329005c94406dd2886676F759", + "WatcherPrecompileImpl": "0xe1CA4da421C52161B4EecCE6F5Cb2937554e2958", + "WatcherPrecompileLimits": "0x4A645F050166FaBdA7ce44BE90B0A61073C19696", + "WatcherPrecompileLimitsImpl": "0x4Fd04B0D4903e630F169BB228be52750E6B5331a" }, "11155420": { - "ContractFactoryPlug": "0xDd73B7C6Bc8Cb4FFB3F0b460FF0985A91622f7E1", - "FastSwitchboard": "0x0CdaCA0B9F9bFfF1D9B660173BB8BE46c4fB71Da", - "FeesPlug": "0x95D672aaF036887aABEe72a6496D583317e6e48B", - "Socket": "0xDad99e0D8bA0ffe4955523ea0BCbe8F3DE3446D1", - "SocketBatcher": "0xb8D652C7D1dfA7578c6F119712c7b4c812BB3b57", - "startBlock": 25810565 + "ContractFactoryPlug": "0x95Be4D8500e3e5C970802c64b0755027d4Fc5C9F", + "FastSwitchboard": "0xe7858f1dc202f5E9C9B3ee6db052F45164a88534", + "FeesPlug": "0x29b77ecEf2D163528d1F4A235c0795daDD2DA1Bf", + "Socket": "0xE09CC429e77EE5DBeF68f3796b2A33BBDF39C03C", + "SocketBatcher": "0x9175d90706a2b17f0aE025ce5A6C76e64850c2f5", + "startBlock": 27158986, + "TestUSDC": "0x3D7515519beab77B541497626CFB7E764F6887CD" } } diff --git a/deployments/dev_verification.json b/deployments/dev_verification.json index cc9bb657..cb1975f3 100644 --- a/deployments/dev_verification.json +++ b/deployments/dev_verification.json @@ -44,8 +44,81 @@ [84532, "0x3339Cf48f1F9cf31b6F8c2664d144c7444eBBB18", "EVMX"] ] ], - "421614": [], + "421614": [ + [ + "0x092194e4Cd90d950ED91bD216472A18cCA7cd8F7", + "FeesPlug", + "contracts/protocol/payload-delivery/FeesPlug.sol", + [ + "0xBd0436A6E0dee11e9359767142Ed6bD7B48ba258", + "0x3339Cf48f1F9cf31b6F8c2664d144c7444eBBB18" + ] + ], + [ + "0x44f182553Ccdd82f95b592a935Bd7Dd90FF7F292", + "FeesPlug", + "contracts/protocol/payload-delivery/FeesPlug.sol", + [ + "0xBd0436A6E0dee11e9359767142Ed6bD7B48ba258", + "0x3339Cf48f1F9cf31b6F8c2664d144c7444eBBB18" + ] + ] + ], "7625382": [ + [ + "0xe1CA4da421C52161B4EecCE6F5Cb2937554e2958", + "WatcherPrecompile", + "contracts/protocol/watcherPrecompile/core/WatcherPrecompile.sol", + [] + ], + [ + "0x23364acdd298EBB3Dfd3c1835C5ACd7f77E3E2bD", + "AuctionManager", + "contracts/protocol/payload-delivery/AuctionManager.sol", + [] + ], + [ + "0x915eA695e03265998cd455B4df2A1EAeb1b61e74", + "DeliveryHelper", + "contracts/protocol/payload-delivery/app-gateway/DeliveryHelper.sol", + [] + ], + [ + "0xB577c29F7Cbb1bBB314dD8E74059Aa5BF72838b0", + "FeesManager", + "contracts/protocol/payload-delivery/FeesManager.sol", + [] + ], + [ + "0x1BCe40d84499Db8E7Bc65277A32f0abd56588CC7", + "WatcherPrecompile", + "contracts/protocol/watcherPrecompile/core/WatcherPrecompile.sol", + [] + ], + [ + "0x060b4a50EcCC9Cf329005c94406dd2886676F759", + "WatcherPrecompileConfig", + "contracts/protocol/watcherPrecompile/WatcherPrecompileConfig.sol", + [] + ], + [ + "0x4Fd04B0D4903e630F169BB228be52750E6B5331a", + "WatcherPrecompileLimits", + "contracts/protocol/watcherPrecompile/WatcherPrecompileLimits.sol", + [] + ], + [ + "0x4b067469E7865d9959E0E658BA77ec4b0F28FB15", + "AddressResolver", + "contracts/protocol/AddressResolver.sol", + [] + ], + [ + "0xb30eBbA571721923175751AE7aF5aC90cfb1E892", + "ERC1967Factory", + "lib/solady/src/utils/ERC1967Factory.sol", + [] + ], [ "0x2c50B6e519e0705A396E5c8652E5D447F37f9796", "FeesManager", @@ -158,5 +231,24 @@ [11155111, "0x3339Cf48f1F9cf31b6F8c2664d144c7444eBBB18", "EVMX"] ] ], - "11155420": [] + "11155420": [ + [ + "0x78775DCd56fBdb903e4d83AE51924797a74AD49d", + "FeesPlug", + "contracts/protocol/payload-delivery/FeesPlug.sol", + [ + "0xE09CC429e77EE5DBeF68f3796b2A33BBDF39C03C", + "0x3339Cf48f1F9cf31b6F8c2664d144c7444eBBB18" + ] + ], + [ + "0x8eD0cebe57236Bf85b0a74951CeE74357B1d381D", + "FeesPlug", + "contracts/protocol/payload-delivery/FeesPlug.sol", + [ + "0xE09CC429e77EE5DBeF68f3796b2A33BBDF39C03C", + "0x3339Cf48f1F9cf31b6F8c2664d144c7444eBBB18" + ] + ] + ] } diff --git a/deployments/stage_addresses.json b/deployments/stage_addresses.json index 61a298c8..d950e823 100644 --- a/deployments/stage_addresses.json +++ b/deployments/stage_addresses.json @@ -1,52 +1,54 @@ { "43": { - "AddressResolver": "0x4846430BB142385e581C894AE92a4CF0722aEC21", - "AddressResolverImpl": "0x0F13F50f7dED1da9A4845366C2AB5120a1A17549", - "AuctionManager": "0x222574Dab9bb404Cb49a9445CD4d9555e8B52Cf5", - "AuctionManagerImpl": "0xbe1b573aa1B6ddD1A7a27aE0Aa6A38FA5d26fc67", - "DeliveryHelper": "0x436Ea32C3198500d113B006dBdc6fF1Bebd10769", - "DeliveryHelperImpl": "0x40f7fdE05bFa9F7c9B55a582B0783352856BCd03", - "ERC1967Factory": "0xF362fdCAbbd1f58AAd998d4c7Aef4020365092C8", - "FeesManager": "0x9745623Aaa299500F93d2B1B4Efb7b3EC5e60FFc", - "FeesManagerImpl": "0x71956F006Ec5434581D3Fd5E7224BB3bae231907", - "startBlock": 2368668, - "WatcherPrecompile": "0xEbdA834fAc9ca4B86AdD442083c1650f8497EdCb", - "WatcherPrecompileConfig": "0x4D38091442c78B4cb2bB22AFF61552bc72d4BF8e", - "WatcherPrecompileConfigImpl": "0xDf9d7b339Db52Fc58f2c72ffAd3a87201FB16b30", - "WatcherPrecompileImpl": "0xbAeF84edEae864Ff22Bd9c9912AdfF84aD490d82", - "WatcherPrecompileLimits": "0x0CF9B01E7d1ef769D160F53289244f74197B4149", - "WatcherPrecompileLimitsImpl": "0x0bA474851A0703eC69964FB8264304AF357cd16D" + "AddressResolver": "0x21a9AFDfbEb0399D4a12f3AA1324042Be2B57F8e", + "AddressResolverImpl": "0x794b92C2Ade7D33Fb34d138B13014C63aB27CBC0", + "AuctionManager": "0x87E15a6f9Cbe482f67683Ec3f7294f12d221C8bA", + "AuctionManagerImpl": "0xfddb38811a0774E66ABD5F3Ae960bFB7E7415029", + "DeliveryHelper": "0xb399b60C22A32512a24F01C4401f43BfF979A49F", + "DeliveryHelperImpl": "0xa07e38cAB46eAA358C3653C63219f1009e8F7789", + "ERC1967Factory": "0x98ea7A5601f203DE56d86BDCA69fC3019377D6B1", + "FeesManager": "0x30e07016eB24570629Bc8765CA307394Af90B27C", + "FeesManagerImpl": "0x9F10A0c71178dbD4d049f2C04fD0e34966134b9e", + "startBlock": 5480301, + "WatcherPrecompile": "0x426509517074E0fBf15F8aAB2472711FB456C58C", + "WatcherPrecompileConfig": "0x49094ECAF26d8295BcBD73b0Ff17215348E7b253", + "WatcherPrecompileConfigImpl": "0xd69E17Ce715f49Cd2B16C64cf75201A56Ce0E90d", + "WatcherPrecompileImpl": "0xB423eE3bffc3604F96B59cF419C48AE05b8E9d0b", + "WatcherPrecompileLimits": "0x54B315eC6c7059b19164BC0e5335643d5bBAad4f", + "WatcherPrecompileLimitsImpl": "0x0e26C8CFCABC04c642696A625664553e2C183bbe" }, "84532": { - "ContractFactoryPlug": "0x5AF9cA0Ce2Bc991FcE955f8c993fb0A5464B289F", - "FastSwitchboard": "0x0594497C89ECF66bC67204EE89770C4e799De3f9", - "FeesPlug": "0x0EBC6E395503eF135b7a45FfC7d42C2A2bc56D54", - "Socket": "0x92562Ae6526aB8B4fFF9Fa8ECAb6db67f0753693", - "SocketBatcher": "0xE4036898F51842E7DdFD36Dc8eAa7D4B207c5DEe", - "startBlock": 23664081 + "ContractFactoryPlug": "0x8e51D99Bf353Ebba50080D0452546fd2EBAd86A4", + "FastSwitchboard": "0x4C114115755c39dB81a06fBfEb4b08302Abe7beE", + "FeesPlug": "0x9161a99deD597fe519E03D319053CA1669118dDA", + "Socket": "0x36Ae239a92faee6aFF4df9749d592dA7c00717Be", + "SocketBatcher": "0x8fa361816874a11a66D02EC84b28E1A931B4035e", + "startBlock": 25218634, + "TestUSDC": "0xfD51918C0572512901fFA79F822c99A475d22BB4" }, "421614": { - "ContractFactoryPlug": "0x5F710Ac554DD22819F9411fa554265EEf827247d", - "FastSwitchboard": "0xBD0158415Eb99B5e1dBA1e4E534f916ba82380f9", - "FeesPlug": "0x9E263f6c7C199d9c147E30764A8cae1175184CB8", - "Socket": "0x22c1275677E600e2b049B69D929E2ccAAf4B880E", - "SocketBatcher": "0x92f85fe2CbB2D9ab577E4D167E629095497325b9", - "startBlock": 136470235 + "ContractFactoryPlug": "0x65C066BE05CB4622393fADc1Bf3dE8eEdEcB3817", + "FastSwitchboard": "0xF121f4B97F7C902eeD4b188B08052Da9A1FD5aBe", + "FeesPlug": "0xDfE94B9b14de382Ed13C8A7F387884808D0f7E0b", + "Socket": "0xDAB25fB82cc1b1611Fb9016FB50222dBFcD1BCf5", + "SocketBatcher": "0x4e7163Ce9F7F335138fB32827d6f99f174060897", + "startBlock": 148801970, + "TestUSDC": "0xa03Cbf13f331aF7c0fD7F2E28E6Cbc13F879E3F3" }, "11155111": { - "ContractFactoryPlug": "0xd36C1Dcb65CB09b7fCFABf153D7cdd42312C782E", - "FastSwitchboard": "0x36AC527afA283c95EA7dD11c8E93225d9F139028", - "FeesPlug": "0xc4008CCB59413cC2745d33549e5BE16A2d1DD061", - "Socket": "0x899AE7770eFb9714aF717d03c0d577e41d78ed48", - "SocketBatcher": "0xE90649F3BA488D91c7e8E3025F639F435Fa85243", - "startBlock": 23664095 + "FastSwitchboard": "0x1eFD3AF2317B9E6E7492718878f69De747C9e7c3", + "FeesPlug": "0xfE555AD869ac24305471F0755976c556425E8D23", + "Socket": "0xae59BA0Bd0D92232B3B6304185448C9Fe5445f4d", + "SocketBatcher": "0xdaE4538FbbEf41B2Feb5c79DD2fFC9720AF13d7b", + "TestUSDC": "0xbcaDE56f86a819994d0F66b98e921C484bE6FE4e" }, "11155420": { - "ContractFactoryPlug": "0x6320Ff773a4E01Cb8EB849EA906F17Cf6c48Ff9c", - "FastSwitchboard": "0xd94741a4654953817faEe228739a6d10C0683839", - "FeesPlug": "0x89634ecFea933aFaD5d3D6557b13cb8D466313d2", - "Socket": "0x2420B85D7e126d1948a4602f0c78a685655292Bd", - "SocketBatcher": "0xBD6770182fB47DD77924aDf3F200246Ab851f9c2", - "startBlock": 25646940 + "ContractFactoryPlug": "0x469B536c5Df15948c8759FEEE5DB1c17790d4152", + "FastSwitchboard": "0xd8bCd4b4Bc4b0f5cb279B6FAdCEd733614f34F51", + "FeesPlug": "0x6734a30B8f2d210faefa5aeD4E11b674C59641F1", + "Socket": "0x11fbd3a7031b28607973fc44d4d24B26DEfac886", + "SocketBatcher": "0x2c2060f5586751676fC2Af96cc8bE9BF0c7A8770", + "startBlock": 27201458, + "TestUSDC": "0xa0E1738a9Fc0698789866e09d7A335d30128C5C5" } } diff --git a/deployments/stage_verification.json b/deployments/stage_verification.json index 61c8987d..88f93e4b 100644 --- a/deployments/stage_verification.json +++ b/deployments/stage_verification.json @@ -1,162 +1,56 @@ { "43": [ [ - "0x71956F006Ec5434581D3Fd5E7224BB3bae231907", - "FeesManager", - "contracts/protocol/payload-delivery/FeesManager.sol", - [] - ], - [ - "0xbAeF84edEae864Ff22Bd9c9912AdfF84aD490d82", - "WatcherPrecompile", - "contracts/protocol/watcherPrecompile/WatcherPrecompile.sol", - [] - ], - [ - "0x02Bd15aa48BAE9A92E23a3F30Be0c1bD253970Cf", - "WatcherPrecompile", - "contracts/protocol/watcherPrecompile/WatcherPrecompile.sol", - [] - ], - [ - "0xbe1b573aa1B6ddD1A7a27aE0Aa6A38FA5d26fc67", + "0xfddb38811a0774E66ABD5F3Ae960bFB7E7415029", "AuctionManager", "contracts/protocol/payload-delivery/AuctionManager.sol", [] ], [ - "0x40f7fdE05bFa9F7c9B55a582B0783352856BCd03", + "0xa07e38cAB46eAA358C3653C63219f1009e8F7789", "DeliveryHelper", "contracts/protocol/payload-delivery/app-gateway/DeliveryHelper.sol", [] ], [ - "0xd151bD217704F72f717C2111207e6Bb33B609f61", + "0x9F10A0c71178dbD4d049f2C04fD0e34966134b9e", "FeesManager", "contracts/protocol/payload-delivery/FeesManager.sol", [] ], [ - "0x2916aC09Be088427E1a25968DA332F1F1eFa62d2", + "0xB423eE3bffc3604F96B59cF419C48AE05b8E9d0b", "WatcherPrecompile", - "contracts/protocol/watcherPrecompile/WatcherPrecompile.sol", + "contracts/protocol/watcherPrecompile/core/WatcherPrecompile.sol", [] ], [ - "0xDf9d7b339Db52Fc58f2c72ffAd3a87201FB16b30", + "0xd69E17Ce715f49Cd2B16C64cf75201A56Ce0E90d", "WatcherPrecompileConfig", "contracts/protocol/watcherPrecompile/WatcherPrecompileConfig.sol", [] ], [ - "0x0bA474851A0703eC69964FB8264304AF357cd16D", + "0x0e26C8CFCABC04c642696A625664553e2C183bbe", "WatcherPrecompileLimits", "contracts/protocol/watcherPrecompile/WatcherPrecompileLimits.sol", [] ], [ - "0x0F13F50f7dED1da9A4845366C2AB5120a1A17549", + "0x794b92C2Ade7D33Fb34d138B13014C63aB27CBC0", "AddressResolver", "contracts/protocol/AddressResolver.sol", [] ], [ - "0xF362fdCAbbd1f58AAd998d4c7Aef4020365092C8", + "0x98ea7A5601f203DE56d86BDCA69fC3019377D6B1", "ERC1967Factory", "lib/solady/src/utils/ERC1967Factory.sol", [] ] ], - "84532": [ - [ - "0x5AF9cA0Ce2Bc991FcE955f8c993fb0A5464B289F", - "ContractFactoryPlug", - "contracts/protocol/payload-delivery/ContractFactoryPlug.sol", - [ - "0x92562Ae6526aB8B4fFF9Fa8ECAb6db67f0753693", - "0x3339Cf48f1F9cf31b6F8c2664d144c7444eBBB18" - ] - ], - [ - "0x0EBC6E395503eF135b7a45FfC7d42C2A2bc56D54", - "FeesPlug", - "contracts/protocol/payload-delivery/FeesPlug.sol", - [ - "0x92562Ae6526aB8B4fFF9Fa8ECAb6db67f0753693", - "0x3339Cf48f1F9cf31b6F8c2664d144c7444eBBB18" - ] - ], - [ - "0x0594497C89ECF66bC67204EE89770C4e799De3f9", - "FastSwitchboard", - "contracts/protocol/socket/switchboard/FastSwitchboard.sol", - [ - 84532, - "0x92562Ae6526aB8B4fFF9Fa8ECAb6db67f0753693", - "0x3339Cf48f1F9cf31b6F8c2664d144c7444eBBB18" - ] - ], - [ - "0xE4036898F51842E7DdFD36Dc8eAa7D4B207c5DEe", - "SocketBatcher", - "contracts/protocol/socket/SocketBatcher.sol", - [ - "0x3339Cf48f1F9cf31b6F8c2664d144c7444eBBB18", - "0x92562Ae6526aB8B4fFF9Fa8ECAb6db67f0753693" - ] - ], - [ - "0x92562Ae6526aB8B4fFF9Fa8ECAb6db67f0753693", - "Socket", - "contracts/protocol/socket/Socket.sol", - [84532, "0x3339Cf48f1F9cf31b6F8c2664d144c7444eBBB18", "EVMX"] - ] - ], + "84532": [], "421614": [], - "11155111": [ - [ - "0xd36C1Dcb65CB09b7fCFABf153D7cdd42312C782E", - "ContractFactoryPlug", - "contracts/protocol/payload-delivery/ContractFactoryPlug.sol", - [ - "0x899AE7770eFb9714aF717d03c0d577e41d78ed48", - "0x3339Cf48f1F9cf31b6F8c2664d144c7444eBBB18" - ] - ], - [ - "0xc4008CCB59413cC2745d33549e5BE16A2d1DD061", - "FeesPlug", - "contracts/protocol/payload-delivery/FeesPlug.sol", - [ - "0x899AE7770eFb9714aF717d03c0d577e41d78ed48", - "0x3339Cf48f1F9cf31b6F8c2664d144c7444eBBB18" - ] - ], - [ - "0x36AC527afA283c95EA7dD11c8E93225d9F139028", - "FastSwitchboard", - "contracts/protocol/socket/switchboard/FastSwitchboard.sol", - [ - 11155111, - "0x899AE7770eFb9714aF717d03c0d577e41d78ed48", - "0x3339Cf48f1F9cf31b6F8c2664d144c7444eBBB18" - ] - ], - [ - "0xE90649F3BA488D91c7e8E3025F639F435Fa85243", - "SocketBatcher", - "contracts/protocol/socket/SocketBatcher.sol", - [ - "0x3339Cf48f1F9cf31b6F8c2664d144c7444eBBB18", - "0x899AE7770eFb9714aF717d03c0d577e41d78ed48" - ] - ], - [ - "0x899AE7770eFb9714aF717d03c0d577e41d78ed48", - "Socket", - "contracts/protocol/socket/Socket.sol", - [11155111, "0x3339Cf48f1F9cf31b6F8c2664d144c7444eBBB18", "EVMX"] - ] - ], + "11155111": [], "11155420": [] } diff --git a/hardhat-scripts/config/config.ts b/hardhat-scripts/config/config.ts index d7236e79..e0964075 100644 --- a/hardhat-scripts/config/config.ts +++ b/hardhat-scripts/config/config.ts @@ -1,6 +1,6 @@ import { config as dotenvConfig } from "dotenv"; dotenvConfig(); -import { ethers } from "ethers"; +import { ethers, utils } from "ethers"; import { ChainSlug, DeploymentMode } from "../../src"; export const mode = process.env.DEPLOYMENT_MODE as @@ -23,25 +23,65 @@ export const logConfig = () => { ); }; -export const chains: Array = [ - ChainSlug.ARBITRUM_SEPOLIA, - ChainSlug.OPTIMISM_SEPOLIA, - ChainSlug.BASE_SEPOLIA, - ChainSlug.SEPOLIA, -]; +export const getChains = () => { + switch (mode) { + case DeploymentMode.LOCAL: + return [ChainSlug.ARBITRUM_SEPOLIA, ChainSlug.OPTIMISM_SEPOLIA]; + case DeploymentMode.DEV: + return [ChainSlug.ARBITRUM_SEPOLIA, ChainSlug.OPTIMISM_SEPOLIA]; + case DeploymentMode.STAGE: + return [ + ChainSlug.OPTIMISM_SEPOLIA, + ChainSlug.ARBITRUM_SEPOLIA, + ChainSlug.BASE_SEPOLIA, + ]; + case DeploymentMode.PROD: + return [ + ChainSlug.OPTIMISM_SEPOLIA, + ChainSlug.ARBITRUM_SEPOLIA, + ChainSlug.BASE_SEPOLIA, + ChainSlug.SEPOLIA, + ]; + default: + throw new Error(`Invalid deployment mode: ${mode}`); + } +}; + +export const chains: Array = getChains(); export const EVM_CHAIN_ID_MAP: Record = { [DeploymentMode.LOCAL]: 7625382, [DeploymentMode.DEV]: 7625382, [DeploymentMode.STAGE]: 43, [DeploymentMode.PROD]: 3605, }; -export const auctionEndDelaySeconds = 0; +// Addresses export const watcher = "0xb62505feacC486e809392c65614Ce4d7b051923b"; export const transmitter = "0x138e9840861C983DC0BB9b3e941FB7C0e9Ade320"; -export const MAX_FEES = ethers.utils.parseEther("0.001"); + +// Chain config export const EVMX_CHAIN_ID = EVM_CHAIN_ID_MAP[mode]; +export const MAX_FEES = ethers.utils.parseEther("0.001"); + +// Auction parameters +export const auctionEndDelaySeconds = 0; +export const BID_TIMEOUT = 600; // 10 minutes +export const EXPIRY_TIME = 300; // 5 minutes +export const MAX_RE_AUCTION_COUNT = 5; +export const AUCTION_MANAGER_FUNDING_AMOUNT = ethers.utils.parseEther("100"); +// TestUSDC +export const TEST_USDC_NAME = "testUSDC"; +export const TEST_USDC_SYMBOL = "testUSDC"; +export const TEST_USDC_INITIAL_SUPPLY = ethers.utils.parseEther( + "1000000000000000000000000" +); +export const TEST_USDC_DECIMALS = 6; + +// Watcher Precompile Fees +export const QUERY_FEES = utils.parseEther("0.000001"); +export const FINALIZE_FEES = utils.parseEther("0.000001"); +export const TIMEOUT_FEES = utils.parseEther("0.000001"); +export const CALLBACK_FEES = utils.parseEther("0.000001"); + +// Other constants export const DEFAULT_MAX_LIMIT = 100; -export const BID_TIMEOUT = 600; -export const EXPIRY_TIME = 300; export const UPGRADE_VERSION = 1; -export const MAX_RE_AUCTION_COUNT = 5; diff --git a/hardhat-scripts/constants/enums.ts b/hardhat-scripts/constants/enums.ts index d7707602..011570b2 100644 --- a/hardhat-scripts/constants/enums.ts +++ b/hardhat-scripts/constants/enums.ts @@ -4,6 +4,8 @@ export enum CORE_CONTRACTS { FastSwitchboard = "FastSwitchboard", FeesPlug = "FeesPlug", ContractFactoryPlug = "ContractFactoryPlug", + TestUSDC = "TestUSDC", + SocketFeeManager = "SocketFeeManager", } export enum EVMxCoreContracts { diff --git a/hardhat-scripts/constants/roles.ts b/hardhat-scripts/constants/roles.ts index 99ff3d91..ec875446 100644 --- a/hardhat-scripts/constants/roles.ts +++ b/hardhat-scripts/constants/roles.ts @@ -4,4 +4,5 @@ export enum ROLES { GOVERNANCE_ROLE = "GOVERNANCE_ROLE", WATCHER_ROLE = "WATCHER_ROLE", TRANSMITTER_ROLE = "TRANSMITTER_ROLE", + SWITCHBOARD_DISABLER_ROLE = "SWITCHBOARD_DISABLER_ROLE", } diff --git a/hardhat-scripts/deploy/1.deploy.ts b/hardhat-scripts/deploy/1.deploy.ts index bc3c6715..fdb51e5a 100644 --- a/hardhat-scripts/deploy/1.deploy.ts +++ b/hardhat-scripts/deploy/1.deploy.ts @@ -1,11 +1,12 @@ import { ChainAddressesObj, ChainSlug } from "../../src"; import { config } from "dotenv"; -import { Contract, Signer, utils, Wallet } from "ethers"; +import { BigNumber, Contract, Signer, utils, Wallet } from "ethers"; import { formatEther } from "ethers/lib/utils"; import { ethers } from "hardhat"; import { CORE_CONTRACTS, DeploymentAddresses, + ETH_ADDRESS, EVMxCoreContracts, FAST_SWITCHBOARD_TYPE, IMPLEMENTATION_SLOT, @@ -13,6 +14,7 @@ import { import { DeployParams, getAddresses, + getInstance, getOrDeploy, storeAddresses, } from "../utils"; @@ -27,6 +29,15 @@ import { DEFAULT_MAX_LIMIT, MAX_RE_AUCTION_COUNT, mode, + TEST_USDC_INITIAL_SUPPLY, + TEST_USDC_DECIMALS, + TEST_USDC_NAME, + TEST_USDC_SYMBOL, + QUERY_FEES, + FINALIZE_FEES, + TIMEOUT_FEES, + CALLBACK_FEES, + AUCTION_MANAGER_FUNDING_AMOUNT, } from "../config/config"; config(); @@ -131,7 +142,7 @@ const deployEVMxContracts = async () => { deployUtils = await deployContractWithProxy( EVMxCoreContracts.WatcherPrecompile, - `contracts/protocol/watcherPrecompile/WatcherPrecompile.sol`, + `contracts/protocol/watcherPrecompile/core/WatcherPrecompile.sol`, [ EVMxOwner, addressResolver.address, @@ -215,6 +226,54 @@ const deployEVMxContracts = async () => { deployUtils.signer ); + let watcherPrecompileLimits = await getInstance( + EVMxCoreContracts.WatcherPrecompileLimits, + deployUtils.addresses[EVMxCoreContracts.WatcherPrecompileLimits] + ); + watcherPrecompileLimits = watcherPrecompileLimits.connect( + deployUtils.signer + ); + await updateContractSettings( + watcherPrecompileLimits, + "queryFees", + "setQueryFees", + QUERY_FEES, + deployUtils.signer + ); + + await updateContractSettings( + watcherPrecompileLimits, + "finalizeFees", + "setFinalizeFees", + FINALIZE_FEES, + deployUtils.signer + ); + + await updateContractSettings( + watcherPrecompileLimits, + "timeoutFees", + "setTimeoutFees", + TIMEOUT_FEES, + deployUtils.signer + ); + + await updateContractSettings( + watcherPrecompileLimits, + "callBackFees", + "setCallBackFees", + CALLBACK_FEES, + deployUtils.signer + ); + + const feesManager = await getInstance( + EVMxCoreContracts.FeesManager, + deployUtils.addresses[EVMxCoreContracts.FeesManager] + ); + await fundAuctionManager( + feesManager.connect(deployUtils.signer), + deployUtils.addresses[EVMxCoreContracts.AuctionManager], + deployUtils.signer + ); deployUtils.addresses.startBlock = (deployUtils.addresses.startBlock ? deployUtils.addresses.startBlock @@ -230,6 +289,56 @@ const deployEVMxContracts = async () => { } }; +export const fundAuctionManager = async ( + feesManager: Contract, + auctionManagerAddress: string, + watcherSigner: Signer +) => { + const currentCredits = await feesManager.getAvailableCredits( + auctionManagerAddress + ); + console.log("Current credits:", currentCredits.toString()); + if (currentCredits.gte(BigNumber.from(AUCTION_MANAGER_FUNDING_AMOUNT))) { + console.log( + `Auction manager ${auctionManagerAddress} already has credits, skipping funding` + ); + return; + } + const signatureNonce = Date.now(); + const digest = ethers.utils.keccak256( + ethers.utils.defaultAbiCoder.encode( + ["address", "uint32", "address", "uint256", "address", "uint32"], + [ + auctionManagerAddress, + EVMX_CHAIN_ID, + ETH_ADDRESS, + AUCTION_MANAGER_FUNDING_AMOUNT, + feesManager.address, + EVMX_CHAIN_ID, + ] + ) + ); + const signature = await watcherSigner.signMessage( + ethers.utils.arrayify(digest) + ); + const tx = await feesManager + .connect(watcherSigner) + .depositCredits( + auctionManagerAddress, + EVMX_CHAIN_ID, + ETH_ADDRESS, + signatureNonce, + signature, + { + value: AUCTION_MANAGER_FUNDING_AMOUNT, + } + ); + console.log( + `Funding auction manager ${auctionManagerAddress} with ${AUCTION_MANAGER_FUNDING_AMOUNT} ETH, txHash: `, + tx.hash + ); + await tx.wait(); +}; const deploySocketContracts = async () => { try { let addresses: DeploymentAddresses; @@ -288,6 +397,23 @@ const deploySocketContracts = async () => { ); deployUtils.addresses[contractName] = sb.address; + contractName = CORE_CONTRACTS.TestUSDC; + + const testUSDC: Contract = await getOrDeploy( + contractName, + contractName, + `contracts/helpers/${contractName}.sol`, + [ + TEST_USDC_NAME, + TEST_USDC_SYMBOL, + TEST_USDC_DECIMALS, + socketOwner, + TEST_USDC_INITIAL_SUPPLY, + ], + deployUtils + ); + deployUtils.addresses[contractName] = testUSDC.address; + contractName = CORE_CONTRACTS.FeesPlug; const feesPlug: Contract = await getOrDeploy( contractName, @@ -298,6 +424,8 @@ const deploySocketContracts = async () => { ); deployUtils.addresses[contractName] = feesPlug.address; + await whitelistToken(feesPlug, testUSDC.address, deployUtils.signer); + contractName = CORE_CONTRACTS.ContractFactoryPlug; const contractFactoryPlug: Contract = await getOrDeploy( contractName, @@ -328,24 +456,20 @@ const deploySocketContracts = async () => { } }; -async function initializeSigVerifier( - contract: Contract, - getterMethod: string, - setterMethod: string, - requiredAddress: string, - initParams: any[], +async function whitelistToken( + feesPlug: Contract, + tokenAddress: string, signer: Signer ) { - const currentValue = await contract.connect(signer)[getterMethod](); - - if (currentValue.toLowerCase() !== requiredAddress.toLowerCase()) { - console.log({ - setterMethod, - current: currentValue, - required: requiredAddress, - }); - const tx = await contract.connect(signer)[setterMethod](...initParams); - console.log(`Setting ${getterMethod} for ${contract.address} to`, tx.hash); + const isWhitelisted = await feesPlug + .connect(signer) + .whitelistedTokens(tokenAddress); + if (!isWhitelisted) { + const tx = await feesPlug.connect(signer).whitelistToken(tokenAddress); + console.log( + `Whitelisting token ${tokenAddress} for ${feesPlug.address}`, + tx.hash + ); await tx.wait(); } } @@ -354,18 +478,23 @@ async function updateContractSettings( contract: Contract, getterMethod: string, setterMethod: string, - requiredAddress: string, + requiredValue: string | BigNumber, signer: Signer ) { const currentValue = await contract.connect(signer)[getterMethod](); - if (currentValue.toLowerCase() !== requiredAddress.toLowerCase()) { + if ( + (typeof currentValue === "string" && + currentValue.toLowerCase() !== String(requiredValue).toLowerCase()) || + (BigNumber.isBigNumber(currentValue) && + currentValue.toString() !== requiredValue.toString()) + ) { console.log({ setterMethod, current: currentValue, - required: requiredAddress, + required: requiredValue, }); - const tx = await contract.connect(signer)[setterMethod](requiredAddress); + const tx = await contract.connect(signer)[setterMethod](requiredValue); console.log(`Setting ${getterMethod} for ${contract.address} to`, tx.hash); await tx.wait(); } diff --git a/hardhat-scripts/deploy/2.roles.ts b/hardhat-scripts/deploy/2.roles.ts index ae578b73..8ef83acc 100644 --- a/hardhat-scripts/deploy/2.roles.ts +++ b/hardhat-scripts/deploy/2.roles.ts @@ -20,7 +20,11 @@ import { ROLES } from "../constants/roles"; import { getWatcherSigner, getSocketSigner } from "../utils/sign"; export const REQUIRED_ROLES = { FastSwitchboard: [ROLES.WATCHER_ROLE, ROLES.RESCUE_ROLE], - Socket: [ROLES.GOVERNANCE_ROLE, ROLES.RESCUE_ROLE], + Socket: [ + ROLES.GOVERNANCE_ROLE, + ROLES.RESCUE_ROLE, + ROLES.SWITCHBOARD_DISABLER_ROLE, + ], FeesPlug: [ROLES.RESCUE_ROLE], ContractFactoryPlug: [ROLES.RESCUE_ROLE], }; diff --git a/hardhat-scripts/deploy/4.connect.ts b/hardhat-scripts/deploy/4.connect.ts index 1b0bfd84..71223808 100644 --- a/hardhat-scripts/deploy/4.connect.ts +++ b/hardhat-scripts/deploy/4.connect.ts @@ -1,4 +1,4 @@ -import { Contract, ethers, Wallet } from "ethers"; +import { constants, Contract, ethers, Wallet } from "ethers"; import { ChainAddressesObj, ChainSlug } from "../../src"; import { chains, EVMX_CHAIN_ID, mode } from "../config"; import { @@ -16,22 +16,25 @@ import { getWatcherSigner, signWatcherMessage } from "../utils/sign"; const plugs = [CORE_CONTRACTS.ContractFactoryPlug, CORE_CONTRACTS.FeesPlug]; export type AppGatewayConfig = { plug: string; - appGateway: string; + appGatewayId: string; switchboard: string; chainSlug: number; }; // Maps plug contracts to their corresponding app gateways -export const getAppGateway = (plug: string, addresses: DeploymentAddresses) => { +export const getAppGatewayId = ( + plug: string, + addresses: DeploymentAddresses +) => { let address: string = ""; switch (plug) { case CORE_CONTRACTS.ContractFactoryPlug: address = addresses?.[EVMX_CHAIN_ID]?.[EVMxCoreContracts.DeliveryHelper]; if (!address) throw new Error(`DeliveryHelper not found on EVMX`); - return address; + return ethers.utils.hexZeroPad(address, 32); case CORE_CONTRACTS.FeesPlug: address = addresses?.[EVMX_CHAIN_ID]?.[EVMxCoreContracts.FeesManager]; if (!address) throw new Error(`FeesManager not found on EVMX`); - return address; + return ethers.utils.hexZeroPad(address, 32); default: throw new Error(`Unknown plug: ${plug}`); } @@ -44,21 +47,35 @@ export const checkIfAddressExists = (address: string, name: string) => { address == "0x" || address.length != 42 ) { - throw Error(`${name} not found`); + throw Error(`${name} not found : ${address}`); } return address; }; +export const checkIfAppGatewayIdExists = ( + appGatewayId: string, + name: string +) => { + if ( + appGatewayId == constants.HashZero || + !appGatewayId || + appGatewayId == "0x" || + appGatewayId.length != 66 + ) { + throw Error(`${name} not found : ${appGatewayId}`); + } + return appGatewayId; +}; export const isConfigSetOnSocket = async ( plug: Contract, socket: Contract, - appGateway: string, + appGatewayId: string, switchboard: string ) => { const plugConfigRegistered = await socket.getPlugConfig(plug.address); return ( - plugConfigRegistered.appGateway.toLowerCase() === - appGateway.toLowerCase() && + plugConfigRegistered.appGatewayId.toLowerCase() === + appGatewayId.toLowerCase() && plugConfigRegistered.switchboard.toLowerCase() === switchboard.toLowerCase() ); }; @@ -84,22 +101,22 @@ async function connectPlug( // Get switchboard and app gateway addresses const switchboard = addr[CORE_CONTRACTS.FastSwitchboard]; checkIfAddressExists(switchboard, "Switchboard"); - const appGateway = getAppGateway(plugContract, addresses); - checkIfAddressExists(appGateway, "AppGateway"); + const appGatewayId = getAppGatewayId(plugContract, addresses); + checkIfAppGatewayIdExists(appGatewayId, "AppGatewayId"); // Check if config is already set - if (await isConfigSetOnSocket(plug, socket, appGateway, switchboard)) { + if (await isConfigSetOnSocket(plug, socket, appGatewayId, switchboard)) { console.log(`${plugContract} Socket Config on ${chain} already set!`); return; } // Connect the plug const tx = await plug.functions["connectSocket"]( - appGateway, + appGatewayId, socket.address, switchboard ); console.log( - `Connecting ${plugContract} on ${chain} to ${appGateway} tx hash: ${tx.hash}` + `Connecting ${plugContract} on ${chain} to ${appGatewayId} tx hash: ${tx.hash}` ); await tx.wait(); } @@ -126,12 +143,12 @@ export const isConfigSetOnEVMx = async ( watcher: Contract, chain: number, plug: string, - appGateway: string, + appGatewayId: string, switchboard: string ) => { const plugConfigRegistered = await watcher.getPlugConfigs(chain, plug); return ( - plugConfigRegistered[0].toLowerCase() === appGateway?.toLowerCase() && + plugConfigRegistered[0].toLowerCase() === appGatewayId?.toLowerCase() && plugConfigRegistered[1].toLowerCase() === switchboard.toLowerCase() ); }; @@ -160,17 +177,17 @@ export const updateConfigEVMx = async () => { const addr = addresses[chain]!; for (const plugContract of plugs) { - const appGateway = getAppGateway(plugContract, addresses); + const appGatewayId = getAppGatewayId(plugContract, addresses); const switchboard = addr[CORE_CONTRACTS.FastSwitchboard]; checkIfAddressExists(switchboard, "Switchboard"); - checkIfAddressExists(appGateway, "AppGateway"); + checkIfAppGatewayIdExists(appGatewayId, "AppGatewayId"); if ( await isConfigSetOnEVMx( watcherPrecompileConfig, chain, addr[plugContract], - appGateway, + appGatewayId, switchboard ) ) { @@ -179,7 +196,7 @@ export const updateConfigEVMx = async () => { } appConfigs.push({ plug: addr[plugContract], - appGateway, + appGatewayId: appGatewayId, switchboard: addr[CORE_CONTRACTS.FastSwitchboard], chainSlug: chain, }); @@ -193,7 +210,7 @@ export const updateConfigEVMx = async () => { const encodedMessage = ethers.utils.defaultAbiCoder.encode( [ "bytes4", - "tuple(address plug,address appGateway,address switchboard,uint32 chainSlug)[]", + "tuple(address plug,bytes32 appGatewayId,address switchboard,uint32 chainSlug)[]", ], [ watcherPrecompileConfig.interface.getSighash("setAppGateways"), diff --git a/hardhat-scripts/deploy/6.setupEnv.ts b/hardhat-scripts/deploy/6.setupEnv.ts index 6f9a7f83..1fe5cc72 100644 --- a/hardhat-scripts/deploy/6.setupEnv.ts +++ b/hardhat-scripts/deploy/6.setupEnv.ts @@ -39,6 +39,10 @@ const updatedLines = lines.map((line) => { return `ARBITRUM_FEES_PLUG=${ latestAddresses[ChainSlug.ARBITRUM_SEPOLIA]["FeesPlug"] }`; + } else if (line.startsWith("ARBITRUM_TEST_USDC=")) { + return `ARBITRUM_TEST_USDC=${ + latestAddresses[ChainSlug.ARBITRUM_SEPOLIA]["TestUSDC"] + }`; } return line; // Return the line unchanged if it doesn't match any of the above }); diff --git a/package.json b/package.json index 80d12f1f..86bf9215 100644 --- a/package.json +++ b/package.json @@ -9,7 +9,7 @@ "publishConfig": { "access": "public" }, - "version": "1.1.9", + "version": "1.1.15", "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/script/admin/RescueFunds.s.sol b/script/admin/RescueFunds.s.sol new file mode 100644 index 00000000..dbd5a3dd --- /dev/null +++ b/script/admin/RescueFunds.s.sol @@ -0,0 +1,70 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.19; + +import {Script} from "forge-std/Script.sol"; +import {console} from "forge-std/console.sol"; +import {FeesPlug} from "../../contracts/protocol/payload-delivery/FeesPlug.sol"; + +contract RescueFundsScript is Script { + address constant NATIVE_TOKEN = 0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE; + + struct ChainConfig { + address feesPlug; + string rpc; + string name; + } + + function rescueFromChain( + ChainConfig memory config, + address sender, + uint256 deployerKey + ) internal { + uint256 fork = vm.createFork(config.rpc); + vm.selectFork(fork); + uint256 balance = address(config.feesPlug).balance; + + if (balance > 0) { + console.log("%s Fees Plug Balance:", config.name); + console.log(balance); + + vm.startBroadcast(deployerKey); + FeesPlug(config.feesPlug).rescueFunds(NATIVE_TOKEN, sender, balance); + vm.stopBroadcast(); + } + } + + function run() external { + uint256 deployerPrivateKey = vm.envUint("SPONSOR_KEY"); + address sender = vm.envAddress("SENDER_ADDRESS"); + + ChainConfig[] memory chains = new ChainConfig[](4); + + chains[0] = ChainConfig({ + feesPlug: vm.envAddress("ARBITRUM_FEES_PLUG"), + rpc: vm.envString("ARBITRUM_SEPOLIA_RPC"), + name: "Arbitrum" + }); + + chains[1] = ChainConfig({ + feesPlug: vm.envAddress("BASE_FEES_PLUG"), + rpc: vm.envString("BASE_SEPOLIA_RPC"), + name: "Base" + }); + + chains[2] = ChainConfig({ + feesPlug: vm.envAddress("OPTIMISM_FEES_PLUG"), + rpc: vm.envString("OPTIMISM_SEPOLIA_RPC"), + name: "Optimism" + }); + + chains[3] = ChainConfig({ + feesPlug: vm.envAddress("SEPOLIA_FEES_PLUG"), + rpc: vm.envString("SEPOLIA_RPC"), + name: "Sepolia" + }); + + for (uint i = 0; i < chains.length; i++) { + rescueFromChain(chains[i], sender, deployerPrivateKey); + } + } +} diff --git a/script/admin/UpdateAppEVMxLimits.s.sol b/script/admin/UpdateAppEVMxLimits.s.sol index 4389c1be..4d23d16c 100644 --- a/script/admin/UpdateAppEVMxLimits.s.sol +++ b/script/admin/UpdateAppEVMxLimits.s.sol @@ -1,8 +1,8 @@ -// SPDX-License-Identifier: MIT -pragma solidity ^0.8.13; +// SPDX-License-Identifier: GPL-3.0-only +pragma solidity ^0.8.21; import "forge-std/Script.sol"; -import {WatcherPrecompile} from "../../contracts/protocol/watcherPrecompile/WatcherPrecompile.sol"; +import {WatcherPrecompile} from "../../contracts/protocol/watcherPrecompile/core/WatcherPrecompile.sol"; import {UpdateLimitParams} from "../../contracts/protocol/utils/common/Structs.sol"; import {SCHEDULE, QUERY, FINALIZE} from "../../contracts/protocol/utils/common/Constants.sol"; diff --git a/script/admin/mock/DeployEVMx.s.sol b/script/admin/mock/DeployEVMx.s.sol index e3f71807..c268e42c 100644 --- a/script/admin/mock/DeployEVMx.s.sol +++ b/script/admin/mock/DeployEVMx.s.sol @@ -1,5 +1,5 @@ -// SPDX-License-Identifier: MIT -pragma solidity ^0.8.0; +// SPDX-License-Identifier: GPL-3.0-only +pragma solidity ^0.8.21; import {Script} from "forge-std/Script.sol"; import {console} from "forge-std/console.sol"; diff --git a/script/admin/mock/DeploySocket.s.sol b/script/admin/mock/DeploySocket.s.sol index 603d8eea..00ed4674 100644 --- a/script/admin/mock/DeploySocket.s.sol +++ b/script/admin/mock/DeploySocket.s.sol @@ -1,5 +1,5 @@ -// SPDX-License-Identifier: MIT -pragma solidity ^0.8.0; +// SPDX-License-Identifier: GPL-3.0-only +pragma solidity ^0.8.21; import {Script} from "forge-std/Script.sol"; import {console} from "forge-std/console.sol"; diff --git a/script/counter/DeployEVMxCounterApp.s.sol b/script/counter/DeployEVMxCounterApp.s.sol index a62b7e81..42c09af7 100644 --- a/script/counter/DeployEVMxCounterApp.s.sol +++ b/script/counter/DeployEVMxCounterApp.s.sol @@ -1,10 +1,9 @@ -// SPDX-License-Identifier: MIT -pragma solidity ^0.8.0; +// SPDX-License-Identifier: GPL-3.0-only +pragma solidity ^0.8.21; import {Script} from "forge-std/Script.sol"; import {console} from "forge-std/console.sol"; import {CounterAppGateway} from "../../test/apps/app-gateways/counter/CounterAppGateway.sol"; -import {Fees} from "../../contracts/protocol/utils/common/Structs.sol"; import {ETH_ADDRESS} from "../../contracts/protocol/utils/common/Constants.sol"; // source .env && forge script script/counter/deployEVMxCounterApp.s.sol --broadcast --skip-simulation --legacy --gas-price 0 @@ -18,11 +17,7 @@ contract CounterDeploy is Script { vm.startBroadcast(deployerPrivateKey); // Setting fee payment on Arbitrum Sepolia - Fees memory fees = Fees({ - feePoolChain: 421614, - feePoolToken: ETH_ADDRESS, - amount: 0.00001 ether - }); + uint256 fees = 10 ether; CounterAppGateway gateway = new CounterAppGateway(addressResolver, fees); diff --git a/script/counter/DeployOnchainCounters.s.sol b/script/counter/DeployOnchainCounters.s.sol index d786e76b..531e2b95 100644 --- a/script/counter/DeployOnchainCounters.s.sol +++ b/script/counter/DeployOnchainCounters.s.sol @@ -1,5 +1,5 @@ -// SPDX-License-Identifier: MIT -pragma solidity ^0.8.0; +// SPDX-License-Identifier: GPL-3.0-only +pragma solidity ^0.8.21; import {Script} from "forge-std/Script.sol"; import {console} from "forge-std/console.sol"; diff --git a/script/counter/IncrementCountersFromApp.s.sol b/script/counter/IncrementCountersFromApp.s.sol index c048237b..92c688c7 100644 --- a/script/counter/IncrementCountersFromApp.s.sol +++ b/script/counter/IncrementCountersFromApp.s.sol @@ -1,5 +1,5 @@ -// SPDX-License-Identifier: MIT -pragma solidity ^0.8.0; +// SPDX-License-Identifier: GPL-3.0-only +pragma solidity ^0.8.21; import {Script} from "forge-std/Script.sol"; import {console} from "forge-std/console.sol"; diff --git a/script/counter/ReadOnchainCounters.s.sol b/script/counter/ReadOnchainCounters.s.sol index 534acd66..011ab4fb 100644 --- a/script/counter/ReadOnchainCounters.s.sol +++ b/script/counter/ReadOnchainCounters.s.sol @@ -1,5 +1,5 @@ -// SPDX-License-Identifier: MIT -pragma solidity ^0.8.0; +// SPDX-License-Identifier: GPL-3.0-only +pragma solidity ^0.8.21; import {Script} from "forge-std/Script.sol"; import {console} from "forge-std/console.sol"; diff --git a/script/counter/SetFees.s.sol b/script/counter/SetFees.s.sol index 3d089235..3f87f4ac 100644 --- a/script/counter/SetFees.s.sol +++ b/script/counter/SetFees.s.sol @@ -1,11 +1,11 @@ -// SPDX-License-Identifier: MIT -pragma solidity ^0.8.0; +// SPDX-License-Identifier: GPL-3.0-only +pragma solidity ^0.8.21; import {Script} from "forge-std/Script.sol"; import {console} from "forge-std/console.sol"; import {ETH_ADDRESS} from "../../contracts/protocol/utils/common/Constants.sol"; import {CounterAppGateway} from "../../test/apps/app-gateways/counter/CounterAppGateway.sol"; -import {Fees} from "../../contracts/protocol/utils/common/Structs.sol"; + // source .env && forge script script/counter/DeployCounterOnchain.s.sol --broadcast --skip-simulation --legacy --gas-price 0 contract CounterSetFees is Script { function run() external { @@ -21,11 +21,7 @@ contract CounterSetFees is Script { console.log("Setting fees..."); // Setting fee payment on Arbitrum Sepolia - Fees memory fees = Fees({ - feePoolChain: 421614, - feePoolToken: ETH_ADDRESS, - amount: 0.00001 ether - }); - appGateway.setFees(fees); + uint256 fees = 0.00001 ether; + // appGateway.setFees(fees); } } diff --git a/script/counter/WithdrawFeesArbitrumFeesPlug.s.sol b/script/counter/WithdrawFeesArbitrumFeesPlug.s.sol index fc954d26..ecd8b2b6 100644 --- a/script/counter/WithdrawFeesArbitrumFeesPlug.s.sol +++ b/script/counter/WithdrawFeesArbitrumFeesPlug.s.sol @@ -1,5 +1,5 @@ -// SPDX-License-Identifier: MIT -pragma solidity ^0.8.0; +// SPDX-License-Identifier: GPL-3.0-only +pragma solidity ^0.8.21; import {Script} from "forge-std/Script.sol"; import {console} from "forge-std/console.sol"; @@ -17,11 +17,7 @@ contract WithdrawFees is Script { address appGatewayAddress = vm.envAddress("APP_GATEWAY"); CounterAppGateway appGateway = CounterAppGateway(appGatewayAddress); - uint256 availableFees = feesManager.getAvailableFees( - 421614, - appGatewayAddress, - ETH_ADDRESS - ); + uint256 availableFees = feesManager.getMaxCreditsAvailableForWithdraw(appGatewayAddress); console.log("Available fees:", availableFees); if (availableFees > 0) { diff --git a/script/helpers/AppGatewayFeeBalance.s.sol b/script/helpers/AppGatewayFeeBalance.s.sol index 0743a7d0..8e80861f 100644 --- a/script/helpers/AppGatewayFeeBalance.s.sol +++ b/script/helpers/AppGatewayFeeBalance.s.sol @@ -1,10 +1,9 @@ -// SPDX-License-Identifier: MIT -pragma solidity ^0.8.0; +// SPDX-License-Identifier: GPL-3.0-only +pragma solidity ^0.8.21; import {Script} from "forge-std/Script.sol"; import {console} from "forge-std/console.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"; contract CheckDepositedFees is Script { @@ -12,21 +11,14 @@ contract CheckDepositedFees is Script { vm.createSelectFork(vm.envString("EVMX_RPC")); FeesManager feesManager = FeesManager(payable(vm.envAddress("FEES_MANAGER"))); address appGateway = vm.envAddress("APP_GATEWAY"); - uint32 chain = 421614; - address token = ETH_ADDRESS; - (uint256 deposited, uint256 blocked) = feesManager.appGatewayFeeBalances( - appGateway, - chain, - token - ); + + (uint256 totalCredits, uint256 blockedCredits) = feesManager.userCredits(appGateway); console.log("App Gateway:", appGateway); console.log("Fees Manager:", address(feesManager)); - console.logUint(chain); - console.log("Token:", token); - console.log("Deposited fees:", deposited); - console.log("Blocked fees:", blocked); + console.log("totalCredits fees:", totalCredits); + console.log("blockedCredits fees:", blockedCredits); - uint256 availableFees = feesManager.getAvailableFees(chain, appGateway, token); + uint256 availableFees = feesManager.getAvailableCredits(appGateway); console.log("Available fees:", availableFees); } } diff --git a/script/helpers/CheckAppEVMxLimits.s.sol b/script/helpers/CheckAppEVMxLimits.s.sol index 7c1f16a0..d31c2cdc 100644 --- a/script/helpers/CheckAppEVMxLimits.s.sol +++ b/script/helpers/CheckAppEVMxLimits.s.sol @@ -1,8 +1,8 @@ -// SPDX-License-Identifier: MIT -pragma solidity ^0.8.13; +// SPDX-License-Identifier: GPL-3.0-only +pragma solidity ^0.8.21; import "forge-std/Script.sol"; -import {WatcherPrecompile} from "../../contracts/protocol/watcherPrecompile/WatcherPrecompile.sol"; +import {WatcherPrecompile} from "../../contracts/protocol/watcherPrecompile/core/WatcherPrecompile.sol"; import {LimitParams} from "../../contracts/protocol/utils/common/Structs.sol"; import {SCHEDULE, QUERY, FINALIZE} from "../../contracts/protocol/utils/common/Constants.sol"; diff --git a/script/helpers/PayFeesInArbitrumETH.s.sol b/script/helpers/PayFeesInArbitrumETH.s.sol index ebe4f0e2..0940009a 100644 --- a/script/helpers/PayFeesInArbitrumETH.s.sol +++ b/script/helpers/PayFeesInArbitrumETH.s.sol @@ -1,10 +1,9 @@ -// SPDX-License-Identifier: MIT -pragma solidity ^0.8.0; +// SPDX-License-Identifier: GPL-3.0-only +pragma solidity ^0.8.21; import {Script} from "forge-std/Script.sol"; import {console} from "forge-std/console.sol"; import {FeesPlug} from "../../contracts/protocol/payload-delivery/FeesPlug.sol"; -import {Fees} from "../../contracts/protocol/utils/common/Structs.sol"; import {ETH_ADDRESS} from "../../contracts/protocol/utils/common/Constants.sol"; // source .env && forge script script/helpers/PayFeesInArbitrumETH.s.sol --broadcast --skip-simulation @@ -24,6 +23,6 @@ contract DepositFees is Script { console.log("App Gateway:", appGateway); console.log("Fees Plug:", address(feesPlug)); uint feesAmount = 0.001 ether; - feesPlug.deposit{value: feesAmount}(ETH_ADDRESS, appGateway, feesAmount); + feesPlug.depositToFeeAndNative(ETH_ADDRESS, appGateway, feesAmount); } } diff --git a/script/helpers/PayFeesInArbitrumTestUSDC.s.sol b/script/helpers/PayFeesInArbitrumTestUSDC.s.sol new file mode 100644 index 00000000..c7fe8111 --- /dev/null +++ b/script/helpers/PayFeesInArbitrumTestUSDC.s.sol @@ -0,0 +1,35 @@ +// SPDX-License-Identifier: GPL-3.0-only +pragma solidity ^0.8.21; + +import {Script} from "forge-std/Script.sol"; +import {console} from "forge-std/console.sol"; +import {FeesPlug} from "../../contracts/protocol/payload-delivery/FeesPlug.sol"; +import {ETH_ADDRESS} from "../../contracts/protocol/utils/common/Constants.sol"; +import {TestUSDC} from "../../contracts/helpers/TestUSDC.sol"; +// source .env && forge script script/helpers/PayFeesInArbitrumETH.s.sol --broadcast --skip-simulation +contract DepositFees is Script { + function run() external { + uint256 feesAmount = 100000000; + vm.createSelectFork(vm.envString("ARBITRUM_SEPOLIA_RPC")); + + uint256 privateKey = vm.envUint("SPONSOR_KEY"); + vm.startBroadcast(privateKey); + FeesPlug feesPlug = FeesPlug(payable(vm.envAddress("ARBITRUM_FEES_PLUG"))); + address appGateway = vm.envAddress("APP_GATEWAY"); + TestUSDC testUSDCContract = TestUSDC(vm.envAddress("ARBITRUM_TEST_USDC")); + + // mint test USDC to sender + testUSDCContract.mint(vm.addr(privateKey), feesAmount); + // approve fees plug to spend test USDC + testUSDCContract.approve(address(feesPlug), feesAmount); + + address sender = vm.addr(privateKey); + console.log("Sender address:", sender); + uint256 balance = testUSDCContract.balanceOf(sender); + console.log("Sender balance in wei:", balance); + console.log("App Gateway:", appGateway); + console.log("Fees Plug:", address(feesPlug)); + console.log("Fees Amount:", feesAmount); + feesPlug.depositToFeeAndNative(address(testUSDCContract), appGateway, feesAmount); + } +} diff --git a/setupInfraContracts.sh b/setupInfraContracts.sh index dee0dde8..9d916681 100644 --- a/setupInfraContracts.sh +++ b/setupInfraContracts.sh @@ -12,3 +12,4 @@ time npx hardhat run hardhat-scripts/misc-scripts/errorCodes.ts --no-compile time npx hardhat run hardhat-scripts/misc-scripts/eventTopics.ts --no-compile time npx hardhat run hardhat-scripts/misc-scripts/functionSigs.ts --no-compile time npx hardhat run hardhat-scripts/verify/verify.ts --no-compile +yarn lint \ No newline at end of file diff --git a/src/enums.ts b/src/enums.ts index 87a79cd5..689543ce 100644 --- a/src/enums.ts +++ b/src/enums.ts @@ -11,6 +11,7 @@ export enum Events { ExecutionFailed = "ExecutionFailed", PlugConnected = "PlugConnected", AppGatewayCallRequested = "AppGatewayCallRequested", + AppGatewayCallFailed = "AppGatewayCallFailed", // FeesPlug FeesDeposited = "FeesDeposited", diff --git a/src/events.ts b/src/events.ts index 1893f5b0..b12fa610 100644 --- a/src/events.ts +++ b/src/events.ts @@ -11,6 +11,7 @@ export const feesPlugEvents = [Events.FeesDeposited]; export const watcherPrecompileEvents = [ Events.CalledAppGateway, + Events.AppGatewayCallFailed, Events.RequestSubmitted, Events.QueryRequested, Events.FinalizeRequested, diff --git a/test/DeliveryHelper.t.sol b/test/DeliveryHelper.t.sol index fec81dff..85d4b42b 100644 --- a/test/DeliveryHelper.t.sol +++ b/test/DeliveryHelper.t.sol @@ -1,5 +1,5 @@ -// SPDX-License-Identifier: MIT -pragma solidity ^0.8.0; +// SPDX-License-Identifier: GPL-3.0-only +pragma solidity ^0.8.21; import "../contracts/protocol/payload-delivery/app-gateway/DeliveryHelper.sol"; import "../contracts/protocol/payload-delivery/FeesManager.sol"; @@ -31,7 +31,7 @@ contract DeliveryHelperTest is SetupTest { uint40 indexed requestCount, address indexed appGateway, PayloadSubmitParams[] payloadSubmitParams, - Fees fees, + uint256 maxFees, address auctionManager, bool onlyReadRequests ); @@ -115,28 +115,37 @@ contract DeliveryHelperTest is SetupTest { arbConfig = deploySocket(arbChainSlug); optConfig = deploySocket(optChainSlug); connectDeliveryHelper(); + + depositUSDCFees( + address(auctionManager), + OnChainFees({ + chainSlug: arbChainSlug, + token: address(arbConfig.feesTokenUSDC), + amount: 1 ether + }) + ); } function connectDeliveryHelper() internal { vm.startPrank(owner); arbConfig.contractFactoryPlug.initSocket( - address(deliveryHelper), + _encodeAppGatewayId(address(deliveryHelper)), address(arbConfig.socket), address(arbConfig.switchboard) ); optConfig.contractFactoryPlug.initSocket( - address(deliveryHelper), + _encodeAppGatewayId(address(deliveryHelper)), address(optConfig.socket), address(optConfig.switchboard) ); arbConfig.feesPlug.initSocket( - address(feesManager), + _encodeAppGatewayId(address(feesManager)), address(arbConfig.socket), address(arbConfig.switchboard) ); optConfig.feesPlug.initSocket( - address(feesManager), + _encodeAppGatewayId(address(feesManager)), address(optConfig.socket), address(optConfig.switchboard) ); @@ -146,25 +155,25 @@ contract DeliveryHelperTest is SetupTest { gateways[0] = AppGatewayConfig({ plug: address(arbConfig.contractFactoryPlug), chainSlug: arbChainSlug, - appGateway: address(deliveryHelper), + appGatewayId: _encodeAppGatewayId(address(deliveryHelper)), switchboard: address(arbConfig.switchboard) }); gateways[1] = AppGatewayConfig({ plug: address(optConfig.contractFactoryPlug), chainSlug: optChainSlug, - appGateway: address(deliveryHelper), + appGatewayId: _encodeAppGatewayId(address(deliveryHelper)), switchboard: address(optConfig.switchboard) }); gateways[2] = AppGatewayConfig({ plug: address(arbConfig.feesPlug), chainSlug: arbChainSlug, - appGateway: address(feesManager), + appGatewayId: _encodeAppGatewayId(address(feesManager)), switchboard: address(arbConfig.switchboard) }); gateways[3] = AppGatewayConfig({ plug: address(optConfig.feesPlug), chainSlug: optChainSlug, - appGateway: address(feesManager), + appGatewayId: _encodeAppGatewayId(address(feesManager)), switchboard: address(optConfig.switchboard) }); @@ -177,33 +186,60 @@ contract DeliveryHelperTest is SetupTest { //////////////////////////////////// Fees //////////////////////////////////// - function depositFees(address appGateway_, Fees memory fees_) internal { - SocketContracts memory socketConfig = getSocketConfig(fees_.feePoolChain); - socketConfig.feesPlug.deposit{value: fees_.amount}( - fees_.feePoolToken, - appGateway_, - fees_.amount + function depositUSDCFees(address appGateway_, OnChainFees memory fees_) internal { + SocketContracts memory socketConfig = getSocketConfig(fees_.chainSlug); + vm.startPrank(owner); + ERC20(fees_.token).approve(address(socketConfig.feesPlug), fees_.amount); + socketConfig.feesPlug.depositToFeeAndNative(fees_.token, appGateway_, fees_.amount); + vm.stopPrank(); + + bytes32 digest = keccak256( + abi.encode( + appGateway_, + fees_.chainSlug, + fees_.token, + fees_.amount, + address(feesManager), + evmxSlug + ) ); - bytes memory bytesInput = abi.encode( - fees_.feePoolChain, + feesManager.depositCredits{value: fees_.amount}( appGateway_, - fees_.feePoolToken, - fees_.amount + fees_.chainSlug, + fees_.token, + signatureNonce++, + _createSignature(digest, watcherPrivateKey) ); + } + function whitelistAppGateway( + address appGateway_, + address user_, + uint256 userPrivateKey_, + uint32 chainSlug_ + ) internal { + SocketContracts memory socketConfig = getSocketConfig(chainSlug_); + // Create fee approval data with signature bytes32 digest = keccak256( - abi.encode(address(feesManager), evmxSlug, signatureNonce, bytesInput) - ); - bytes memory sig = _createSignature(digest, watcherPrivateKey); - feesManager.incrementFeesDeposited( - fees_.feePoolChain, - appGateway_, - fees_.feePoolToken, - fees_.amount, - signatureNonce++, - sig + abi.encode( + address(feesManager), + evmxSlug, + user_, + appGateway_, + feesManager.userNonce(user_), + true + ) ); + + // Sign with consumeFrom's private key + bytes memory signature = _createSignature(digest, userPrivateKey_); + + // Encode approval data + bytes memory feeApprovalData = abi.encode(user_, appGateway_, true, signature); + + // Call whitelistAppGatewayWithSignature with approval data + feesManager.whitelistAppGatewayWithSignature(feeApprovalData); } ////////////////////////////////// Deployment helpers //////////////////////////////////// @@ -254,7 +290,7 @@ contract DeliveryHelperTest is SetupTest { gateways[i] = AppGatewayConfig({ plug: plug, chainSlug: chainSlug_, - appGateway: address(appGateway_), + appGatewayId: _encodeAppGatewayId(address(appGateway_)), switchboard: address(socketConfig.switchboard) }); } @@ -283,7 +319,11 @@ contract DeliveryHelperTest is SetupTest { function endAuction(uint40 requestCount_) internal { if (auctionEndDelaySeconds == 0) return; - bytes32 timeoutId = _encodeId(evmxSlug, address(watcherPrecompile), timeoutIdCounter++); + bytes32 timeoutId = _encodeTimeoutId( + evmxSlug, + address(watcherPrecompile), + timeoutIdCounter++ + ); bytes memory watcherSignature = _createWatcherSignature( address(watcherPrecompile), @@ -304,7 +344,7 @@ contract DeliveryHelperTest is SetupTest { } //////////////////////////////////// Utils /////////////////////////////////// - function _encodeId( + function _encodeTimeoutId( uint32 chainSlug_, address sbOrWatcher_, uint256 counter_ diff --git a/test/FeesTest.t.sol b/test/FeesTest.t.sol index 4fc2a3a0..1f6a0ba0 100644 --- a/test/FeesTest.t.sol +++ b/test/FeesTest.t.sol @@ -1,5 +1,5 @@ -// SPDX-License-Identifier: MIT -pragma solidity ^0.8.3; +// SPDX-License-Identifier: GPL-3.0-only +pragma solidity ^0.8.21; import "./DeliveryHelper.t.sol"; import {Counter} from "./apps/app-gateways/counter/Counter.sol"; @@ -9,7 +9,7 @@ contract FeesTest is DeliveryHelperTest { uint256 constant depositAmount = 1 ether; uint256 constant feesAmount = 0.01 ether; address receiver = address(uint160(c++)); - + address user = address(uint160(c++)); uint32 feesChainSlug = arbChainSlug; SocketContracts feesConfig; @@ -19,74 +19,102 @@ contract FeesTest is DeliveryHelperTest { setUpDeliveryHelper(); feesConfig = getSocketConfig(feesChainSlug); - counterGateway = new CounterAppGateway(address(addressResolver), createFees(feesAmount)); - depositFees(address(counterGateway), createFees(depositAmount)); + counterGateway = new CounterAppGateway(address(addressResolver), feesAmount); + depositUSDCFees( + address(counterGateway), + OnChainFees({ + chainSlug: feesChainSlug, + token: address(feesConfig.feesTokenUSDC), + amount: depositAmount + }) + ); bytes32[] memory contractIds = new bytes32[](1); contractIds[0] = counterGateway.counter(); _deploy(feesChainSlug, IAppGateway(counterGateway), contractIds); } - function testDistributeFee() public { - uint256 initialFeesPlugBalance = address(feesConfig.feesPlug).balance; - - assertEq( - initialFeesPlugBalance, - address(feesConfig.feesPlug).balance, - "FeesPlug Balance should be correct" + function testWithdrawTransmitterFees() public { + uint256 initialFeesPlugBalance = feesConfig.feesTokenUSDC.balanceOf( + address(feesConfig.feesPlug) ); assertEq( initialFeesPlugBalance, - feesConfig.feesPlug.balanceOf(ETH_ADDRESS), - "FeesPlug balance of counterGateway should be correct" + feesConfig.feesTokenUSDC.balanceOf(address(feesConfig.feesPlug)), + "FeesPlug Balance should be correct" ); - uint256 transmitterReceiverBalanceBefore = address(receiver).balance; - - hoax(transmitterEOA); - uint40 requestCount = feesManager.withdrawTransmitterFees( + uint256 transmitterReceiverBalanceBefore = feesConfig.feesTokenUSDC.balanceOf(receiver); + uint256 withdrawAmount = feesManager.getMaxCreditsAvailableForWithdraw(transmitterEOA); + vm.startPrank(transmitterEOA); + uint40 requestCount = deliveryHelper.withdrawTransmitterFees( feesChainSlug, - ETH_ADDRESS, - address(receiver) + address(feesConfig.feesTokenUSDC), + address(receiver), + withdrawAmount ); + vm.stopPrank(); uint40[] memory batches = watcherPrecompile.getBatches(requestCount); _finalizeBatch(batches[0], new bytes[](0), 0, false); - assertEq( - transmitterReceiverBalanceBefore + bidAmount, - address(receiver).balance, + transmitterReceiverBalanceBefore + withdrawAmount, + feesConfig.feesTokenUSDC.balanceOf(receiver), "Transmitter Balance should be correct" ); assertEq( - initialFeesPlugBalance - bidAmount, - address(feesConfig.feesPlug).balance, + initialFeesPlugBalance - withdrawAmount, + feesConfig.feesTokenUSDC.balanceOf(address(feesConfig.feesPlug)), "FeesPlug Balance should be correct" ); } - function testWithdrawFeeTokens() public { - assertEq( - depositAmount, - feesConfig.feesPlug.balanceOf(ETH_ADDRESS), - "Balance should be correct" - ); - - uint256 receiverBalanceBefore = receiver.balance; + function testWithdrawFeeTokensAppGateway() public { + uint256 receiverBalanceBefore = feesConfig.feesTokenUSDC.balanceOf(receiver); uint256 withdrawAmount = 0.5 ether; - counterGateway.withdrawFeeTokens(feesChainSlug, ETH_ADDRESS, withdrawAmount, receiver); + counterGateway.withdrawFeeTokens( + feesChainSlug, + address(feesConfig.feesTokenUSDC), + withdrawAmount, + receiver + ); executeRequest(new bytes[](0)); assertEq( - depositAmount - withdrawAmount, - address(feesConfig.feesPlug).balance, - "Fees Balance should be correct" + receiverBalanceBefore + withdrawAmount, + feesConfig.feesTokenUSDC.balanceOf(receiver), + "Receiver Balance should be correct" + ); + } + + function testWithdrawFeeTokensUser() public { + depositUSDCFees( + user, + OnChainFees({ + chainSlug: feesChainSlug, + token: address(feesConfig.feesTokenUSDC), + amount: depositAmount + }) ); + uint256 receiverBalanceBefore = feesConfig.feesTokenUSDC.balanceOf(user); + uint256 withdrawAmount = 0.5 ether; + + vm.prank(user); + deliveryHelper.withdrawTo( + feesChainSlug, + address(feesConfig.feesTokenUSDC), + withdrawAmount, + user, + address(auctionManager), + maxFees + ); + executeRequest(new bytes[](0)); + assertEq( receiverBalanceBefore + withdrawAmount, - receiver.balance, + feesConfig.feesTokenUSDC.balanceOf(user), "Receiver Balance should be correct" ); } diff --git a/test/Inbox.t.sol b/test/Inbox.t.sol index f45277fd..c9306913 100644 --- a/test/Inbox.t.sol +++ b/test/Inbox.t.sol @@ -1,29 +1,38 @@ -// SPDX-License-Identifier: MIT -pragma solidity ^0.8.0; +// SPDX-License-Identifier: GPL-3.0-only +pragma solidity ^0.8.21; import {CounterAppGateway} from "./apps/app-gateways/counter/CounterAppGateway.sol"; import {Counter} from "./apps/app-gateways/counter/Counter.sol"; import "./DeliveryHelper.t.sol"; -contract InboxTest is DeliveryHelperTest { +contract TriggerTest is DeliveryHelperTest { uint256 constant feesAmount = 0.01 ether; CounterAppGateway public gateway; - Counter public inbox; + Counter public counter; + + event AppGatewayCallRequested( + bytes32 triggerId, + bytes32 appGatewayId, + address switchboard, + address plug, + bytes overrides, + bytes payload + ); function setUp() public { // Setup core test infrastructure setUpDeliveryHelper(); - // Deploy the inbox contract - inbox = new Counter(); + // Deploy the counter contract + counter = new Counter(); // Deploy the gateway with fees - gateway = new CounterAppGateway(address(addressResolver), createFees(feesAmount)); - gateway.setIsValidPlug(arbChainSlug, address(inbox)); + gateway = new CounterAppGateway(address(addressResolver), feesAmount); + gateway.setIsValidPlug(arbChainSlug, address(counter)); - // Connect the inbox to the gateway and socket - inbox.initSocket( - address(gateway), + // Connect the counter to the gateway and socket + counter.initSocket( + _encodeAppGatewayId(address(gateway)), address(arbConfig.socket), address(arbConfig.switchboard) ); @@ -31,9 +40,9 @@ contract InboxTest is DeliveryHelperTest { // Setup gateway config for the watcher AppGatewayConfig[] memory gateways = new AppGatewayConfig[](1); gateways[0] = AppGatewayConfig({ - plug: address(inbox), + plug: address(counter), chainSlug: arbChainSlug, - appGateway: address(gateway), + appGatewayId: _encodeAppGatewayId(address(gateway)), switchboard: address(arbConfig.switchboard) }); @@ -45,25 +54,47 @@ contract InboxTest is DeliveryHelperTest { watcherPrecompileConfig.setAppGateways(gateways, signatureNonce++, watcherSignature); hoax(watcherEOA); - watcherPrecompileConfig.setIsValidPlug(arbChainSlug, address(inbox), true); + watcherPrecompileConfig.setIsValidPlug(arbChainSlug, address(counter), true); } - function testInboxIncrement() public { + function testIncrementAfterTrigger() public { // Initial counter value should be 0 assertEq(gateway.counterVal(), 0, "Initial gateway counter should be 0"); - + depositUSDCFees( + address(gateway), + OnChainFees({ + chainSlug: arbChainSlug, + token: address(arbConfig.feesTokenUSDC), + amount: 1 ether + }) + ); // Simulate a message from another chain through the watcher uint256 incrementValue = 5; + bytes32 triggerId = _encodeTriggerId(address(arbConfig.socket), arbChainSlug); + bytes memory payload = abi.encodeWithSelector( + CounterAppGateway.increase.selector, + incrementValue + ); + + vm.expectEmit(true, true, true, true); + emit AppGatewayCallRequested( + triggerId, + _encodeAppGatewayId(address(gateway)), + address(arbConfig.switchboard), + address(counter), + bytes(""), + payload + ); + counter.increaseOnGateway(incrementValue); - bytes32 callId = inbox.increaseOnGateway(incrementValue); - CallFromChainParams[] memory params = new CallFromChainParams[](1); - params[0] = CallFromChainParams({ - callId: callId, + TriggerParams[] memory params = new TriggerParams[](1); + params[0] = TriggerParams({ + triggerId: triggerId, chainSlug: arbChainSlug, - appGateway: address(gateway), - plug: address(inbox), - payload: abi.encode(incrementValue), - params: bytes32(0) + appGatewayId: _encodeAppGatewayId(address(gateway)), + plug: address(counter), + payload: payload, + overrides: bytes("") }); bytes memory watcherSignature = _createWatcherSignature( @@ -74,4 +105,11 @@ contract InboxTest is DeliveryHelperTest { // Check counter was incremented assertEq(gateway.counterVal(), incrementValue, "Gateway counter should be incremented"); } + + function _encodeTriggerId(address socket_, uint256 chainSlug_) internal returns (bytes32) { + return + bytes32( + (uint256(chainSlug_) << 224) | (uint256(uint160(socket_)) << 64) | triggerCounter++ + ); + } } diff --git a/test/Migration.t.sol b/test/Migration.t.sol index 0632d583..0dd023c5 100644 --- a/test/Migration.t.sol +++ b/test/Migration.t.sol @@ -1,274 +1,334 @@ -// SPDX-License-Identifier: MIT -pragma solidity ^0.8.0; +// SPDX-License-Identifier: GPL-3.0-only +pragma solidity ^0.8.21; import "./SetupTest.t.sol"; import "../contracts/protocol/AddressResolver.sol"; -import "../contracts/protocol/watcherPrecompile/WatcherPrecompile.sol"; +import "../contracts/protocol/watcherPrecompile/core/WatcherPrecompile.sol"; +import "../contracts/protocol/watcherPrecompile/WatcherPrecompileLimits.sol"; +import "../contracts/protocol/watcherPrecompile/WatcherPrecompileConfig.sol"; import "../contracts/protocol/Forwarder.sol"; import "../contracts/protocol/AsyncPromise.sol"; import "./mock/MockWatcherPrecompileImpl.sol"; -// contract MigrationTest is SetupTest { -// // ERC1967Factory emits this event with both proxy and implementation addresses -// event Upgraded(address indexed proxy, address indexed implementation); -// event ImplementationUpdated(string contractName, address newImplementation); - -// // ERC1967 implementation slot -// bytes32 internal constant _IMPLEMENTATION_SLOT = -// 0x360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc; - -// // Beacon implementation slot -// uint256 internal constant _BEACON_IMPLEMENTATION_SLOT = 0x911c5a209f08d5ec5e; - -// // Beacon slot in ERC1967 -// bytes32 internal constant _BEACON_SLOT = -// 0xa3f0ad74e5423aebfd80d3ef4346578335a9a72aeaee59ff6cb3582b35133d50; - -// // Error selector for Unauthorized error -// bytes4 internal constant UNAUTHORIZED_SELECTOR = 0x82b42900; // bytes4(keccak256("Unauthorized()")) - -// function setUp() public { -// deployEVMxCore(); -// } - -// function getImplementation(address proxy) internal view returns (address) { -// bytes32 value = vm.load(proxy, _IMPLEMENTATION_SLOT); -// return address(uint160(uint256(value))); -// } - -// function getBeaconImplementation(address beacon) internal view returns (address) { -// bytes32 value = vm.load(beacon, bytes32(_BEACON_IMPLEMENTATION_SLOT)); -// return address(uint160(uint256(value))); -// } - -// function getBeacon(address proxy) internal view returns (address) { -// bytes32 value = vm.load(proxy, _BEACON_SLOT); -// return address(uint160(uint256(value))); -// } - -// function testAddressResolverUpgrade() public { -// // Deploy new implementation -// AddressResolver newImpl = new AddressResolver(); - -// // Store old implementation address -// address oldImpl = getImplementation(address(addressResolver)); - -// // Upgrade proxy to new implementation -// vm.startPrank(watcherEOA); -// vm.expectEmit(true, true, true, true, address(proxyFactory)); -// emit Upgraded(address(addressResolver), address(newImpl)); -// proxyFactory.upgradeAndCall(address(addressResolver), address(newImpl), ""); -// vm.stopPrank(); - -// // Verify upgrade was successful -// address newImplAddr = getImplementation(address(addressResolver)); -// assertNotEq(oldImpl, newImplAddr, "Implementation should have changed"); -// assertEq(newImplAddr, address(newImpl), "New implementation not set correctly"); - -// // Verify state is preserved -// assertEq(addressResolver.owner(), watcherEOA, "Owner should be preserved after upgrade"); -// assertEq( -// address(addressResolver.watcherPrecompile__()), -// address(watcherPrecompile), -// "WatcherPrecompile address should be preserved" -// ); -// } - -// function testWatcherPrecompileUpgrade() public { -// // Deploy new implementation -// WatcherPrecompile newImpl = new WatcherPrecompile(); - -// // Store old implementation address -// address oldImpl = getImplementation(address(watcherPrecompile)); - -// // Upgrade proxy to new implementation -// vm.startPrank(watcherEOA); -// vm.expectEmit(true, true, true, true, address(proxyFactory)); -// emit Upgraded(address(watcherPrecompile), address(newImpl)); -// proxyFactory.upgradeAndCall(address(watcherPrecompile), address(newImpl), ""); -// vm.stopPrank(); - -// // Verify upgrade was successful -// address newImplAddr = getImplementation(address(watcherPrecompile)); -// assertNotEq(oldImpl, newImplAddr, "Implementation should have changed"); -// assertEq(newImplAddr, address(newImpl), "New implementation not set correctly"); - -// // Verify state is preserved -// assertEq(watcherPrecompile.owner(), watcherEOA, "Owner should be preserved after upgrade"); -// assertEq( -// address(watcherPrecompile.addressResolver__()), -// address(addressResolver), -// "AddressResolver should be preserved" -// ); -// assertEq( -// watcherPrecompile.defaultLimit(), -// defaultLimit * 10 ** 18, -// "DefaultLimit should be preserved" -// ); -// } - -// function testUpgradeWithInitializationData() public { -// // Deploy new implementation -// MockWatcherPrecompileImpl newImpl = new MockWatcherPrecompileImpl(); - -// // Store old implementation address for verification -// address oldImpl = getImplementation(address(watcherPrecompile)); - -// // Prepare initialization data with new defaultLimit -// uint256 newDefaultLimit = 2000; -// bytes memory initData = abi.encodeWithSelector( -// MockWatcherPrecompileImpl.mockReinitialize.selector, -// watcherEOA, -// address(addressResolver), -// newDefaultLimit -// ); - -// // Upgrade proxy with initialization data -// vm.startPrank(watcherEOA); -// vm.expectEmit(true, true, true, true, address(proxyFactory)); -// emit Upgraded(address(watcherPrecompile), address(newImpl)); -// proxyFactory.upgradeAndCall(address(watcherPrecompile), address(newImpl), initData); -// vm.stopPrank(); - -// // Verify upgrade and initialization was successful -// address newImplAddr = getImplementation(address(watcherPrecompile)); -// assertNotEq(oldImpl, newImplAddr, "Implementation should have changed"); -// assertEq(newImplAddr, address(newImpl), "New implementation not set correctly"); -// assertEq( -// watcherPrecompile.defaultLimit(), -// newDefaultLimit * 10 ** 18, -// "DefaultLimit should be updated" -// ); -// } - -// function testUnauthorizedUpgrade() public { -// // Deploy new implementation -// WatcherPrecompile newImpl = new WatcherPrecompile(); - -// // Try to upgrade from unauthorized account -// address unauthorizedUser = address(0xBEEF); -// vm.startPrank(unauthorizedUser); -// vm.expectRevert(UNAUTHORIZED_SELECTOR); -// proxyFactory.upgradeAndCall(address(watcherPrecompile), address(newImpl), ""); -// vm.stopPrank(); - -// // Verify implementation was not changed -// assertEq( -// getImplementation(address(watcherPrecompile)), -// address(watcherPrecompileImpl), -// "Implementation should not have changed" -// ); -// } - -// function testForwarderBeaconUpgrade() public { -// // Deploy new implementation -// Forwarder newImpl = new Forwarder(); - -// // Get current implementation from beacon -// address oldImpl = getBeaconImplementation(address(addressResolver.forwarderBeacon())); - -// // Upgrade beacon to new implementation -// vm.startPrank(watcherEOA); -// vm.expectEmit(true, true, true, true, address(addressResolver)); -// emit ImplementationUpdated("Forwarder", address(newImpl)); -// addressResolver.setForwarderImplementation(address(newImpl)); -// vm.stopPrank(); - -// // Verify upgrade was successful -// address newImplAddr = getBeaconImplementation(address(addressResolver.forwarderBeacon())); -// assertNotEq(oldImpl, newImplAddr, "Implementation should have changed"); -// assertEq(newImplAddr, address(newImpl), "New implementation not set correctly"); - -// // Deploy a new forwarder and verify it uses the correct beacon -// address newForwarder = addressResolver.getOrDeployForwarderContract( -// address(this), -// address(0x123), -// 1 -// ); -// address beacon = getBeacon(newForwarder); -// assertEq( -// beacon, -// address(addressResolver.forwarderBeacon()), -// "Beacon address not set correctly" -// ); - -// // Get implementation from beacon and verify it matches -// address implFromBeacon = getBeaconImplementation(beacon); -// assertEq( -// implFromBeacon, -// address(newImpl), -// "Beacon implementation should match new implementation" -// ); -// } - -// function testAsyncPromiseBeaconUpgrade() public { -// // Deploy new implementation -// AsyncPromise newImpl = new AsyncPromise(); - -// // Get current implementation from beacon -// address oldImpl = getBeaconImplementation(address(addressResolver.asyncPromiseBeacon())); - -// // Upgrade beacon to new implementation -// vm.startPrank(watcherEOA); -// vm.expectEmit(true, true, true, true, address(addressResolver)); -// emit ImplementationUpdated("AsyncPromise", address(newImpl)); -// addressResolver.setAsyncPromiseImplementation(address(newImpl)); -// vm.stopPrank(); - -// // Verify upgrade was successful -// address newImplAddr = getBeaconImplementation( -// address(addressResolver.asyncPromiseBeacon()) -// ); -// assertNotEq(oldImpl, newImplAddr, "Implementation should have changed"); -// assertEq(newImplAddr, address(newImpl), "New implementation not set correctly"); - -// // Deploy a new async promise and verify it uses the correct beacon -// address newPromise = addressResolver.deployAsyncPromiseContract(address(this)); -// address beacon = getBeacon(newPromise); -// assertEq( -// beacon, -// address(addressResolver.asyncPromiseBeacon()), -// "Beacon address not set correctly" -// ); - -// // Get implementation from beacon and verify it matches -// address implFromBeacon = getBeaconImplementation(beacon); -// assertEq( -// implFromBeacon, -// address(newImpl), -// "Beacon implementation should match new implementation" -// ); -// } - -// function testUnauthorizedBeaconUpgrade() public { -// // Deploy new implementations -// Forwarder newForwarderImpl = new Forwarder(); -// AsyncPromise newAsyncPromiseImpl = new AsyncPromise(); - -// // Try to upgrade from unauthorized account -// address unauthorizedUser = address(0xBEEF); - -// vm.startPrank(unauthorizedUser); -// // Try upgrading forwarder beacon -// vm.expectRevert(abi.encodeWithSelector(Ownable.Unauthorized.selector)); -// addressResolver.setForwarderImplementation(address(newForwarderImpl)); - -// // Try upgrading async promise beacon -// vm.expectRevert(abi.encodeWithSelector(Ownable.Unauthorized.selector)); -// addressResolver.setAsyncPromiseImplementation(address(newAsyncPromiseImpl)); - -// vm.stopPrank(); - -// // Verify implementations were not changed -// assertNotEq( -// getBeaconImplementation(address(addressResolver.forwarderBeacon())), -// address(newForwarderImpl), -// "Forwarder implementation should not have changed" -// ); -// assertNotEq( -// getBeaconImplementation(address(addressResolver.asyncPromiseBeacon())), -// address(newAsyncPromiseImpl), -// "AsyncPromise implementation should not have changed" -// ); -// } -// } +contract MigrationTest is SetupTest { + // ERC1967Factory emits this event with both proxy and implementation addresses + event Upgraded(address indexed proxy, address indexed implementation); + event ImplementationUpdated(string contractName, address newImplementation); + + // ERC1967 implementation slot + bytes32 internal constant _IMPLEMENTATION_SLOT = + 0x360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc; + + // Beacon implementation slot + uint256 internal constant _BEACON_IMPLEMENTATION_SLOT = 0x911c5a209f08d5ec5e; + + // Beacon slot in ERC1967 + bytes32 internal constant _BEACON_SLOT = + 0xa3f0ad74e5423aebfd80d3ef4346578335a9a72aeaee59ff6cb3582b35133d50; + + // Error selector for Unauthorized error + bytes4 internal constant UNAUTHORIZED_SELECTOR = 0x82b42900; // bytes4(keccak256("Unauthorized()")) + + function setUp() public { + deployEVMxCore(); + } + + function getImplementation(address proxy) internal view returns (address) { + bytes32 value = vm.load(proxy, _IMPLEMENTATION_SLOT); + return address(uint160(uint256(value))); + } + + function getBeaconImplementation(address beacon) internal view returns (address) { + bytes32 value = vm.load(beacon, bytes32(_BEACON_IMPLEMENTATION_SLOT)); + return address(uint160(uint256(value))); + } + + function getBeacon(address proxy) internal view returns (address) { + bytes32 value = vm.load(proxy, _BEACON_SLOT); + return address(uint160(uint256(value))); + } + + function testAddressResolverUpgrade() public { + // Deploy new implementation + AddressResolver newImpl = new AddressResolver(); + + // Store old implementation address + address oldImpl = getImplementation(address(addressResolver)); + + // Upgrade proxy to new implementation + vm.startPrank(watcherEOA); + vm.expectEmit(true, true, true, true, address(proxyFactory)); + emit Upgraded(address(addressResolver), address(newImpl)); + proxyFactory.upgradeAndCall(address(addressResolver), address(newImpl), ""); + vm.stopPrank(); + + // Verify upgrade was successful + address newImplAddr = getImplementation(address(addressResolver)); + assertNotEq(oldImpl, newImplAddr, "Implementation should have changed"); + assertEq(newImplAddr, address(newImpl), "New implementation not set correctly"); + + // Verify state is preserved + assertEq(addressResolver.owner(), watcherEOA, "Owner should be preserved after upgrade"); + assertEq( + address(addressResolver.watcherPrecompile__()), + address(watcherPrecompile), + "WatcherPrecompile address should be preserved" + ); + } + + function testWatcherPrecompileUpgrade() public { + // Deploy new implementation + WatcherPrecompile newImpl = new WatcherPrecompile(); + + // Store old implementation address + address oldImpl = getImplementation(address(watcherPrecompile)); + + // Upgrade proxy to new implementation + vm.startPrank(watcherEOA); + vm.expectEmit(true, true, true, true, address(proxyFactory)); + emit Upgraded(address(watcherPrecompile), address(newImpl)); + proxyFactory.upgradeAndCall(address(watcherPrecompile), address(newImpl), ""); + vm.stopPrank(); + + // Verify upgrade was successful + address newImplAddr = getImplementation(address(watcherPrecompile)); + assertNotEq(oldImpl, newImplAddr, "Implementation should have changed"); + assertEq(newImplAddr, address(newImpl), "New implementation not set correctly"); + + // Verify state is preserved + assertEq(watcherPrecompile.owner(), watcherEOA, "Owner should be preserved after upgrade"); + assertEq( + address(watcherPrecompile.watcherPrecompileConfig__()), + address(watcherPrecompileConfig), + "WatcherPrecompileConfig should be preserved" + ); + assertEq(watcherPrecompile.evmxSlug(), evmxSlug, "EvmxSlug should be preserved"); + } + + function testWatcherPrecompileLimitsUpgrade() public { + // Deploy new implementation + WatcherPrecompileLimits newImpl = new WatcherPrecompileLimits(); + + // Store old implementation address + address oldImpl = getImplementation(address(watcherPrecompileLimits)); + + // Upgrade proxy to new implementation + vm.startPrank(watcherEOA); + vm.expectEmit(true, true, true, true, address(proxyFactory)); + emit Upgraded(address(watcherPrecompileLimits), address(newImpl)); + proxyFactory.upgradeAndCall(address(watcherPrecompileLimits), address(newImpl), ""); + vm.stopPrank(); + + // Verify upgrade was successful + address newImplAddr = getImplementation(address(watcherPrecompileLimits)); + assertNotEq(oldImpl, newImplAddr, "Implementation should have changed"); + assertEq(newImplAddr, address(newImpl), "New implementation not set correctly"); + + // Verify state is preserved + assertEq(watcherPrecompile.owner(), watcherEOA, "Owner should be preserved after upgrade"); + assertEq( + address(watcherPrecompileLimits.addressResolver__()), + address(addressResolver), + "AddressResolver should be preserved" + ); + assertEq( + watcherPrecompileLimits.defaultLimit(), + defaultLimit * 10 ** 18, + "DefaultLimit should be preserved" + ); + } + + function testWatcherPrecompileConfigUpgrade() public { + // Deploy new implementation + WatcherPrecompileConfig newImpl = new WatcherPrecompileConfig(); + + // Store old implementation address + address oldImpl = getImplementation(address(watcherPrecompileConfig)); + + // Upgrade proxy to new implementation + vm.startPrank(watcherEOA); + vm.expectEmit(true, true, true, true, address(proxyFactory)); + emit Upgraded(address(watcherPrecompileConfig), address(newImpl)); + proxyFactory.upgradeAndCall(address(watcherPrecompileConfig), address(newImpl), ""); + vm.stopPrank(); + + // Verify upgrade was successful + address newImplAddr = getImplementation(address(watcherPrecompileConfig)); + assertNotEq(oldImpl, newImplAddr, "Implementation should have changed"); + assertEq(newImplAddr, address(newImpl), "New implementation not set correctly"); + + // Verify state is preserved + assertEq( + watcherPrecompileConfig.owner(), + watcherEOA, + "Owner should be preserved after upgrade" + ); + assertEq( + address(watcherPrecompileConfig.addressResolver__()), + address(addressResolver), + "AddressResolver should be preserved" + ); + assertEq(watcherPrecompileConfig.evmxSlug(), 1, "EvmxSlug should be preserved"); + } + + function testUpgradeWithInitializationData() public { + // Deploy new implementation + MockWatcherPrecompileImpl newImpl = new MockWatcherPrecompileImpl(); + + // Store old implementation address for verification + address oldImpl = getImplementation(address(watcherPrecompile)); + + // Prepare initialization data with new defaultLimit + uint256 newDefaultLimit = 2000; + bytes memory initData = abi.encodeWithSelector( + MockWatcherPrecompileImpl.mockReinitialize.selector, + watcherEOA, + address(addressResolver), + newDefaultLimit + ); + + // Upgrade proxy with initialization data + vm.startPrank(watcherEOA); + vm.expectEmit(true, true, true, true, address(proxyFactory)); + emit Upgraded(address(watcherPrecompile), address(newImpl)); + proxyFactory.upgradeAndCall(address(watcherPrecompile), address(newImpl), initData); + vm.stopPrank(); + + // Verify upgrade and initialization was successful + address newImplAddr = getImplementation(address(watcherPrecompile)); + assertNotEq(oldImpl, newImplAddr, "Implementation should have changed"); + assertEq(newImplAddr, address(newImpl), "New implementation not set correctly"); + assertEq(watcherPrecompile.evmxSlug(), evmxSlug, "EvmxSlug should be preserved"); + } + + function testUnauthorizedUpgrade() public { + // Deploy new implementation + WatcherPrecompile newImpl = new WatcherPrecompile(); + + // Try to upgrade from unauthorized account + address unauthorizedUser = address(0xBEEF); + vm.startPrank(unauthorizedUser); + vm.expectRevert(UNAUTHORIZED_SELECTOR); + proxyFactory.upgradeAndCall(address(watcherPrecompile), address(newImpl), ""); + vm.stopPrank(); + + // Verify implementation was not changed + assertEq( + getImplementation(address(watcherPrecompile)), + address(watcherPrecompileImpl), + "Implementation should not have changed" + ); + } + + function testForwarderBeaconUpgrade() public { + // Deploy new implementation + Forwarder newImpl = new Forwarder(); + + // Get current implementation from beacon + address oldImpl = getBeaconImplementation(address(addressResolver.forwarderBeacon())); + + // Upgrade beacon to new implementation + vm.startPrank(watcherEOA); + vm.expectEmit(true, true, true, true, address(addressResolver)); + emit ImplementationUpdated("Forwarder", address(newImpl)); + addressResolver.setForwarderImplementation(address(newImpl)); + vm.stopPrank(); + + // Verify upgrade was successful + address newImplAddr = getBeaconImplementation(address(addressResolver.forwarderBeacon())); + assertNotEq(oldImpl, newImplAddr, "Implementation should have changed"); + assertEq(newImplAddr, address(newImpl), "New implementation not set correctly"); + + // Deploy a new forwarder and verify it uses the correct beacon + address newForwarder = addressResolver.getOrDeployForwarderContract( + address(this), + address(0x123), + 1 + ); + address beacon = getBeacon(newForwarder); + assertEq( + beacon, + address(addressResolver.forwarderBeacon()), + "Beacon address not set correctly" + ); + + // Get implementation from beacon and verify it matches + address implFromBeacon = getBeaconImplementation(beacon); + assertEq( + implFromBeacon, + address(newImpl), + "Beacon implementation should match new implementation" + ); + } + + function testAsyncPromiseBeaconUpgrade() public { + // Deploy new implementation + AsyncPromise newImpl = new AsyncPromise(); + + // Get current implementation from beacon + address oldImpl = getBeaconImplementation(address(addressResolver.asyncPromiseBeacon())); + + // Upgrade beacon to new implementation + vm.startPrank(watcherEOA); + vm.expectEmit(true, true, true, true, address(addressResolver)); + emit ImplementationUpdated("AsyncPromise", address(newImpl)); + addressResolver.setAsyncPromiseImplementation(address(newImpl)); + vm.stopPrank(); + + // Verify upgrade was successful + address newImplAddr = getBeaconImplementation( + address(addressResolver.asyncPromiseBeacon()) + ); + assertNotEq(oldImpl, newImplAddr, "Implementation should have changed"); + assertEq(newImplAddr, address(newImpl), "New implementation not set correctly"); + + // Deploy a new async promise and verify it uses the correct beacon + address newPromise = addressResolver.deployAsyncPromiseContract(address(this)); + address beacon = getBeacon(newPromise); + assertEq( + beacon, + address(addressResolver.asyncPromiseBeacon()), + "Beacon address not set correctly" + ); + + // Get implementation from beacon and verify it matches + address implFromBeacon = getBeaconImplementation(beacon); + assertEq( + implFromBeacon, + address(newImpl), + "Beacon implementation should match new implementation" + ); + } + + function testUnauthorizedBeaconUpgrade() public { + // Deploy new implementations + Forwarder newForwarderImpl = new Forwarder(); + AsyncPromise newAsyncPromiseImpl = new AsyncPromise(); + + // Try to upgrade from unauthorized account + address unauthorizedUser = address(0xBEEF); + + vm.startPrank(unauthorizedUser); + // Try upgrading forwarder beacon + vm.expectRevert(abi.encodeWithSelector(Ownable.Unauthorized.selector)); + addressResolver.setForwarderImplementation(address(newForwarderImpl)); + + // Try upgrading async promise beacon + vm.expectRevert(abi.encodeWithSelector(Ownable.Unauthorized.selector)); + addressResolver.setAsyncPromiseImplementation(address(newAsyncPromiseImpl)); + + vm.stopPrank(); + + // Verify implementations were not changed + assertNotEq( + getBeaconImplementation(address(addressResolver.forwarderBeacon())), + address(newForwarderImpl), + "Forwarder implementation should not have changed" + ); + assertNotEq( + getBeaconImplementation(address(addressResolver.asyncPromiseBeacon())), + address(newAsyncPromiseImpl), + "AsyncPromise implementation should not have changed" + ); + } +} diff --git a/test/SetupTest.t.sol b/test/SetupTest.t.sol index 8a6ee9a2..4735ab79 100644 --- a/test/SetupTest.t.sol +++ b/test/SetupTest.t.sol @@ -1,14 +1,14 @@ -// SPDX-License-Identifier: MIT -pragma solidity ^0.8.0; +// SPDX-License-Identifier: GPL-3.0-only +pragma solidity ^0.8.21; import "forge-std/Test.sol"; import "../contracts/protocol/utils/common/Structs.sol"; import "../contracts/protocol/utils/common/Errors.sol"; import "../contracts/protocol/utils/common/Constants.sol"; -import "../contracts/protocol/watcherPrecompile/WatcherPrecompile.sol"; +import "../contracts/protocol/watcherPrecompile/core/WatcherPrecompile.sol"; import "../contracts/protocol/watcherPrecompile/WatcherPrecompileConfig.sol"; import "../contracts/protocol/watcherPrecompile/WatcherPrecompileLimits.sol"; -import "../contracts/protocol/watcherPrecompile/DumpDecoder.sol"; +import "../contracts/protocol/watcherPrecompile/PayloadHeaderDecoder.sol"; import "../contracts/interfaces/IForwarder.sol"; import "../contracts/protocol/utils/common/AccessRoles.sol"; import {Socket} from "../contracts/protocol/socket/Socket.sol"; @@ -17,14 +17,15 @@ import "../contracts/protocol/socket/SocketBatcher.sol"; import "../contracts/protocol/AddressResolver.sol"; import {ContractFactoryPlug} from "../contracts/protocol/payload-delivery/ContractFactoryPlug.sol"; import {FeesPlug} from "../contracts/protocol/payload-delivery/FeesPlug.sol"; - +import {SocketFeeManager} from "../contracts/protocol/socket/SocketFeeManager.sol"; import {ETH_ADDRESS} from "../contracts/protocol/utils/common/Constants.sol"; -import {ResolvedPromises} from "../contracts/protocol/utils/common/Structs.sol"; +import {ResolvedPromises, OnChainFees} from "../contracts/protocol/utils/common/Structs.sol"; import "solady/utils/ERC1967Factory.sol"; +import "./apps/app-gateways/USDC.sol"; contract SetupTest is Test { - using DumpDecoder for bytes32; + using PayloadHeaderDecoder for bytes32; uint public c = 1; address owner = address(uint160(c++)); @@ -40,10 +41,11 @@ contract SetupTest is Test { uint32 evmxSlug = 1; uint256 expiryTime = 10000000; uint256 maxReAuctionCount = 10; - + uint256 socketFees = 0.01 ether; uint256 public signatureNonce; uint256 public payloadIdCounter; uint256 public timeoutIdCounter; + uint256 public triggerCounter; uint256 public defaultLimit = 1000; bytes public asyncPromiseBytecode = type(AsyncPromise).creationCode; @@ -52,10 +54,12 @@ contract SetupTest is Test { struct SocketContracts { uint32 chainSlug; Socket socket; + SocketFeeManager socketFeeManager; FastSwitchboard switchboard; SocketBatcher socketBatcher; ContractFactoryPlug contractFactoryPlug; FeesPlug feesPlug; + ERC20 feesTokenUSDC; } AddressResolver public addressResolver; @@ -74,7 +78,7 @@ contract SetupTest is Test { ERC1967Factory public proxyFactory; event Initialized(uint64 version); - event FinalizeRequested(bytes32 payloadId, PayloadParams payloadParams); + event FinalizeRequested(bytes32 digest, PayloadParams payloadParams); //////////////////////////////////// Setup //////////////////////////////////// @@ -83,10 +87,12 @@ contract SetupTest is Test { SocketBatcher socketBatcher = new SocketBatcher(owner, socket); FastSwitchboard switchboard = new FastSwitchboard(chainSlug_, socket, owner); + ERC20 feesTokenUSDC = new USDC("USDC", "USDC", 6, owner, 1000000000000000000000000); FeesPlug feesPlug = new FeesPlug(address(socket), owner); ContractFactoryPlug contractFactoryPlug = new ContractFactoryPlug(address(socket), owner); - vm.startPrank(owner); + // feePlug whitelist token + feesPlug.whitelistToken(address(feesTokenUSDC)); // socket socket.grantRole(GOVERNANCE_ROLE, address(owner)); @@ -102,7 +108,7 @@ contract SetupTest is Test { address(contractFactoryPlug), address(feesPlug) ); - + SocketFeeManager socketFeeManager = new SocketFeeManager(owner, socketFees); hoax(watcherEOA); watcherPrecompileConfig.setSwitchboard(chainSlug_, FAST, address(switchboard)); @@ -110,10 +116,12 @@ contract SetupTest is Test { SocketContracts({ chainSlug: chainSlug_, socket: socket, + socketFeeManager: socketFeeManager, switchboard: switchboard, socketBatcher: socketBatcher, contractFactoryPlug: contractFactoryPlug, - feesPlug: feesPlug + feesPlug: feesPlug, + feesTokenUSDC: feesTokenUSDC }); } @@ -191,6 +199,11 @@ contract SetupTest is Test { vm.startPrank(watcherEOA); addressResolver.setWatcherPrecompile(address(watcherPrecompile)); + watcherPrecompileLimits.setCallBackFees(1); + watcherPrecompileLimits.setFinalizeFees(1); + watcherPrecompileLimits.setQueryFees(1); + watcherPrecompileLimits.setTimeoutFees(1); + vm.stopPrank(); } @@ -201,7 +214,7 @@ contract SetupTest is Test { for (uint i = 0; i < payloadIds.length; i++) { PayloadParams memory payloadParams = watcherPrecompile.getPayloadParams(payloadIds[i]); - if (payloadParams.dump.getCallType() != CallType.READ) { + if (payloadParams.payloadHeader.getCallType() != CallType.READ) { return false; } } @@ -235,7 +248,7 @@ contract SetupTest is Test { PayloadParams memory payloadParams = watcherPrecompile.getPayloadParams(payloadIds[i]); bool isLastPayload = i == payloadIds.length - 1 && hasMoreBatches; - if (payloadParams.dump.getCallType() == CallType.READ) { + if (payloadParams.payloadHeader.getCallType() == CallType.READ) { _resolveAndExpectFinalizeRequested( payloadParams.payloadId, payloadParams, @@ -243,7 +256,7 @@ contract SetupTest is Test { isLastPayload ); } else { - bytes memory returnData = _uploadProofAndExecute(payloadParams); + (, bytes memory returnData) = _uploadProofAndExecute(payloadParams); _resolveAndExpectFinalizeRequested( payloadParams.payloadId, payloadParams, @@ -257,7 +270,7 @@ contract SetupTest is Test { function _uploadProofAndExecute( PayloadParams memory payloadParams - ) internal returns (bytes memory) { + ) internal returns (bool, bytes memory) { (bytes memory watcherProof, bytes32 digest) = _generateWatcherProof(payloadParams); _writeProof(payloadParams.payloadId, watcherProof); @@ -265,10 +278,19 @@ contract SetupTest is Test { ExecuteParams memory params, SocketBatcher socketBatcher, , - bytes memory transmitterSig + bytes memory transmitterSig, + address refundAddress ) = _getExecuteParams(payloadParams); - return socketBatcher.attestAndExecute(params, digest, watcherProof, transmitterSig); + return + socketBatcher.attestAndExecute( + params, + payloadParams.switchboard, + digest, + watcherProof, + transmitterSig, + refundAddress + ); } function resolvePromises(bytes32[] memory payloadIds, bytes[] memory returnData) internal { @@ -282,31 +304,28 @@ contract SetupTest is Test { return chainSlug_ == arbChainSlug ? arbConfig : optConfig; } - function createFees(uint256 maxFees_) internal view returns (Fees memory) { - return Fees({feePoolChain: arbChainSlug, feePoolToken: ETH_ADDRESS, amount: maxFees_}); - } - function _generateWatcherProof( PayloadParams memory params_ ) internal view returns (bytes memory, bytes32) { - SocketContracts memory socketConfig = getSocketConfig(params_.dump.getChainSlug()); + SocketContracts memory socketConfig = getSocketConfig(params_.payloadHeader.getChainSlug()); DigestParams memory digestParams_ = DigestParams( + address(socketConfig.socket), transmitterEOA, params_.payloadId, params_.deadline, - params_.dump.getCallType(), - params_.dump.getWriteFinality(), + params_.payloadHeader.getCallType(), params_.gasLimit, params_.value, - params_.readAt, params_.payload, params_.target, - params_.appGateway, + _encodeAppGatewayId(params_.appGateway), params_.prevDigestsHash ); bytes32 digest = watcherPrecompile.getDigest(digestParams_); - bytes32 sigDigest = keccak256(abi.encode(address(socketConfig.switchboard), digest)); + bytes32 sigDigest = keccak256( + abi.encode(address(socketConfig.switchboard), socketConfig.chainSlug, digest) + ); bytes memory proof = _createSignature(sigDigest, watcherPrivateKey); return (proof, digest); } @@ -334,32 +353,35 @@ contract SetupTest is Test { ExecuteParams memory params, SocketBatcher socketBatcher, uint256 value, - bytes memory transmitterSig + bytes memory transmitterSig, + address refundAddress ) { - SocketContracts memory socketConfig = getSocketConfig(payloadParams.dump.getChainSlug()); + SocketContracts memory socketConfig = getSocketConfig( + payloadParams.payloadHeader.getChainSlug() + ); bytes32 transmitterDigest = keccak256( abi.encode(address(socketConfig.socket), payloadParams.payloadId) ); transmitterSig = _createSignature(transmitterDigest, transmitterPrivateKey); params = ExecuteParams({ + callType: payloadParams.payloadHeader.getCallType(), deadline: payloadParams.deadline, - callType: payloadParams.dump.getCallType(), - writeFinality: payloadParams.dump.getWriteFinality(), gasLimit: payloadParams.gasLimit, - readAt: payloadParams.readAt, + value: payloadParams.value, payload: payloadParams.payload, target: payloadParams.target, - requestCount: payloadParams.dump.getRequestCount(), - batchCount: payloadParams.dump.getBatchCount(), - payloadCount: payloadParams.dump.getPayloadCount(), + requestCount: payloadParams.payloadHeader.getRequestCount(), + batchCount: payloadParams.payloadHeader.getBatchCount(), + payloadCount: payloadParams.payloadHeader.getPayloadCount(), prevDigestsHash: payloadParams.prevDigestsHash, - switchboard: payloadParams.switchboard + extraData: bytes("") }); value = payloadParams.value; socketBatcher = socketConfig.socketBatcher; + refundAddress = transmitterEOA; } function _resolvePromise(bytes32 payloadId, bytes memory returnData) internal { @@ -398,4 +420,12 @@ contract SetupTest is Test { mstore(add(sig, 64), sigS) } } + + 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/test/SocketFeeManager.t.sol b/test/SocketFeeManager.t.sol new file mode 100644 index 00000000..e57dc809 --- /dev/null +++ b/test/SocketFeeManager.t.sol @@ -0,0 +1,151 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.0; + +import {CounterAppGateway} from "./apps/app-gateways/counter/CounterAppGateway.sol"; +import {Counter} from "./apps/app-gateways/counter/Counter.sol"; +import "./SetupTest.t.sol"; +import {SocketFeeManager} from "../contracts/protocol/socket/SocketFeeManager.sol"; +import {MockFastSwitchboard} from "./mock/MockFastSwitchboard.sol"; +import {ExecuteParams, TransmissionParams, CallType, WriteFinality} from "../contracts/protocol/utils/common/Structs.sol"; +import {GOVERNANCE_ROLE, RESCUE_ROLE} from "../contracts/protocol/utils/common/AccessRoles.sol"; +import {Test} from "forge-std/Test.sol"; + +contract SocketFeeManagerTest is SetupTest { + Counter public counter; + address public gateway = address(5); + MockFastSwitchboard public mockSwitchboard; + Socket public socket; + SocketFeeManager public socketFeeManager; + + function setUp() public { + socket = new Socket(arbChainSlug, owner, "test"); + vm.prank(owner); + socket.grantRole(GOVERNANCE_ROLE, address(owner)); + socketFeeManager = new SocketFeeManager(owner, socketFees); + mockSwitchboard = new MockFastSwitchboard(arbChainSlug, address(socket), owner); + mockSwitchboard.registerSwitchboard(); + + counter = new Counter(); + counter.initSocket(_encodeAppGatewayId(gateway), address(socket), address(mockSwitchboard)); + } + + function testSuccessfulExecutionWithFeeManagerNotSet() public { + // Create execute params + ( + ExecuteParams memory executeParams, + TransmissionParams memory transmissionParams + ) = _getExecutionParams(abi.encodeWithSelector(Counter.increase.selector)); + + // Fund the contract with enough ETH for fees + vm.deal(address(this), socketFees); + + // Execute with fees + socket.execute{value: socketFees}(executeParams, transmissionParams); + + // Check counter was incremented + assertEq(counter.counter(), 1, "Counter should be incremented"); + assertEq( + address(socketFeeManager).balance, + 0, + "Socket fee manager should have 0 balance when fees manager not set" + ); + } + + function testSuccessfulExecutionWithFeeManagerSet() public { + // Set the fee manager in socket + vm.prank(owner); + socket.setSocketFeeManager(address(socketFeeManager)); + + // Create execute params + ( + ExecuteParams memory executeParams, + TransmissionParams memory transmissionParams + ) = _getExecutionParams(abi.encodeWithSelector(Counter.increase.selector)); + + // Fund the contract with enough ETH for fees + vm.deal(address(this), socketFees); + + // Execute with fees + socket.execute{value: socketFees}(executeParams, transmissionParams); + + // Check counter was incremented + assertEq(counter.counter(), 1, "Counter should be incremented"); + assertEq( + address(socketFeeManager).balance, + socketFees, + "Socket fee manager should have received the fees" + ); + } + + function testSuccessfulExecutionWithInsufficientFees() public { + // Set the fee manager in socket + vm.prank(owner); + socket.setSocketFeeManager(address(socketFeeManager)); + + // Create execute params + ( + ExecuteParams memory executeParams, + TransmissionParams memory transmissionParams + ) = _getExecutionParams(abi.encodeWithSelector(Counter.increase.selector)); + + // Fund with insufficient ETH + vm.deal(address(this), socketFees - 1); + + // Should revert with InsufficientMsgValue + vm.expectRevert(Socket.InsufficientMsgValue.selector); + socket.execute{value: socketFees - 1}(executeParams, transmissionParams); + } + + function testExecutionFailed() public { + // Set the fee manager in socket + vm.prank(owner); + socket.setSocketFeeManager(address(socketFeeManager)); + + // Create execute params with invalid payload to force failure + ( + ExecuteParams memory executeParams, + TransmissionParams memory transmissionParams + ) = _getExecutionParams(abi.encodeWithSelector(bytes4(keccak256("invalid()")))); + + // Fund the contract with enough ETH for fees + vm.deal(address(this), socketFees); + + // Execute with fees + socket.execute{value: socketFees}(executeParams, transmissionParams); + + // Check counter was not incremented + assertEq(counter.counter(), 0, "Counter should not be incremented"); + assertEq( + address(socketFeeManager).balance, + 0, + "Socket fee manager should not receive fees on failed execution" + ); + } + + function _getExecutionParams( + bytes memory payload + ) internal view returns (ExecuteParams memory, TransmissionParams memory) { + ExecuteParams memory executeParams = ExecuteParams({ + callType: CallType.WRITE, + deadline: block.timestamp + 1 days, + gasLimit: 100000, + value: 0, + payload: payload, + target: address(counter), + requestCount: 0, + batchCount: 0, + payloadCount: 0, + prevDigestsHash: bytes32(0), + extraData: bytes("") + }); + + TransmissionParams memory transmissionParams = TransmissionParams({ + transmitterSignature: bytes(""), + socketFees: socketFees, + extraData: bytes(""), + refundAddress: transmitterEOA + }); + + return (executeParams, transmissionParams); + } +} diff --git a/test/Storage.t.sol b/test/Storage.t.sol index 67f31252..523d7834 100644 --- a/test/Storage.t.sol +++ b/test/Storage.t.sol @@ -1,5 +1,5 @@ -// SPDX-License-Identifier: MIT -pragma solidity ^0.8.0; +// SPDX-License-Identifier: GPL-3.0-only +pragma solidity ^0.8.21; import "./DeliveryHelper.t.sol"; diff --git a/test/apps/Counter.t.sol b/test/apps/Counter.t.sol index 6462a03f..f2c58c2c 100644 --- a/test/apps/Counter.t.sol +++ b/test/apps/Counter.t.sol @@ -1,4 +1,4 @@ -// SPDX-License-Identifier: UNLICENSED +// SPDX-License-Identifier: GPL-3.0-only pragma solidity ^0.8.21; import {CounterAppGateway} from "./app-gateways/counter/CounterAppGateway.sol"; @@ -16,8 +16,15 @@ contract CounterTest is DeliveryHelperTest { function deploySetup() internal { setUpDeliveryHelper(); - counterGateway = new CounterAppGateway(address(addressResolver), createFees(feesAmount)); - depositFees(address(counterGateway), createFees(1 ether)); + counterGateway = new CounterAppGateway(address(addressResolver), feesAmount); + depositUSDCFees( + address(counterGateway), + OnChainFees({ + chainSlug: arbChainSlug, + token: address(arbConfig.feesTokenUSDC), + amount: 1 ether + }) + ); counterId = counterGateway.counter(); contractIds[0] = counterId; diff --git a/test/apps/ParallelCounter.t.sol b/test/apps/ParallelCounter.t.sol index b7fdd7bf..5e7e193f 100644 --- a/test/apps/ParallelCounter.t.sol +++ b/test/apps/ParallelCounter.t.sol @@ -1,4 +1,4 @@ -// SPDX-License-Identifier: UNLICENSED +// SPDX-License-Identifier: GPL-3.0-only pragma solidity ^0.8.21; import {CounterAppGateway} from "./app-gateways/counter/CounterAppGateway.sol"; @@ -16,11 +16,15 @@ contract ParallelCounterTest is DeliveryHelperTest { function deploySetup() internal { setUpDeliveryHelper(); - parallelCounterGateway = new CounterAppGateway( - address(addressResolver), - createFees(feesAmount) + parallelCounterGateway = new CounterAppGateway(address(addressResolver), feesAmount); + depositUSDCFees( + address(parallelCounterGateway), + OnChainFees({ + chainSlug: arbChainSlug, + token: address(arbConfig.feesTokenUSDC), + amount: 1 ether + }) ); - depositFees(address(parallelCounterGateway), createFees(1 ether)); counterId1 = parallelCounterGateway.counter1(); counterId2 = parallelCounterGateway.counter(); contractIds[0] = counterId1; diff --git a/test/apps/SuperToken.t.sol b/test/apps/SuperToken.t.sol index 45d62893..bd5c88ab 100644 --- a/test/apps/SuperToken.t.sol +++ b/test/apps/SuperToken.t.sol @@ -1,5 +1,5 @@ -// SPDX-License-Identifier: MIT -pragma solidity ^0.8.0; +// SPDX-License-Identifier: GPL-3.0-only +pragma solidity ^0.8.21; import {SuperTokenAppGateway} from "./app-gateways/super-token/SuperTokenAppGateway.sol"; import {SuperToken} from "./app-gateways/super-token/SuperToken.sol"; @@ -65,7 +65,7 @@ contract SuperTokenTest is DeliveryHelperTest { SuperTokenAppGateway superTokenApp = new SuperTokenAppGateway( address(addressResolver), owner, - createFees(maxFees), + maxFees, SuperTokenAppGateway.ConstructorParams({ name_: "SUPER TOKEN", symbol_: "SUPER", @@ -76,7 +76,14 @@ contract SuperTokenTest is DeliveryHelperTest { ); // Enable app gateways to do all operations in the Watcher: Read, Write and Schedule on EVMx // Watcher sets the limits for apps in this SOCKET protocol version - depositFees(address(superTokenApp), createFees(1 ether)); + depositUSDCFees( + address(superTokenApp), + OnChainFees({ + chainSlug: arbChainSlug, + token: address(arbConfig.feesTokenUSDC), + amount: 1 ether + }) + ); appContracts = AppContracts({ superTokenApp: superTokenApp, diff --git a/test/apps/app-gateways/USDC.sol b/test/apps/app-gateways/USDC.sol new file mode 100644 index 00000000..df08d360 --- /dev/null +++ b/test/apps/app-gateways/USDC.sol @@ -0,0 +1,41 @@ +pragma solidity ^0.8.21; + +import "solady/tokens/ERC20.sol"; + +contract USDC is ERC20 { + string private _name; + string private _symbol; + uint8 private _decimals; + + constructor( + string memory name_, + string memory symbol_, + uint8 decimals_, + address initialSupplyHolder_, + uint256 initialSupply_ + ) { + _name = name_; + _symbol = symbol_; + _decimals = decimals_; + _mint(initialSupplyHolder_, initialSupply_); + } + function name() public view virtual override returns (string memory) { + return _name; + } + + function symbol() public view virtual override returns (string memory) { + return _symbol; + } + + function decimals() public view virtual override returns (uint8) { + return _decimals; + } + + function mint(address to, uint256 amount) external { + _mint(to, amount); + } + + function burn(address from, uint256 amount) external { + _burn(from, amount); + } +} diff --git a/test/apps/app-gateways/counter/Counter.sol b/test/apps/app-gateways/counter/Counter.sol index 9625635d..214bab43 100644 --- a/test/apps/app-gateways/counter/Counter.sol +++ b/test/apps/app-gateways/counter/Counter.sol @@ -1,12 +1,17 @@ -// SPDX-License-Identifier: GPL-3.0 -pragma solidity >=0.7.0 <0.9.0; +// SPDX-License-Identifier: GPL-3.0-only +pragma solidity ^0.8.21; import "solady/auth/Ownable.sol"; import "../../../../contracts/base/PlugBase.sol"; +interface ICounterAppGateway { + function increase(uint256 value_) external returns (bytes32); +} + contract Counter is Ownable, PlugBase { uint256 public counter; event CounterIncreased(uint256 value); + function increase() external onlySocket { counter++; emit CounterIncreased(counter); @@ -17,6 +22,7 @@ contract Counter is Ownable, PlugBase { } function increaseOnGateway(uint256 value_) external returns (bytes32) { - return _callAppGateway(abi.encode(value_), bytes32(0)); + // can set overrides here: _setOverrides(params_); + return ICounterAppGateway(address(socket__)).increase(value_); } } diff --git a/test/apps/app-gateways/counter/CounterAppGateway.sol b/test/apps/app-gateways/counter/CounterAppGateway.sol index deef0e53..f5bca934 100644 --- a/test/apps/app-gateways/counter/CounterAppGateway.sol +++ b/test/apps/app-gateways/counter/CounterAppGateway.sol @@ -1,5 +1,5 @@ -// SPDX-License-Identifier: GPL-3.0 -pragma solidity >=0.7.0 <0.9.0; +// SPDX-License-Identifier: GPL-3.0-only +pragma solidity ^0.8.21; import "../../../../contracts/base/AppGatewayBase.sol"; import "../../../../contracts/interfaces/IForwarder.sol"; @@ -17,15 +17,15 @@ contract CounterAppGateway is AppGatewayBase, Ownable { uint256 public optCounter; event TimeoutResolved(uint256 creationTimestamp, uint256 executionTimestamp); - constructor(address addressResolver_, Fees memory fees_) AppGatewayBase(addressResolver_) { + constructor(address addressResolver_, uint256 fees_) AppGatewayBase(addressResolver_) { creationCodeWithArgs[counter] = abi.encodePacked(type(Counter).creationCode); creationCodeWithArgs[counter1] = abi.encodePacked(type(Counter).creationCode); - _setOverrides(fees_); + _setMaxFees(fees_); _initializeOwner(msg.sender); } // deploy contracts - function deployContracts(uint32 chainSlug_) external async { + function deployContracts(uint32 chainSlug_) external async(bytes("")) { _deploy(counter, chainSlug_, IsPlug.YES); } @@ -33,14 +33,14 @@ contract CounterAppGateway is AppGatewayBase, Ownable { _deploy(counter, chainSlug_, IsPlug.YES); } - function deployParallelContracts(uint32 chainSlug_) external async { + function deployParallelContracts(uint32 chainSlug_) external async(bytes("")) { _setOverrides(Parallel.ON); _deploy(counter, chainSlug_, IsPlug.YES); _deploy(counter1, chainSlug_, IsPlug.YES); _setOverrides(Parallel.OFF); } - function deployMultiChainContracts(uint32[] memory chainSlugs_) external async { + function deployMultiChainContracts(uint32[] memory chainSlugs_) external async(bytes("")) { _setOverrides(Parallel.ON); for (uint32 i = 0; i < chainSlugs_.length; i++) { _deploy(counter, chainSlugs_[i], IsPlug.YES); @@ -53,7 +53,7 @@ contract CounterAppGateway is AppGatewayBase, Ownable { return; } - function incrementCounters(address[] memory instances_) public async { + function incrementCounters(address[] memory instances_) public async(bytes("")) { // the increase function is called on given list of instances // this for (uint256 i = 0; i < instances_.length; i++) { @@ -69,7 +69,7 @@ contract CounterAppGateway is AppGatewayBase, Ownable { } } - function readCounters(address[] memory instances_) public async { + function readCounters(address[] memory instances_) public async(bytes("")) { // the increase function is called on given list of instances _setOverrides(Read.ON, Parallel.ON); for (uint256 i = 0; i < instances_.length; i++) { @@ -80,7 +80,7 @@ contract CounterAppGateway is AppGatewayBase, Ownable { _setOverrides(Read.OFF, Parallel.OFF); } - function readCounterAtBlock(address instance_, uint256 blockNumber_) public async { + function readCounterAtBlock(address instance_, uint256 blockNumber_) public async(bytes("")) { uint32 chainSlug = IForwarder(instance_).getChainSlug(); _setOverrides(Read.ON, Parallel.ON, blockNumber_); ICounter(instance_).getCounter(); @@ -97,19 +97,13 @@ contract CounterAppGateway is AppGatewayBase, Ownable { } } - // INBOX + // trigger from a chain function setIsValidPlug(uint32 chainSlug_, address plug_) public { watcherPrecompileConfig().setIsValidPlug(chainSlug_, plug_, true); } - function callFromChain( - uint32, - address, - bytes32, - bytes calldata payload_ - ) external override onlyWatcherPrecompile { - uint256 value = abi.decode(payload_, (uint256)); - counterVal += value; + function increase(uint256 value_) external onlyWatcherPrecompile { + counterVal += value_; } // TIMEOUT @@ -126,8 +120,8 @@ contract CounterAppGateway is AppGatewayBase, Ownable { } // UTILS - function setFees(Fees memory fees_) public { - fees = fees_; + function setMaxFees(uint256 fees_) public { + maxFees = fees_; } function withdrawFeeTokens( @@ -139,12 +133,12 @@ contract CounterAppGateway is AppGatewayBase, Ownable { return _withdrawFeeTokens(chainSlug_, token_, amount_, receiver_); } - function testOnChainRevert(uint32 chainSlug) public async { + function testOnChainRevert(uint32 chainSlug) public async(bytes("")) { address instance = forwarderAddresses[counter][chainSlug]; ICounter(instance).wrongFunction(); } - function testCallBackRevert(uint32 chainSlug) public async { + function testCallBackRevert(uint32 chainSlug) public async(bytes("")) { // the increase function is called on given list of instances _setOverrides(Read.ON, Parallel.ON); address instance = forwarderAddresses[counter][chainSlug]; diff --git a/test/apps/app-gateways/counter/ICounter.sol b/test/apps/app-gateways/counter/ICounter.sol index 0bb3b319..453fde1c 100644 --- a/test/apps/app-gateways/counter/ICounter.sol +++ b/test/apps/app-gateways/counter/ICounter.sol @@ -1,5 +1,5 @@ -// SPDX-License-Identifier: MIT -pragma solidity >=0.7.0 <0.9.0; +// SPDX-License-Identifier: GPL-3.0-only +pragma solidity ^0.8.21; interface ICounter { function increase() external; diff --git a/test/apps/app-gateways/super-token/ISuperToken.sol b/test/apps/app-gateways/super-token/ISuperToken.sol index 77dbb353..75923f3d 100644 --- a/test/apps/app-gateways/super-token/ISuperToken.sol +++ b/test/apps/app-gateways/super-token/ISuperToken.sol @@ -1,4 +1,4 @@ -// SPDX-License-Identifier: MIT +// SPDX-License-Identifier: GPL-3.0-only pragma solidity ^0.8.21; interface ISuperToken { diff --git a/test/apps/app-gateways/super-token/SuperToken.sol b/test/apps/app-gateways/super-token/SuperToken.sol index 8c6de970..f1bf0247 100644 --- a/test/apps/app-gateways/super-token/SuperToken.sol +++ b/test/apps/app-gateways/super-token/SuperToken.sol @@ -1,4 +1,4 @@ -// SPDX-License-Identifier: MIT +// SPDX-License-Identifier: GPL-3.0-only pragma solidity ^0.8.21; import "solady/tokens/ERC20.sol"; diff --git a/test/apps/app-gateways/super-token/SuperTokenAppGateway.sol b/test/apps/app-gateways/super-token/SuperTokenAppGateway.sol index c1cd0457..fbaf6f15 100644 --- a/test/apps/app-gateways/super-token/SuperTokenAppGateway.sol +++ b/test/apps/app-gateways/super-token/SuperTokenAppGateway.sol @@ -1,4 +1,4 @@ -// SPDX-License-Identifier: Unlicense +// SPDX-License-Identifier: GPL-3.0-only pragma solidity ^0.8.21; import "solady/auth/Ownable.sol"; @@ -29,7 +29,7 @@ contract SuperTokenAppGateway is AppGatewayBase, Ownable { constructor( address addressResolver_, address owner_, - Fees memory fees_, + uint256 fees_, ConstructorParams memory params_ ) AppGatewayBase(addressResolver_) { creationCodeWithArgs[superToken] = abi.encodePacked( @@ -45,11 +45,11 @@ contract SuperTokenAppGateway is AppGatewayBase, Ownable { // sets the fees data like max fees, chain and token for all transfers // they can be updated for each transfer as well - _setOverrides(fees_); + _setMaxFees(fees_); _initializeOwner(owner_); } - function deployContracts(uint32 chainSlug_) external async { + function deployContracts(uint32 chainSlug_) external async(bytes("")) { bytes memory initData = abi.encodeWithSelector(SuperToken.setOwner.selector, owner()); _deploy(superToken, chainSlug_, IsPlug.YES, initData); } @@ -60,7 +60,7 @@ contract SuperTokenAppGateway is AppGatewayBase, Ownable { return; } - function transfer(bytes memory order_) external async { + function transfer(bytes memory order_) external async(bytes("")) { TransferOrder memory order = abi.decode(order_, (TransferOrder)); ISuperToken(order.srcToken).burn(order.user, order.srcAmount); ISuperToken(order.dstToken).mint(order.user, order.srcAmount); diff --git a/test/mock/MockFastSwitchboard.sol b/test/mock/MockFastSwitchboard.sol new file mode 100644 index 00000000..3341fab8 --- /dev/null +++ b/test/mock/MockFastSwitchboard.sol @@ -0,0 +1,36 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.0; + +import "../../contracts/interfaces/ISwitchboard.sol"; +import "../../contracts/interfaces/ISocket.sol"; +contract MockFastSwitchboard is ISwitchboard { + address public owner; + ISocket public immutable socket__; + + // chain slug of deployed chain + uint32 public immutable chainSlug; + + /** + * @dev Constructor of SwitchboardBase + * @param chainSlug_ Chain slug of deployment chain + * @param socket_ socket_ contract + */ + constructor(uint32 chainSlug_, address socket_, address owner_) { + chainSlug = chainSlug_; + socket__ = ISocket(socket_); + owner = owner_; + } + + function attest(bytes32, bytes calldata) external { + // TODO: implement + } + + function allowPayload(bytes32, bytes32) external pure returns (bool) { + // digest has enough attestations + return true; + } + + function registerSwitchboard() external { + socket__.registerSwitchboard(); + } +} diff --git a/test/mock/MockSocket.sol b/test/mock/MockSocket.sol index da1765a9..7f460c2c 100644 --- a/test/mock/MockSocket.sol +++ b/test/mock/MockSocket.sol @@ -1,7 +1,7 @@ // SPDX-License-Identifier: GPL-3.0-only pragma solidity ^0.8.21; -import {PlugDisconnected, InvalidAppGateway} from "../../contracts/protocol/utils/common/Errors.sol"; +import {InvalidAppGateway} from "../../contracts/protocol/utils/common/Errors.sol"; import "../../contracts/interfaces/ISwitchboard.sol"; import "../../contracts/interfaces/ISocket.sol"; @@ -16,7 +16,7 @@ import "../../contracts/interfaces/ISocket.sol"; contract MockSocket is ISocket { struct PlugConfig { // address of the sibling plug on the remote chain - address appGateway; + bytes32 appGatewayId; // switchboard instance for the plug connection ISwitchboard switchboard__; } @@ -26,12 +26,12 @@ contract MockSocket is ISocket { function getPlugConfig( address plugAddress_ - ) external view returns (address appGateway, address switchboard__) { + ) external view returns (bytes32 appGatewayId, address switchboard__) { PlugConfig memory _plugConfig = _plugConfigs[plugAddress_]; - return (_plugConfig.appGateway, address(_plugConfig.switchboard__)); + return (_plugConfig.appGatewayId, address(_plugConfig.switchboard__)); } - function connect(address appGateway_, address switchboard_) external override {} + function connect(bytes32 appGatewayId_, address switchboard_) external override {} function registerSwitchboard() external override {} @@ -53,9 +53,6 @@ contract MockSocket is ISocket { * @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 */ @@ -65,7 +62,7 @@ contract MockSocket is ISocket { //////////////////////////////////////////////////////////// ////////////////////// State Vars ////////////////////////// //////////////////////////////////////////////////////////// - uint64 public callCounter; + uint64 public triggerCounter; uint32 public chainSlug; enum ExecutionStatus { @@ -89,22 +86,20 @@ contract MockSocket is ISocket { /** * @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) { + bytes calldata overrides + ) external returns (bytes32 triggerId) { PlugConfig memory plugConfig = _plugConfigs[msg.sender]; // creates a unique ID for the message - callId = _encodeCallId(plugConfig.appGateway); + triggerId = _encodeTriggerId(plugConfig.appGatewayId); emit AppGatewayCallRequested( - callId, - chainSlug, + triggerId, + plugConfig.appGatewayId, + address(plugConfig.switchboard__), msg.sender, - plugConfig.appGateway, - params, + overrides, payload ); } @@ -113,9 +108,9 @@ contract MockSocket is ISocket { * @notice Executes a payload that has been delivered by transmitters and authenticated by switchboards */ function execute( - ExecuteParams memory executeParams_, - bytes memory transmitterSignature_ - ) external payable override returns (bytes memory) { + ExecuteParams calldata executeParams_, + TransmissionParams calldata transmissionParams_ + ) external payable override returns (bool, bytes memory) { // execute payload // return // _execute( @@ -136,7 +131,7 @@ contract MockSocket is ISocket { 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(); + if (!switchboard__.allowPayload(digest_, payloadId_)) revert VerificationFailed(); } /** @@ -151,13 +146,13 @@ contract MockSocket is ISocket { bytes memory ) internal returns (bytes memory) { bytes memory returnData = hex"00010203"; - emit ExecutionSuccess(payloadId_, returnData); + emit ExecutionSuccess(payloadId_, false, 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. + * @param id_ The ID of the payload 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_) { @@ -165,21 +160,21 @@ contract MockSocket is ISocket { } /** - * @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. + * @dev Decodes the chain ID from a given payload ID. + * @param id_ The ID of the payload to decode the chain slug from. + * @return chainSlug_ The chain slug decoded from the 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) { + // triggerCounter++ will take care of call id overflow as well + // triggerId(256) = localChainSlug(32) | appGateway_(160) | nonce(64) + function _encodeTriggerId(bytes32 appGatewayId_) internal returns (bytes32) { return bytes32( - (uint256(chainSlug) << 224) | (uint256(uint160(appGateway_)) << 64) | callCounter++ + (uint256(chainSlug) << 224) | (uint256(appGatewayId_) << 64) | triggerCounter++ ); } } diff --git a/test/mock/MockWatcherPrecompile.sol b/test/mock/MockWatcherPrecompile.sol index 42247124..3cd1681c 100644 --- a/test/mock/MockWatcherPrecompile.sol +++ b/test/mock/MockWatcherPrecompile.sol @@ -1,13 +1,13 @@ -// SPDX-License-Identifier: Unlicense +// SPDX-License-Identifier: GPL-3.0-only pragma solidity ^0.8.21; import "../../contracts/interfaces/IAppGateway.sol"; import "../../contracts/interfaces/IWatcherPrecompile.sol"; import "../../contracts/interfaces/IPromise.sol"; -import {TimeoutRequest, CallFromChainParams, PlugConfig, ResolvedPromises, AppGatewayConfig} from "../../contracts/protocol/utils/common/Structs.sol"; +import {TimeoutRequest, TriggerParams, 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 {TimeoutDelayTooLarge, TimeoutAlreadyResolved, ResolvingTimeoutTooEarly, CallFailed, AppGatewayAlreadyCalled} from "../../contracts/protocol/utils/common/Errors.sol"; import "solady/utils/ERC1967Factory.sol"; /// @title WatcherPrecompile @@ -16,8 +16,6 @@ contract MockWatcherPrecompile { uint256 public maxTimeoutDelayInSeconds = 24 * 60 * 60; // 24 hours /// @notice Counter for tracking payload execution requests uint256 public payloadCounter; - /// @notice Counter for tracking timeout requests - uint256 public timeoutCounter; /// @notice Mapping to store timeout requests /// @dev timeoutId => TimeoutRequest struct mapping(bytes32 => TimeoutRequest) public timeoutRequests; @@ -26,16 +24,8 @@ contract MockWatcherPrecompile { /// @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 - ); + event CalledAppGateway(bytes32 triggerId); /// @notice Emitted when a new query is requested /// @param chainSlug The identifier of the destination chain @@ -81,9 +71,8 @@ contract MockWatcherPrecompile { /// @param delayInSeconds_ The delay in seconds function setTimeout(bytes calldata payload_, uint256 delayInSeconds_) external { uint256 executeAt = block.timestamp + delayInSeconds_; - bytes32 timeoutId = _encodeTimeoutId(timeoutCounter++); + bytes32 timeoutId = _encodeTimeoutId(); timeoutRequests[timeoutId] = TimeoutRequest( - timeoutId, msg.sender, delayInSeconds_, executeAt, @@ -154,18 +143,11 @@ contract MockWatcherPrecompile { } } - // ================== On-Chain Inbox ================== + // ================== On-Chain Trigger ================== - function callAppGateways(CallFromChainParams[] calldata params_) external { + function callAppGateways(TriggerParams[] 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 - ); + emit CalledAppGateway(params_[i].triggerId); } } @@ -191,9 +173,9 @@ contract MockWatcherPrecompile { ); } - function _encodeTimeoutId(uint256 timeoutCounter_) internal view returns (bytes32) { + function _encodeTimeoutId() internal returns (bytes32) { // watcher address (160 bits) | counter (64 bits) - return bytes32((uint256(uint160(address(this))) << 64) | timeoutCounter_); + return bytes32((uint256(uint160(address(this))) << 64) | payloadCounter++); } /// @notice Retrieves the configuration for a specific plug on a network @@ -204,9 +186,9 @@ contract MockWatcherPrecompile { function getPlugConfigs( uint32 chainSlug_, address plug_ - ) public view returns (address, address) { + ) public view returns (bytes32, address) { return ( - _plugConfigs[chainSlug_][plug_].appGateway, + _plugConfigs[chainSlug_][plug_].appGatewayId, _plugConfigs[chainSlug_][plug_].switchboard ); } diff --git a/test/mock/MockWatcherPrecompileImpl.sol b/test/mock/MockWatcherPrecompileImpl.sol index e888a9bb..86f49e22 100644 --- a/test/mock/MockWatcherPrecompileImpl.sol +++ b/test/mock/MockWatcherPrecompileImpl.sol @@ -1,7 +1,7 @@ -// SPDX-License-Identifier: MIT -pragma solidity ^0.8.0; +// SPDX-License-Identifier: GPL-3.0-only +pragma solidity ^0.8.21; -import "../../contracts/protocol/watcherPrecompile/WatcherPrecompile.sol"; +import "../../contracts/protocol/watcherPrecompile/core/WatcherPrecompile.sol"; contract MockWatcherPrecompileImpl is WatcherPrecompile { // Mock function to test reinitialization with version 2