diff --git a/.gitignore b/.gitignore index f72715cd..99e108e6 100644 --- a/.gitignore +++ b/.gitignore @@ -29,4 +29,5 @@ broadcast/ deployments/local_addresses.json deployments/local_verification.json -testScript.sh \ No newline at end of file +testScript.sh +CLAUDE.md diff --git a/Errors.md b/Errors.md index e374a824..77179268 100644 --- a/Errors.md +++ b/Errors.md @@ -1,73 +1,30 @@ # Custom Error Codes -## base/PlugBase.sol - -| Error | Signature | -| ---------------------------- | ------------ | -| `SocketAlreadyInitialized()` | `0xc9500b00` | - -## interfaces/IWatcherPrecompile.sol - -| 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 - -| Error | Signature | -| ---------------------------- | ------------ | -| `InvalidAppGateway(address)` | `0x0e66940d` | - -## protocol/AsyncPromise.sol +## evmx/fees/FeesPool.sol -| Error | Signature | -| ------------------------------- | ------------ | -| `PromiseAlreadyResolved()` | `0x56b63537` | -| `OnlyForwarderOrLocalInvoker()` | `0xa9fb0b28` | -| `PromiseAlreadySetUp()` | `0x927c53d5` | -| `PromiseRevertFailed()` | `0x0175b9de` | - -## protocol/payload-delivery/AuctionManager.sol +| Error | Signature | +| ------------------ | ------------ | +| `TransferFailed()` | `0x90b8ec18` | -| Error | Signature | -| ---------------------------- | ------------ | -| `InvalidBid()` | `0xc6388ef7` | -| `MaxReAuctionCountReached()` | `0xf2b4388c` | - -## protocol/payload-delivery/ContractFactoryPlug.sol +## evmx/helpers/AsyncPromise.sol | Error | Signature | | -------------------------- | ------------ | -| `DeploymentFailed()` | `0x30116425` | -| `ExecutionFailed()` | `0xacfdb444` | -| `information(bool,,bytes)` | `0x3a82a1f3` | +| `PromiseAlreadyResolved()` | `0x56b63537` | +| `OnlyInvoker()` | `0x74ed21f5` | +| `PromiseAlreadySetUp()` | `0x927c53d5` | +| `PromiseRevertFailed()` | `0x0175b9de` | +| `NotLatestPromise()` | `0x39ca95d3` | -## protocol/payload-delivery/FeesManager.sol +## evmx/plugs/ContractFactoryPlug.sol | Error | Signature | | -------------------------------- | ------------ | -| `InsufficientCreditsAvailable()` | `0xe61dc0aa` | -| `NoFeesForTransmitter()` | `0x248bac55` | -| `NoCreditsBlocked()` | `0xada9eb4c` | -| `InvalidCaller()` | `0x48f5c3ed` | -| `InvalidUserSignature()` | `0xe3fb657c` | -| `AppGatewayNotWhitelisted()` | `0x84e5309f` | -| `InvalidAmount()` | `0x2c5211c6` | -| `InsufficientBalance()` | `0xf4d678b8` | +| `DeploymentFailed()` | `0x30116425` | +| `ExecutionFailed(bytes32,bytes)` | `0xd255d8a3` | +| `information(bool,,bytes)` | `0x3a82a1f3` | -## protocol/payload-delivery/FeesPlug.sol +## evmx/plugs/FeesPlug.sol | Error | Signature | | --------------------------------------------------- | ------------ | @@ -75,119 +32,111 @@ | `InvalidDepositAmount()` | `0xfe9ba5cd` | | `TokenNotWhitelisted(address)` | `0xea3bff2e` | -## protocol/payload-delivery/app-gateway/DeliveryUtils.sol - -| Error | Signature | -| ------------------------------------ | ------------ | -| `PayloadTooLarge()` | `0x492f620d` | -| `OnlyAppGateway()` | `0xfec944ea` | -| `WinningBidExists()` | `0xe8733654` | -| `InsufficientFees()` | `0x8d53e553` | -| `ReadOnlyRequests()` | `0x5f16b0e6` | -| `RequestPayloadCountLimitExceeded()` | `0xcbef144b` | -| `MaxMsgValueLimitExceeded()` | `0x97b4e8ce` | +## evmx/watcher/RequestHandler.sol -## protocol/payload-delivery/app-gateway/FeesHelpers.sol - -| Error | Signature | -| --------------------------------------------- | ------------ | -| `NewMaxFeesLowerThanCurrent(uint256,uint256)` | `0x1345dda1` | +| Error | Signature | +| ----------------------- | ------------ | +| `InsufficientMaxFees()` | `0x0e5bc492` | -## protocol/socket/Socket.sol +## protocol/Socket.sol | Error | Signature | | ----------------------------------------- | ------------ | | `PayloadAlreadyExecuted(ExecutionStatus)` | `0xf4c54edd` | | `VerificationFailed()` | `0x439cc0cd` | | `LowGasLimit()` | `0xd38edae0` | -| `InvalidSlug()` | `0x290a8315` | -| `DeadlinePassed()` | `0x70f65caa` | | `InsufficientMsgValue()` | `0x78f38f76` | -| `ReadOnlyCall()` | `0xcf8fd6f1` | -## protocol/socket/SocketConfig.sol +## protocol/SocketConfig.sol | Error | Signature | | ------------------------------- | ------------ | -| `InvalidConnection()` | `0x63228f29` | -| `InvalidSwitchboard()` | `0xf63c9e4d` | | `SwitchboardExists()` | `0x2dff8555` | | `SwitchboardExistsOrDisabled()` | `0x1c7d2487` | -## protocol/socket/SocketFeeManager.sol +## protocol/SocketFeeManager.sol | Error | Signature | | -------------------- | ------------ | | `InsufficientFees()` | `0x8d53e553` | | `FeeTooLow()` | `0x732f9413` | -## protocol/socket/switchboard/FastSwitchboard.sol +## protocol/SocketUtils.sol + +| Error | Signature | +| -------------------- | ------------ | +| `OnlyOffChain()` | `0x9cbfe066` | +| `SimulationFailed()` | `0x2fbab3ac` | + +## protocol/switchboard/FastSwitchboard.sol | Error | Signature | | ------------------- | ------------ | | `AlreadyAttested()` | `0x35d90805` | | `WatcherNotFound()` | `0xa278e4ad` | -## protocol/utils/AccessControl.sol +## utils/AccessControl.sol | Error | Signature | | ------------------- | ------------ | | `NoPermit(bytes32)` | `0x962f6333` | -## protocol/utils/AddressResolverUtil.sol +## utils/common/Errors.sol -| Error | Signature | -| ----------------------------------------- | ------------ | -| `OnlyPayloadDelivery()` | `0x7ccc3a43` | -| `OnlyWatcherPrecompile()` | `0x663a892a` | -| `OnlyWatcherPrecompileOrDeliveryHelper()` | `0xe93a2814` | - -## protocol/utils/common/Errors.sol - -| Error | Signature | -| ---------------------------- | ------------ | -| `NotSocket()` | `0xc59f8f7c` | -| `ZeroAddress()` | `0xd92e233d` | -| `TimeoutDelayTooLarge()` | `0xc10bfe64` | -| `TimeoutAlreadyResolved()` | `0x7dc8be06` | -| `ResolvingTimeoutTooEarly()` | `0x28fd4c50` | -| `LimitReached()` | `0x3dd19101` | -| `FeesAlreadyPaid()` | `0xd3b1ad69` | -| `NotAuctionManager()` | `0x87944c26` | -| `CallFailed()` | `0x3204506f` | -| `PlugNotFound()` | `0x5f1ac76a` | -| `InvalidAppGateway()` | `0x82ded261` | -| `AppGatewayAlreadyCalled()` | `0xb224683f` | -| `InvalidInboxCaller()` | `0x4f1aa61e` | -| `InvalidCallerTriggered()` | `0x3292d247` | -| `PromisesNotResolved()` | `0xb91dbe7d` | -| `InvalidPromise()` | `0x45f2d176` | -| `InvalidTransmitter()` | `0x58a70a0a` | -| `FeesNotSet()` | `0x2a831034` | -| `InvalidTokenAddress()` | `0x1eb00b06` | -| `InvalidWatcherSignature()` | `0x5029f14f` | -| `NonceUsed()` | `0x1f6d5aef` | -| `AuctionClosed()` | `0x36b6b46d` | -| `AuctionAlreadyStarted()` | `0x628e3883` | -| `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` | - -## protocol/watcherPrecompile/WatcherPrecompileLimits.sol - -| Error | Signature | -| ---------------------------- | ------------ | -| `WatcherFeesNotSet(bytes32)` | `0x1ce1de3f` | +| Error | Signature | +| --------------------------------------------- | ------------ | +| `ZeroAddress()` | `0xd92e233d` | +| `InvalidTransmitter()` | `0x58a70a0a` | +| `InvalidTokenAddress()` | `0x1eb00b06` | +| `InvalidSwitchboard()` | `0xf63c9e4d` | +| `SocketAlreadyInitialized()` | `0xc9500b00` | +| `NotSocket()` | `0xc59f8f7c` | +| `PlugNotFound()` | `0x5f1ac76a` | +| `ResolvingScheduleTooEarly()` | `0x207e8731` | +| `CallFailed()` | `0x3204506f` | +| `InvalidAppGateway()` | `0x82ded261` | +| `AppGatewayAlreadyCalled()` | `0xb224683f` | +| `InvalidCallerTriggered()` | `0x3292d247` | +| `InvalidPromise()` | `0x45f2d176` | +| `InvalidWatcherSignature()` | `0x5029f14f` | +| `NonceUsed()` | `0x1f6d5aef` | +| `AsyncModifierNotSet()` | `0xcae106f9` | +| `WatcherNotSet()` | `0x42d473a7` | +| `InvalidTarget()` | `0x82d5d76a` | +| `InvalidIndex()` | `0x63df8171` | +| `InvalidChainSlug()` | `0xbff6b106` | +| `InvalidPayloadSize()` | `0xfbdf7954` | +| `InvalidOnChainAddress()` | `0xb758c606` | +| `InvalidScheduleDelay()` | `0x9a993219` | +| `AuctionClosed()` | `0x36b6b46d` | +| `AuctionNotOpen()` | `0xf0460077` | +| `BidExceedsMaxFees()` | `0x4c923f3c` | +| `LowerBidAlreadyExists()` | `0xaaa1f709` | +| `RequestCountMismatch()` | `0x98bbcbff` | +| `InvalidAmount()` | `0x2c5211c6` | +| `InsufficientCreditsAvailable()` | `0xe61dc0aa` | +| `InsufficientBalance()` | `0xf4d678b8` | +| `InvalidCaller()` | `0x48f5c3ed` | +| `InvalidGateway()` | `0xfc9dfe85` | +| `RequestAlreadyCancelled()` | `0xc70f47d8` | +| `DeadlineNotPassedForOnChainRevert()` | `0x7006aa10` | +| `InvalidBid()` | `0xc6388ef7` | +| `MaxReAuctionCountReached()` | `0xf2b4388c` | +| `MaxMsgValueLimitExceeded()` | `0x97b4e8ce` | +| `OnlyWatcherAllowed()` | `0xdf7d227c` | +| `InvalidPrecompileData()` | `0x320062c0` | +| `InvalidCallType()` | `0x39d2eb55` | +| `NotRequestHandler()` | `0x8f8cba5b` | +| `NotInvoker()` | `0x8a6353d1` | +| `NotPromiseResolver()` | `0x86d876b2` | +| `RequestPayloadCountLimitExceeded()` | `0xcbef144b` | +| `InsufficientFees()` | `0x8d53e553` | +| `RequestAlreadySettled()` | `0x66fad465` | +| `NoWriteRequest()` | `0x9dcd3065` | +| `AlreadyAssigned()` | `0x9688dc51` | +| `OnlyAppGateway()` | `0xfec944ea` | +| `NewMaxFeesLowerThanCurrent(uint256,uint256)` | `0x1345dda1` | +| `InvalidContract()` | `0x6eefed20` | +| `InvalidData()` | `0x5cb045db` | +| `InvalidSignature()` | `0x8baa579f` | +| `DeadlinePassed()` | `0x70f65caa` | diff --git a/EventTopics.md b/EventTopics.md index cb976877..36cadb7d 100644 --- a/EventTopics.md +++ b/EventTopics.md @@ -1,38 +1,115 @@ # Event Topics -## ProxyFactory +## AuctionManager -| Event | Arguments | Topic | -| -------------- | ----------------------------------------------------------- | -------------------------------------------------------------------- | -| `AdminChanged` | `(proxy: address, admin: address)` | `0x7e644d79422f17c01e4894b5f4f588d331ebfa28653d42ae832dc59e38c9798f` | -| `Deployed` | `(proxy: address, implementation: address, admin: address)` | `0xc95935a66d15e0da5e412aca0ad27ae891d20b2fb91cf3994b6a3bf2b8178082` | -| `Upgraded` | `(proxy: address, implementation: address)` | `0x5d611f318680d00598bb735d61bacf0c514c6b50e1e5ad30040a4df2b12791c7` | +| Event | Arguments | Topic | +| ---------------------------- | ------------------------------------------- | -------------------------------------------------------------------- | +| `AuctionEndDelaySecondsSet` | `(auctionEndDelaySeconds: uint256)` | `0xf38f0d9dc8459cf5426728c250d115196a4c065ebc1a6c29da24764a8c0da722` | +| `AuctionEnded` | `(requestCount: uint40, winningBid: tuple)` | `0xede4ec1efc469fac10dcb4930f70be4cd21f3700ed61c91967c19a7cd7c0d86e` | +| `AuctionRestarted` | `(requestCount: uint40)` | `0x071867b21946ec4655665f0d4515d3757a5a52f144c762ecfdfb11e1da542b82` | +| `AuctionStarted` | `(requestCount: uint40)` | `0xcd040613cf8ef0cfcaa3af0d711783e827a275fc647c116b74595bf17cb9364f` | +| `BidPlaced` | `(requestCount: uint40, bid: tuple)` | `0x7f79485e4c9aeea5d4899bc6f7c63b22ac1f4c01d2d28c801e94732fee657b5d` | +| `Initialized` | `(version: uint64)` | `0xc7f505b2f371ae2175ee4913f4499e1f2633a7b5936321eed1cdaeb6115181d2` | +| `MaxReAuctionCountSet` | `(maxReAuctionCount: uint256)` | `0x2f6fadde7ab8ab83d21ab10c3bc09dde179f8696d47c4176581facf0c6f96bbf` | +| `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` | -## TestUSDC +## Socket -| Event | Arguments | Topic | -| ---------- | ----------------------------------------------------- | -------------------------------------------------------------------- | -| `Approval` | `(owner: address, spender: address, amount: uint256)` | `0x8c5be1e5ebec7d5bd14f71427d1e84f3dd0314c0f7b2291e5b200ac8c7c3b925` | -| `Transfer` | `(from: address, to: address, amount: uint256)` | `0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef` | +| 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 + +| Event | Arguments | Topic | +| ---------------------------- | ---------------------------------------- | -------------------------------------------------------------------- | +| `OwnershipHandoverCanceled` | `(pendingOwner: address)` | `0xfa7b8eab7da67f412cc9575ed43464468f9bfbae89d1675917346ca6d8fe3c92` | +| `OwnershipHandoverRequested` | `(pendingOwner: address)` | `0xdbf36a107da19e49527a7176a1babf963b4b0ff8cde35ee35d6cd8f1f9ac7e1d` | +| `OwnershipTransferred` | `(oldOwner: address, newOwner: address)` | `0x8be0079c531659141344cd1fd0a4f28419497f9722a3daafe3b4186f6b6457e0` | + +## SocketFeeManager + +| 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` | + +## FeesManager + +| Event | Arguments | Topic | +| ----------------------------- | ------------------------------------------------------------------------------------------------------- | -------------------------------------------------------------------- | +| `CreditsBlocked` | `(requestCount: uint40, consumeFrom: address, amount: uint256)` | `0xf037c15aef41440aa823cf1fdeaea332105d8b23d52557f6670189b5d76f1eed` | +| `CreditsTransferred` | `(from: address, to: address, amount: uint256)` | `0xed198cadddd93e734cbf04cb1c3226d9bafaeb504cedbd8ee36b830b0cb9e7a5` | +| `CreditsUnblocked` | `(requestCount: uint40, consumeFrom: address)` | `0x45db29ef2701319155cac058aa2f56ce1f73e0e238161d3db9f8c9a47655210d` | +| `CreditsUnblockedAndAssigned` | `(requestCount: uint40, consumeFrom: address, transmitter: address, amount: uint256)` | `0x38fd327622576a468e1b2818b00f50c8854703633ef8e583e1f31662888ffac2` | +| `CreditsUnwrapped` | `(consumeFrom: address, amount: uint256)` | `0xdcc9473b722b4c953617ab373840b365298a520bc7f20ce94fa7314f4a857774` | +| `CreditsWrapped` | `(consumeFrom: address, amount: uint256)` | `0x40246503613721eb4acf4020c6c56b6a16e5d08713316db0bea5210e8819c592` | +| `Deposited` | `(chainSlug: uint32, token: address, depositTo: address, creditAmount: uint256, nativeAmount: uint256)` | `0x72aedd284699bbd7a987e6942b824cfd6c627e354cb5a0760ac5768acd473f4a` | +| `FeesPlugSet` | `(chainSlug: uint32, feesPlug: address)` | `0xa8c4be32b96cca895f1f0f4684e6b377b2c4513bc35eb57a13afb6b5efb2c0ce` | +| `FeesPoolSet` | `(feesPool: address)` | `0xd07af3fd70b48ab3c077a8d45c3a288498d905d0e3d1e65bc171f6c2e890d8ef` | +| `Initialized` | `(version: uint64)` | `0xc7f505b2f371ae2175ee4913f4499e1f2633a7b5936321eed1cdaeb6115181d2` | +| `OwnershipHandoverCanceled` | `(pendingOwner: address)` | `0xfa7b8eab7da67f412cc9575ed43464468f9bfbae89d1675917346ca6d8fe3c92` | +| `OwnershipHandoverRequested` | `(pendingOwner: address)` | `0xdbf36a107da19e49527a7176a1babf963b4b0ff8cde35ee35d6cd8f1f9ac7e1d` | +| `OwnershipTransferred` | `(oldOwner: address, newOwner: address)` | `0x8be0079c531659141344cd1fd0a4f28419497f9722a3daafe3b4186f6b6457e0` | + +## FeesPool + +| Event | Arguments | Topic | +| ---------------------------- | ----------------------------------------------- | -------------------------------------------------------------------- | +| `NativeDeposited` | `(from: address, amount: uint256)` | `0xb5d7700fb0cf415158b8db7cc7c39f0eab16a825c92e221404b4c8bb099b4bbb` | +| `NativeWithdrawn` | `(success: bool, to: address, amount: uint256)` | `0xa81f1c8490022ee829d2e1a231053f5dbecad46caee71f6ea38a9db663a3f12b` | +| `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` | ## AddressResolver -| 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` | +| Event | Arguments | Topic | +| ------------------------------ | --------------------------------------------------- | -------------------------------------------------------------------- | +| `AsyncDeployerUpdated` | `(asyncDeployer_: address)` | `0x4df9cdd01544e8f6b0326650bc0b55611f47ce5ba2faa522d21fb675e9fc1f73` | +| `ContractAddressUpdated` | `(contractId_: bytes32, contractAddress_: address)` | `0xdf5ec2c15e11ce657bb21bc09c0b5ba95e315b4dba9934c6e311f47559babf28` | +| `DefaultAuctionManagerUpdated` | `(defaultAuctionManager_: address)` | `0x60f296739208a505ead7fb622df0f76b7791b824481b120a2300bdaf85e3e3d6` | +| `DeployForwarderUpdated` | `(deployForwarder_: address)` | `0x237b9bc9fef7508a02ca9ccca81f6965e500064a58024cae1218035da865fd2b` | +| `FeesManagerUpdated` | `(feesManager_: address)` | `0x94e67aa1341a65767dfde81e62fd265bfbade1f5744bfd3cd73f99a6eca0572a` | +| `Initialized` | `(version: uint64)` | `0xc7f505b2f371ae2175ee4913f4499e1f2633a7b5936321eed1cdaeb6115181d2` | +| `OwnershipHandoverCanceled` | `(pendingOwner: address)` | `0xfa7b8eab7da67f412cc9575ed43464468f9bfbae89d1675917346ca6d8fe3c92` | +| `OwnershipHandoverRequested` | `(pendingOwner: address)` | `0xdbf36a107da19e49527a7176a1babf963b4b0ff8cde35ee35d6cd8f1f9ac7e1d` | +| `OwnershipTransferred` | `(oldOwner: address, newOwner: address)` | `0x8be0079c531659141344cd1fd0a4f28419497f9722a3daafe3b4186f6b6457e0` | +| `WatcherUpdated` | `(watcher_: address)` | `0xc13081d38d92b454cdb6ca20bbc65c12fa43a7a14a1529204ced5b6350052bb0` | + +## AsyncDeployer + +| Event | Arguments | Topic | +| ---------------------------- | ---------------------------------------------------- | -------------------------------------------------------------------- | +| `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` | ## AsyncPromise @@ -40,27 +117,35 @@ | ------------- | ------------------- | -------------------------------------------------------------------- | | `Initialized` | `(version: uint64)` | `0xc7f505b2f371ae2175ee4913f4499e1f2633a7b5936321eed1cdaeb6115181d2` | +## DeployForwarder + +| Event | Arguments | Topic | +| ---------------------------- | ---------------------------------------- | -------------------------------------------------------------------- | +| `Initialized` | `(version: uint64)` | `0xc7f505b2f371ae2175ee4913f4499e1f2633a7b5936321eed1cdaeb6115181d2` | +| `OwnershipHandoverCanceled` | `(pendingOwner: address)` | `0xfa7b8eab7da67f412cc9575ed43464468f9bfbae89d1675917346ca6d8fe3c92` | +| `OwnershipHandoverRequested` | `(pendingOwner: address)` | `0xdbf36a107da19e49527a7176a1babf963b4b0ff8cde35ee35d6cd8f1f9ac7e1d` | +| `OwnershipTransferred` | `(oldOwner: address, newOwner: address)` | `0x8be0079c531659141344cd1fd0a4f28419497f9722a3daafe3b4186f6b6457e0` | + ## Forwarder | Event | Arguments | Topic | | ------------- | ------------------- | -------------------------------------------------------------------- | | `Initialized` | `(version: uint64)` | `0xc7f505b2f371ae2175ee4913f4499e1f2633a7b5936321eed1cdaeb6115181d2` | -## AuctionManager +## ProxyFactory -| Event | Arguments | Topic | -| ---------------------------- | ------------------------------------------- | -------------------------------------------------------------------- | -| `AuctionEndDelaySecondsSet` | `(auctionEndDelaySeconds: uint256)` | `0xf38f0d9dc8459cf5426728c250d115196a4c065ebc1a6c29da24764a8c0da722` | -| `AuctionEnded` | `(requestCount: uint40, winningBid: tuple)` | `0xede4ec1efc469fac10dcb4930f70be4cd21f3700ed61c91967c19a7cd7c0d86e` | -| `AuctionRestarted` | `(requestCount: uint40)` | `0x071867b21946ec4655665f0d4515d3757a5a52f144c762ecfdfb11e1da542b82` | -| `AuctionStarted` | `(requestCount: uint40)` | `0xcd040613cf8ef0cfcaa3af0d711783e827a275fc647c116b74595bf17cb9364f` | -| `BidPlaced` | `(requestCount: uint40, bid: tuple)` | `0x7f79485e4c9aeea5d4899bc6f7c63b22ac1f4c01d2d28c801e94732fee657b5d` | -| `Initialized` | `(version: uint64)` | `0xc7f505b2f371ae2175ee4913f4499e1f2633a7b5936321eed1cdaeb6115181d2` | -| `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 | +| -------------- | ----------------------------------------------------------- | -------------------------------------------------------------------- | +| `AdminChanged` | `(proxy: address, admin: address)` | `0x7e644d79422f17c01e4894b5f4f588d331ebfa28653d42ae832dc59e38c9798f` | +| `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` | ## ContractFactoryPlug @@ -74,24 +159,6 @@ | `RoleGranted` | `(role: bytes32, grantee: address)` | `0x2ae6a113c0ed5b78a53413ffbb7679881f11145ccfba4fb92e863dfcd5a1d2f3` | | `RoleRevoked` | `(role: bytes32, revokee: address)` | `0x155aaafb6329a2098580462df33ec4b7441b19729b9601c5fc17ae1cf99a8a52` | -## FeesManager - -| 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 | @@ -107,85 +174,54 @@ | `TokenRemovedFromWhitelist` | `(token: address)` | `0xdd2e6d9f52cbe8f695939d018b7d4a216dc613a669876163ac548b916489d917` | | `TokenWhitelisted` | `(token: address)` | `0x6a65f90b1a644d2faac467a21e07e50e3f8fa5846e26231d30ae79a417d3d262` | -## Socket - -| 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 - -| Event | Arguments | Topic | -| ---------------------------- | ---------------------------------------- | -------------------------------------------------------------------- | -| `OwnershipHandoverCanceled` | `(pendingOwner: address)` | `0xfa7b8eab7da67f412cc9575ed43464468f9bfbae89d1675917346ca6d8fe3c92` | -| `OwnershipHandoverRequested` | `(pendingOwner: address)` | `0xdbf36a107da19e49527a7176a1babf963b4b0ff8cde35ee35d6cd8f1f9ac7e1d` | -| `OwnershipTransferred` | `(oldOwner: address, newOwner: address)` | `0x8be0079c531659141344cd1fd0a4f28419497f9722a3daafe3b4186f6b6457e0` | - -## SocketFeeManager +## Configurations + +| Event | Arguments | Topic | +| ---------------------------- | ------------------------------------------------------------------------ | -------------------------------------------------------------------- | +| `Initialized` | `(version: uint64)` | `0xc7f505b2f371ae2175ee4913f4499e1f2633a7b5936321eed1cdaeb6115181d2` | +| `IsValidPlugSet` | `(appGateway: address, chainSlug: uint32, plug: address, isValid: bool)` | `0x61cccc7387868fc741379c7acd9dd346e0ca2e5c067dc5b156fbbc55b1c2fcf5` | +| `OwnershipHandoverCanceled` | `(pendingOwner: address)` | `0xfa7b8eab7da67f412cc9575ed43464468f9bfbae89d1675917346ca6d8fe3c92` | +| `OwnershipHandoverRequested` | `(pendingOwner: address)` | `0xdbf36a107da19e49527a7176a1babf963b4b0ff8cde35ee35d6cd8f1f9ac7e1d` | +| `OwnershipTransferred` | `(oldOwner: address, newOwner: address)` | `0x8be0079c531659141344cd1fd0a4f28419497f9722a3daafe3b4186f6b6457e0` | +| `PlugAdded` | `(appGatewayId: bytes32, chainSlug: uint32, plug: address)` | `0x7b3e14230a721c4737d275f9a63b92c44cb657bcfddbe6fe9b4d9cd9bd8d4a95` | +| `SocketSet` | `(chainSlug: uint32, socket: address)` | `0x5b13a5470e66a2ec5e9b32af5f9e23fe304864892918c60fffd22509ca73ac97` | +| `SwitchboardSet` | `(chainSlug: uint32, sbType: bytes32, switchboard: address)` | `0x6273f161f4a795e66ef3585d9b4442ef3796b32337157fdfb420b5281e4cf2e3` | + +## PromiseResolver + +| Event | Arguments | Topic | +| -------------------- | ------------------------------------------------ | -------------------------------------------------------------------- | +| `MarkedRevert` | `(payloadId: bytes32, isRevertingOnchain: bool)` | `0xcf1fd844cb4d32cbebb5ca6ce4ac834fe98da3ddac44deb77fffd22ad933824c` | +| `PromiseNotResolved` | `(payloadId: bytes32, asyncPromise: address)` | `0xbcf0d0c678940566e9e64f0c871439395bd5fb5c39bca3547b126fe6ee467937` | +| `PromiseResolved` | `(payloadId: bytes32, asyncPromise: address)` | `0x1b1b5810494fb3e17f7c46547e6e67cd6ad3e6001ea6fb7d12ea0241ba13c4ba` | + +## RequestHandler + +| Event | Arguments | Topic | +| ---------------------------- | ------------------------------------------------------------------------------------------------------------------------------- | -------------------------------------------------------------------- | +| `FeesIncreased` | `(requestCount: uint40, newMaxFees: uint256)` | `0xf258fca4e49b803ee2a4c2e33b6fcf18bc3982df21f111c00677025bf1ccbb6a` | +| `Initialized` | `(version: uint64)` | `0xc7f505b2f371ae2175ee4913f4499e1f2633a7b5936321eed1cdaeb6115181d2` | +| `OwnershipHandoverCanceled` | `(pendingOwner: address)` | `0xfa7b8eab7da67f412cc9575ed43464468f9bfbae89d1675917346ca6d8fe3c92` | +| `OwnershipHandoverRequested` | `(pendingOwner: address)` | `0xdbf36a107da19e49527a7176a1babf963b4b0ff8cde35ee35d6cd8f1f9ac7e1d` | +| `OwnershipTransferred` | `(oldOwner: address, newOwner: address)` | `0x8be0079c531659141344cd1fd0a4f28419497f9722a3daafe3b4186f6b6457e0` | +| `RequestCancelled` | `(requestCount: uint40)` | `0xff191657769be72fc08def44c645014c60d18cb24b9ca05c9a33406a28253245` | +| `RequestCompletedWithErrors` | `(requestCount: uint40)` | `0xd8d9915dc14b5a29b66cb263e1ea1e99e60418fc21d97f0fbf09cae1281291e2` | +| `RequestSettled` | `(requestCount: uint40, winner: address)` | `0x1234f98acbe1548b214f4528461a5377f1e2349569c04caa59325e488e7d2aa4` | +| `RequestSubmitted` | `(hasWrite: bool, requestCount: uint40, totalEstimatedWatcherFees: uint256, requestParams: tuple, payloadParamsArray: tuple[])` | `0x762bac43d5d7689b8911c5654a9d5550804373cead33bc98282067e6166e518f` | + +## Watcher | Event | Arguments | Topic | | ---------------------------- | ---------------------------------------- | -------------------------------------------------------------------- | +| `AppGatewayCallFailed` | `(triggerId: bytes32)` | `0xcaf8475fdade8465ea31672463949e6cf1797fdcdd11eeddbbaf857e1e5907b7` | +| `CalledAppGateway` | `(triggerId: bytes32)` | `0xf659ffb3875368f54fb4ab8f5412ac4518af79701a48076f7a58d4448e4bdd0b` | +| `Initialized` | `(version: uint64)` | `0xc7f505b2f371ae2175ee4913f4499e1f2633a7b5936321eed1cdaeb6115181d2` | | `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` | `(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` | -| `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 | -| ------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------- | -------------------------------------------------------------------- | -| `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` | +| `TriggerFailed` | `(triggerId: bytes32)` | `0x4386783bb0f7cad4ba12f033dbec03dc3441e7757a122f3097a7a4d945c98040` | +| `TriggerFeesSet` | `(triggerFees: uint256)` | `0x7df3967b7c8727af5ac0ee9825d88aafeb899d769bc428b91f8967fa0b623084` | +| `TriggerSucceeded` | `(triggerId: bytes32)` | `0x92d20fbcbf31370b8218e10ed00c5aad0e689022da30a08905ba5ced053219eb` | ## FastSwitchboard @@ -198,27 +234,36 @@ | `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` | +## ReadPrecompile + +| Event | Arguments | Topic | +| --------------- | ---------------------------------------------------------------------- | -------------------------------------------------------------------- | +| `ExpiryTimeSet` | `(expiryTime: uint256)` | `0x07e837e13ad9a34715a6bd45f49bbf12de19f06df79cb0be12b3a7d7f2397fa9` | +| `ReadFeesSet` | `(readFees: uint256)` | `0xc674cb6dde3a59f84dbf226832e606ffc54ac8a169e1568fc834c7813010f926` | +| `ReadRequested` | `(transaction: tuple, readAtBlockNumber: uint256, payloadId: bytes32)` | `0x42d9c65d4f6e45462ae6206adb3e388e046b7daa1dc8699d9380cac72ff5db0b` | + +## SchedulePrecompile + +| Event | Arguments | Topic | +| ------------------------------ | ---------------------------------------------------------------- | -------------------------------------------------------------------- | +| `ExpiryTimeSet` | `(expiryTime_: uint256)` | `0x07e837e13ad9a34715a6bd45f49bbf12de19f06df79cb0be12b3a7d7f2397fa9` | +| `MaxScheduleDelayInSecondsSet` | `(maxScheduleDelayInSeconds_: uint256)` | `0xfd5e4f0e96753ffb08a583390c2f151c51001d8e560625ab93b7fa7b4dac6d75` | +| `ScheduleCallbackFeesSet` | `(scheduleCallbackFees_: uint256)` | `0x82a2f41efc81ce7bfabc0affda7354dae42a3d09bd74a6196e8904b223138a52` | +| `ScheduleFeesPerSecondSet` | `(scheduleFeesPerSecond_: uint256)` | `0x7901a21229f6d2543d8676f53e21214d15f42513e7d46e0dcb510357222bdc7c` | +| `ScheduleRequested` | `(payloadId: bytes32, executeAfter: uint256, deadline: uint256)` | `0xd099d3e3d0f0e2c9c40e0066affeea125aab71d763b7ab0a279ccec3dff70b64` | +| `ScheduleResolved` | `(payloadId: bytes32)` | `0x925dc6c3ebffa07cac89d6e9675f1a5d04e045f2ed9a4fa442665935cb73e26b` | + +## WritePrecompile + +| Event | Arguments | Topic | +| ------------------------------- | ---------------------------------------------------------------------------------------------------------------- | -------------------------------------------------------------------- | +| `ChainMaxMsgValueLimitsUpdated` | `(chainSlug: uint32, maxMsgValueLimit: uint256)` | `0x439087d094fe7dacbba3f0c67032041952d8bd58a891e15af10ced28fed0eb91` | +| `ContractFactoryPlugSet` | `(chainSlug: uint32, contractFactoryPlug: address)` | `0x85bfa413b9e5e225278f51af2ac872988e0a9374263b118d963c50945ea888bb` | +| `ExpiryTimeSet` | `(expiryTime: uint256)` | `0x07e837e13ad9a34715a6bd45f49bbf12de19f06df79cb0be12b3a7d7f2397fa9` | +| `FeesSet` | `(writeFees: uint256)` | `0x3346af6da1932164d501f2ec28f8c5d686db5828a36b77f2da4332d89184fe7b` | +| `Initialized` | `(version: uint64)` | `0xc7f505b2f371ae2175ee4913f4499e1f2633a7b5936321eed1cdaeb6115181d2` | +| `OwnershipHandoverCanceled` | `(pendingOwner: address)` | `0xfa7b8eab7da67f412cc9575ed43464468f9bfbae89d1675917346ca6d8fe3c92` | +| `OwnershipHandoverRequested` | `(pendingOwner: address)` | `0xdbf36a107da19e49527a7176a1babf963b4b0ff8cde35ee35d6cd8f1f9ac7e1d` | +| `OwnershipTransferred` | `(oldOwner: address, newOwner: address)` | `0x8be0079c531659141344cd1fd0a4f28419497f9722a3daafe3b4186f6b6457e0` | +| `WriteProofRequested` | `(transmitter: address, digest: bytes32, prevBatchDigestHash: bytes32, deadline: uint256, payloadParams: tuple)` | `0x3247df5b4e8df4ac60c2c1f803b404ee16bc9d84a6b7649865464a8a397b9acb` | +| `WriteProofUploaded` | `(payloadId: bytes32, proof: bytes)` | `0xd8fe3a99a88c9630360418877afdf14e3e79f0f25fee162aeb230633ea740156` | diff --git a/FunctionSignatures.md b/FunctionSignatures.md index e3ef596c..be1d1ac8 100644 --- a/FunctionSignatures.md +++ b/FunctionSignatures.md @@ -1,256 +1,67 @@ # Function Signatures -## ProxyFactory - -| Function | Signature | -| ----------------------------- | ------------ | -| `adminOf` | `0x2abbef15` | -| `changeAdmin` | `0x1acfd02a` | -| `deploy` | `0x545e7c61` | -| `deployAndCall` | `0x4314f120` | -| `deployDeterministic` | `0x3729f922` | -| `deployDeterministicAndCall` | `0xa97b90d5` | -| `initCodeHash` | `0xdb4c545e` | -| `predictDeterministicAddress` | `0x5414dff0` | -| `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 | -| ------------------------------- | ------------ | -| `asyncPromiseBeacon` | `0xc0fbc0ef` | -| `asyncPromiseCounter` | `0x97cdbf4c` | -| `asyncPromiseImplementation` | `0x59531b8d` | -| `cancelOwnershipHandover` | `0x54d1f13d` | -| `clearPromises` | `0x96e03234` | -| `completeOwnershipHandover` | `0xf04e283e` | -| `contractsToGateways` | `0x5bc03a67` | -| `defaultAuctionManager` | `0x8f27cdc6` | -| `deliveryHelper` | `0x71eaa36f` | -| `deployAsyncPromiseContract` | `0x00afbf9d` | -| `feesManager` | `0x05a9e073` | -| `forwarderBeacon` | `0x945709ae` | -| `forwarderImplementation` | `0xe38d60a1` | -| `getAsyncPromiseAddress` | `0xb6400df5` | -| `getForwarderAddress` | `0x48c0b3e0` | -| `getOrDeployForwarderContract` | `0xe8d616a8` | -| `getPromises` | `0xa01afb0d` | -| `initialize` | `0xc4d66de8` | -| `owner` | `0x8da5cb5b` | -| `ownershipHandoverExpiresAt` | `0xfee81cf4` | -| `renounceOwnership` | `0x715018a6` | -| `requestOwnershipHandover` | `0x25692962` | -| `setAsyncPromiseImplementation` | `0xeb506eab` | -| `setContractsToGateways` | `0xb08dd08b` | -| `setDefaultAuctionManager` | `0xede8b4b5` | -| `setDeliveryHelper` | `0x75523822` | -| `setFeesManager` | `0x1c89382a` | -| `setForwarderImplementation` | `0x83b1e974` | -| `setWatcherPrecompile` | `0x5ca44c9b` | -| `transferOwnership` | `0xf2fde38b` | -| `version` | `0x54fd4d50` | -| `watcherPrecompile__` | `0x1de360c3` | - -## AsyncPromise - -| Function | Signature | -| ------------------------- | ------------ | -| `addressResolver__` | `0x6a750469` | -| `callbackData` | `0xef44c272` | -| `callbackSelector` | `0x2764f92f` | -| `deliveryHelper__` | `0xc031dfb4` | -| `forwarder` | `0xf645d4f9` | -| `initialize` | `0xc0c53b8b` | -| `localInvoker` | `0x45eb87f4` | -| `markOnchainRevert` | `0x7734a84e` | -| `markResolved` | `0xdd94d9b2` | -| `resolved` | `0x3f6fa655` | -| `state` | `0xc19d93fb` | -| `then` | `0x0bf2ba15` | -| `watcherPrecompileConfig` | `0x8618a912` | -| `watcherPrecompileLimits` | `0xa71cd97d` | -| `watcherPrecompile__` | `0x1de360c3` | - -## Forwarder - -| Function | Signature | -| ------------------------- | ------------ | -| `addressResolver__` | `0x6a750469` | -| `chainSlug` | `0xb349ba65` | -| `deliveryHelper__` | `0xc031dfb4` | -| `getChainSlug` | `0x0b8c6568` | -| `getOnChainAddress` | `0x9da48789` | -| `initialize` | `0x647c576c` | -| `latestAsyncPromise` | `0xb8a8ba52` | -| `latestPromiseCaller` | `0xdfe580a8` | -| `latestRequestCount` | `0x198b9a47` | -| `onChainAddress` | `0x8bd0b363` | -| `then` | `0x0bf2ba15` | -| `watcherPrecompileConfig` | `0x8618a912` | -| `watcherPrecompileLimits` | `0xa71cd97d` | -| `watcherPrecompile__` | `0x1de360c3` | - ## 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` | -| `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 | | ---------------------------- | ------------ | -| `appGatewayId` | `0x1c335f49` | -| `cancelOwnershipHandover` | `0x54d1f13d` | -| `completeOwnershipHandover` | `0xf04e283e` | -| `connectSocket` | `0x258d19c8` | -| `deployContract` | `0xa0695389` | -| `getAddress` | `0x94ca2cb5` | -| `grantRole` | `0x2f2ff15d` | -| `hasRole` | `0x91d14854` | -| `initSocket` | `0xa07d8545` | -| `isSocketInitialized` | `0x9a7d9a9b` | -| `overrides` | `0x4a85f041` | -| `owner` | `0x8da5cb5b` | -| `ownershipHandoverExpiresAt` | `0xfee81cf4` | -| `renounceOwnership` | `0x715018a6` | -| `requestOwnershipHandover` | `0x25692962` | -| `rescueFunds` | `0x6ccae054` | -| `revokeRole` | `0xd547741f` | -| `socket__` | `0xc6a261d2` | -| `transferOwnership` | `0xf2fde38b` | - -## FeesManager - -| 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 | -| ---------------------------- | ------------ | -| `appGatewayId` | `0x1c335f49` | +| `addressResolver__` | `0x6a750469` | +| `asyncDeployer__` | `0x2a39e801` | +| `auctionEndDelaySeconds` | `0x9087dfdb` | +| `auctionManager` | `0xb0192f9a` | +| `auctionStatus` | `0xd7d5fbf6` | +| `bid` | `0xfcdf49c2` | +| `bidTimeout` | `0x94090d0b` | | `cancelOwnershipHandover` | `0x54d1f13d` | | `completeOwnershipHandover` | `0xf04e283e` | -| `connectSocket` | `0x258d19c8` | -| `depositToFee` | `0xef0db49f` | -| `depositToFeeAndNative` | `0x5f952be8` | -| `depositToNative` | `0xe2665889` | +| `consumeFrom` | `0x40dd78be` | +| `creationCodeWithArgs` | `0xc126dcc4` | +| `deployForwarder__` | `0xd4e3b034` | +| `endAuction` | `0x1212e653` | +| `evmxSlug` | `0x8bae77c2` | +| `expireBid` | `0x1dd5022c` | +| `feesManager__` | `0x70568b58` | +| `forwarderAddresses` | `0x5390fdcb` | +| `getOnChainAddress` | `0xb6abffd7` | +| `getOverrideParams` | `0x54f0a866` | | `grantRole` | `0x2f2ff15d` | +| `handleRevert` | `0x44792f25` | | `hasRole` | `0x91d14854` | -| `initSocket` | `0xa07d8545` | -| `isSocketInitialized` | `0x9a7d9a9b` | -| `overrides` | `0x4a85f041` | +| `initialize` | `0x86891c9b` | +| `initializeOnChain` | `0x86f01739` | +| `isAsyncModifierSet` | `0xb69e0c4a` | +| `isValidPromise` | `0xb690b962` | +| `maxFees` | `0xe83e34b1` | +| `maxReAuctionCount` | `0xc367b376` | +| `onCompleteData` | `0xb52fa926` | +| `onRequestComplete` | `0x5ed1f959` | +| `overrideParams` | `0xec5490fe` | | `owner` | `0x8da5cb5b` | | `ownershipHandoverExpiresAt` | `0xfee81cf4` | -| `removeTokenFromWhitelist` | `0x306275be` | +| `reAuctionCount` | `0x9b4b22d3` | | `renounceOwnership` | `0x715018a6` | | `requestOwnershipHandover` | `0x25692962` | | `rescueFunds` | `0x6ccae054` | | `revokeRole` | `0xd547741f` | -| `socket__` | `0xc6a261d2` | +| `sbType` | `0x745de344` | +| `setAddress` | `0x85bf312c` | +| `setAuctionEndDelaySeconds` | `0x88606b1a` | +| `setMaxReAuctionCount` | `0x64c71403` | | `transferOwnership` | `0xf2fde38b` | -| `whitelistToken` | `0x6247f6f2` | -| `whitelistedTokens` | `0xdaf9c210` | -| `withdrawFees` | `0xe55dc4e6` | +| `watcher__` | `0x300bb063` | +| `winningBids` | `0x9133f232` | ## Socket | Function | Signature | | ---------------------------- | ------------ | +| `OFF_CHAIN_CALLER` | `0xcb2cb4f6` | | `cancelOwnershipHandover` | `0x54d1f13d` | | `chainSlug` | `0xb349ba65` | | `completeOwnershipHandover` | `0xf04e283e` | | `connect` | `0xb3bde1aa` | | `disableSwitchboard` | `0xe545b261` | | `enableSwitchboard` | `0xf97a498a` | -| `execute` | `0x68ef086b` | +| `execute` | `0xafa8b480` | | `getPlugConfig` | `0xf9778ee0` | | `grantRole` | `0x2f2ff15d` | | `hasRole` | `0x91d14854` | @@ -267,6 +78,7 @@ | `revokeRole` | `0xd547741f` | | `setMaxCopyBytes` | `0x4fc7d6e9` | | `setSocketFeeManager` | `0x25bd97e5` | +| `simulate` | `0x91bf8275` | | `socketFeeManager` | `0xde5b8838` | | `transferOwnership` | `0xf2fde38b` | | `triggerCounter` | `0x8b0021de` | @@ -276,7 +88,7 @@ | Function | Signature | | ---------------------------- | ------------ | -| `attestAndExecute` | `0xa11d3bdc` | +| `attestAndExecute` | `0x66c7748a` | | `cancelOwnershipHandover` | `0x54d1f13d` | | `completeOwnershipHandover` | `0xf04e283e` | | `owner` | `0x8da5cb5b` | @@ -298,7 +110,7 @@ | `hasRole` | `0x91d14854` | | `owner` | `0x8da5cb5b` | | `ownershipHandoverExpiresAt` | `0xfee81cf4` | -| `payAndCheckFees` | `0xbaa56229` | +| `payAndCheckFees` | `0xd9d29ae3` | | `renounceOwnership` | `0x715018a6` | | `requestOwnershipHandover` | `0x25692962` | | `rescueFunds` | `0x6ccae054` | @@ -307,109 +119,356 @@ | `socketFees` | `0xab1b33a8` | | `transferOwnership` | `0xf2fde38b` | -## WatcherPrecompileConfig +## FeesManager + +| Function | Signature | +| -------------------------------- | ------------ | +| `addressResolver__` | `0x6a750469` | +| `approveAppGatewayWithSignature` | `0x94b649ec` | +| `approveAppGateways` | `0x86d23ab2` | +| `asyncDeployer__` | `0x2a39e801` | +| `blockCredits` | `0x9e434307` | +| `cancelOwnershipHandover` | `0x54d1f13d` | +| `completeOwnershipHandover` | `0xf04e283e` | +| `deployForwarder__` | `0xd4e3b034` | +| `deposit` | `0x5671d329` | +| `evmxSlug` | `0x8bae77c2` | +| `feesManager__` | `0x70568b58` | +| `feesPlugs` | `0x23f5ee8a` | +| `feesPool` | `0x6b259690` | +| `getAvailableCredits` | `0xb065a8e5` | +| `initialize` | `0xbf2c8539` | +| `isApproved` | `0xa389783e` | +| `isCreditSpendable` | `0x4f8990fd` | +| `isNonceUsed` | `0xcab7e8eb` | +| `onRequestComplete` | `0x5ed1f959` | +| `owner` | `0x8da5cb5b` | +| `ownershipHandoverExpiresAt` | `0xfee81cf4` | +| `renounceOwnership` | `0x715018a6` | +| `requestBlockedCredits` | `0xb62d25ac` | +| `requestOwnershipHandover` | `0x25692962` | +| `rescueFunds` | `0x6ccae054` | +| `sbType` | `0x745de344` | +| `setFeesPlug` | `0xeab75f36` | +| `setFeesPool` | `0xd6684588` | +| `tokenOnChainBalances` | `0x3b27866d` | +| `transferCredits` | `0xf1686c89` | +| `transferOwnership` | `0xf2fde38b` | +| `unblockAndAssignCredits` | `0x01958181` | +| `unblockCredits` | `0xa0b32314` | +| `unwrap` | `0x7647691d` | +| `userCredits` | `0x20babb92` | +| `watcher__` | `0x300bb063` | +| `withdrawCredits` | `0xcfc6dbd9` | +| `wrap` | `0x023276f0` | + +## FeesPool + +| Function | Signature | +| ---------------------------- | ------------ | +| `cancelOwnershipHandover` | `0x54d1f13d` | +| `completeOwnershipHandover` | `0xf04e283e` | +| `getBalance` | `0x12065fe0` | +| `grantRole` | `0x2f2ff15d` | +| `hasRole` | `0x91d14854` | +| `owner` | `0x8da5cb5b` | +| `ownershipHandoverExpiresAt` | `0xfee81cf4` | +| `renounceOwnership` | `0x715018a6` | +| `requestOwnershipHandover` | `0x25692962` | +| `revokeRole` | `0xd547741f` | +| `transferOwnership` | `0xf2fde38b` | +| `withdraw` | `0xf3fef3a3` | + +## AddressResolver + +| Function | Signature | +| ---------------------------- | ------------ | +| `asyncDeployer__` | `0x2a39e801` | +| `cancelOwnershipHandover` | `0x54d1f13d` | +| `completeOwnershipHandover` | `0xf04e283e` | +| `contractAddresses` | `0xf689e892` | +| `defaultAuctionManager` | `0x8f27cdc6` | +| `deployForwarder__` | `0xd4e3b034` | +| `feesManager__` | `0x70568b58` | +| `initialize` | `0xc4d66de8` | +| `owner` | `0x8da5cb5b` | +| `ownershipHandoverExpiresAt` | `0xfee81cf4` | +| `renounceOwnership` | `0x715018a6` | +| `requestOwnershipHandover` | `0x25692962` | +| `rescueFunds` | `0x6ccae054` | +| `setAsyncDeployer` | `0xcb0ffff8` | +| `setContractAddress` | `0xe001f841` | +| `setDefaultAuctionManager` | `0xede8b4b5` | +| `setDeployForwarder` | `0xaeaee8a6` | +| `setFeesManager` | `0x1c89382a` | +| `setWatcher` | `0x24f48bc5` | +| `transferOwnership` | `0xf2fde38b` | +| `watcher__` | `0x300bb063` | + +## AsyncDeployer + +| Function | Signature | +| ------------------------------- | ------------ | +| `addressResolver__` | `0x6a750469` | +| `asyncDeployer__` | `0x2a39e801` | +| `asyncPromiseBeacon` | `0xc0fbc0ef` | +| `asyncPromiseCounter` | `0x97cdbf4c` | +| `asyncPromiseImplementation` | `0x59531b8d` | +| `cancelOwnershipHandover` | `0x54d1f13d` | +| `completeOwnershipHandover` | `0xf04e283e` | +| `deployAsyncPromiseContract` | `0x9851be0b` | +| `deployForwarder__` | `0xd4e3b034` | +| `feesManager__` | `0x70568b58` | +| `forwarderBeacon` | `0x945709ae` | +| `forwarderImplementation` | `0xe38d60a1` | +| `getAsyncPromiseAddress` | `0x104f39b4` | +| `getForwarderAddress` | `0x48c0b3e0` | +| `getOrDeployForwarderContract` | `0x0aa178de` | +| `initialize` | `0x485cc955` | +| `owner` | `0x8da5cb5b` | +| `ownershipHandoverExpiresAt` | `0xfee81cf4` | +| `renounceOwnership` | `0x715018a6` | +| `requestOwnershipHandover` | `0x25692962` | +| `rescueFunds` | `0x6ccae054` | +| `setAsyncPromiseImplementation` | `0xeb506eab` | +| `setForwarderImplementation` | `0x83b1e974` | +| `transferOwnership` | `0xf2fde38b` | +| `watcher__` | `0x300bb063` | + +## AsyncPromise + +| Function | Signature | +| ------------------- | ------------ | +| `addressResolver__` | `0x6a750469` | +| `asyncDeployer__` | `0x2a39e801` | +| `callbackData` | `0xef44c272` | +| `callbackSelector` | `0x2764f92f` | +| `deployForwarder__` | `0xd4e3b034` | +| `exceededMaxCopy` | `0xaf598c7c` | +| `feesManager__` | `0x70568b58` | +| `initialize` | `0x0ece6089` | +| `localInvoker` | `0x45eb87f4` | +| `markOnchainRevert` | `0xd0e7af1b` | +| `markResolved` | `0x822d5d1f` | +| `requestCount` | `0x5badbe4c` | +| `rescueFunds` | `0x6ccae054` | +| `returnData` | `0xebddbaf6` | +| `state` | `0xc19d93fb` | +| `then` | `0x0bf2ba15` | +| `watcher__` | `0x300bb063` | + +## DeployForwarder | Function | Signature | | ---------------------------- | ------------ | | `addressResolver__` | `0x6a750469` | +| `asyncDeployer__` | `0x2a39e801` | +| `cancelOwnershipHandover` | `0x54d1f13d` | +| `completeOwnershipHandover` | `0xf04e283e` | +| `deploy` | `0x940f11af` | +| `deployForwarder__` | `0xd4e3b034` | +| `deployerSwitchboardType` | `0xaa381f9a` | +| `feesManager__` | `0x70568b58` | +| `initialize` | `0x6133f985` | +| `owner` | `0x8da5cb5b` | +| `ownershipHandoverExpiresAt` | `0xfee81cf4` | +| `renounceOwnership` | `0x715018a6` | +| `requestOwnershipHandover` | `0x25692962` | +| `rescueFunds` | `0x6ccae054` | +| `saltCounter` | `0xa04c6809` | +| `transferOwnership` | `0xf2fde38b` | +| `watcher__` | `0x300bb063` | + +## Forwarder + +| Function | Signature | +| ------------------- | ------------ | +| `addressResolver__` | `0x6a750469` | +| `asyncDeployer__` | `0x2a39e801` | +| `chainSlug` | `0xb349ba65` | +| `deployForwarder__` | `0xd4e3b034` | +| `feesManager__` | `0x70568b58` | +| `getChainSlug` | `0x0b8c6568` | +| `getOnChainAddress` | `0x9da48789` | +| `initialize` | `0x647c576c` | +| `onChainAddress` | `0x8bd0b363` | +| `rescueFunds` | `0x6ccae054` | +| `watcher__` | `0x300bb063` | + +## ProxyFactory + +| Function | Signature | +| ----------------------------- | ------------ | +| `adminOf` | `0x2abbef15` | +| `changeAdmin` | `0x1acfd02a` | +| `deploy` | `0x545e7c61` | +| `deployAndCall` | `0x4314f120` | +| `deployDeterministic` | `0x3729f922` | +| `deployDeterministicAndCall` | `0xa97b90d5` | +| `initCodeHash` | `0xdb4c545e` | +| `predictDeterministicAddress` | `0x5414dff0` | +| `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` | + +## ContractFactoryPlug + +| Function | Signature | +| ---------------------------- | ------------ | +| `appGatewayId` | `0x1c335f49` | +| `cancelOwnershipHandover` | `0x54d1f13d` | +| `completeOwnershipHandover` | `0xf04e283e` | +| `connectSocket` | `0x258d19c8` | +| `deployContract` | `0xa0695389` | +| `getAddress` | `0x94ca2cb5` | +| `grantRole` | `0x2f2ff15d` | +| `hasRole` | `0x91d14854` | +| `initSocket` | `0xa07d8545` | +| `isSocketInitialized` | `0x9a7d9a9b` | +| `overrides` | `0x4a85f041` | +| `owner` | `0x8da5cb5b` | +| `ownershipHandoverExpiresAt` | `0xfee81cf4` | +| `renounceOwnership` | `0x715018a6` | +| `requestOwnershipHandover` | `0x25692962` | +| `rescueFunds` | `0x6ccae054` | +| `revokeRole` | `0xd547741f` | +| `socket__` | `0xc6a261d2` | +| `transferOwnership` | `0xf2fde38b` | + +## Configurations + +| Function | Signature | +| ---------------------------- | ------------ | | `cancelOwnershipHandover` | `0x54d1f13d` | | `completeOwnershipHandover` | `0xf04e283e` | -| `contractFactoryPlug` | `0xd8427483` | -| `deliveryHelper__` | `0xc031dfb4` | -| `evmxSlug` | `0x8bae77c2` | -| `feesPlug` | `0xd1ba159d` | | `getPlugConfigs` | `0x8a028c38` | -| `initialize` | `0x6ecf2b22` | -| `isNonceUsed` | `0x5d00bb12` | +| `initialize` | `0x485cc955` | | `isValidPlug` | `0xec8aef74` | | `owner` | `0x8da5cb5b` | | `ownershipHandoverExpiresAt` | `0xfee81cf4` | | `renounceOwnership` | `0x715018a6` | | `requestOwnershipHandover` | `0x25692962` | -| `setAppGateways` | `0xdd8539d4` | -| `setIsValidPlug` | `0xb3a6bbcf` | -| `setOnChainContracts` | `0x33fa78c2` | +| `rescueFunds` | `0x6ccae054` | +| `setAppGatewayConfigs` | `0xd137fcbb` | +| `setIsValidPlug` | `0xf41332b0` | +| `setSocket` | `0x075c40be` | | `setSwitchboard` | `0x61706f1e` | | `sockets` | `0xb44a23ab` | | `switchboards` | `0xaa539546` | | `transferOwnership` | `0xf2fde38b` | -| `verifyConnections` | `0xf269ab50` | -| `watcherPrecompileConfig` | `0x8618a912` | -| `watcherPrecompileLimits` | `0xa71cd97d` | -| `watcherPrecompile__` | `0x1de360c3` | - -## WatcherPrecompileLimits - -| 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 +| `verifyConnections` | `0xa53b6fad` | +| `watcher__` | `0x300bb063` | + +## PromiseResolver + +| Function | Signature | +| ----------------- | ------------ | +| `markRevert` | `0x56501015` | +| `rescueFunds` | `0x6ccae054` | +| `resolvePromises` | `0xbf8484b8` | +| `watcher__` | `0x300bb063` | + +## RequestHandler | Function | Signature | | ------------------------------ | ------------ | | `addressResolver__` | `0x6a750469` | -| `batch` | `0xd9307dd8` | -| `bidTimeout` | `0x94090d0b` | +| `assignTransmitter` | `0xae5e9c48` | +| `asyncDeployer__` | `0x2a39e801` | | `cancelOwnershipHandover` | `0x54d1f13d` | -| `cancelRequest` | `0x50ad0779` | -| `chainMaxMsgValueLimit` | `0x01d1e126` | -| `clearQueue` | `0xf22cb874` | +| `cancelRequest` | `0x3b5fd6fb` | +| `cancelRequestForReverts` | `0x82970278` | | `completeOwnershipHandover` | `0xf04e283e` | -| `deliveryHelper__` | `0xc031dfb4` | -| `finishRequest` | `0xeab148c0` | -| `getDeliveryHelperPlugAddress` | `0xb709bd9f` | -| `getFees` | `0xfbf4ec4b` | -| `getRequestMetadata` | `0x5f1dde51` | -| `handleRequestReverts` | `0x8fe9734f` | -| `increaseFees` | `0xe9b304da` | -| `initialize` | `0x7265580f` | +| `deployForwarder__` | `0xd4e3b034` | +| `feesManager__` | `0x70568b58` | +| `getBatchPayloadIds` | `0xfd83cd1f` | +| `getPayload` | `0xb48fd0fe` | +| `getPrecompileFees` | `0xabac263c` | +| `getRequest` | `0xcf39abf6` | +| `getRequestBatchIds` | `0xe138fadb` | +| `handleRevert` | `0xcc88d3f9` | +| `increaseFees` | `0x10205541` | +| `initialize` | `0x485cc955` | +| `nextBatchCount` | `0x333a3963` | +| `nextRequestCount` | `0xfef72893` | | `owner` | `0x8da5cb5b` | | `ownershipHandoverExpiresAt` | `0xfee81cf4` | -| `queue` | `0x1b9396f5` | -| `queuePayloadParams` | `0x3c362159` | +| `payloadCounter` | `0x550ce1d5` | +| `precompiles` | `0x9932450b` | | `renounceOwnership` | `0x715018a6` | | `requestOwnershipHandover` | `0x25692962` | -| `requests` | `0xb71a5e58` | -| `saltCounter` | `0xa04c6809` | -| `startRequestProcessing` | `0x5ca2100f` | +| `rescueFunds` | `0x6ccae054` | +| `setPrecompile` | `0x122e0042` | +| `setRequestPayloadCountLimit` | `0x8526582b` | +| `submitRequest` | `0xbb299a2c` | | `transferOwnership` | `0xf2fde38b` | -| `updateBidTimeout` | `0xa29f83d1` | -| `updateChainMaxMsgValueLimits` | `0x0b32de76` | -| `watcherPrecompileConfig` | `0x8618a912` | -| `watcherPrecompileLimits` | `0xa71cd97d` | -| `watcherPrecompile__` | `0x1de360c3` | -| `withdrawTo` | `0x2ba9d5bb` | -| `withdrawTransmitterFees` | `0x38ff6dd2` | +| `updateRequestAndProcessBatch` | `0x46464471` | +| `watcher__` | `0x300bb063` | + +## Watcher + +| Function | Signature | +| ---------------------------- | ------------ | +| `addressResolver__` | `0x6a750469` | +| `appGatewayTemp` | `0x1394c029` | +| `asyncDeployer__` | `0x2a39e801` | +| `callAppGateways` | `0x0050bef1` | +| `cancelOwnershipHandover` | `0x54d1f13d` | +| `cancelRequest` | `0x50ad0779` | +| `clearQueue` | `0xf22cb874` | +| `completeOwnershipHandover` | `0xf04e283e` | +| `configurations__` | `0x52a3bbeb` | +| `deployForwarder__` | `0xd4e3b034` | +| `evmxSlug` | `0x8bae77c2` | +| `feesManager__` | `0x70568b58` | +| `getCurrentRequestCount` | `0x5715abbb` | +| `getPayloadParams` | `0xae5eeb77` | +| `getPrecompileFees` | `0xabac263c` | +| `getRequestParams` | `0x71263d0d` | +| `increaseFees` | `0xe9b304da` | +| `initialize` | `0xaaf7fc1a` | +| `isAppGatewayCalled` | `0xa79da6c7` | +| `isNonceUsed` | `0x5d00bb12` | +| `isWatcher` | `0x84785ecd` | +| `latestAsyncPromise` | `0xb8a8ba52` | +| `owner` | `0x8da5cb5b` | +| `ownershipHandoverExpiresAt` | `0xfee81cf4` | +| `payloadQueue` | `0x74f00ffb` | +| `promiseResolver__` | `0xdee152be` | +| `queue` | `0xf03ca7f7` | +| `queueAndSubmit` | `0xf0fb9665` | +| `renounceOwnership` | `0x715018a6` | +| `requestHandler__` | `0x55184561` | +| `requestOwnershipHandover` | `0x25692962` | +| `rescueFunds` | `0xa58c6fc5` | +| `setCoreContracts` | `0xefa891c4` | +| `setIsValidPlug` | `0x7fc82ff6` | +| `setTriggerFees` | `0xaeb30511` | +| `submitRequest` | `0x4890b5ef` | +| `transferOwnership` | `0xf2fde38b` | +| `triggerFees` | `0x73f76aec` | +| `triggerFromChainSlug` | `0xd12b4f12` | +| `triggerFromPlug` | `0x3b847d12` | +| `watcherMultiCall` | `0x8021e82b` | +| `watcher__` | `0x300bb063` | ## FastSwitchboard @@ -433,61 +492,68 @@ | `socket__` | `0xc6a261d2` | | `transferOwnership` | `0xf2fde38b` | -## WatcherPrecompile +## ReadPrecompile -| 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` | +| Function | Signature | +| ------------------------------ | ------------ | +| `expiryTime` | `0x99bc0aea` | +| `getPrecompileFees` | `0xb7a3d04c` | +| `handlePayload` | `0x1d5e1d98` | +| `readFees` | `0xe06357a2` | +| `rescueFunds` | `0x6ccae054` | +| `resolvePayload` | `0xea92e825` | +| `setExpiryTime` | `0x30fc4cff` | +| `setFees` | `0x3d18678e` | +| `validateAndGetPrecompileData` | `0xab172aab` | +| `watcher__` | `0x300bb063` | + +## SchedulePrecompile + +| Function | Signature | +| ------------------------------ | ------------ | +| `expiryTime` | `0x99bc0aea` | +| `getPrecompileFees` | `0xb7a3d04c` | +| `handlePayload` | `0x1d5e1d98` | +| `maxScheduleDelayInSeconds` | `0x3ef01cdb` | +| `rescueFunds` | `0x6ccae054` | +| `resolvePayload` | `0xea92e825` | +| `scheduleCallbackFees` | `0x4c5b6007` | +| `scheduleFeesPerSecond` | `0x852a74c1` | +| `setExpiryTime` | `0x30fc4cff` | +| `setMaxScheduleDelayInSeconds` | `0x12953318` | +| `setScheduleCallbackFees` | `0xec8fd71e` | +| `setScheduleFeesPerSecond` | `0x28e59e57` | +| `validateAndGetPrecompileData` | `0xab172aab` | +| `watcher__` | `0x300bb063` | + +## WritePrecompile + +| Function | Signature | +| ------------------------------ | ------------ | +| `cancelOwnershipHandover` | `0x54d1f13d` | +| `chainMaxMsgValueLimit` | `0x01d1e126` | +| `completeOwnershipHandover` | `0xf04e283e` | +| `contractFactoryPlugs` | `0x35426631` | +| `digestHashes` | `0xd1a862bf` | +| `expiryTime` | `0x99bc0aea` | +| `getDigest` | `0xdd4bf97b` | +| `getPrecompileFees` | `0xb7a3d04c` | +| `getPrevBatchDigestHash` | `0x372863a1` | +| `handlePayload` | `0x1d5e1d98` | +| `initialize` | `0xeb990c59` | +| `owner` | `0x8da5cb5b` | +| `ownershipHandoverExpiresAt` | `0xfee81cf4` | +| `renounceOwnership` | `0x715018a6` | +| `requestOwnershipHandover` | `0x25692962` | +| `rescueFunds` | `0x6ccae054` | +| `resolvePayload` | `0xea92e825` | +| `setContractFactoryPlugs` | `0xc067b6dd` | +| `setExpiryTime` | `0x30fc4cff` | +| `setFees` | `0x3d18678e` | +| `transferOwnership` | `0xf2fde38b` | +| `updateChainMaxMsgValueLimits` | `0x6a7aa6ac` | +| `uploadProof` | `0x81b48fcf` | +| `validateAndGetPrecompileData` | `0xab172aab` | +| `watcherProofs` | `0x3fa3166b` | +| `watcher__` | `0x300bb063` | +| `writeFees` | `0x5c664aeb` | diff --git a/contracts/evmx/AddressResolverUtil.sol b/contracts/evmx/AddressResolverUtil.sol deleted file mode 100644 index 7e08fcc0..00000000 --- a/contracts/evmx/AddressResolverUtil.sol +++ /dev/null @@ -1,104 +0,0 @@ -// SPDX-License-Identifier: GPL-3.0-only -pragma solidity ^0.8.21; - -import "./interfaces/IAddressResolver.sol"; -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 -/// @dev Provides access control and address resolution functionality for the system -abstract contract AddressResolverUtil { - /// @notice The address resolver contract reference - /// @dev Used to look up system contract addresses - // slot 0 - IAddressResolver public addressResolver__; - - // slots 1-50 reserved for future use - uint256[50] __gap_resolver_util; - - /// @notice Error thrown when an invalid address attempts to call the Payload Delivery only function - error OnlyPayloadDelivery(); - /// @notice Error thrown when an invalid address attempts to call the Watcher only function - error OnlyWatcherPrecompile(); - /// @notice Error thrown when an invalid address attempts to call the Watcher precompile or delivery helper - error OnlyWatcherPrecompileOrDeliveryHelper(); - - /// @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(); - } - - _; - } - - /// @notice Restricts function access to the watcher precompile contract - /// @dev Validates that msg.sender matches the registered watcher precompile address - modifier onlyWatcherPrecompile() { - if (msg.sender != address(addressResolver__.watcherPrecompile__())) { - revert OnlyWatcherPrecompile(); - } - - _; - } - - /// @notice Restricts function access to the watcher precompile contract - /// @dev Validates that msg.sender matches the registered watcher precompile address - modifier onlyWatcherPrecompileOrDeliveryHelper() { - if ( - msg.sender != address(addressResolver__.watcherPrecompile__()) && - msg.sender != addressResolver__.deliveryHelper() - ) { - revert OnlyWatcherPrecompileOrDeliveryHelper(); - } - - _; - } - - /// @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()); - } - - /// @notice Gets the watcher precompile contract interface - /// @return IWatcherPrecompile interface of the registered watcher precompile - /// @dev Resolves and returns the watcher precompile contract for interaction - function watcherPrecompile__() public view returns (IWatcherPrecompile) { - return addressResolver__.watcherPrecompile__(); - } - - /// @notice Gets the watcher precompile config contract interface - /// @return IWatcherPrecompileConfig interface of the registered watcher precompile config - /// @dev Resolves and returns the watcher precompile config contract for interaction - function watcherPrecompileConfig() public view returns (IWatcherPrecompileConfig) { - return addressResolver__.watcherPrecompile__().watcherPrecompileConfig__(); - } - - /// @notice Gets the watcher precompile limits contract interface - /// @return IWatcherPrecompileLimits interface of the registered watcher precompile limits - /// @dev Resolves and returns the watcher precompile limits contract for interaction - function watcherPrecompileLimits() public view returns (IWatcherPrecompileLimits) { - return addressResolver__.watcherPrecompile__().watcherPrecompileLimits__(); - } - - /// @notice Internal function to set the address resolver - /// @param _addressResolver The address of the resolver contract - /// @dev Should be called in the initialization of inheriting contracts - function _setAddressResolver(address _addressResolver) internal { - addressResolver__ = IAddressResolver(_addressResolver); - } - - function _getCoreAppGateway( - address originAppGateway_ - ) internal view returns (address appGateway) { - appGateway = addressResolver__.contractsToGateways(originAppGateway_); - if (appGateway == address(0)) appGateway = originAppGateway_; - } -} diff --git a/contracts/evmx/AsyncPromise.sol b/contracts/evmx/AsyncPromise.sol deleted file mode 100644 index 0f8f48ae..00000000 --- a/contracts/evmx/AsyncPromise.sol +++ /dev/null @@ -1,157 +0,0 @@ -// SPDX-License-Identifier: GPL-3.0-only -pragma solidity ^0.8.21; - -import {Initializable} from "solady/utils/Initializable.sol"; -import {LibCall} from "solady/utils/LibCall.sol"; -import {IPromise} from "./interfaces/IPromise.sol"; -import {IAppGateway} from "./interfaces/IAppGateway.sol"; -import {AddressResolverUtil} from "./AddressResolverUtil.sol"; -import {AsyncPromiseState} from "../utils/common/Structs.sol"; -import {MAX_COPY_BYTES} from "../utils/common/Constants.sol"; - -abstract contract AsyncPromiseStorage is IPromise { - // slots [0-49] reserved for gap - uint256[50] _gap_before; - - // slot 50 - // bytes1 - /// @notice The callback selector to be called on the invoker. - bytes4 public callbackSelector; - // bytes4 - /// @notice Indicates whether the promise has been resolved. - bool public override resolved; - // bytes8 - /// @notice The current state of the async promise. - AsyncPromiseState public state; - // bytes20 - /// @notice The local contract which initiated the async call. - /// @dev The callback will be executed on this address - address public localInvoker; - - // slot 51 - /// @notice The forwarder address which can set the callback selector and data - address public forwarder; - - // slot 52 - /// @notice The callback data to be used when the promise is resolved. - bytes public callbackData; - - // slots [53-102] reserved for gap - uint256[50] _gap_after; - - // slots 103-154 (51) reserved for addr resolver util -} - -/// @title AsyncPromise -/// @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 - error OnlyForwarderOrLocalInvoker(); - /// @notice Error thrown when attempting to set an already existing promise - error PromiseAlreadySetUp(); - /// @notice Error thrown when the promise reverts - error PromiseRevertFailed(); - - constructor() { - _disableInitializers(); // disable for implementation - } - - /// @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 { - localInvoker = invoker_; - forwarder = forwarder_; - - _setAddressResolver(addressResolver_); - } - - /// @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 override onlyWatcherPrecompile returns (bool success) { - if (resolved) revert PromiseAlreadyResolved(); - - resolved = true; - state = AsyncPromiseState.RESOLVED; - - // Call callback to app gateway - if (callbackSelector == bytes4(0)) return true; - - bytes memory combinedCalldata = abi.encodePacked( - callbackSelector, - abi.encode(callbackData, returnData_) - ); - - // 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); - } - - /// @notice Marks the promise as onchain reverting. - /// @dev Only callable by the watcher precompile. - function markOnchainRevert( - uint40 requestCount_, - bytes32 payloadId_ - ) external override onlyWatcherPrecompile { - _handleRevert(requestCount_, payloadId_, AsyncPromiseState.ONCHAIN_REVERTING); - } - - function _handleRevert( - uint40 requestCount_, - bytes32 payloadId_, - AsyncPromiseState state_ - ) internal { - // to update the state in case selector is bytes(0) but reverting onchain - resolved = true; - state = state_; - try IAppGateway(localInvoker).handleRevert(requestCount_, payloadId_) { - // Successfully handled revert - } catch { - revert PromiseRevertFailed(); - } - } - - /// @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 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_; - state = AsyncPromiseState.WAITING_FOR_CALLBACK_EXECUTION; - } - - promise_ = address(this); - } -} diff --git a/contracts/evmx/AuctionManager.sol b/contracts/evmx/AuctionManager.sol new file mode 100644 index 00000000..4faf54f5 --- /dev/null +++ b/contracts/evmx/AuctionManager.sol @@ -0,0 +1,299 @@ +// 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 "./interfaces/IPromise.sol"; +import "./interfaces/IAuctionManager.sol"; + +import "../utils/AccessControl.sol"; +import "../utils/RescueFundsLib.sol"; +import {AuctionNotOpen, AuctionClosed, BidExceedsMaxFees, LowerBidAlreadyExists, InvalidTransmitter, MaxReAuctionCountReached, InvalidBid} from "../utils/common/Errors.sol"; +import {SCHEDULE} from "../utils/common/Constants.sol"; + +import {TRANSMITTER_ROLE} from "../utils/common/AccessRoles.sol"; +import {AppGatewayBase} from "./base/AppGatewayBase.sol"; + +/// @title AuctionManagerStorage +/// @notice Storage for the AuctionManager contract +abstract contract AuctionManagerStorage is IAuctionManager { + // slots [0-49] reserved for gap + uint256[50] _gap_before; + + // slot 50 (32 + 128) + /// @notice The evmx chain slug + uint32 public evmxSlug; + + /// @notice The time after which a bid expires + uint128 public bidTimeout; + + // slot 51 + uint256 public maxReAuctionCount; + + // slot 52 + uint256 public auctionEndDelaySeconds; + + // slot 53 + /// @notice The winning bid for a request (requestCount => Bid) + mapping(uint40 => Bid) public winningBids; + + // slot 54 + /// @notice The auction status for a request (requestCount => AuctionStatus) + mapping(uint40 => AuctionStatus) public override auctionStatus; + + // slot 55 + mapping(uint40 => uint256) public reAuctionCount; + + // slots [56-105] reserved for gap + uint256[50] _gap_after; + + // slots [106-164] 59 slots reserved for app gateway base + // slots [165-214] 50 slots reserved for access control +} + +/// @title AuctionManager +/// @notice Contract for managing auctions and placing bids +contract AuctionManager is AuctionManagerStorage, Initializable, AppGatewayBase, AccessControl { + event AuctionRestarted(uint40 requestCount); + event AuctionStarted(uint40 requestCount); + event AuctionEnded(uint40 requestCount, Bid winningBid); + event BidPlaced(uint40 requestCount, Bid bid); + event AuctionEndDelaySecondsSet(uint256 auctionEndDelaySeconds); + event MaxReAuctionCountSet(uint256 maxReAuctionCount); + + constructor() { + _disableInitializers(); // disable for implementation + } + + /// @param evmxSlug_ The evmx chain slug + /// @param bidTimeout_ The timeout after which a bid expires + /// @param maxReAuctionCount_ The maximum number of re-auctions allowed + /// @param auctionEndDelaySeconds_ The delay in seconds before an auction can end + /// @param addressResolver_ The address of the address resolver + /// @param owner_ The address of the contract owner + + function initialize( + uint32 evmxSlug_, + uint128 bidTimeout_, + uint256 maxReAuctionCount_, + uint256 auctionEndDelaySeconds_, + address addressResolver_, + address owner_ + ) external reinitializer(1) { + evmxSlug = evmxSlug_; + bidTimeout = bidTimeout_; + maxReAuctionCount = maxReAuctionCount_; + auctionEndDelaySeconds = auctionEndDelaySeconds_; + + _initializeOwner(owner_); + _initializeAppGateway(addressResolver_); + } + + function setAuctionEndDelaySeconds(uint256 auctionEndDelaySeconds_) external onlyWatcher { + auctionEndDelaySeconds = auctionEndDelaySeconds_; + emit AuctionEndDelaySecondsSet(auctionEndDelaySeconds_); + } + + function setMaxReAuctionCount(uint256 maxReAuctionCount_) external onlyWatcher { + maxReAuctionCount = maxReAuctionCount_; + emit MaxReAuctionCountSet(maxReAuctionCount_); + } + + /// @notice Places a bid for an auction + /// @dev transmitters should approve credits to the auction manager contract for scheduling requests + /// @param requestCount_ The ID of the auction + /// @param bidFees The bid amount + /// @param transmitterSignature The signature of the transmitter + function bid( + uint40 requestCount_, + uint256 bidFees, + bytes memory transmitterSignature, + bytes memory extraData + ) external override { + if (auctionEndDelaySeconds == 0) { + // todo: temp fix, can be called for random request + if ( + auctionStatus[requestCount_] != AuctionStatus.NOT_STARTED && + auctionStatus[requestCount_] != AuctionStatus.RESTARTED + ) revert AuctionNotOpen(); + } else if ( + auctionStatus[requestCount_] != AuctionStatus.OPEN && + auctionStatus[requestCount_] != AuctionStatus.RESTARTED + ) revert AuctionNotOpen(); + + // check if the transmitter is valid + address transmitter = _recoverSigner( + keccak256(abi.encode(address(this), evmxSlug, requestCount_, bidFees, extraData)), + transmitterSignature + ); + if (!_hasRole(TRANSMITTER_ROLE, transmitter)) revert InvalidTransmitter(); + + // check if the bid is lower than the existing bid + if (bidFees > 0 && winningBids[requestCount_].fee >= bidFees) + revert LowerBidAlreadyExists(); + + uint256 transmitterCredits = getMaxFees(requestCount_); + if (bidFees > transmitterCredits) revert BidExceedsMaxFees(); + + // create a new bid + Bid memory newBid = Bid({fee: bidFees, transmitter: transmitter, extraData: extraData}); + address oldTransmitter = winningBids[requestCount_].transmitter; + winningBids[requestCount_] = newBid; + + // end the auction if the no auction end delay + if (auctionEndDelaySeconds > 0 && auctionStatus[requestCount_] != AuctionStatus.OPEN) { + _startAuction(requestCount_); + _createRequest( + auctionEndDelaySeconds, + deductScheduleFees( + transmitter, + oldTransmitter == address(0) ? address(this) : newBid.transmitter, + auctionEndDelaySeconds + ), + address(this), + this.endAuction.selector, + abi.encode(requestCount_) + ); + } else { + _endAuction(requestCount_); + } + + emit BidPlaced(requestCount_, newBid); + } + + function _startAuction(uint40 requestCount_) internal { + if (auctionStatus[requestCount_] != AuctionStatus.OPEN) revert AuctionNotOpen(); + auctionStatus[requestCount_] = AuctionStatus.OPEN; + emit AuctionStarted(requestCount_); + } + + /// @notice Ends an auction + /// @param requestCount_ The ID of the auction + function endAuction(uint40 requestCount_) external override onlyWatcher { + if ( + auctionStatus[requestCount_] == AuctionStatus.CLOSED || + auctionStatus[requestCount_] == AuctionStatus.NOT_STARTED + ) return; + _endAuction(requestCount_); + } + + function _endAuction(uint40 requestCount_) internal { + // get the winning bid, if no transmitter is set, revert + Bid memory winningBid = winningBids[requestCount_]; + auctionStatus[requestCount_] = AuctionStatus.CLOSED; + + if (winningBid.transmitter != address(0)) { + // todo: might block the request processing if transmitter don't have enough balance for this schedule + // this case can hit when bid timeout is more than 0 + + // set the bid expiration time + // useful in case a transmitter did bid but did not execute payloads + _createRequest( + bidTimeout, + deductScheduleFees(winningBid.transmitter, address(this), bidTimeout), + winningBid.transmitter, + this.expireBid.selector, + abi.encode(requestCount_) + ); + + // start the request processing, it will queue the request + watcher__().requestHandler__().assignTransmitter(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 override onlyWatcher { + if (reAuctionCount[requestCount_] >= maxReAuctionCount) revert MaxReAuctionCountReached(); + RequestParams memory requestParams = _getRequestParams(requestCount_); + + // if executed or cancelled, bid is not expired + if ( + requestParams.requestTrackingParams.payloadsRemaining == 0 || + requestParams.requestTrackingParams.isRequestCancelled + ) return; + + delete winningBids[requestCount_]; + auctionStatus[requestCount_] = AuctionStatus.RESTARTED; + reAuctionCount[requestCount_]++; + + watcher__().requestHandler__().assignTransmitter( + requestCount_, + Bid({fee: 0, transmitter: address(0), extraData: ""}) + ); + emit AuctionRestarted(requestCount_); + } + + function _createRequest( + uint256 delayInSeconds_, + uint256 maxFees_, + address consumeFrom_, + bytes4 callbackSelector_, + bytes memory callbackData_ + ) internal { + OverrideParams memory overrideParams; + overrideParams.callType = SCHEDULE; + overrideParams.delayInSeconds = delayInSeconds_; + + QueueParams memory queueParams; + queueParams.overrideParams = overrideParams; + + // queue and create request + watcher__().queue(queueParams, address(this)); + then(callbackSelector_, callbackData_); + watcher__().submitRequest(maxFees_, address(this), consumeFrom_, bytes("")); + } + + /// @notice Returns the quoted transmitter fees for a request + /// @dev returns the max fees quoted by app gateway subtracting the watcher fees + /// @param requestCount_ The request id + /// @return The quoted transmitter fees + function getMaxFees(uint40 requestCount_) internal view returns (uint256) { + RequestParams memory requestParams = _getRequestParams(requestCount_); + // check if the bid is for this auction manager + if (requestParams.auctionManager != address(this)) revert InvalidBid(); + // get the total fees required for the watcher precompile ops + return requestParams.requestFeesDetails.maxFees; + } + + function deductScheduleFees( + address from_, + address to_, + uint256 delayInSeconds_ + ) internal returns (uint256 watcherFees) { + watcherFees = watcher__().getPrecompileFees(SCHEDULE, abi.encode(delayInSeconds_)); + feesManager__().transferCredits(from_, to_, watcherFees); + } + + function _getRequestParams(uint40 requestCount_) internal view returns (RequestParams memory) { + return watcher__().getRequestParams(requestCount_); + } + + /// @notice Recovers the signer of a message + /// @param digest_ The digest of the message + /// @param signature_ The signature of the message + /// @return signer The signer of the message + function _recoverSigner( + bytes32 digest_, + bytes memory 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_); + } + + /** + * @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. + * @param token_ The address of the token contract. + * @param rescueTo_ The address where rescued tokens need to be sent. + * @param amount_ The amount of tokens to be rescued. + */ + function rescueFunds(address token_, address rescueTo_, uint256 amount_) external onlyWatcher { + RescueFundsLib._rescueFunds(token_, rescueTo_, amount_); + } +} diff --git a/contracts/evmx/Forwarder.sol b/contracts/evmx/Forwarder.sol deleted file mode 100644 index f6d214c2..00000000 --- a/contracts/evmx/Forwarder.sol +++ /dev/null @@ -1,144 +0,0 @@ -// SPDX-License-Identifier: GPL-3.0-only -pragma solidity ^0.8.21; - -import "./interfaces/IAddressResolver.sol"; -import "./interfaces/IMiddleware.sol"; -import "./interfaces/IAppGateway.sol"; -import "./interfaces/IPromise.sol"; -import "./interfaces/IForwarder.sol"; -import {AddressResolverUtil} from "./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 slug on which the contract is deployed - uint32 public chainSlug; - /// @notice on-chain address associated with this forwarder - address public onChainAddress; - - // slot 51 - /// @notice caches the latest async promise address for the last call - address public latestAsyncPromise; - - // 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 { - constructor() { - _disableInitializers(); // disable for implementation - } - - /// @notice Initializer to replace constructor for upgradeable contracts - /// @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_, - address addressResolver_ - ) public initializer { - chainSlug = chainSlug_; - onChainAddress = onChainAddress_; - _setAddressResolver(addressResolver_); - } - - /// @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 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. - /// @return The on-chain address. - function getOnChainAddress() external view returns (address) { - return onChainAddress; - } - - /// @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 middleware and deploys the promise contract - fallback() external { - if (address(deliveryHelper__()) == address(0)) { - 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); - - // 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 middleware. - deliveryHelper__().queue( - QueuePayloadParams({ - chainSlug: chainSlug, - callType: isReadCall == Read.ON ? CallType.READ : CallType.WRITE, - isParallel: isParallelCall, - isPlug: IsPlug.NO, - writeFinality: writeFinality, - asyncPromise: latestAsyncPromise, - switchboard: switchboard, - target: onChainAddress, - appGateway: msg.sender, - gasLimit: gasLimit, - value: value, - readAt: readAt, - payload: msg.data, - initCallData: bytes("") - }) - ); - } -} diff --git a/contracts/evmx/base/AppGatewayBase.sol b/contracts/evmx/base/AppGatewayBase.sol index 1c8af3a6..3e60dce0 100644 --- a/contracts/evmx/base/AppGatewayBase.sol +++ b/contracts/evmx/base/AppGatewayBase.sol @@ -1,72 +1,55 @@ // SPDX-License-Identifier: GPL-3.0-only pragma solidity ^0.8.21; -import "../AddressResolverUtil.sol"; +import "../helpers/AddressResolverUtil.sol"; import "../interfaces/IAppGateway.sol"; import "../interfaces/IForwarder.sol"; -import "../interfaces/IMiddleware.sol"; import "../interfaces/IPromise.sol"; -import {InvalidPromise, FeesNotSet, AsyncModifierNotUsed} from "../../utils/common/Errors.sol"; -import {FAST} from "../../utils/common/Constants.sol"; +import {InvalidPromise, AsyncModifierNotSet} from "../../utils/common/Errors.sol"; +import {FAST, READ, WRITE, SCHEDULE} from "../../utils/common/Constants.sol"; +import {IsPlug, QueueParams, Read, WriteFinality, Parallel} from "../../utils/common/Structs.sol"; /// @title AppGatewayBase /// @notice Abstract contract for the app gateway /// @dev This contract contains helpers for contract deployment, overrides, hooks and request processing abstract contract AppGatewayBase is AddressResolverUtil, IAppGateway { - OverrideParams public overrideParams; + // 50 slots reserved for address resolver util + // slot 51 bool public isAsyncModifierSet; + address public consumeFrom; + + // slot 52 address public auctionManager; + + // slot 53 + uint256 public maxFees; + + // slot 54 bytes32 public sbType; + + // slot 55 bytes public onCompleteData; - uint256 public maxFees; + // slot 56 + OverrideParams public overrideParams; + + // slot 57 mapping(address => bool) public isValidPromise; + + // slot 58 mapping(bytes32 => mapping(uint32 => address)) public override forwarderAddresses; - mapping(bytes32 => bytes) public creationCodeWithArgs; - address public consumeFrom; + // slot 59 + mapping(bytes32 => bytes) public creationCodeWithArgs; /// @notice Modifier to treat functions async - 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_; + modifier async() { + _preAsync(); _; _postAsync(); } - function _postAsync() internal { - isAsyncModifierSet = false; - - 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() { @@ -76,65 +59,56 @@ abstract contract AppGatewayBase is AddressResolverUtil, IAppGateway { _; } - /// @notice Constructor for AppGatewayBase + /// @notice Initializer for AppGatewayBase /// @param addressResolver_ The address resolver address - constructor(address addressResolver_) { - _setAddressResolver(addressResolver_); + function _initializeAppGateway(address addressResolver_) internal { sbType = FAST; + _setAddressResolver(addressResolver_); } - /// @notice Sets the switchboard type - /// @param sbType_ The switchboard type - function _setSbType(bytes32 sbType_) internal { - sbType = sbType_; - } + //////////////////////////////////////////////////////////////////////////////////////////////// + ///////////////////////////////// ASYNC HELPERS //////////////////////////////////////////////// + //////////////////////////////////////////////////////////////////////////////////////////////// - /// @notice Creates a contract ID - /// @param contractName_ The contract name - /// @return bytes32 The contract ID - function _createContractId(string memory contractName_) internal pure returns (bytes32) { - return keccak256(abi.encode(contractName_)); + function _preAsync() internal { + isAsyncModifierSet = true; + _clearOverrides(); + watcher__().clearQueue(); } - /// @notice Gets the current async ID - /// @return uint40 The current async ID - function _getCurrentAsyncId() internal view returns (uint40) { - return watcherPrecompile__().getCurrentRequestCount(); - } + function _postAsync() internal { + isAsyncModifierSet = false; - /// @notice Sets the auction manager - /// @param auctionManager_ The auction manager - function _setAuctionManager(address auctionManager_) internal { - auctionManager = auctionManager_; + (, address[] memory promises) = watcher__().submitRequest( + maxFees, + auctionManager, + consumeFrom, + onCompleteData + ); + _markValidPromises(promises); } - /// @notice Marks the promises as valid - function _markValidPromises() internal { - address[] memory promises = addressResolver__.getPromises(); - for (uint256 i = 0; i < promises.length; i++) { - isValidPromise[promises[i]] = true; - } + function then(bytes4 selector_, bytes memory data_) internal { + IPromise(watcher__().latestAsyncPromise()).then(selector_, data_); } - /// @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 - /// @param isValid Boolean flag indicating whether the contract is authorized (true) or not (false) - /// @dev This function retrieves the onchain address using the contractId and chainSlug, then calls the watcher precompile to update the plug's validity status - function _setValidPlug(uint32 chainSlug_, bytes32 contractId, bool isValid) internal { - address onchainAddress = getOnChainAddress(contractId, chainSlug_); - watcherPrecompileConfig().setIsValidPlug(chainSlug_, onchainAddress, isValid); + /// @notice Schedules a function to be called after a delay + /// @param delayInSeconds_ The delay in seconds + /// @dev callback function and data is set in .then call + function _setSchedule(uint256 delayInSeconds_) internal { + if (!isAsyncModifierSet) revert AsyncModifierNotSet(); + overrideParams.callType = SCHEDULE; + overrideParams.delayInSeconds = delayInSeconds_; + + QueueParams memory queueParams; + queueParams.overrideParams = overrideParams; + watcher__().queue(queueParams, address(this)); } - //////////////////////////////////////////////////////////////////////////////////////////////// ///////////////////////////////// DEPLOY HELPERS /////////////////////////////////////////////////// - //////////////////////////////////////////////////////////////////////////////////////////////// - /// @notice Deploys a contract - /// @param contractId_ The contract ID - /// @param chainSlug_ The chain slug function _deploy(bytes32 contractId_, uint32 chainSlug_, IsPlug isPlug_) internal { - _deploy(contractId_, chainSlug_, isPlug_, new bytes(0)); + _deploy(contractId_, chainSlug_, isPlug_, bytes("")); } /// @notice Deploys a contract @@ -146,31 +120,15 @@ abstract contract AppGatewayBase is AddressResolverUtil, IAppGateway { IsPlug isPlug_, bytes memory initCallData_ ) internal { - if (!isAsyncModifierSet) revert AsyncModifierNotUsed(); - - address asyncPromise = addressResolver__.deployAsyncPromiseContract(address(this)); - IPromise(asyncPromise).then(this.setAddress.selector, abi.encode(chainSlug_, contractId_)); + deployForwarder__().deploy( + isPlug_, + chainSlug_, + initCallData_, + creationCodeWithArgs[contractId_] + ); - isValidPromise[asyncPromise] = true; + then(this.setAddress.selector, abi.encode(chainSlug_, contractId_)); onCompleteData = abi.encode(chainSlug_, true); - - QueuePayloadParams memory queuePayloadParams = QueuePayloadParams({ - chainSlug: chainSlug_, - callType: CallType.DEPLOY, - isParallel: overrideParams.isParallelCall, - isPlug: isPlug_, - writeFinality: overrideParams.writeFinality, - asyncPromise: asyncPromise, - switchboard: watcherPrecompileConfig().switchboards(chainSlug_, sbType), - target: address(0), - appGateway: address(this), - gasLimit: overrideParams.gasLimit, - value: overrideParams.value, - readAt: overrideParams.readAt, - payload: creationCodeWithArgs[contractId_], - initCallData: initCallData_ - }); - IMiddleware(deliveryHelper__()).queue(queuePayloadParams); } /// @notice Sets the address for a deployed contract @@ -178,20 +136,22 @@ abstract contract AppGatewayBase is AddressResolverUtil, IAppGateway { /// @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), + forwarderAddresses[contractId][chainSlug] = asyncDeployer__().getOrDeployForwarderContract( abi.decode(returnData_, (address)), chainSlug ); + } - forwarderAddresses[contractId][chainSlug] = forwarderContractAddress; + /// @notice Reverts the transaction + /// @param requestCount_ The request count + function _revertTx(uint40 requestCount_) internal { + watcher__().cancelRequest(requestCount_); } - /// @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 increases the transaction maxFees + /// @param requestCount_ The request count + function _increaseFees(uint40 requestCount_, uint256 newMaxFees_) internal { + watcher__().increaseFees(requestCount_, newMaxFees_); } /// @notice Gets the on-chain address @@ -210,10 +170,94 @@ abstract contract AppGatewayBase is AddressResolverUtil, IAppGateway { .getOnChainAddress(); } + //////////////////////////////////////////////////////////////////////////////////////////////// + ///////////////////////////////// UTILS //////////////////////////////////////////////// + //////////////////////////////////////////////////////////////////////////////////////////////// + + /// @notice Creates a contract ID + /// @param contractName_ The contract name + /// @return bytes32 The contract ID + function _createContractId(string memory contractName_) internal pure returns (bytes32) { + return keccak256(abi.encode(contractName_)); + } + + /// @notice Gets the current request count + /// @return uint40 The current request count + function _getCurrentRequestCount() internal view returns (uint40) { + return watcher__().getCurrentRequestCount(); + } + + /// @notice Marks the promises as valid + function _markValidPromises(address[] memory promises_) internal { + for (uint256 i = 0; i < promises_.length; i++) { + isValidPromise[promises_[i]] = true; + } + } + + //////////////////////////////////////////////////////////////////////////////////////////////// + ///////////////////////////////// ADMIN HELPERS //////////////////////////////////////////////// + //////////////////////////////////////////////////////////////////////////////////////////////// + + /// @notice Sets the auction manager + /// @param auctionManager_ The auction manager + function _setAuctionManager(address auctionManager_) internal { + auctionManager = auctionManager_; + } + + /// @notice Sets the switchboard type + /// @param sbType_ The switchboard type + function _setSbType(bytes32 sbType_) internal { + sbType = sbType_; + } + + /// @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 + /// @param isValid Boolean flag indicating whether the contract is authorized (true) or not (false) + /// @dev This function retrieves the onchain address using the contractId_ and chainSlug, then calls the watcher precompile to update the plug's validity status + function _setValidPlug(bool isValid, uint32 chainSlug_, bytes32 contractId_) internal { + address onchainAddress = getOnChainAddress(contractId_, chainSlug_); + watcher__().setIsValidPlug(isValid, chainSlug_, onchainAddress); + } + + function _approveFeesWithSignature(bytes memory feesApprovalData_) internal { + if (feesApprovalData_.length == 0) return; + (consumeFrom, , ) = feesManager__().approveAppGatewayWithSignature(feesApprovalData_); + } + + /// @notice Withdraws fee tokens + /// @param chainSlug_ The chain slug + /// @param token_ The token address + /// @param amount_ The amount + /// @param receiver_ The receiver address + function _withdrawCredits( + uint32 chainSlug_, + address token_, + uint256 amount_, + address receiver_ + ) internal { + AppGatewayApprovals[] memory approvals = new AppGatewayApprovals[](1); + approvals[0] = AppGatewayApprovals({appGateway: address(feesManager__()), approval: true}); + feesManager__().approveAppGateways(approvals); + feesManager__().withdrawCredits(chainSlug_, token_, amount_, maxFees, receiver_); + } + //////////////////////////////////////////////////////////////////////////////////////////////// ///////////////////////////////// TX OVERRIDE HELPERS /////////////////////////////////////////////////// //////////////////////////////////////////////////////////////////////////////////////////////// + function _clearOverrides() internal { + overrideParams.callType = WRITE; + overrideParams.isParallelCall = Parallel.OFF; + overrideParams.gasLimit = 0; + overrideParams.value = 0; + overrideParams.readAtBlockNumber = 0; + overrideParams.writeFinality = WriteFinality.LOW; + overrideParams.delayInSeconds = 0; + consumeFrom = address(this); + onCompleteData = bytes(""); + } + /// @notice Sets multiple overrides in one call /// @param isReadCall_ The read call flag /// @param fees_ The maxFees configuration @@ -225,19 +269,15 @@ abstract contract AppGatewayBase is AddressResolverUtil, IAppGateway { uint256 gasLimit_, uint256 fees_ ) internal { - overrideParams.isReadCall = isReadCall_; + _setCallType(isReadCall_); overrideParams.isParallelCall = isParallelCall_; overrideParams.gasLimit = gasLimit_; 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 Modifier to treat functions async with consume from address + function _setOverrides(address consumeFrom_) internal { + consumeFrom = consumeFrom_; } /// @notice Sets isReadCall, maxFees and gasLimit overrides @@ -245,7 +285,7 @@ abstract contract AppGatewayBase is AddressResolverUtil, IAppGateway { /// @param isParallelCall_ The sequential call flag /// @param gasLimit_ The gas limit function _setOverrides(Read isReadCall_, Parallel isParallelCall_, uint256 gasLimit_) internal { - overrideParams.isReadCall = isReadCall_; + _setCallType(isReadCall_); overrideParams.isParallelCall = isParallelCall_; overrideParams.gasLimit = gasLimit_; } @@ -254,7 +294,7 @@ abstract contract AppGatewayBase is AddressResolverUtil, IAppGateway { /// @param isReadCall_ The read call flag /// @param isParallelCall_ The sequential call flag function _setOverrides(Read isReadCall_, Parallel isParallelCall_) internal { - overrideParams.isReadCall = isReadCall_; + _setCallType(isReadCall_); overrideParams.isParallelCall = isParallelCall_; } @@ -272,24 +312,24 @@ abstract contract AppGatewayBase is AddressResolverUtil, IAppGateway { /// @notice Sets isParallelCall overrides /// @param isParallelCall_ The sequential call flag - /// @param readAt_ The read anchor value. Currently block number. - function _setOverrides(Parallel isParallelCall_, uint256 readAt_) internal { + /// @param readAtBlockNumber_ The read anchor value. Currently block number. + function _setOverrides(Parallel isParallelCall_, uint256 readAtBlockNumber_) internal { overrideParams.isParallelCall = isParallelCall_; - overrideParams.readAt = readAt_; + overrideParams.readAtBlockNumber = readAtBlockNumber_; } /// @notice Sets isReadCall overrides /// @param isReadCall_ The read call flag function _setOverrides(Read isReadCall_) internal { - overrideParams.isReadCall = isReadCall_; + _setCallType(isReadCall_); } /// @notice Sets isReadCall overrides /// @param isReadCall_ The read call flag - /// @param readAt_ The read anchor value. Currently block number. - function _setOverrides(Read isReadCall_, uint256 readAt_) internal { - overrideParams.isReadCall = isReadCall_; - overrideParams.readAt = readAt_; + /// @param readAtBlockNumber_ The read anchor value. Currently block number. + function _setOverrides(Read isReadCall_, uint256 readAtBlockNumber_) internal { + _setCallType(isReadCall_); + overrideParams.readAtBlockNumber = readAtBlockNumber_; } /// @notice Sets gasLimit overrides @@ -298,6 +338,10 @@ abstract contract AppGatewayBase is AddressResolverUtil, IAppGateway { overrideParams.gasLimit = gasLimit_; } + function _setCallType(Read isReadCall_) internal { + overrideParams.callType = isReadCall_ == Read.OFF ? WRITE : READ; + } + function _setMsgValue(uint256 value_) internal { overrideParams.value = value_; } @@ -308,58 +352,8 @@ abstract contract AppGatewayBase is AddressResolverUtil, IAppGateway { maxFees = fees_; } - function getOverrideParams() - public - view - returns (Read, Parallel, WriteFinality, uint256, uint256, uint256, bytes32) - { - return ( - overrideParams.isReadCall, - overrideParams.isParallelCall, - overrideParams.writeFinality, - overrideParams.readAt, - overrideParams.gasLimit, - overrideParams.value, - sbType - ); - } - - //////////////////////////////////////////////////////////////////////////////////////////////// - ///////////////////////////////// ASYNC BATCH HELPERS ///////////////////////////////////////////////// - //////////////////////////////////////////////////////////////////////////////////////////////// - - /// @notice Reverts the transaction - /// @param requestCount_ The async ID - function _revertTx(uint40 requestCount_) internal { - deliveryHelper__().cancelRequest(requestCount_); - } - - /// @notice increases the transaction maxFees - /// @param requestCount_ The async ID - function _increaseFees(uint40 requestCount_, uint256 newMaxFees_) internal { - deliveryHelper__().increaseFees(requestCount_, newMaxFees_); - } - - /// @notice Withdraws fee tokens - /// @param chainSlug_ The chain slug - /// @param token_ The token address - /// @param amount_ The amount - /// @param receiver_ The receiver address - function _withdrawFeeTokens( - uint32 chainSlug_, - address token_, - uint256 amount_, - address receiver_ - ) internal returns (uint40) { - return - deliveryHelper__().withdrawTo( - chainSlug_, - token_, - amount_, - receiver_, - auctionManager, - maxFees - ); + function getOverrideParams() public view returns (OverrideParams memory, bytes32) { + return (overrideParams, sbType); } //////////////////////////////////////////////////////////////////////////////////////////////// @@ -373,25 +367,21 @@ abstract contract AppGatewayBase is AddressResolverUtil, IAppGateway { function onRequestComplete( uint40, bytes calldata onCompleteData_ - ) external override onlyDeliveryHelper { + ) external override onlyWatcher { if (onCompleteData_.length == 0) return; (uint32 chainSlug, bool isDeploy) = abi.decode(onCompleteData_, (uint32, bool)); if (isDeploy) { - initialize(chainSlug); + initializeOnChain(chainSlug); } } /// @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 {} + function initializeOnChain(uint32 chainSlug_) public virtual {} /// @notice hook to handle the revert in callbacks or onchain executions /// @dev can be overridden by the app gateway to add custom logic - /// @param requestCount_ The async ID /// @param payloadId_ The payload ID - function handleRevert( - uint40 requestCount_, - bytes32 payloadId_ - ) external override onlyPromises {} + function handleRevert(bytes32 payloadId_) external override onlyPromises {} } diff --git a/contracts/evmx/fees/Credit.sol b/contracts/evmx/fees/Credit.sol new file mode 100644 index 00000000..fb9dd643 --- /dev/null +++ b/contracts/evmx/fees/Credit.sol @@ -0,0 +1,308 @@ +// SPDX-License-Identifier: GPL-3.0-only +pragma solidity ^0.8.21; + +import "solady/utils/Initializable.sol"; +import "solady/utils/ECDSA.sol"; +import "solady/utils/SafeTransferLib.sol"; +import "solady/auth/Ownable.sol"; + +import "../interfaces/IFeesManager.sol"; +import "../interfaces/IFeesPlug.sol"; +import "../interfaces/IFeesPool.sol"; + +import {AddressResolverUtil} from "../helpers/AddressResolverUtil.sol"; +import {NonceUsed, InvalidAmount, InsufficientCreditsAvailable, InsufficientBalance, InvalidChainSlug, NotRequestHandler} from "../../utils/common/Errors.sol"; +import {WRITE} from "../../utils/common/Constants.sol"; +import "../../utils/RescueFundsLib.sol"; + +abstract contract FeesManagerStorage is IFeesManager { + // slots [0-49] reserved for gap + uint256[50] _gap_before; + + // slot 50 + /// @notice evmx slug + uint32 public evmxSlug; + + IFeesPool public feesPool; + + // slot 51 + /// @notice switchboard type + bytes32 public sbType; + + // slot 52 + /// @notice user credits => stores fees for user, app gateway, transmitters and watcher precompile + mapping(address => UserCredits) public userCredits; + + // slot 53 + /// @notice Mapping to track request credits details for each request count + /// @dev requestCount => RequestFee + mapping(uint40 => uint256) public requestBlockedCredits; + + // slot 54 + // user approved app gateways + // userAddress => appGateway => isApproved + mapping(address => mapping(address => bool)) public isApproved; + + // slot 55 + // token pool balances + // chainSlug => token address => amount + mapping(uint32 => mapping(address => uint256)) public tokenOnChainBalances; + + // slot 56 + /// @notice Mapping to track nonce to whether it has been used + /// @dev address => signatureNonce => isNonceUsed + /// @dev used by watchers or other users in signatures + mapping(address => mapping(uint256 => bool)) public isNonceUsed; + + // slot 57 + /// @notice Mapping to track fees plug for each chain slug + /// @dev chainSlug => fees plug address + mapping(uint32 => address) public feesPlugs; + + // slots [58-107] reserved for gap + uint256[50] _gap_after; + + // slots [108-157] 50 slots reserved for address resolver util +} + +/// @title UserUtils +/// @notice Contract for managing user utils +abstract contract Credit is FeesManagerStorage, Initializable, Ownable, AddressResolverUtil { + /// @notice Emitted when fees deposited are updated + /// @param chainSlug The chain identifier + /// @param token The token address + /// @param depositTo The address to deposit to + /// @param creditAmount The credit amount added + /// @param nativeAmount The native amount transferred + event Deposited( + uint32 indexed chainSlug, + address indexed token, + address indexed depositTo, + uint256 creditAmount, + uint256 nativeAmount + ); + + /// @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 Emitted when credits are transferred + event CreditsTransferred(address indexed from, address indexed to, uint256 amount); + + /// @notice Emitted when fees plug is set + event FeesPlugSet(uint32 indexed chainSlug, address indexed feesPlug); + + /// @notice Emitted when fees pool is set + event FeesPoolSet(address indexed feesPool); + + function setFeesPlug(uint32 chainSlug_, address feesPlug_) external onlyOwner { + feesPlugs[chainSlug_] = feesPlug_; + emit FeesPlugSet(chainSlug_, feesPlug_); + } + + function setFeesPool(address feesPool_) external onlyOwner { + feesPool = IFeesPool(feesPool_); + emit FeesPoolSet(feesPool_); + } + + /// @notice Deposits credits and native tokens to a user + /// @param depositTo_ The address to deposit the credits to + /// @param chainSlug_ The chain slug + /// @param token_ The token address + /// @param nativeAmount_ The native amount + /// @param creditAmount_ The credit amount + function deposit( + uint32 chainSlug_, + address token_, + address depositTo_, + uint256 nativeAmount_, + uint256 creditAmount_ + ) external override onlyWatcher { + tokenOnChainBalances[chainSlug_][token_] += creditAmount_ + nativeAmount_; + + UserCredits storage userCredit = userCredits[depositTo_]; + userCredit.totalCredits += creditAmount_; + + if (nativeAmount_ > 0) { + // if native transfer fails, add to credit + bool success = feesPool.withdraw(depositTo_, nativeAmount_); + + if (!success) { + userCredit.totalCredits += nativeAmount_; + nativeAmount_ = 0; + creditAmount_ += nativeAmount_; + } + } + + emit Deposited(chainSlug_, token_, depositTo_, creditAmount_, nativeAmount_); + } + + function wrap(address receiver_) external payable override { + UserCredits storage userCredit = userCredits[receiver_]; + + uint256 amount = msg.value; + if (amount == 0) revert InvalidAmount(); + userCredit.totalCredits += amount; + + // reverts if transfer fails + SafeTransferLib.safeTransferETH(address(feesPool), amount); + emit CreditsWrapped(receiver_, amount); + } + + function unwrap(uint256 amount_, address receiver_) external { + UserCredits storage userCredit = userCredits[msg.sender]; + if (userCredit.totalCredits < amount_) revert InsufficientCreditsAvailable(); + userCredit.totalCredits -= amount_; + + bool success = feesPool.withdraw(receiver_, amount_); + if (!success) revert InsufficientBalance(); + + emit CreditsUnwrapped(receiver_, amount_); + } + + /// @notice Returns available (unblocked) credits for a gateway + /// @param consumeFrom_ The app gateway address + /// @return The available credit amount + function getAvailableCredits(address consumeFrom_) public view override returns (uint256) { + UserCredits memory userCredit = userCredits[consumeFrom_]; + return userCredit.totalCredits - userCredit.blockedCredits; + } + + /// @notice Checks if the user has enough credits + /// @param consumeFrom_ address to consume from + /// @param spender_ address to spend from + /// @param amount_ amount to spend + /// @return True if the user has enough credits, false otherwise + function isCreditSpendable( + address consumeFrom_, + address spender_, + uint256 amount_ + ) public view override returns (bool) { + // If consumeFrom_ is not same as spender_ or spender_ is not watcher, check if it is approved + if (!_isWatcher(spender_) && consumeFrom_ != spender_) { + if (!isApproved[consumeFrom_][spender_]) return false; + } + + return getAvailableCredits(consumeFrom_) >= amount_; + } + + function transferCredits(address from_, address to_, uint256 amount_) external override { + if (!isCreditSpendable(from_, msg.sender, amount_)) revert InsufficientCreditsAvailable(); + userCredits[from_].totalCredits -= amount_; + userCredits[to_].totalCredits += amount_; + + emit CreditsTransferred(from_, to_, amount_); + } + + /// @notice Approves multiple app gateways for the caller + /// @param params_ Array of app gateway addresses to approve + function approveAppGateways(AppGatewayApprovals[] calldata params_) external override { + for (uint256 i = 0; i < params_.length; i++) { + isApproved[msg.sender][params_[i].appGateway] = params_[i].approval; + } + } + + /// @notice Approves an app gateway for the caller + /// @dev Approval data is encoded to make app gateways compatible with future changes + /// @param feeApprovalData_ The fee approval data + /// @return consumeFrom The consume from address + /// @return spender The app gateway address + /// @return approval The approval status + function approveAppGatewayWithSignature( + bytes memory feeApprovalData_ + ) external returns (address consumeFrom, address spender, bool approval) { + uint256 nonce; + bytes memory signature_; + (spender, approval, nonce, signature_) = abi.decode( + feeApprovalData_, + (address, bool, uint256, bytes) + ); + bytes32 digest = keccak256(abi.encode(address(this), evmxSlug, spender, nonce, approval)); + consumeFrom = _recoverSigner(digest, signature_); + + if (isNonceUsed[consumeFrom][nonce]) revert NonceUsed(); + isNonceUsed[consumeFrom][nonce] = true; + isApproved[consumeFrom][spender] = approval; + return (consumeFrom, spender, approval); + } + + /// @notice Withdraws funds to a specified receiver + /// @dev This function is used to withdraw fees from the fees plug + /// @dev assumed that transmitter can bid for their request on AM + /// @param chainSlug_ The chain identifier + /// @param token_ The address of the token + /// @param credits_ The amount of tokens to withdraw + /// @param maxFees_ The fees needed to process the withdraw + /// @param receiver_ The address of the receiver + function withdrawCredits( + uint32 chainSlug_, + address token_, + uint256 credits_, + uint256 maxFees_, + address receiver_ + ) public override { + address consumeFrom = msg.sender; + + // Check if amount is available in fees plug + uint256 availableCredits = getAvailableCredits(consumeFrom); + if (availableCredits < credits_ + maxFees_) revert InsufficientCreditsAvailable(); + + userCredits[consumeFrom].totalCredits -= credits_; + tokenOnChainBalances[chainSlug_][token_] -= credits_; + + // Add it to the queue and submit request + _createRequest( + chainSlug_, + consumeFrom, + maxFees_, + abi.encodeCall(IFeesPlug.withdrawFees, (token_, receiver_, credits_)) + ); + } + + function _createRequest( + uint32 chainSlug_, + address consumeFrom_, + uint256 maxFees_, + bytes memory payload_ + ) internal { + OverrideParams memory overrideParams; + overrideParams.callType = WRITE; + overrideParams.writeFinality = WriteFinality.LOW; + // todo: can add gas limit here + + QueueParams memory queueParams; + queueParams.overrideParams = overrideParams; + queueParams.transaction = Transaction({ + chainSlug: chainSlug_, + target: _getFeesPlugAddress(chainSlug_), + payload: payload_ + }); + queueParams.switchboardType = sbType; + + // queue and create request + watcher__().queueAndSubmit(queueParams, maxFees_, address(0), consumeFrom_, bytes("")); + } + + function _getFeesPlugAddress(uint32 chainSlug_) internal view returns (address) { + if (feesPlugs[chainSlug_] == address(0)) revert InvalidChainSlug(); + return feesPlugs[chainSlug_]; + } + + function _getRequestParams(uint40 requestCount_) internal view returns (RequestParams memory) { + return watcher__().getRequestParams(requestCount_); + } + + function _recoverSigner( + bytes32 digest_, + bytes memory 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_); + } + + /// @notice hook called by watcher precompile when request is finished + function onRequestComplete(uint40, bytes memory) external {} +} diff --git a/contracts/evmx/fees/FeesManager.sol b/contracts/evmx/fees/FeesManager.sol new file mode 100644 index 00000000..306b27c2 --- /dev/null +++ b/contracts/evmx/fees/FeesManager.sol @@ -0,0 +1,126 @@ +// SPDX-License-Identifier: GPL-3.0-only +pragma solidity ^0.8.21; + +import "./Credit.sol"; + +/// @title FeesManager +/// @notice Contract for managing fees +contract FeesManager is Credit { + /// @notice Emitted when fees are blocked for a batch + /// @param requestCount The batch identifier + /// @param consumeFrom The consume from address + /// @param amount The blocked amount + event CreditsBlocked(uint40 indexed requestCount, address indexed consumeFrom, uint256 amount); + + /// @notice Emitted when fees are unblocked and assigned to a transmitter + /// @param requestCount The batch identifier + /// @param consumeFrom The consume from address + /// @param transmitter The transmitter address + /// @param amount The unblocked amount + event CreditsUnblockedAndAssigned( + uint40 indexed requestCount, + address indexed consumeFrom, + address indexed transmitter, + uint256 amount + ); + + /// @notice Emitted when fees are unblocked + /// @param requestCount The batch identifier + /// @param consumeFrom The consume from address + event CreditsUnblocked(uint40 indexed requestCount, address indexed consumeFrom); + + modifier onlyRequestHandler() { + if (msg.sender != address(watcher__().requestHandler__())) revert NotRequestHandler(); + _; + } + + constructor() { + _disableInitializers(); // disable for implementation + } + + /// @notice Initializer function to replace constructor + /// @param addressResolver_ The address of the address resolver + /// @param owner_ The address of the owner + /// @param evmxSlug_ The evmx chain slug + function initialize( + uint32 evmxSlug_, + address addressResolver_, + address feesPool_, + address owner_, + bytes32 sbType_ + ) public reinitializer(1) { + evmxSlug = evmxSlug_; + sbType = sbType_; + feesPool = IFeesPool(feesPool_); + _setAddressResolver(addressResolver_); + _initializeOwner(owner_); + } + + /////////////////////// FEES MANAGEMENT /////////////////////// + + /// @notice Blocks fees for a request count + /// @param requestCount_ The batch identifier + /// @param consumeFrom_ The fees payer address + /// @param credits_ The total fees to block + /// @dev Only callable by delivery helper + function blockCredits( + uint40 requestCount_, + address consumeFrom_, + uint256 credits_ + ) external override onlyRequestHandler { + if (getAvailableCredits(consumeFrom_) < credits_) revert InsufficientCreditsAvailable(); + + UserCredits storage userCredit = userCredits[consumeFrom_]; + userCredit.blockedCredits += credits_; + requestBlockedCredits[requestCount_] = credits_; + emit CreditsBlocked(requestCount_, consumeFrom_, credits_); + } + + /// @notice Unblocks fees after successful execution and assigns them to the transmitter + /// @param requestCount_ The request count of the executed batch + /// @param assignTo_ The address of the transmitter + function unblockAndAssignCredits( + uint40 requestCount_, + address assignTo_ + ) external override onlyRequestHandler { + uint256 blockedCredits = requestBlockedCredits[requestCount_]; + if (blockedCredits == 0) return; + + address consumeFrom = _getRequestParams(requestCount_).requestFeesDetails.consumeFrom; + // Unblock fees from deposit + UserCredits storage userCredit = userCredits[consumeFrom]; + userCredit.blockedCredits -= blockedCredits; + userCredit.totalCredits -= blockedCredits; + + // Assign fees to transmitter + userCredits[assignTo_].totalCredits += blockedCredits; + + // Clean up storage + delete requestBlockedCredits[requestCount_]; + emit CreditsUnblockedAndAssigned(requestCount_, consumeFrom, assignTo_, blockedCredits); + } + + function unblockCredits(uint40 requestCount_) external override onlyRequestHandler { + uint256 blockedCredits = requestBlockedCredits[requestCount_]; + if (blockedCredits == 0) return; + + // Unblock fees from deposit + address consumeFrom = _getRequestParams(requestCount_).requestFeesDetails.consumeFrom; + UserCredits storage userCredit = userCredits[consumeFrom]; + userCredit.blockedCredits -= blockedCredits; + + delete requestBlockedCredits[requestCount_]; + emit CreditsUnblocked(requestCount_, consumeFrom); + } + + /** + * @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. + * @param token_ The address of the token contract. + * @param rescueTo_ The address where rescued tokens need to be sent. + * @param amount_ The amount of tokens to be rescued. + */ + function rescueFunds(address token_, address rescueTo_, uint256 amount_) external onlyWatcher { + RescueFundsLib._rescueFunds(token_, rescueTo_, amount_); + } +} diff --git a/contracts/evmx/fees/FeesPool.sol b/contracts/evmx/fees/FeesPool.sol new file mode 100644 index 00000000..f7aeb73d --- /dev/null +++ b/contracts/evmx/fees/FeesPool.sol @@ -0,0 +1,51 @@ +// SPDX-License-Identifier: GPL-3.0-only +pragma solidity ^0.8.21; + +import "../../utils/AccessControl.sol"; +import "../../utils/common/AccessRoles.sol"; +import "../interfaces/IFeesPool.sol"; +import "solady/utils/SafeTransferLib.sol"; + +/** + * @title FeesPool + * @notice Contract to store native fees that can be pulled by fees manager + */ +contract FeesPool is IFeesPool, AccessControl { + error TransferFailed(); + + /** + * @param owner_ The address of the owner + */ + constructor(address owner_) { + _setOwner(owner_); + + // to rescue funds + _grantRole(FEE_MANAGER_ROLE, owner_); + } + + /** + * @notice Allows fees manager to withdraw native tokens + * @param to_ The address to withdraw to + * @param amount_ The amount to withdraw + * @return success Whether the withdrawal was successful + */ + function withdraw( + address to_, + uint256 amount_ + ) external onlyRole(FEE_MANAGER_ROLE) returns (bool success) { + if (amount_ == 0) return true; + success = SafeTransferLib.trySafeTransferETH(to_, amount_, gasleft()); + emit NativeWithdrawn(success, to_, amount_); + } + + /** + * @notice Returns the current balance of native tokens in the pool + */ + function getBalance() external view returns (uint256) { + return address(this).balance; + } + + receive() external payable { + emit NativeDeposited(msg.sender, msg.value); + } +} diff --git a/contracts/evmx/helpers/AddressResolver.sol b/contracts/evmx/helpers/AddressResolver.sol new file mode 100644 index 00000000..f11c1a10 --- /dev/null +++ b/contracts/evmx/helpers/AddressResolver.sol @@ -0,0 +1,113 @@ +// SPDX-License-Identifier: GPL-3.0-only +pragma solidity ^0.8.21; + +import {Initializable} from "solady/utils/Initializable.sol"; +import "solady/auth/Ownable.sol"; +import "../../utils/RescueFundsLib.sol"; +import "../../utils/common/Errors.sol"; +import "../interfaces/IAddressResolver.sol"; + +abstract contract AddressResolverStorage is IAddressResolver { + // slots [0-49] reserved for gap + uint256[50] _gap_before; + + // slot 50 + IWatcher public override watcher__; + + // slot 51 + IFeesManager public override feesManager__; + + // slot 52 + IAsyncDeployer public override asyncDeployer__; + + // slot 53 + IDeployForwarder public override deployForwarder__; + + // slot 54 + address public override defaultAuctionManager; + + // slot 55 + mapping(bytes32 => address) public override contractAddresses; +} + +/// @title AddressResolver Contract +/// @notice This contract is responsible for fetching latest core addresses and deploying Forwarder and AsyncPromise contracts. +/// @dev Inherits the Ownable contract and implements the IAddressResolver interface. +contract AddressResolver is AddressResolverStorage, Initializable, Ownable { + modifier onlyWatcher() { + if (msg.sender != address(watcher__)) revert OnlyWatcherAllowed(); + _; + } + + /// @notice Constructor to initialize the contract + /// @dev it deploys the forwarder and async promise implementations and beacons for them + /// @dev this contract is owner of the beacons for upgrading later + 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) { + _initializeOwner(owner_); + } + + /// @notice Updates the address of the watcher contract + /// @param watcher_ The address of the watcher contract + function setWatcher(address watcher_) external override onlyOwner { + watcher__ = IWatcher(watcher_); + emit WatcherUpdated(watcher_); + } + + /// @notice Updates the address of the fees manager + /// @param feesManager_ The address of the fees manager + function setFeesManager(address feesManager_) external override onlyOwner { + feesManager__ = IFeesManager(feesManager_); + emit FeesManagerUpdated(feesManager_); + } + + /// @notice Updates the address of the async deployer + /// @param asyncDeployer_ The address of the async deployer + function setAsyncDeployer(address asyncDeployer_) external override onlyOwner { + asyncDeployer__ = IAsyncDeployer(asyncDeployer_); + emit AsyncDeployerUpdated(asyncDeployer_); + } + + /// @notice Updates the address of the default auction manager + /// @param defaultAuctionManager_ The address of the default auction manager + function setDefaultAuctionManager(address defaultAuctionManager_) external override onlyOwner { + defaultAuctionManager = defaultAuctionManager_; + emit DefaultAuctionManagerUpdated(defaultAuctionManager_); + } + + /// @notice Updates the address of the deploy forwarder + /// @param deployForwarder_ The address of the deploy forwarder + function setDeployForwarder(address deployForwarder_) external override onlyOwner { + deployForwarder__ = IDeployForwarder(deployForwarder_); + emit DeployForwarderUpdated(deployForwarder_); + } + + /// @notice Updates the address of a contract + /// @param contractId_ The id of the contract + /// @param contractAddress_ The address of the contract + function setContractAddress( + bytes32 contractId_, + address contractAddress_ + ) external override onlyOwner { + contractAddresses[contractId_] = contractAddress_; + emit ContractAddressUpdated(contractId_, contractAddress_); + } + + /** + * @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. + * @param token_ The address of the token contract. + * @param rescueTo_ The address where rescued tokens need to be sent. + * @param amount_ The amount of tokens to be rescued. + */ + function rescueFunds(address token_, address rescueTo_, uint256 amount_) external onlyWatcher { + RescueFundsLib._rescueFunds(token_, rescueTo_, amount_); + } +} diff --git a/contracts/evmx/helpers/AddressResolverUtil.sol b/contracts/evmx/helpers/AddressResolverUtil.sol new file mode 100644 index 00000000..66663c7d --- /dev/null +++ b/contracts/evmx/helpers/AddressResolverUtil.sol @@ -0,0 +1,67 @@ +// SPDX-License-Identifier: GPL-3.0-only +pragma solidity ^0.8.21; + +import "../interfaces/IAddressResolver.sol"; +import "../interfaces/IWatcher.sol"; +import "../interfaces/IFeesManager.sol"; +import "../interfaces/IAsyncDeployer.sol"; +import {OnlyWatcherAllowed} from "../../utils/common/Errors.sol"; + +/// @title AddressResolverUtil +/// @notice Utility contract for resolving system contract addresses +/// @dev Provides access control and address resolution functionality for the system +abstract contract AddressResolverUtil { + /// @notice The address resolver contract reference + /// @dev Used to look up system contract addresses + // slot 0 + IAddressResolver public addressResolver__; + + // slots 1-49 reserved for future use + uint256[49] __gap_resolver_util; + + /// @notice Restricts function access to the watcher precompile contract + /// @dev Validates that msg.sender matches the registered watcher precompile address + modifier onlyWatcher() { + if (!_isWatcher(msg.sender)) revert OnlyWatcherAllowed(); + _; + } + + function _isWatcher(address account_) internal view returns (bool) { + return (watcher__().isWatcher(account_) || account_ == address(watcher__())); + } + + /// @notice Gets the watcher precompile contract interface + /// @return IWatcher interface of the registered watcher precompile + /// @dev Resolves and returns the watcher precompile contract for interaction + function watcher__() public view returns (IWatcher) { + return addressResolver__.watcher__(); + } + + /// @notice Gets the watcher precompile contract interface + /// @return IWatcher interface of the registered watcher precompile + /// @dev Resolves and returns the watcher precompile contract for interaction + function feesManager__() public view returns (IFeesManager) { + return addressResolver__.feesManager__(); + } + + /// @notice Gets the async deployer contract interface + /// @return IAsyncDeployer interface of the registered async deployer + /// @dev Resolves and returns the async deployer contract for interaction + function asyncDeployer__() public view returns (IAsyncDeployer) { + return addressResolver__.asyncDeployer__(); + } + + /// @notice Gets the deploy forwarder contract interface + /// @return IDeployForwarder interface of the registered deploy forwarder + /// @dev Resolves and returns the deploy forwarder contract for interaction + function deployForwarder__() public view returns (IDeployForwarder) { + return addressResolver__.deployForwarder__(); + } + + /// @notice Internal function to set the address resolver + /// @param _addressResolver The address of the resolver contract + /// @dev Should be called in the initialization of inheriting contracts + function _setAddressResolver(address _addressResolver) internal { + addressResolver__ = IAddressResolver(_addressResolver); + } +} diff --git a/contracts/evmx/AddressResolver.sol b/contracts/evmx/helpers/AsyncDeployer.sol similarity index 55% rename from contracts/evmx/AddressResolver.sol rename to contracts/evmx/helpers/AsyncDeployer.sol index 35e9dca9..735d3241 100644 --- a/contracts/evmx/AddressResolver.sol +++ b/contracts/evmx/helpers/AsyncDeployer.sol @@ -1,74 +1,44 @@ // SPDX-License-Identifier: GPL-3.0-only pragma solidity ^0.8.21; -import {Ownable} from "solady/auth/Ownable.sol"; import {LibClone} from "solady/utils/LibClone.sol"; import {UpgradeableBeacon} from "solady/utils/UpgradeableBeacon.sol"; import {Initializable} from "solady/utils/Initializable.sol"; -import "./interfaces/IAddressResolver.sol"; +import "solady/auth/Ownable.sol"; +import "../interfaces/IAsyncDeployer.sol"; import {Forwarder} from "./Forwarder.sol"; import {AsyncPromise} from "./AsyncPromise.sol"; +import {AddressResolverUtil} from "./AddressResolverUtil.sol"; +import "../../utils/RescueFundsLib.sol"; -abstract contract AddressResolverStorage is IAddressResolver { +abstract contract AsyncDeployerStorage is IAsyncDeployer { // slots [0-49] reserved for gap uint256[50] _gap_before; // slot 50 - IWatcherPrecompile public override watcherPrecompile__; - - // slot 51 UpgradeableBeacon public forwarderBeacon; - // slot 52 + // slot 51 UpgradeableBeacon public asyncPromiseBeacon; - // slot 53 - address public override deliveryHelper; - - // slot 54 - address public override feesManager; - - // slot 55 + // slot 52 address public forwarderImplementation; - // slot 56 + // slot 53 address public asyncPromiseImplementation; - // slot 57 - address[] internal _promises; - - // slot 58 + // slot 54 uint256 public asyncPromiseCounter; - // slot 59 - uint64 public version; - address public override defaultAuctionManager; - - // slot 60 - mapping(address => address) public override contractsToGateways; - - // slots [61-110] reserved for gap + // slots [55-104] reserved for gap uint256[50] _gap_after; -} -/// @title AddressResolver Contract -/// @notice This contract is responsible for fetching latest core addresses and deploying Forwarder and AsyncPromise contracts. -/// @dev Inherits the Ownable contract and implements the IAddressResolver interface. -contract AddressResolver is AddressResolverStorage, Initializable, Ownable { - /// @notice Error thrown if AppGateway contract was already set by a different address - error InvalidAppGateway(address contractAddress_); - - /// @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_); + // slots [105-154] 50 slots reserved for address resolver util +} +/// @title AsyncDeployer Contract +/// @notice This contract is responsible for deploying Forwarder and AsyncPromise contracts. +contract AsyncDeployer is AsyncDeployerStorage, Initializable, AddressResolverUtil, Ownable { constructor() { _disableInitializers(); // disable for implementation } @@ -77,9 +47,9 @@ contract AddressResolver is AddressResolverStorage, Initializable, Ownable { /// @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; + function initialize(address owner_, address addressResolver_) public reinitializer(1) { _initializeOwner(owner_); + _setAddressResolver(addressResolver_); forwarderImplementation = address(new Forwarder()); asyncPromiseImplementation = address(new AsyncPromise()); @@ -96,10 +66,9 @@ contract AddressResolver is AddressResolverStorage, Initializable, Ownable { /// @param chainSlug_ The chain slug /// @return newForwarder The address of the deployed Forwarder proxy contract function getOrDeployForwarderContract( - address appGateway_, address chainContractAddress_, uint32 chainSlug_ - ) public returns (address newForwarder) { + ) public override returns (address newForwarder) { // predict address address forwarderAddress = getForwarderAddress(chainContractAddress_, chainSlug_); @@ -117,23 +86,40 @@ contract AddressResolver is AddressResolverStorage, Initializable, Ownable { // deploys the proxy newForwarder = _deployProxy(salt, address(forwarderBeacon), initData); - // sets the config - _setConfig(appGateway_, newForwarder); - // emits the event emit ForwarderDeployed(newForwarder, salt); } + /// @notice Deploys an AsyncPromise proxy contract + /// @param invoker_ The address of the invoker + /// @return newAsyncPromise The address of the deployed AsyncPromise proxy contract + function deployAsyncPromiseContract( + address invoker_, + uint40 requestCount_ + ) external override onlyWatcher returns (address newAsyncPromise) { + // creates init data and salt + (bytes32 salt, bytes memory initData) = _createAsyncPromiseParams(invoker_, requestCount_); + asyncPromiseCounter++; + + // deploys the proxy + newAsyncPromise = _deployProxy(salt, address(asyncPromiseBeacon), initData); + emit AsyncPromiseDeployed(newAsyncPromise, salt); + } + function _createForwarderParams( address chainContractAddress_, uint32 chainSlug_ ) internal view returns (bytes32 salt, bytes memory initData) { - bytes memory constructorArgs = abi.encode(chainSlug_, chainContractAddress_, address(this)); + bytes memory constructorArgs = abi.encode( + chainSlug_, + chainContractAddress_, + address(addressResolver__) + ); initData = abi.encodeWithSelector( Forwarder.initialize.selector, chainSlug_, chainContractAddress_, - address(this) + address(addressResolver__) ); // creates salt with constructor args @@ -141,39 +127,27 @@ contract AddressResolver is AddressResolverStorage, Initializable, Ownable { } function _createAsyncPromiseParams( - address invoker_ + address invoker_, + uint40 requestCount_ ) internal view returns (bytes32 salt, bytes memory initData) { - bytes memory constructorArgs = abi.encode(invoker_, msg.sender, address(this)); + bytes memory constructorArgs = abi.encode( + requestCount_, + invoker_, + address(addressResolver__) + ); // creates init data initData = abi.encodeWithSelector( AsyncPromise.initialize.selector, + requestCount_, invoker_, - msg.sender, - address(this) + address(addressResolver__) ); // creates salt with a counter salt = keccak256(abi.encodePacked(constructorArgs, asyncPromiseCounter)); } - /// @notice Deploys an AsyncPromise proxy contract - /// @param invoker_ The address of the invoker - /// @return newAsyncPromise The address of the deployed AsyncPromise proxy contract - 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); - - emit AsyncPromiseDeployed(newAsyncPromise, salt); - } - function _deployProxy( bytes32 salt_, address beacon_, @@ -189,31 +163,6 @@ contract AddressResolver is AddressResolverStorage, Initializable, Ownable { return proxy; } - /// @notice Clears the list of promises - /// @dev this function helps in queueing the promises and whitelisting on gateway at the end. - function clearPromises() external { - delete _promises; - } - - /// @notice Gets the list of promises - /// @return array of promises deployed while queueing async calls - function getPromises() external view returns (address[] memory) { - return _promises; - } - - /// @notice Sets the contract to gateway mapping - /// @param contractAddress_ The address of the contract - function setContractsToGateways(address contractAddress_) external { - if ( - contractsToGateways[contractAddress_] != address(0) && - contractsToGateways[contractAddress_] != msg.sender - ) { - revert InvalidAppGateway(contractAddress_); - } - contractsToGateways[contractAddress_] = msg.sender; - emit ContractsToGatewaysUpdated(contractAddress_, msg.sender); - } - /// @notice Gets the predicted address of a Forwarder proxy contract /// @param chainContractAddress_ The address of the chain contract /// @param chainSlug_ The chain slug @@ -221,7 +170,7 @@ contract AddressResolver is AddressResolverStorage, Initializable, Ownable { function getForwarderAddress( address chainContractAddress_, uint32 chainSlug_ - ) public view returns (address) { + ) public view override returns (address) { (bytes32 salt, ) = _createForwarderParams(chainContractAddress_, chainSlug_); return _predictProxyAddress(salt, address(forwarderBeacon)); } @@ -229,8 +178,11 @@ contract AddressResolver is AddressResolverStorage, Initializable, Ownable { /// @notice Gets the predicted address of an AsyncPromise proxy contract /// @param invoker_ The address of the invoker /// @return The predicted address of the AsyncPromise proxy contract - function getAsyncPromiseAddress(address invoker_) public view returns (address) { - (bytes32 salt, ) = _createAsyncPromiseParams(invoker_); + function getAsyncPromiseAddress( + address invoker_, + uint40 requestCount_ + ) public view override returns (address) { + (bytes32 salt, ) = _createAsyncPromiseParams(invoker_, requestCount_); return _predictProxyAddress(salt, address(asyncPromiseBeacon)); } @@ -243,50 +195,28 @@ contract AddressResolver is AddressResolverStorage, Initializable, Ownable { LibClone.predictDeterministicAddressERC1967BeaconProxy(beacon_, salt_, address(this)); } - function _setConfig(address appGateway_, address newForwarder_) internal { - address gateway = contractsToGateways[appGateway_]; - contractsToGateways[newForwarder_] = gateway; - } - /// @notice Updates the implementation contract for Forwarder /// @param implementation_ The new implementation address - function setForwarderImplementation(address implementation_) external onlyOwner { + function setForwarderImplementation(address implementation_) external override onlyOwner { forwarderBeacon.upgradeTo(implementation_); emit ImplementationUpdated("Forwarder", implementation_); } /// @notice Updates the implementation contract for AsyncPromise /// @param implementation_ The new implementation address - function setAsyncPromiseImplementation(address implementation_) external onlyOwner { + function setAsyncPromiseImplementation(address implementation_) external override onlyOwner { asyncPromiseBeacon.upgradeTo(implementation_); emit ImplementationUpdated("AsyncPromise", implementation_); } - /// @notice Updates the address of the delivery helper - /// @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_); + /** + * @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. + * @param token_ The address of the token contract. + * @param rescueTo_ The address where rescued tokens need to be sent. + * @param amount_ The amount of tokens to be rescued. + */ + function rescueFunds(address token_, address rescueTo_, uint256 amount_) external onlyWatcher { + RescueFundsLib._rescueFunds(token_, rescueTo_, amount_); } } diff --git a/contracts/evmx/helpers/AsyncPromise.sol b/contracts/evmx/helpers/AsyncPromise.sol new file mode 100644 index 00000000..f727947d --- /dev/null +++ b/contracts/evmx/helpers/AsyncPromise.sol @@ -0,0 +1,170 @@ +// SPDX-License-Identifier: GPL-3.0-only +pragma solidity ^0.8.21; + +import {Initializable} from "solady/utils/Initializable.sol"; +import {LibCall} from "solady/utils/LibCall.sol"; +import {AddressResolverUtil} from "./AddressResolverUtil.sol"; +import {IAppGateway} from "../interfaces/IAppGateway.sol"; +import "../interfaces/IPromise.sol"; +import {NotInvoker, RequestCountMismatch} from "../../utils/common/Errors.sol"; +import "../../utils/RescueFundsLib.sol"; + +abstract contract AsyncPromiseStorage is IPromise { + // slots [0-49] reserved for gap + uint256[50] _gap_before; + + // slot 50 (4 + 8 + 8 + 40 + 160) + /// @notice The callback selector to be called on the invoker. + bytes4 public callbackSelector; + + /// @notice The flag to check if the promise exceeded the max copy limit + bool public override exceededMaxCopy; + + /// @notice The current state of the async promise. + AsyncPromiseState public override state; + + /// @notice The request count of the promise + uint40 public override requestCount; + + /// @notice The local contract which initiated the call. + /// @dev The callback will be executed on this address + address public override localInvoker; + + // slot 51 + /// @notice The return data of the promise + bytes public override returnData; + + // slot 52 + /// @notice The callback data to be used when the promise is resolved. + bytes public callbackData; + + // slots [53-102] reserved for gap + uint256[50] _gap_after; + + // slots 103-154 (51) reserved for addr resolver util +} + +/// @title AsyncPromise +/// @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 local invoker can set then's promise callback + error OnlyInvoker(); + /// @notice Error thrown when attempting to set an already existing promise + error PromiseAlreadySetUp(); + /// @notice Error thrown when the promise reverts + error PromiseRevertFailed(); + /// @notice Error thrown when the promise is not the latest promise set by the watcher + error NotLatestPromise(); + + constructor() { + _disableInitializers(); // disable for implementation + } + + /// @notice Initialize promise states + /// @param invoker_ The address of the local invoker + /// @param addressResolver_ The address resolver contract address + function initialize( + uint40 requestCount_, + address invoker_, + address addressResolver_ + ) public reinitializer(1) { + localInvoker = invoker_; + requestCount = requestCount_; + _setAddressResolver(addressResolver_); + } + + /// @notice Marks the promise as resolved and executes the callback if set. + /// @dev Only callable by the watcher precompile. + /// @param resolvedPromise_ The data returned from the async payload execution. + function markResolved( + PromiseReturnData memory resolvedPromise_ + ) external override onlyWatcher returns (bool success) { + if ( + state == AsyncPromiseState.CALLBACK_REVERTING || + state == AsyncPromiseState.ONCHAIN_REVERTING || + state == AsyncPromiseState.RESOLVED + ) revert PromiseAlreadyResolved(); + state = AsyncPromiseState.RESOLVED; + + // Call callback to app gateway + if (callbackSelector == bytes4(0)) { + success = true; + } else { + exceededMaxCopy = resolvedPromise_.exceededMaxCopy; + returnData = resolvedPromise_.returnData; + + bytes memory combinedCalldata = abi.encodePacked( + callbackSelector, + abi.encode(callbackData, resolvedPromise_.returnData) + ); + + (success, , ) = localInvoker.tryCall(0, gasleft(), 0, combinedCalldata); + if (!success) { + state = AsyncPromiseState.CALLBACK_REVERTING; + _handleRevert(resolvedPromise_.payloadId); + } + } + } + + /// @notice Marks the promise as onchain reverting. + /// @dev Only callable by the watcher precompile. + function markOnchainRevert( + PromiseReturnData memory resolvedPromise_ + ) external override onlyWatcher { + if ( + state == AsyncPromiseState.CALLBACK_REVERTING || + state == AsyncPromiseState.ONCHAIN_REVERTING || + state == AsyncPromiseState.RESOLVED + ) revert PromiseAlreadyResolved(); + + // to update the state in case selector is bytes(0) but reverting onchain + state = AsyncPromiseState.ONCHAIN_REVERTING; + exceededMaxCopy = resolvedPromise_.exceededMaxCopy; + returnData = resolvedPromise_.returnData; + _handleRevert(resolvedPromise_.payloadId); + } + + /// @notice Handles the revert of the promise. + /// @dev Only callable by the watcher. + /// @dev handleRevert function can be retried till it succeeds + function _handleRevert(bytes32 payloadId_) internal { + try IAppGateway(localInvoker).handleRevert(payloadId_) {} catch { + // todo: in this case, promise will stay unresolved + revert PromiseRevertFailed(); + } + } + + /// @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. + function then(bytes4 selector_, bytes memory data_) external override { + if (msg.sender != localInvoker) revert NotInvoker(); + // if the promise is already set up, revert + if (state != AsyncPromiseState.WAITING_FOR_CALLBACK_SELECTOR) { + revert PromiseAlreadySetUp(); + } + if (watcher__().latestAsyncPromise() != address(this)) revert NotLatestPromise(); + if (requestCount != watcher__().getCurrentRequestCount()) revert RequestCountMismatch(); + + // if the promise is waiting for the callback selector, set it and update the state + callbackSelector = selector_; + callbackData = data_; + state = AsyncPromiseState.WAITING_FOR_CALLBACK_EXECUTION; + } + + /** + * @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. + * @param token_ The address of the token contract. + * @param rescueTo_ The address where rescued tokens need to be sent. + * @param amount_ The amount of tokens to be rescued. + */ + function rescueFunds(address token_, address rescueTo_, uint256 amount_) external onlyWatcher { + RescueFundsLib._rescueFunds(token_, rescueTo_, amount_); + } +} diff --git a/contracts/evmx/helpers/DeployForwarder.sol b/contracts/evmx/helpers/DeployForwarder.sol new file mode 100644 index 00000000..86a46f3e --- /dev/null +++ b/contracts/evmx/helpers/DeployForwarder.sol @@ -0,0 +1,113 @@ +// SPDX-License-Identifier: GPL-3.0-only +pragma solidity ^0.8.21; + +import "solady/auth/Ownable.sol"; +import {Initializable} from "solady/utils/Initializable.sol"; +import {IAppGateway} from "../interfaces/IAppGateway.sol"; +import {IContractFactoryPlug} from "../interfaces/IContractFactoryPlug.sol"; +import {IDeployForwarder} from "../interfaces/IDeployForwarder.sol"; +import {AsyncModifierNotSet} from "../../utils/common/Errors.sol"; +import {QueueParams, OverrideParams, Transaction} from "../../utils/common/Structs.sol"; +import {WRITE} from "../../utils/common/Constants.sol"; +import {encodeAppGatewayId} from "../../utils/common/IdUtils.sol"; +import "../../utils/RescueFundsLib.sol"; +import "./AddressResolverUtil.sol"; + +/// @title DeployForwarder +/// @notice contract responsible for handling deployment requests +contract DeployForwarder is IDeployForwarder, Initializable, AddressResolverUtil, Ownable { + // slots [0-49] 50 slots reserved for address resolver util + + // slots [50-99] reserved for gap + uint256[50] _gap_before; + + // slot 100 + /// @notice The counter for the salt used to generate/deploy the contract address + uint256 public override saltCounter; + + // slot 101 + bytes32 public override deployerSwitchboardType; + + constructor() { + _disableInitializers(); // disable for implementation + } + + function initialize( + address owner_, + address addressResolver_, + bytes32 deployerSwitchboardType_ + ) public reinitializer(1) { + deployerSwitchboardType = deployerSwitchboardType_; + _setAddressResolver(addressResolver_); + _initializeOwner(owner_); + } + + /// @notice Deploys a contract + /// @param chainSlug_ The chain slug + function deploy( + IsPlug isPlug_, + uint32 chainSlug_, + bytes memory initCallData_, + bytes memory payload_ + ) external { + address msgSender = msg.sender; + bool isAsyncModifierSet = IAppGateway(msgSender).isAsyncModifierSet(); + if (!isAsyncModifierSet) revert AsyncModifierNotSet(); + + // fetch the override params from app gateway + (OverrideParams memory overrideParams, bytes32 plugSwitchboardType) = IAppGateway(msgSender) + .getOverrideParams(); + + QueueParams memory queueParams; + queueParams.overrideParams = overrideParams; + queueParams.overrideParams.callType = WRITE; + queueParams.switchboardType = deployerSwitchboardType; + queueParams.transaction = Transaction({ + chainSlug: chainSlug_, + target: address(0), + payload: _createPayload( + isPlug_, + plugSwitchboardType, + msgSender, + chainSlug_, + payload_, + initCallData_ + ) + }); + + watcher__().queue(queueParams, msg.sender); + } + + function _createPayload( + IsPlug isPlug_, + bytes32 plugSwitchboardType_, + address appGateway_, + uint32 chainSlug_, + bytes memory payload_, + bytes memory initCallData_ + ) internal returns (bytes memory payload) { + bytes32 salt = keccak256(abi.encode(appGateway_, chainSlug_, saltCounter++)); + + // app gateway is set in the plug deployed on chain + payload = abi.encodeWithSelector( + IContractFactoryPlug.deployContract.selector, + isPlug_, + salt, + encodeAppGatewayId(appGateway_), + watcher__().configurations__().switchboards(chainSlug_, plugSwitchboardType_), + payload_, + initCallData_ + ); + } + + /** + * @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. + * @param token_ The address of the token contract. + * @param rescueTo_ The address where rescued tokens need to be sent. + * @param amount_ The amount of tokens to be rescued. + */ + function rescueFunds(address token_, address rescueTo_, uint256 amount_) external onlyWatcher { + RescueFundsLib._rescueFunds(token_, rescueTo_, amount_); + } +} diff --git a/contracts/evmx/helpers/Forwarder.sol b/contracts/evmx/helpers/Forwarder.sol new file mode 100644 index 00000000..8b778061 --- /dev/null +++ b/contracts/evmx/helpers/Forwarder.sol @@ -0,0 +1,103 @@ +// SPDX-License-Identifier: GPL-3.0-only +pragma solidity ^0.8.21; + +import "solady/utils/Initializable.sol"; +import "./AddressResolverUtil.sol"; +import "../interfaces/IAddressResolver.sol"; +import "../interfaces/IAppGateway.sol"; +import "../interfaces/IForwarder.sol"; +import {QueueParams, OverrideParams, Transaction} from "../../utils/common/Structs.sol"; +import {AsyncModifierNotSet, WatcherNotSet, InvalidOnChainAddress} from "../../utils/common/Errors.sol"; +import "../../utils/RescueFundsLib.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 slug on which the contract is deployed + uint32 public chainSlug; + /// @notice on-chain address associated with this forwarder + address public onChainAddress; + + // slots [51-100] reserved for gap + uint256[50] _gap_after; + + // slots [101-150] 50 slots reserved for address 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 { + constructor() { + _disableInitializers(); // disable for implementation + } + + /// @notice Initializer to replace constructor for upgradeable contracts + /// @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_, + address addressResolver_ + ) public reinitializer(1) { + if (onChainAddress_ == address(0)) revert InvalidOnChainAddress(); + chainSlug = chainSlug_; + onChainAddress = onChainAddress_; + _setAddressResolver(addressResolver_); + } + + /// @notice Returns the on-chain address associated with this forwarder. + /// @return The on-chain address. + function getOnChainAddress() external view override returns (address) { + return onChainAddress; + } + + /// @notice Returns the chain slug on which the contract is deployed. + /// @return chain slug + function getChainSlug() external view override returns (uint32) { + return chainSlug; + } + + /** + * @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. + * @param token_ The address of the token contract. + * @param rescueTo_ The address where rescued tokens need to be sent. + * @param amount_ The amount of tokens to be rescued. + */ + function rescueFunds(address token_, address rescueTo_, uint256 amount_) external onlyWatcher { + RescueFundsLib._rescueFunds(token_, rescueTo_, amount_); + } + + /// @notice Fallback function to process the contract calls to onChainAddress + /// @dev It queues the calls in the middleware and deploys the promise contract + fallback() external { + if (address(watcher__()) == address(0)) { + revert WatcherNotSet(); + } + + // validates if the async modifier is set + address msgSender = msg.sender; + bool isAsyncModifierSet = IAppGateway(msgSender).isAsyncModifierSet(); + if (!isAsyncModifierSet) revert AsyncModifierNotSet(); + + // fetch the override params from app gateway + (OverrideParams memory overrideParams, bytes32 sbType) = IAppGateway(msgSender) + .getOverrideParams(); + + // Queue the call in the middleware. + QueueParams memory queueParams; + queueParams.overrideParams = overrideParams; + queueParams.transaction = Transaction({ + chainSlug: chainSlug, + target: onChainAddress, + payload: msg.data + }); + queueParams.switchboardType = sbType; + watcher__().queue(queueParams, msgSender); + } +} diff --git a/contracts/evmx/interfaces/IAddressResolver.sol b/contracts/evmx/interfaces/IAddressResolver.sol index 1e8684bc..9c226dbf 100644 --- a/contracts/evmx/interfaces/IAddressResolver.sol +++ b/contracts/evmx/interfaces/IAddressResolver.sol @@ -1,87 +1,49 @@ // SPDX-License-Identifier: GPL-3.0-only pragma solidity ^0.8.21; -import "./IWatcherPrecompile.sol"; +import "./IWatcher.sol"; +import "./IFeesManager.sol"; +import "./IAsyncDeployer.sol"; +import "./IDeployForwarder.sol"; /// @title IAddressResolver /// @notice Interface for resolving system contract addresses /// @dev Provides address lookup functionality for core system components interface IAddressResolver { - /// @notice Emitted when a new address is set in the resolver - /// @param name The identifier of the contract - /// @param oldAddress The previous address of the contract - /// @param newAddress The new address of the contract - event AddressSet(bytes32 indexed name, address oldAddress, address newAddress); + /// @notice Event emitted when the fees manager is updated + event FeesManagerUpdated(address feesManager_); + /// @notice Event emitted when the watcher precompile is updated + event WatcherUpdated(address watcher_); + /// @notice Event emitted when the async deployer is updated + event AsyncDeployerUpdated(address asyncDeployer_); + /// @notice Event emitted when the default auction manager is updated + event DefaultAuctionManagerUpdated(address defaultAuctionManager_); + /// @notice Event emitted when the deploy forwarder is updated + event DeployForwarderUpdated(address deployForwarder_); + /// @notice Event emitted when the contract address is updated + event ContractAddressUpdated(bytes32 contractId_, address contractAddress_); + + // System component addresses + function watcher__() external view returns (IWatcher); + + function feesManager__() external view returns (IFeesManager); + + function asyncDeployer__() external view returns (IAsyncDeployer); - /// @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 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 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 The auction manager contract address - /// @dev Returns zero address if not configured function defaultAuctionManager() external view returns (address); - /// @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); + function deployForwarder__() external view returns (IDeployForwarder); + + function contractAddresses(bytes32 contractId_) external view returns (address); - /// @notice Maps contract addresses to their corresponding gateway addresses - /// @param contractAddress_ The address of the contract to lookup - /// @return The gateway address associated with the contract - function contractsToGateways(address contractAddress_) external view returns (address); + function setWatcher(address watcher_) external; - /// @notice Gets the list of all deployed async promise contracts - /// @return Array of async promise contract addresses - function getPromises() external view returns (address[] memory); + function setFeesManager(address feesManager_) 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; + function setAsyncDeployer(address asyncDeployer_) external; - /// @notice Clears the list of deployed async promise contracts array - function clearPromises() external; + function setDefaultAuctionManager(address defaultAuctionManager_) external; - /// @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_, - address chainContractAddress_, - uint32 chainSlug_ - ) external returns (address); + function setDeployForwarder(address deployForwarder_) external; - /// @notice Deploys a new async promise contract - /// @param invoker_ The address that can invoke/execute the promise - /// @return The address of the newly deployed async promise contract - function deployAsyncPromiseContract(address invoker_) external returns (address); + function setContractAddress(bytes32 contractId_, address contractAddress_) external; } diff --git a/contracts/evmx/interfaces/IAppGateway.sol b/contracts/evmx/interfaces/IAppGateway.sol index 7036cd43..132e8c96 100644 --- a/contracts/evmx/interfaces/IAppGateway.sol +++ b/contracts/evmx/interfaces/IAppGateway.sol @@ -1,7 +1,7 @@ // SPDX-License-Identifier: GPL-3.0-only pragma solidity ^0.8.21; -import {Read, Parallel, QueuePayloadParams, OverrideParams, WriteFinality, PayloadParams} from "../../utils/common/Structs.sol"; +import {OverrideParams} from "../../utils/common/Structs.sol"; /// @title IAppGateway /// @notice Interface for the app gateway @@ -11,17 +11,9 @@ interface IAppGateway { 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 overrideParams_ The override parameters /// @return sbType_ The switchboard type - function getOverrideParams() - external - view - returns (Read, Parallel, WriteFinality, uint256, uint256, uint256, bytes32); + function getOverrideParams() external view returns (OverrideParams memory, bytes32); /// @notice Handles the request complete event /// @param requestCount_ The request count @@ -29,13 +21,12 @@ interface IAppGateway { function onRequestComplete(uint40 requestCount_, bytes calldata onCompleteData_) external; /// @notice Handles the revert event - /// @param requestCount_ The request count /// @param payloadId_ The payload id - function handleRevert(uint40 requestCount_, bytes32 payloadId_) external; + function handleRevert(bytes32 payloadId_) external; /// @notice initialize the contracts on chain /// @param chainSlug_ The chain slug - function initialize(uint32 chainSlug_) external; + function initializeOnChain(uint32 chainSlug_) external; /// @notice get the on-chain address of a contract /// @param contractId_ The contract id diff --git a/contracts/evmx/interfaces/IAsyncDeployer.sol b/contracts/evmx/interfaces/IAsyncDeployer.sol new file mode 100644 index 00000000..9023db36 --- /dev/null +++ b/contracts/evmx/interfaces/IAsyncDeployer.sol @@ -0,0 +1,48 @@ +// SPDX-License-Identifier: GPL-3.0-only +pragma solidity ^0.8.21; + +/// @title IAsyncDeployer +/// @notice Interface for deploying Forwarder and AsyncPromise contracts +/// @dev Provides address lookup functionality for core system components +interface IAsyncDeployer { + /// @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); + + // Forwarder Management + function getOrDeployForwarderContract( + address chainContractAddress_, + uint32 chainSlug_ + ) external returns (address); + + function getForwarderAddress( + address chainContractAddress_, + uint32 chainSlug_ + ) external view returns (address); + + function setForwarderImplementation(address implementation_) external; + + // Async Promise Management + function deployAsyncPromiseContract( + address invoker_, + uint40 requestCount_ + ) external returns (address); + + function getAsyncPromiseAddress( + address invoker_, + uint40 requestCount_ + ) external view returns (address); + + function setAsyncPromiseImplementation(address implementation_) external; +} diff --git a/contracts/evmx/interfaces/IAuctionManager.sol b/contracts/evmx/interfaces/IAuctionManager.sol index c741c8e9..5bacbf3a 100644 --- a/contracts/evmx/interfaces/IAuctionManager.sol +++ b/contracts/evmx/interfaces/IAuctionManager.sol @@ -1,32 +1,41 @@ // SPDX-License-Identifier: GPL-3.0-only pragma solidity ^0.8.21; -import {Bid, RequestMetadata, RequestParams} from "../../utils/common/Structs.sol"; +import {QueueParams, OverrideParams, Transaction, Bid, RequestParams} from "../../utils/common/Structs.sol"; interface IAuctionManager { + enum AuctionStatus { + NOT_STARTED, + OPEN, + CLOSED, + RESTARTED, + EXPIRED + } + /// @notice Bids for an auction /// @param requestCount_ The request count - /// @param fee_ The fee - /// @param transmitterSignature_ The transmitter signature - /// @param extraData_ The extra data + /// @param bidFees The bid amount + /// @param transmitterSignature The signature of the transmitter + /// @param extraData The extra data function bid( uint40 requestCount_, - uint256 fee_, - bytes memory transmitterSignature_, - bytes memory extraData_ + uint256 bidFees, + bytes memory transmitterSignature, + bytes memory extraData ) external; /// @notice Ends an auction /// @param requestCount_ The request count function endAuction(uint40 requestCount_) external; + /// @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; + /// @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); + function auctionStatus(uint40 requestCount_) external view returns (AuctionStatus); } diff --git a/contracts/evmx/interfaces/IWatcherPrecompileConfig.sol b/contracts/evmx/interfaces/IConfigurations.sol similarity index 54% rename from contracts/evmx/interfaces/IWatcherPrecompileConfig.sol rename to contracts/evmx/interfaces/IConfigurations.sol index fcfc4026..eb795a76 100644 --- a/contracts/evmx/interfaces/IWatcherPrecompileConfig.sol +++ b/contracts/evmx/interfaces/IConfigurations.sol @@ -3,27 +3,17 @@ pragma solidity ^0.8.21; import {AppGatewayConfig, PlugConfig} from "../../utils/common/Structs.sol"; -/// @title IWatcherPrecompileConfig +/// @title IConfigurations /// @notice Interface for the Watcher Precompile system that handles payload verification and execution /// @dev Defines core functionality for payload processing and promise resolution -interface IWatcherPrecompileConfig { - /// @notice The chain slug of the watcher precompile - function evmxSlug() external view returns (uint32); - - /// @notice Maps chain slug to their associated switchboard - function switchboards(uint32 chainSlug, bytes32 sbType) external view returns (address); - - /// @notice Maps chain slug to their associated socket - function sockets(uint32 chainSlug) external view returns (address); - - /// @notice Maps chain slug to their associated contract factory plug - function contractFactoryPlug(uint32 chainSlug) external view returns (address); - - /// @notice Maps chain slug to their associated fees plug - function feesPlug(uint32 chainSlug) external view returns (address); - - /// @notice Maps nonce to whether it has been used - function isNonceUsed(uint256 nonce) external view returns (bool); +interface IConfigurations { + /// @notice Verifies connections between components + function verifyConnections( + uint32 chainSlug_, + address target_, + address appGateway_, + bytes32 switchboardType_ + ) external view; /// @notice Maps app gateway, chain slug and plug to validity function isValidPlug( @@ -32,32 +22,36 @@ interface IWatcherPrecompileConfig { address plug ) external view returns (bool); - /// @notice Sets the switchboard for a network - 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 (bytes32, address); - /// @notice Verifies connections between components - function verifyConnections( - uint32 chainSlug_, - address target_, - address appGateway_, - address switchboard_, - address middleware_ - ) external view; + /// @notice Maps chain slug to their associated socket + /// @param chainSlug_ The chain slug + /// @return The socket + function sockets(uint32 chainSlug_) external view returns (address); - function setAppGateways( - AppGatewayConfig[] calldata configs_, - uint256 signatureNonce_, - bytes calldata signature_ + /// @notice Returns the socket for a given chain slug + /// @param chainSlug_ The chain slug + /// @return The socket + function switchboards(uint32 chainSlug_, bytes32 sbType_) external view returns (address); + + /// @notice Sets the switchboard for a network + 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( + bool isValid_, + uint32 chainSlug_, + address plug_, + address appGateway_ ) external; + function setAppGatewayConfigs(AppGatewayConfig[] calldata configs_) external; + + /// @notice Sets the socket for a chain slug + function setSocket(uint32 chainSlug_, address socket_) external; } diff --git a/contracts/evmx/interfaces/IDeployForwarder.sol b/contracts/evmx/interfaces/IDeployForwarder.sol new file mode 100644 index 00000000..71b1e72d --- /dev/null +++ b/contracts/evmx/interfaces/IDeployForwarder.sol @@ -0,0 +1,28 @@ +// SPDX-License-Identifier: GPL-3.0-only +pragma solidity ^0.8.21; + +import {IsPlug} from "../../utils/common/Structs.sol"; + +/// @title IDeployForwarder +/// @notice Interface for the DeployForwarder contract responsible for handling deployment requests +interface IDeployForwarder { + /// @notice Returns the current salt counter used for contract deployments + /// @return The salt counter + function saltCounter() external view returns (uint256); + + /// @notice Returns the deployer switchboard type + /// @return The deployer switchboard type + function deployerSwitchboardType() external view returns (bytes32); + + /// @notice Deploys a contract + /// @param isPlug_ Whether the contract is a plug + /// @param chainSlug_ The chain slug + /// @param initCallData_ The initialization calldata for the contract + /// @param payload_ The payload for the contract + function deploy( + IsPlug isPlug_, + uint32 chainSlug_, + bytes memory initCallData_, + bytes memory payload_ + ) external; +} diff --git a/contracts/evmx/interfaces/IFeesManager.sol b/contracts/evmx/interfaces/IFeesManager.sol index d7afdbaf..8e4a495b 100644 --- a/contracts/evmx/interfaces/IFeesManager.sol +++ b/contracts/evmx/interfaces/IFeesManager.sol @@ -1,66 +1,47 @@ // SPDX-License-Identifier: GPL-3.0-only pragma solidity ^0.8.21; - -import {Bid, QueuePayloadParams, PayloadSubmitParams, AppGatewayWhitelistParams} from "../../utils/common/Structs.sol"; +import {WriteFinality, UserCredits, AppGatewayApprovals, OverrideParams, Transaction, QueueParams, RequestParams} from "../../utils/common/Structs.sol"; interface IFeesManager { - function blockCredits( - address consumeFrom_, - uint256 transmitterCredits_, - uint40 requestCount_ + function deposit( + uint32 chainSlug_, + address token_, + address depositTo_, + uint256 nativeAmount_, + uint256 creditAmount_ ) external; - function unblockCredits(uint40 requestCount_) external; + function wrap(address receiver_) external payable; - function isUserCreditsEnough( + function unwrap(uint256 amount_, address receiver_) external; + + function getAvailableCredits(address consumeFrom_) external view returns (uint256); + + function isCreditSpendable( address consumeFrom_, - address appGateway_, + address spender_, uint256 amount_ ) external view returns (bool); - function unblockAndAssignCredits(uint40 requestCount_, address transmitter_) external; - - function assignWatcherPrecompileCreditsFromRequestCount( - uint256 fees_, - uint40 requestCount_ - ) external; + function transferCredits(address from_, address to_, uint256 amount_) external; - function assignWatcherPrecompileCreditsFromAddress( - uint256 fees_, - address consumeFrom_ - ) external; + function approveAppGateways(AppGatewayApprovals[] calldata params_) external; - function whitelistAppGatewayWithSignature( + function approveAppGatewayWithSignature( bytes memory feeApprovalData_ - ) external returns (address consumeFrom, address appGateway, bool isApproved); - - function whitelistAppGateways(AppGatewayWhitelistParams[] calldata params_) external; - - function getWithdrawTransmitterCreditsPayloadParams( - address transmitter_, - uint32 chainSlug_, - address token_, - address receiver_, - uint256 amount_ - ) external returns (PayloadSubmitParams[] memory); - - function getMaxCreditsAvailableForWithdraw( - address transmitter_ - ) external view returns (uint256); + ) external returns (address consumeFrom, address spender, bool approval); function withdrawCredits( - address originAppGatewayOrUser_, uint32 chainSlug_, address token_, - uint256 amount_, + uint256 credits_, + uint256 maxFees_, address receiver_ ) external; - function depositCredits( - address depositTo_, - uint32 chainSlug_, - address token_, - uint256 signatureNonce_, - bytes memory signature_ - ) external payable; + function blockCredits(uint40 requestCount_, address consumeFrom_, uint256 credits_) external; + + function unblockAndAssignCredits(uint40 requestCount_, address assignTo_) external; + + function unblockCredits(uint40 requestCount_) external; } diff --git a/contracts/evmx/interfaces/IFeesPlug.sol b/contracts/evmx/interfaces/IFeesPlug.sol index 46797d62..24cc719a 100644 --- a/contracts/evmx/interfaces/IFeesPlug.sol +++ b/contracts/evmx/interfaces/IFeesPlug.sol @@ -2,9 +2,23 @@ pragma solidity ^0.8.21; interface IFeesPlug { - function depositToFee(address token_, address receiver_, uint256 amount_) external; + /// @notice Event emitted when fees are deposited + event FeesDeposited( + address token, + address receiver, + uint256 creditAmount, + uint256 nativeAmount + ); + /// @notice Event emitted when fees are withdrawn + 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); - function depositToFeeAndNative(address token_, address receiver_, uint256 amount_) external; + function depositCredit(address token_, address receiver_, uint256 amount_) external; + + function depositCreditAndNative(address token_, address receiver_, uint256 amount_) external; function depositToNative(address token_, address receiver_, uint256 amount_) external; diff --git a/contracts/evmx/interfaces/IFeesPool.sol b/contracts/evmx/interfaces/IFeesPool.sol new file mode 100644 index 00000000..b2dcf1a1 --- /dev/null +++ b/contracts/evmx/interfaces/IFeesPool.sol @@ -0,0 +1,11 @@ +// SPDX-License-Identifier: GPL-3.0-only +pragma solidity ^0.8.21; + +interface IFeesPool { + event NativeDeposited(address indexed from, uint256 amount); + event NativeWithdrawn(bool success, address indexed to, uint256 amount); + + function withdraw(address to_, uint256 amount_) external returns (bool success); + + function getBalance() external view returns (uint256); +} diff --git a/contracts/evmx/interfaces/IMiddleware.sol b/contracts/evmx/interfaces/IMiddleware.sol deleted file mode 100644 index 3fc7d2e2..00000000 --- a/contracts/evmx/interfaces/IMiddleware.sol +++ /dev/null @@ -1,77 +0,0 @@ -// SPDX-License-Identifier: GPL-3.0-only -pragma solidity ^0.8.21; -import {PayloadSubmitParams, QueuePayloadParams, Bid, WriteFinality, BatchParams, CallType, Parallel, IsPlug, RequestMetadata} from "../../utils/common/Structs.sol"; - -/// @title IMiddleware -/// @notice Interface for the Middleware contract -interface IMiddleware { - /// @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( - 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_, - 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; - - /// @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/evmx/interfaces/IPrecompile.sol b/contracts/evmx/interfaces/IPrecompile.sol new file mode 100644 index 00000000..6241434c --- /dev/null +++ b/contracts/evmx/interfaces/IPrecompile.sol @@ -0,0 +1,37 @@ +// SPDX-License-Identifier: GPL-3.0-only +pragma solidity ^0.8.21; + +import {QueueParams, PayloadParams} from "../../utils/common/Structs.sol"; + +/// @title IPrecompile +/// @notice Interface for precompile functionality +interface IPrecompile { + /// @notice Gets precompile fees + /// @param precompileData_ The precompile data + /// @return fees The fees required for processing + function getPrecompileFees(bytes memory precompileData_) external view returns (uint256 fees); + + /// @notice Gets precompile data and fees for queue parameters + /// @param queueParams_ The queue parameters to process + /// @return precompileData The encoded precompile data + /// @return estimatedFees Estimated fees required for processing + function validateAndGetPrecompileData( + QueueParams calldata queueParams_, + address appGateway_ + ) external view returns (bytes memory precompileData, uint256 estimatedFees); + + /// @notice Handles payload processing and returns fees + /// @param transmitter The address of the transmitter + /// @param payloadParams The payload parameters to handle + /// @return fees The fees required for processing + /// @return deadline The deadline for processing + /// @return precompileData The encoded precompile data + function handlePayload( + address transmitter, + PayloadParams calldata payloadParams + ) external returns (uint256 fees, uint256 deadline, bytes memory precompileData); + + /// @notice Resolves a payload + /// @param payloadParams The payload parameters to resolve + function resolvePayload(PayloadParams calldata payloadParams) external; +} diff --git a/contracts/evmx/interfaces/IPromise.sol b/contracts/evmx/interfaces/IPromise.sol index c43d6627..9809d549 100644 --- a/contracts/evmx/interfaces/IPromise.sol +++ b/contracts/evmx/interfaces/IPromise.sol @@ -1,27 +1,39 @@ // SPDX-License-Identifier: GPL-3.0-only pragma solidity ^0.8.21; +import {AsyncPromiseState, PromiseReturnData} from "../../utils/common/Structs.sol"; + /// @title IPromise interface IPromise { + /// @notice The current state of the async promise. + function state() external view returns (AsyncPromiseState); + + /// @notice The local contract which initiated the async call. + /// @dev The callback will be executed on this address + function localInvoker() external view returns (address); + + /// @notice The request count of the promise + function requestCount() external view returns (uint40); + + /// @notice The flag to check if the promise exceeded the max copy limit + function exceededMaxCopy() external view returns (bool); + + /// @notice The return data of the promise + function returnData() external view returns (bytes memory); + /// @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_); + function then(bytes4 selector_, bytes memory data_) external; /// @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. + /// @param resolvedPromise_ The data returned from the async payload execution. function markResolved( - uint40 requestCount_, - bytes32 payloadId_, - bytes memory returnData_ + PromiseReturnData memory resolvedPromise_ ) 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); + function markOnchainRevert(PromiseReturnData memory resolvedPromise_) external; } diff --git a/contracts/evmx/interfaces/IPromiseResolver.sol b/contracts/evmx/interfaces/IPromiseResolver.sol new file mode 100644 index 00000000..0834bf90 --- /dev/null +++ b/contracts/evmx/interfaces/IPromiseResolver.sol @@ -0,0 +1,20 @@ +// SPDX-License-Identifier: GPL-3.0-only +pragma solidity ^0.8.21; + +import {PayloadParams, PromiseReturnData} from "../../utils/common/Structs.sol"; + +/// @title IPromiseResolver +/// @notice Interface for resolving async promises +interface IPromiseResolver { + /// @notice Resolves a promise with the given data + /// @param promiseReturnData_ The promises to resolve + function resolvePromises(PromiseReturnData[] memory promiseReturnData_) external; + + /// @notice Rejects a promise with the given reason + /// @param isRevertingOnchain_ Whether the promise is reverting onchain + /// @param resolvedPromise_ The resolved promise + function markRevert( + PromiseReturnData memory resolvedPromise_, + bool isRevertingOnchain_ + ) external; +} diff --git a/contracts/evmx/interfaces/IRequestHandler.sol b/contracts/evmx/interfaces/IRequestHandler.sol new file mode 100644 index 00000000..e2871ef1 --- /dev/null +++ b/contracts/evmx/interfaces/IRequestHandler.sol @@ -0,0 +1,45 @@ +// SPDX-License-Identifier: GPL-3.0-only +pragma solidity ^0.8.21; + +import "../../utils/common/Structs.sol"; +import "../interfaces/IPrecompile.sol"; + +interface IRequestHandler { + function getRequestBatchIds(uint40 requestCount_) external view returns (uint40[] memory); + + function getBatchPayloadIds(uint40 batchCount_) external view returns (bytes32[] memory); + + function getRequest(uint40 requestCount_) external view returns (RequestParams memory); + + function getPayload(bytes32 payloadId_) external view returns (PayloadParams memory); + + function getPrecompileFees( + bytes4 precompile_, + bytes memory precompileData_ + ) external view returns (uint256); + + function nextRequestCount() external view returns (uint40); + + function setPrecompile(bytes4 callType_, IPrecompile precompile_) external; + + function submitRequest( + uint256 maxFees_, + address auctionManager_, + address consumeFrom_, + address appGateway_, + QueueParams[] calldata queueParams_, + bytes memory onCompleteData_ + ) external returns (uint40 requestCount, address[] memory promiseList); + + function assignTransmitter(uint40 requestCount_, Bid memory bid_) external; + + function updateRequestAndProcessBatch(uint40 requestCount_, bytes32 payloadId_) external; + + function cancelRequestForReverts(uint40 requestCount) external; + + function cancelRequest(uint40 requestCount, address appGateway_) external; + + function handleRevert(uint40 requestCount) external; + + function increaseFees(uint40 requestCount_, uint256 newMaxFees_, address appGateway_) external; +} diff --git a/contracts/evmx/interfaces/IWatcher.sol b/contracts/evmx/interfaces/IWatcher.sol new file mode 100644 index 00000000..9aea4486 --- /dev/null +++ b/contracts/evmx/interfaces/IWatcher.sol @@ -0,0 +1,87 @@ +// SPDX-License-Identifier: GPL-3.0-only +pragma solidity ^0.8.21; +import "../../utils/common/Errors.sol"; +import "../../utils/common/Structs.sol"; + +import "./IRequestHandler.sol"; +import "./IConfigurations.sol"; +import "./IPromiseResolver.sol"; + +/// @title IWatcher +/// @notice Interface for the Watcher Precompile system that handles payload verification and execution +/// @dev Defines core functionality for payload processing and promise resolution +interface IWatcher { + /// @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); + + function requestHandler__() external view returns (IRequestHandler); + + function configurations__() external view returns (IConfigurations); + + function promiseResolver__() external view returns (IPromiseResolver); + + /// @notice Returns the request params for a given request count + /// @param requestCount_ The request count + /// @return The request params + function getRequestParams(uint40 requestCount_) external view returns (RequestParams memory); + + /// @notice Returns the request params for a given request count + /// @param payloadId_ The payload id + /// @return The request params + function getPayloadParams(bytes32 payloadId_) external view returns (PayloadParams memory); + + /// @notice Returns the current request count + /// @return The current request count + function getCurrentRequestCount() external view returns (uint40); + + /// @notice Returns the latest async promise deployed for a payload queued + /// @return The latest async promise + function latestAsyncPromise() external view returns (address); + + /// @notice Queues a payload for execution + /// @param queueParams_ The parameters for the payload + function queue( + QueueParams calldata queueParams_, + address appGateway_ + ) external returns (address, uint40); + + /// @notice Clears the queue of payloads + function clearQueue() external; + + function submitRequest( + uint256 maxFees, + address auctionManager, + address consumeFrom, + bytes calldata onCompleteData + ) external returns (uint40 requestCount, address[] memory promises); + + function queueAndSubmit( + QueueParams memory queue_, + uint256 maxFees, + address auctionManager, + address consumeFrom, + bytes calldata onCompleteData + ) external returns (uint40 requestCount, address[] memory promises); + + /// @notice Returns the precompile fees for a given precompile + /// @param precompile_ The precompile + /// @param precompileData_ The precompile data + /// @return The precompile fees + function getPrecompileFees( + bytes4 precompile_, + bytes memory precompileData_ + ) external view returns (uint256); + + function cancelRequest(uint40 requestCount_) external; + + function increaseFees(uint40 requestCount_, uint256 newFees_) external; + + function setIsValidPlug(bool isValid_, uint32 chainSlug_, address onchainAddress_) external; + + function isWatcher(address account_) external view returns (bool); +} diff --git a/contracts/evmx/interfaces/IWatcherPrecompile.sol b/contracts/evmx/interfaces/IWatcherPrecompile.sol deleted file mode 100644 index 1dd0dabb..00000000 --- a/contracts/evmx/interfaces/IWatcherPrecompile.sol +++ /dev/null @@ -1,186 +0,0 @@ -// SPDX-License-Identifier: GPL-3.0-only -pragma solidity ^0.8.21; - -import {DigestParams, ResolvedPromises, PayloadParams, TriggerParams, PayloadSubmitParams, RequestParams} from "../../utils/common/Structs.sol"; -import {IWatcherPrecompileLimits} from "./IWatcherPrecompileLimits.sol"; -import {IWatcherPrecompileConfig} from "./IWatcherPrecompileConfig.sol"; - -/// @title IWatcherPrecompile -/// @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 { - /// @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); - - /// @notice Emitted when a finalize request is made - event FinalizeRequested(bytes32 digest, PayloadParams params); - - /// @notice Emitted when a request is finalized - /// @param payloadId The unique identifier for the request - /// @param proof The proof from the watcher - event Finalized(bytes32 indexed payloadId, bytes proof); - - /// @notice Emitted when a promise is resolved - /// @param payloadId The unique identifier for the resolved promise - event PromiseResolved(bytes32 indexed payloadId, address asyncPromise); - - /// @notice Emitted when a promise is not resolved - /// @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); - - /// @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 callback - /// @param payload The payload data - /// @param executedAt The epoch time when the task was executed - /// @param returnData The return data from the callback - event TimeoutResolved( - bytes32 timeoutId, - address target, - bytes payload, - uint256 executedAt, - bytes returnData - ); - - event RequestSubmitted( - address middleware, - uint40 requestCount, - 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 when a timeout request is invalid - error InvalidTimeoutRequest(); - /// @notice Error thrown when a payload id is invalid - error InvalidPayloadId(); - /// @notice Error thrown when a caller is invalid - error InvalidCaller(); - /// @notice Error thrown when a gateway is invalid - error InvalidGateway(); - /// @notice Error thrown when a switchboard is invalid - error InvalidSwitchboard(); - /// @notice Error thrown when a request is already cancelled - error RequestAlreadyCancelled(); - - error RequestCancelled(); - error AlreadyStarted(); - error RequestNotProcessing(); - error InvalidLevelNumber(); - error DeadlineNotPassedForOnChainRevert(); - - /// @notice Calculates the digest hash of payload parameters - /// @param params_ The payload parameters - /// @return digest The calculated digest - function getDigest(DigestParams memory params_) external pure returns (bytes32 digest); - - /// @notice Gets the batch IDs for a request - /// @param requestCount_ The request count - /// @return Array of batch IDs - function getBatches(uint40 requestCount_) external view returns (uint40[] memory); - - /// @notice Gets the payload IDs for a batch - /// @param batchCount_ The batch count - /// @return Array of payload IDs - function getBatchPayloadIds(uint40 batchCount_) external view returns (bytes32[] memory); - - /// @notice Gets the payload parameters for a payload ID - /// @param payloadId_ The payload ID - /// @return The payload parameters - function getPayloadParams(bytes32 payloadId_) external view returns (PayloadParams memory); - - function setTimeout( - uint256 delayInSeconds_, - bytes calldata payload_ - ) external returns (bytes32); - - function resolveTimeout( - bytes32 timeoutId_, - uint256 signatureNonce_, - bytes calldata signature_ - ) external; - - function query(PayloadParams memory params_) external; - - function finalized( - bytes32 payloadId_, - bytes calldata proof_, - uint256 signatureNonce_, - bytes calldata signature_ - ) external; - - function updateTransmitter(uint40 requestCount, address transmitter) external; - - function cancelRequest(uint40 requestCount) external; - - function resolvePromises( - ResolvedPromises[] calldata resolvedPromises_, - uint256 signatureNonce_, - bytes calldata signature_ - ) external; - - function markRevert( - bool isRevertingOnchain_, - bytes32 payloadId_, - uint256 signatureNonce_, - bytes calldata signature_ - ) external; - - function setMaxTimeoutDelayInSeconds(uint256 maxTimeoutDelayInSeconds_) external; - - function callAppGateways( - TriggerParams[] calldata params_, - uint256 signatureNonce_, - bytes calldata signature_ - ) external; - - function setExpiryTime(uint256 expiryTime_) external; - - function submitRequest( - PayloadSubmitParams[] calldata payloadSubmitParams - ) external returns (uint40 requestCount); - - function startProcessingRequest(uint40 requestCount, address transmitter) external; - - function getCurrentRequestCount() external view returns (uint40); - - function watcherPrecompileConfig__() external view returns (IWatcherPrecompileConfig); - - 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/evmx/interfaces/IWatcherPrecompileLimits.sol b/contracts/evmx/interfaces/IWatcherPrecompileLimits.sol deleted file mode 100644 index d71447b0..00000000 --- a/contracts/evmx/interfaces/IWatcherPrecompileLimits.sol +++ /dev/null @@ -1,24 +0,0 @@ -// SPDX-License-Identifier: GPL-3.0-only -pragma solidity ^0.8.21; - -import {LimitParams, UpdateLimitParams} from "../../utils/common/Structs.sol"; - -/// @title IWatcherPrecompileLimits -/// @notice Interface for the Watcher Precompile system that handles payload verification and execution -/// @dev Defines core functionality for payload processing and promise resolution -interface IWatcherPrecompileLimits { - 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); -} diff --git a/contracts/evmx/helpers/ProxyFactory.sol b/contracts/evmx/mocks/ProxyFactory.sol similarity index 100% rename from contracts/evmx/helpers/ProxyFactory.sol rename to contracts/evmx/mocks/ProxyFactory.sol diff --git a/contracts/evmx/helpers/TestUSDC.sol b/contracts/evmx/mocks/TestUSDC.sol similarity index 100% rename from contracts/evmx/helpers/TestUSDC.sol rename to contracts/evmx/mocks/TestUSDC.sol diff --git a/contracts/evmx/payload-delivery/AuctionManager.sol b/contracts/evmx/payload-delivery/AuctionManager.sol deleted file mode 100644 index bf625c8b..00000000 --- a/contracts/evmx/payload-delivery/AuctionManager.sol +++ /dev/null @@ -1,239 +0,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 "../interfaces/IAuctionManager.sol"; -import {IMiddleware} from "../interfaces/IMiddleware.sol"; -import {IFeesManager} from "../interfaces/IFeesManager.sol"; -import "../../utils/AccessControl.sol"; -import {AddressResolverUtil} from "../AddressResolverUtil.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; - - // slot 50 - uint32 public evmxSlug; - - // slot 51 - uint256 public maxReAuctionCount; - - // slot 52 - uint256 public auctionEndDelaySeconds; - - // slot 53 - mapping(uint40 => Bid) public winningBids; - - // slot 54 - // requestCount => auction status - mapping(uint40 => bool) public override auctionClosed; - - // slot 55 - mapping(uint40 => bool) public override auctionStarted; - - // slot 56 - mapping(uint40 => uint256) public reAuctionCount; - - // slots [57-106] reserved for gap - uint256[50] _gap_after; - - // slots 107-157 (51) reserved for access control - // slots 158-208 (51) reserved for addr resolver util -} - -/// @title AuctionManager -/// @notice Contract for managing auctions and placing bids -contract AuctionManager is - AuctionManagerStorage, - Initializable, - AccessControl, - AddressResolverUtil -{ - event AuctionRestarted(uint40 requestCount); - event AuctionStarted(uint40 requestCount); - event AuctionEnded(uint40 requestCount, Bid winningBid); - event BidPlaced(uint40 requestCount, Bid bid); - event AuctionEndDelaySecondsSet(uint256 auctionEndDelaySeconds); - - error InvalidBid(); - error MaxReAuctionCountReached(); - - constructor() { - _disableInitializers(); // disable for implementation - } - - /// @notice Initializer function to replace constructor - /// @param evmxSlug_ The chain slug for the VM - /// @param auctionEndDelaySeconds_ The delay in seconds before an auction can end - /// @param addressResolver_ The address of the address resolver - /// @param owner_ The address of the contract owner - /// @param maxReAuctionCount_ The maximum number of re-auctions allowed - function initialize( - uint32 evmxSlug_, - uint256 auctionEndDelaySeconds_, - address addressResolver_, - address owner_, - uint256 maxReAuctionCount_ - ) public reinitializer(1) { - _setAddressResolver(addressResolver_); - _initializeOwner(owner_); - - evmxSlug = evmxSlug_; - auctionEndDelaySeconds = auctionEndDelaySeconds_; - maxReAuctionCount = maxReAuctionCount_; - } - - function setAuctionEndDelaySeconds(uint256 auctionEndDelaySeconds_) external onlyOwner { - auctionEndDelaySeconds = auctionEndDelaySeconds_; - emit AuctionEndDelaySecondsSet(auctionEndDelaySeconds_); - } - - /// @notice Places a bid for an auction - /// @param requestCount_ The ID of the auction - /// @param bidFees The bid amount - /// @param transmitterSignature The signature of the transmitter - function bid( - uint40 requestCount_, - 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_, bidFees, extraData)), - transmitterSignature - ); - if (!_hasRole(TRANSMITTER_ROLE, transmitter)) revert InvalidTransmitter(); - - 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) && - bidFees >= winningBids[requestCount_].fee - ) revert LowerBidAlreadyExists(); - - // create a new bid - Bid memory newBid = Bid({fee: bidFees, transmitter: transmitter, extraData: extraData}); - - // update the winning bid - winningBids[requestCount_] = newBid; - - // end the auction if the no auction end delay - if (auctionEndDelaySeconds > 0) { - _startAuction(requestCount_); - watcherPrecompile__().setTimeout( - auctionEndDelaySeconds, - abi.encodeWithSelector(this.endAuction.selector, requestCount_) - ); - } else { - _endAuction(requestCount_); - } - - emit BidPlaced(requestCount_, newBid); - } - - 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 { - // 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_); - - // if executed, bid is not expired - if (requestParams.payloadsRemaining == 0 || requestParams.isRequestCancelled) return; - winningBids[requestCount_] = Bid({fee: 0, transmitter: address(0), extraData: ""}); - auctionClosed[requestCount_] = false; - reAuctionCount[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_ - ) 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_); - } - - function _getRequestMetadata( - uint40 requestCount_ - ) internal view returns (RequestMetadata memory) { - return IMiddleware(addressResolver__.deliveryHelper()).getRequestMetadata(requestCount_); - } -} diff --git a/contracts/evmx/payload-delivery/FeesManager.sol b/contracts/evmx/payload-delivery/FeesManager.sol deleted file mode 100644 index 7f5e5e61..00000000 --- a/contracts/evmx/payload-delivery/FeesManager.sol +++ /dev/null @@ -1,498 +0,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 "../interfaces/IFeesManager.sol"; -import {AddressResolverUtil} from "../AddressResolverUtil.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 - uint256[50] _gap_before; - - // slot 50 - uint256 public feesCounter; - - // slot 51 - uint32 public evmxSlug; - - // slot 52 - bytes32 public sbType; - - // 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 request credits details for each request count - /// @dev requestCount => RequestFee - mapping(uint40 => uint256) public requestCountCredits; - - // @dev amount - uint256 public watcherPrecompileCredits; - - // slot 56 - /// @notice Mapping to track nonce to whether it has been used - /// @dev signatureNonce => isNonceUsed - mapping(uint256 => bool) public isNonceUsed; - - // slots [57-106] reserved for gap - uint256[50] _gap_after; - - // slots 107-157 (51) reserved for addr resolver util -} - -/// @title FeesManager -/// @notice Contract for managing fees -contract FeesManager is FeesManagerStorage, Initializable, Ownable, AddressResolverUtil { - /// @notice Emitted when fees are blocked for a batch - /// @param requestCount The batch identifier - /// @param consumeFrom The consume from address - /// @param amount The blocked 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 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 CreditsDeposited( - uint32 indexed chainSlug, - address indexed appGateway, - address indexed token, - uint256 amount - ); - - /// @notice Emitted when fees are unblocked and assigned to a transmitter - /// @param requestCount The batch identifier - /// @param transmitter The transmitter address - /// @param amount The unblocked amount - event CreditsUnblockedAndAssigned( - uint40 indexed requestCount, - address indexed transmitter, - uint256 amount - ); - - /// @notice Emitted when fees are unblocked - /// @param requestCount The batch identifier - /// @param appGateway The app gateway address - 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 InsufficientCreditsAvailable(); - /// @notice Error thrown when no fees are available for a transmitter - error NoFeesForTransmitter(); - /// @notice Error thrown when no fees was blocked - 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 - } - - /// @notice Initializer function to replace constructor - /// @param addressResolver_ The address of the address resolver - /// @param owner_ The address of the owner - /// @param evmxSlug_ The evmx chain slug - function initialize( - address addressResolver_, - address owner_, - uint32 evmxSlug_, - bytes32 sbType_ - ) public reinitializer(1) { - evmxSlug = evmxSlug_; - sbType = sbType_; - _setAddressResolver(addressResolver_); - _initializeOwner(owner_); - } - - /// @notice Returns available (unblocked) fees for a gateway - /// @param consumeFrom_ The app gateway address - /// @return The available fee amount - 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 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 token_, - uint256 signatureNonce_, - bytes memory 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) - ); - - 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); - } - - function wrap() external payable { - UserCredits storage userCredit = userCredits[msg.sender]; - userCredit.totalCredits += msg.value; - emit CreditsWrapped(msg.sender, msg.value); - } - - 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) { - // 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 - ) - ); - if (_recoverSigner(digest, signature_) != consumeFrom) revert InvalidUserSignature(); - isAppGatewayWhitelisted[consumeFrom][appGateway] = isApproved; - userNonce[consumeFrom]++; - - return (consumeFrom, appGateway, isApproved); - } - - 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(); - _; - } - - /// @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 - if (getAvailableCredits(consumeFrom_) < transmitterCredits_) - revert InsufficientCreditsAvailable(); - - UserCredits storage userCredit = userCredits[consumeFrom_]; - userCredit.blockedCredits += transmitterCredits_; - - requestCountCredits[requestCount_] = transmitterCredits_; - - 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 unblockAndAssignCredits( - uint40 requestCount_, - address transmitter_ - ) external override onlyDeliveryHelper { - uint256 blockedCredits = requestCountCredits[requestCount_]; - if (blockedCredits == 0) return; - RequestMetadata memory requestMetadata = deliveryHelper__().getRequestMetadata( - requestCount_ - ); - uint256 fees = requestMetadata.winningBid.fee; - - // Unblock fees from deposit - _useBlockedUserCredits(requestMetadata.consumeFrom, blockedCredits, fees); - - // Assign fees to transmitter - userCredits[transmitter_].totalCredits += fees; - - // Clean up storage - 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 unblockCredits(uint40 requestCount_) external { - RequestMetadata memory requestMetadata = deliveryHelper__().getRequestMetadata( - requestCount_ - ); - - if ( - msg.sender != requestMetadata.auctionManager && - msg.sender != address(deliveryHelper__()) - ) revert InvalidCaller(); - - uint256 blockedCredits = requestCountCredits[requestCount_]; - if (blockedCredits == 0) return; - - // Unblock fees from deposit - UserCredits storage userCredit = userCredits[requestMetadata.consumeFrom]; - userCredit.blockedCredits -= blockedCredits; - - 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 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 withdrawCredits( - address originAppGatewayOrUser_, - uint32 chainSlug_, - address token_, - uint256 amount_, - address receiver_ - ) public { - if (msg.sender != address(deliveryHelper__())) originAppGatewayOrUser_ = msg.sender; - address source = _getCoreAppGateway(originAppGatewayOrUser_); - - // Check if amount is available in fees plug - uint256 availableAmount = getAvailableCredits(source); - if (availableAmount < amount_) revert InsufficientCreditsAvailable(); - - _useAvailableUserCredits(source, amount_); - tokenPoolBalances[chainSlug_][token_] -= amount_; - - // Add it to the queue and submit request - _queue(chainSlug_, abi.encodeCall(IFeesPlug.withdrawFees, (token_, receiver_, amount_))); - } - - /// @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_, - 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, - chainSlug: chainSlug_, - callType: CallType.WRITE, - isParallel: Parallel.OFF, - writeFinality: WriteFinality.LOW, - asyncPromise: address(0), - switchboard: _getSwitchboard(chainSlug_), - target: _getFeesPlugAddress(chainSlug_), - appGateway: address(this), - gasLimit: 10000000, - value: 0, - readAt: 0, - payload: payload - }); - 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) { - return watcherPrecompile__().watcherPrecompileConfig__().switchboards(chainSlug_, sbType); - } - - function _createQueuePayloadParams( - uint32 chainSlug_, - bytes memory payload_ - ) internal view returns (QueuePayloadParams memory) { - return - QueuePayloadParams({ - chainSlug: chainSlug_, - callType: CallType.WRITE, - isParallel: Parallel.OFF, - isPlug: IsPlug.NO, - writeFinality: WriteFinality.LOW, - asyncPromise: address(0), - switchboard: _getSwitchboard(chainSlug_), - target: _getFeesPlugAddress(chainSlug_), - appGateway: address(this), - gasLimit: 10000000, - value: 0, - readAt: 0, - payload: payload_, - initCallData: bytes("") - }); - } - - /// @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_, - payload_ - ); - deliveryHelper__().queue(queuePayloadParams); - } - - function _getFeesPlugAddress(uint32 chainSlug_) internal view returns (address) { - return watcherPrecompileConfig().feesPlug(chainSlug_); - } - - function _recoverSigner( - bytes32 digest_, - bytes memory 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_); - } -} diff --git a/contracts/evmx/payload-delivery/app-gateway/DeliveryHelper.sol b/contracts/evmx/payload-delivery/app-gateway/DeliveryHelper.sol deleted file mode 100644 index 24a78e3b..00000000 --- a/contracts/evmx/payload-delivery/app-gateway/DeliveryHelper.sol +++ /dev/null @@ -1,115 +0,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 - } - - /// @notice Initializer function to replace constructor - /// @param addressResolver_ The address resolver contract - /// @param owner_ The owner address - function initialize( - address addressResolver_, - address owner_, - uint128 bidTimeout_ - ) public reinitializer(1) { - _setAddressResolver(addressResolver_); - _initializeOwner(owner_); - - 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_ - ) external onlyAuctionManager(requestCount_) { - if (requests[requestCount_].onlyReadRequests) revert ReadOnlyRequests(); - 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 = winningBid_; - - if (!isRestarted) { - watcherPrecompile__().startProcessingRequest(requestCount_, winningBid_.transmitter); - } else { - watcherPrecompile__().updateTransmitter(requestCount_, winningBid_.transmitter); - } - } - - /// @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()).unblockAndAssignCredits( - requestCount_, - requestMetadata_.winningBid.transmitter - ); - - if (requestMetadata_.appGateway.code.length > 0) { - IAppGateway(requestMetadata_.appGateway).onRequestComplete( - requestCount_, - requestMetadata_.onCompleteData - ); - } - } - - /// @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(); - } - - _settleFees(requestCount_); - watcherPrecompile__().cancelRequest(requestCount_); - emit RequestCancelled(requestCount_); - } - - /// @notice For request reverts, settles the fees - /// @param requestCount_ The ID of the request - function handleRequestReverts(uint40 requestCount_) external onlyWatcherPrecompile { - _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()).unblockAndAssignCredits( - requestCount_, - requests[requestCount_].winningBid.transmitter - ); - } else { - // 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) { - return requests[requestCount_]; - } -} diff --git a/contracts/evmx/payload-delivery/app-gateway/DeliveryHelperStorage.sol b/contracts/evmx/payload-delivery/app-gateway/DeliveryHelperStorage.sol deleted file mode 100644 index f6fbcc15..00000000 --- a/contracts/evmx/payload-delivery/app-gateway/DeliveryHelperStorage.sol +++ /dev/null @@ -1,44 +0,0 @@ -// SPDX-License-Identifier: GPL-3.0-only -pragma solidity ^0.8.21; - -import "../../interfaces/IMiddleware.sol"; -import {IAddressResolver} from "../../interfaces/IAddressResolver.sol"; -import {IContractFactoryPlug} from "../../interfaces/IContractFactoryPlug.sol"; -import {IAppGateway} from "../../interfaces/IAppGateway.sol"; -import {IAuctionManager} from "../../interfaces/IAuctionManager.sol"; -import {IFeesManager} from "../../interfaces/IFeesManager.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 -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; - - // 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; - - // 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/evmx/payload-delivery/app-gateway/DeliveryUtils.sol b/contracts/evmx/payload-delivery/app-gateway/DeliveryUtils.sol deleted file mode 100644 index 14dc88bb..00000000 --- a/contracts/evmx/payload-delivery/app-gateway/DeliveryUtils.sol +++ /dev/null @@ -1,92 +0,0 @@ -// SPDX-License-Identifier: GPL-3.0-only -pragma solidity ^0.8.21; - -import "solady/utils/Initializable.sol"; -import {Ownable} from "solady/auth/Ownable.sol"; -import {AddressResolverUtil} from "../../AddressResolverUtil.sol"; -import "./DeliveryHelperStorage.sol"; - -/// @notice Abstract contract for managing asynchronous payloads -abstract contract DeliveryUtils is - DeliveryHelperStorage, - Initializable, - Ownable, - AddressResolverUtil -{ - // slots [156-206] reserved for gap - uint256[50] _gap_delivery_utils; - - /// @notice Error thrown if payload is too large - error PayloadTooLarge(); - /// @notice Error thrown if trying to cancel a batch without being the application gateway - error OnlyAppGateway(); - /// @notice Error thrown when a winning bid exists - error WinningBidExists(); - /// @notice Error thrown when a bid is insufficient - error InsufficientFees(); - /// @notice Error thrown when a request contains only reads - error ReadOnlyRequests(); - - /// @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, - 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(); - _; - } - - /// @notice Gets the payload delivery plug address - /// @param chainSlug_ The chain identifier - /// @return address The address of the payload delivery plug - function getDeliveryHelperPlugAddress(uint32 chainSlug_) public view returns (address) { - return watcherPrecompileConfig().contractFactoryPlug(chainSlug_); - } - - /// @notice Updates the bid timeout - /// @param newBidTimeout_ The new bid timeout value - function updateBidTimeout(uint128 newBidTimeout_) external onlyOwner { - 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/evmx/payload-delivery/app-gateway/FeesHelpers.sol b/contracts/evmx/payload-delivery/app-gateway/FeesHelpers.sol deleted file mode 100644 index f1845865..00000000 --- a/contracts/evmx/payload-delivery/app-gateway/FeesHelpers.sol +++ /dev/null @@ -1,99 +0,0 @@ -// SPDX-License-Identifier: GPL-3.0-only -pragma solidity ^0.8.21; - -import "./RequestQueue.sol"; - -/// @title RequestAsync -/// @notice Abstract contract for managing asynchronous payload batches -abstract contract FeesHelpers is RequestQueue { - // 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(); - if (requests[requestCount_].maxFees >= newMaxFees_) - revert NewMaxFeesLowerThanCurrent(requests[requestCount_].maxFees, newMaxFees_); - requests[requestCount_].maxFees = newMaxFees_; - emit FeesIncreased(appGateway, requestCount_, newMaxFees_); - } - - /// @notice Withdraws funds to a specified receiver - /// @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 - /// @param fees_ The fees data - function withdrawTo( - uint32 chainSlug_, - address token_, - uint256 amount_, - address receiver_, - address auctionManager_, - uint256 fees_ - ) external returns (uint40) { - 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_ - ); - - 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); - } - - /// @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/evmx/payload-delivery/app-gateway/RequestQueue.sol b/contracts/evmx/payload-delivery/app-gateway/RequestQueue.sol deleted file mode 100644 index 73a7b809..00000000 --- a/contracts/evmx/payload-delivery/app-gateway/RequestQueue.sol +++ /dev/null @@ -1,235 +0,0 @@ -// SPDX-License-Identifier: GPL-3.0-only -pragma solidity ^0.8.21; - -import "./DeliveryUtils.sol"; - -/// @notice Abstract contract for managing asynchronous payloads -abstract contract RequestQueue is DeliveryUtils { - // slots [207-257] reserved for gap - uint256[50] _gap_queue_async; - - /// @notice Clears the call parameters array - function clearQueue() public { - delete queuePayloadParams; - } - - /// @notice Queues a new payload - /// @param queuePayloadParams_ The call parameters - function queue(QueuePayloadParams memory queuePayloadParams_) external { - queuePayloadParams.push(queuePayloadParams_); - } - - /// @notice Initiates a batch of payloads - /// @param maxFees_ The fees data - /// @param auctionManager_ The auction manager address - /// @return requestCount The ID of the batch - function batch( - uint256 maxFees_, - address auctionManager_, - address consumeFrom_, - bytes memory onCompleteData_ - ) external returns (uint40 requestCount) { - address appGateway = _getCoreAppGateway(msg.sender); - 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_, - address consumeFrom_, - uint256 maxFees_, - bytes memory onCompleteData_ - ) internal returns (uint40 requestCount) { - if (queuePayloadParams.length == 0) return 0; - - 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, - uint256 queryCount, - uint256 finalizeCount - ) = _createPayloadSubmitParamsArray(); - - 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: params.appGateway, - auctionManager: params.auctionManager, - maxFees: params.maxFees, - winningBid: Bid({fee: 0, transmitter: address(0), extraData: new bytes(0)}), - onCompleteData: params.onCompleteData, - onlyReadRequests: params.onlyReadRequests, - consumeFrom: consumeFrom_, - queryCount: params.queryCount, - finalizeCount: params.finalizeCount - }); - - requestCount = watcherPrecompile__().submitRequest(payloadSubmitParamsArray); - requests[requestCount] = requestMetadata; - - 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, - params.appGateway, - payloadSubmitParamsArray, - 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, - bool onlyReadRequests, - uint256 queryCount, - uint256 finalizeCount - ) - { - payloadDetailsArray = new PayloadSubmitParams[](queuePayloadParams.length); - onlyReadRequests = queuePayloadParams[0].callType == CallType.READ; - - uint256 currentLevel = 0; - for (uint256 i = 0; i < queuePayloadParams.length; i++) { - if (queuePayloadParams[i].callType == CallType.READ) { - queryCount++; - } else { - onlyReadRequests = false; - finalizeCount++; - } - - // Update level for calls - if (i > 0 && queuePayloadParams[i].isParallel != Parallel.ON) { - currentLevel = currentLevel + 1; - } - - 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 - function _createPayloadDetails( - uint256 level_, - QueuePayloadParams memory queuePayloadParams_ - ) internal returns (PayloadSubmitParams memory) { - bytes memory payload = queuePayloadParams_.payload; - address target = queuePayloadParams_.target; - if (queuePayloadParams_.callType == CallType.DEPLOY) { - (payload, target) = _createDeployPayloadDetails(queuePayloadParams_); - } - - if (payload.length > PAYLOAD_SIZE_LIMIT) revert PayloadTooLarge(); - if (queuePayloadParams_.value > chainMaxMsgValueLimit[queuePayloadParams_.chainSlug]) - revert MaxMsgValueLimitExceeded(); - - return - PayloadSubmitParams({ - levelNumber: level_, - chainSlug: queuePayloadParams_.chainSlug, - callType: queuePayloadParams_.callType, - isParallel: queuePayloadParams_.isParallel, - writeFinality: queuePayloadParams_.writeFinality, - asyncPromise: queuePayloadParams_.asyncPromise, - switchboard: queuePayloadParams_.switchboard, - target: target, - appGateway: queuePayloadParams_.appGateway, - gasLimit: queuePayloadParams_.gasLimit == 0 - ? 10_000_000 - : queuePayloadParams_.gasLimit, - value: queuePayloadParams_.value, - readAt: queuePayloadParams_.readAt, - payload: payload - }); - } -} diff --git a/contracts/evmx/payload-delivery/ContractFactoryPlug.sol b/contracts/evmx/plugs/ContractFactoryPlug.sol similarity index 91% rename from contracts/evmx/payload-delivery/ContractFactoryPlug.sol rename to contracts/evmx/plugs/ContractFactoryPlug.sol index c3691374..29c8fd64 100644 --- a/contracts/evmx/payload-delivery/ContractFactoryPlug.sol +++ b/contracts/evmx/plugs/ContractFactoryPlug.sol @@ -19,7 +19,7 @@ contract ContractFactoryPlug is PlugBase, AccessControl, IContractFactoryPlug { /// @notice Error thrown if it failed to deploy the create2 contract error DeploymentFailed(); - error ExecutionFailed(); + error ExecutionFailed(bytes32 appGatewayId, bytes returnData); /// @notice Constructor for the ContractFactoryPlug /// @param socket_ The socket address @@ -65,16 +65,7 @@ contract ContractFactoryPlug is PlugBase, AccessControl, IContractFactoryPlug { initCallData_ ); - if (!success) { - // Additional error logging - assembly { - let ptr := mload(0x40) - returndatacopy(ptr, 0, returndatasize()) - log0(ptr, returndatasize()) - } - - revert ExecutionFailed(); - } + if (!success) revert ExecutionFailed(appGatewayId_, returnData_); returnData = returnData_; } diff --git a/contracts/evmx/payload-delivery/FeesPlug.sol b/contracts/evmx/plugs/FeesPlug.sol similarity index 71% rename from contracts/evmx/payload-delivery/FeesPlug.sol rename to contracts/evmx/plugs/FeesPlug.sol index 8d7fa8a8..cc8bde5e 100644 --- a/contracts/evmx/payload-delivery/FeesPlug.sol +++ b/contracts/evmx/plugs/FeesPlug.sol @@ -2,20 +2,24 @@ pragma solidity ^0.8.21; import "solady/utils/SafeTransferLib.sol"; -import "solady/tokens/ERC20.sol"; import "../../protocol/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, FeesAlreadyPaid} from "../../utils/common/Errors.sol"; +import {InvalidTokenAddress} from "../../utils/common/Errors.sol"; -/// @title FeesManager +interface IERC20 { + function balanceOf(address account) external view returns (uint256); +} + +/// @title FeesPlug /// @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 { + using SafeTransferLib for address; + /// @notice Mapping to store if a token is whitelisted mapping(address => bool) public whitelistedTokens; @@ -26,27 +30,6 @@ contract FeesPlug is IFeesPlug, PlugBase, AccessControl { /// @notice Error thrown when token is not whitelisted error TokenNotWhitelisted(address token_); - /// @notice Event emitted when fees are deposited - event FeesDeposited( - address token, - address receiver, - uint256 creditAmount, - uint256 nativeAmount - ); - /// @notice Event emitted when fees are withdrawn - 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); - - /// @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 @@ -55,31 +38,18 @@ contract FeesPlug is IFeesPlug, PlugBase, AccessControl { _initializeOwner(owner_); } - /// @notice Withdraws fees - /// @param token_ The token address - /// @param amount_ The amount - /// @param receiver_ The receiver address - function withdrawFees( - address token_, - address receiver_, - uint256 amount_ - ) external override onlySocket isUserCreditsEnough(token_, amount_) { - SafeTransferLib.safeTransfer(token_, receiver_, amount_); - emit FeesWithdrawn(token_, receiver_, amount_); - } - - function depositToFee(address token_, address receiver_, uint256 amount_) external override { + /////////////////////// DEPOSIT AND WITHDRAWAL /////////////////////// + function depositCredit(address token_, address receiver_, uint256 amount_) external override { _deposit(token_, receiver_, amount_, 0); } - function depositToFeeAndNative( + function depositCreditAndNative( address token_, address receiver_, uint256 amount_ ) external override { uint256 nativeAmount_ = amount_ / 10; - uint256 creditAmount_ = amount_ - nativeAmount_; - _deposit(token_, receiver_, creditAmount_, nativeAmount_); + _deposit(token_, receiver_, amount_ - nativeAmount_, nativeAmount_); } function depositToNative(address token_, address receiver_, uint256 amount_) external override { @@ -97,12 +67,29 @@ contract FeesPlug is IFeesPlug, PlugBase, AccessControl { uint256 creditAmount_, uint256 nativeAmount_ ) internal { - uint256 totalAmount_ = creditAmount_ + nativeAmount_; if (!whitelistedTokens[token_]) revert TokenNotWhitelisted(token_); - SafeTransferLib.safeTransferFrom(token_, msg.sender, address(this), totalAmount_); + token_.safeTransferFrom(msg.sender, address(this), creditAmount_ + nativeAmount_); emit FeesDeposited(token_, receiver_, creditAmount_, nativeAmount_); } + /// @notice Withdraws fees + /// @param token_ The token address + /// @param amount_ The amount + /// @param receiver_ The receiver address + function withdrawFees( + address token_, + address receiver_, + uint256 amount_ + ) external override onlySocket { + uint256 balance = IERC20(token_).balanceOf(address(this)); + if (balance < amount_) revert InsufficientTokenBalance(token_, balance, amount_); + + token_.safeTransfer(receiver_, amount_); + emit FeesWithdrawn(token_, receiver_, amount_); + } + + /////////////////////// ADMIN FUNCTIONS /////////////////////// + /// @notice Adds a token to the whitelist /// @param token_ The token address to whitelist function whitelistToken(address token_) external onlyOwner { diff --git a/contracts/evmx/watcherPrecompile/WatcherPrecompileConfig.sol b/contracts/evmx/watcher/Configurations.sol similarity index 51% rename from contracts/evmx/watcherPrecompile/WatcherPrecompileConfig.sol rename to contracts/evmx/watcher/Configurations.sol index b0a3984f..2674f7fb 100644 --- a/contracts/evmx/watcherPrecompile/WatcherPrecompileConfig.sol +++ b/contracts/evmx/watcher/Configurations.sol @@ -2,65 +2,47 @@ pragma solidity ^0.8.21; 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 "../AddressResolverUtil.sol"; -import {InvalidWatcherSignature, NonceUsed} from "../../utils/common/Errors.sol"; -import "./core/WatcherIdUtils.sol"; - -/// @title WatcherPrecompileConfig -/// @notice Configuration contract for the Watcher Precompile system -/// @dev Handles the mapping between networks, plugs, and app gateways for payload execution -contract WatcherPrecompileConfig is - IWatcherPrecompileConfig, - Initializable, - Ownable, - AddressResolverUtil -{ - // slots 0-50 (51) reserved for addr resolver util - - // slots [51-100]: gap for future storage variables +import "../interfaces/IConfigurations.sol"; +import {WatcherBase} from "./WatcherBase.sol"; +import {encodeAppGatewayId} from "../../utils/common/IdUtils.sol"; +import {InvalidGateway, InvalidSwitchboard} from "../../utils/common/Errors.sol"; +import "solady/auth/Ownable.sol"; +import "../../utils/RescueFundsLib.sol"; + +abstract contract ConfigurationsStorage is IConfigurations { + // slots [0-49] reserved for gap uint256[50] _gap_before; - // slot 101: evmxSlug - /// @notice The chain slug of the watcher precompile - uint32 public evmxSlug; - - // slot 102: _plugConfigs + // slot 50 /// @notice Maps network and plug to their configuration /// @dev chainSlug => plug => PlugConfig mapping(uint32 => mapping(address => PlugConfig)) internal _plugConfigs; - // slot 103: switchboards + // slot 51 /// @notice Maps chain slug to their associated switchboard /// @dev chainSlug => sb type => switchboard address mapping(uint32 => mapping(bytes32 => address)) public switchboards; - // slot 104: sockets + // slot 52 /// @notice Maps chain slug to their associated socket /// @dev chainSlug => socket address mapping(uint32 => address) public sockets; - // 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 106: feesPlug - /// @notice Maps chain slug to their associated fees plug - /// @dev chainSlug => fees plug address - mapping(uint32 => address) public feesPlug; + // slot 53 + /// @notice Maps app gateway, chain slug, and plug to whether it is valid + /// @dev appGateway => chainSlug => plug => isValid + mapping(address => mapping(uint32 => mapping(address => bool))) public isValidPlug; - // slot 107: isNonceUsed - /// @notice Maps nonce to whether it has been used - /// @dev signatureNonce => isValid - mapping(uint256 => bool) public isNonceUsed; + // slots [54-103] reserved for gap + uint256[50] _gap_after; - // slot 108: isValidPlug - // appGateway => chainSlug => plug => isValid - mapping(address => mapping(uint32 => mapping(address => bool))) public isValidPlug; + // 1 slot reserved for watcher base +} +/// @title Configurations +/// @notice Configuration contract for the Watcher Precompile system +/// @dev Handles the mapping between networks, plugs, and app gateways for payload execution +contract Configurations is ConfigurationsStorage, Initializable, Ownable, WatcherBase { /// @notice Emitted when a new plug is configured for an app gateway /// @param appGatewayId The id of the app gateway /// @param chainSlug The identifier of the destination network @@ -73,17 +55,10 @@ contract WatcherPrecompileConfig is /// @param switchboard The address of the switchboard event SwitchboardSet(uint32 chainSlug, bytes32 sbType, address switchboard); - /// @notice Emitted when contracts are set for a network + /// @notice Emitted when socket is set for a network /// @param chainSlug The identifier of the network /// @param socket The address of the socket - /// @param contractFactoryPlug The address of the contract factory plug - /// @param feesPlug The address of the fees plug - event OnChainContractSet( - uint32 chainSlug, - address socket, - address contractFactoryPlug, - address feesPlug - ); + event SocketSet(uint32 chainSlug, address socket); /// @notice Emitted when a valid plug is set for an app gateway /// @param appGateway The address of the app gateway @@ -92,60 +67,38 @@ contract WatcherPrecompileConfig is /// @param isValid Whether the plug is valid event IsValidPlugSet(address appGateway, uint32 chainSlug, address plug, bool isValid); - error InvalidGateway(); - error InvalidSwitchboard(); + constructor() { + _disableInitializers(); // disable for implementation + } - /// @notice Initial initialization (version 1) - function initialize( - address owner_, - address addressResolver_, - uint32 evmxSlug_ - ) public reinitializer(1) { - _setAddressResolver(addressResolver_); + function initialize(address watcher_, address owner_) external reinitializer(1) { _initializeOwner(owner_); - - evmxSlug = evmxSlug_; + _initializeWatcher(watcher_); } /// @notice Configures app gateways with their respective plugs and switchboards /// @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_, - bytes calldata signature_ - ) external { - _isWatcherSignatureValid( - abi.encode(this.setAppGateways.selector, configs_), - signatureNonce_, - signature_ - ); - + function setAppGatewayConfigs(AppGatewayConfig[] calldata configs_) external onlyWatcher { 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({ - appGatewayId: configs_[i].appGatewayId, - switchboard: configs_[i].switchboard - }); + _plugConfigs[configs_[i].chainSlug][configs_[i].plug] = configs_[i].plugConfig; - emit PlugAdded(configs_[i].appGatewayId, configs_[i].chainSlug, configs_[i].plug); + emit PlugAdded( + configs_[i].plugConfig.appGatewayId, + configs_[i].chainSlug, + configs_[i].plug + ); } } - /// @notice Sets the socket, contract factory plug, and fees plug for a network + /// @notice Sets the socket for a network /// @param chainSlug_ The identifier of the network - function setOnChainContracts( - uint32 chainSlug_, - address socket_, - address contractFactoryPlug_, - address feesPlug_ - ) external onlyOwner { + /// @param socket_ The address of the socket + function setSocket(uint32 chainSlug_, address socket_) external onlyOwner { sockets[chainSlug_] = socket_; - contractFactoryPlug[chainSlug_] = contractFactoryPlug_; - feesPlug[chainSlug_] = feesPlug_; - - emit OnChainContractSet(chainSlug_, socket_, contractFactoryPlug_, feesPlug_); + emit SocketSet(chainSlug_, socket_); } /// @notice Sets the switchboard for a network @@ -167,9 +120,14 @@ contract WatcherPrecompileConfig is /// @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_); + function setIsValidPlug( + bool isValid_, + uint32 chainSlug_, + address plug_, + address appGateway_ + ) external onlyWatcher { + isValidPlug[appGateway_][chainSlug_][plug_] = isValid_; + emit IsValidPlugSet(appGateway_, chainSlug_, plug_, isValid_); } /// @notice Retrieves the configuration for a specific plug on a network @@ -193,40 +151,26 @@ contract WatcherPrecompileConfig is /// @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 + /// @param switchboardType_ The type of switchboard function verifyConnections( uint32 chainSlug_, address target_, address appGateway_, - address switchboard_, - address middleware_ + bytes32 switchboardType_ ) external view { - // if target is contractFactoryPlug, return - // as connection is with middleware delivery helper and not app gateway - if ( - middleware_ == address(deliveryHelper__()) && target_ == contractFactoryPlug[chainSlug_] - ) return; - (bytes32 appGatewayId, address switchboard) = getPlugConfigs(chainSlug_, target_); - if (appGatewayId != WatcherIdUtils.encodeAppGatewayId(appGateway_)) revert InvalidGateway(); - if (switchboard != switchboard_) revert InvalidSwitchboard(); + if (appGatewayId != encodeAppGatewayId(appGateway_)) revert InvalidGateway(); + if (switchboard != switchboards[chainSlug_][switchboardType_]) revert InvalidSwitchboard(); } - 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(); + /** + * @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. + * @param token_ The address of the token contract. + * @param rescueTo_ The address where rescued tokens need to be sent. + * @param amount_ The amount of tokens to be rescued. + */ + function rescueFunds(address token_, address rescueTo_, uint256 amount_) external onlyWatcher { + RescueFundsLib._rescueFunds(token_, rescueTo_, amount_); } } diff --git a/contracts/evmx/watcher/PromiseResolver.sol b/contracts/evmx/watcher/PromiseResolver.sol new file mode 100644 index 00000000..d226492c --- /dev/null +++ b/contracts/evmx/watcher/PromiseResolver.sol @@ -0,0 +1,107 @@ +// SPDX-License-Identifier: GPL-3.0-only +pragma solidity ^0.8.21; + +import "./WatcherBase.sol"; +import "../interfaces/IPromise.sol"; +import "../interfaces/IPromiseResolver.sol"; +import {DeadlineNotPassedForOnChainRevert} from "../../utils/common/Errors.sol"; +import "../../utils/RescueFundsLib.sol"; + +/// @title PromiseResolver +/// @notice Contract that handles promise resolution and revert marking logic +/// @dev This contract interacts with the Watcher for storage access +contract PromiseResolver is IPromiseResolver, WatcherBase { + /// @notice Emitted when a promise is resolved + /// @param payloadId The unique identifier for the resolved promise + event PromiseResolved(bytes32 indexed payloadId, address asyncPromise); + + /// @notice Emitted when a promise is not resolved + /// @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); + + /// @notice Sets the Watcher address + /// @param watcher_ The address of the Watcher contract + constructor(address watcher_) { + _initializeWatcher(watcher_); + } + + /// @notice Resolves multiple promises with their return data + /// @param promiseReturnData_ Array of resolved promises and their return data + /// @dev This function resolves multiple promises with their return data + /// @dev It also processes the next batch if the current batch is complete + function resolvePromises(PromiseReturnData[] memory promiseReturnData_) external onlyWatcher { + for (uint256 i = 0; i < promiseReturnData_.length; i++) { + (uint40 requestCount, bool success) = _processPromiseResolution(promiseReturnData_[i]); + if (success) { + requestHandler__().updateRequestAndProcessBatch( + requestCount, + promiseReturnData_[i].payloadId + ); + } + } + } + + function _processPromiseResolution( + PromiseReturnData memory resolvedPromise_ + ) internal returns (uint40 requestCount, bool success) { + bytes32 payloadId = resolvedPromise_.payloadId; + PayloadParams memory payloadParams = watcher__.getPayloadParams(payloadId); + if (payloadParams.deadline < block.timestamp) revert DeadlinePassed(); + + address asyncPromise = payloadParams.asyncPromise; + requestCount = payloadParams.requestCount; + + if (asyncPromise != address(0)) { + success = IPromise(asyncPromise).markResolved(resolvedPromise_); + + if (!success) { + emit PromiseNotResolved(payloadId, asyncPromise); + return (requestCount, false); + } + } else { + success = true; + } + + emit PromiseResolved(payloadId, asyncPromise); + } + + /// @notice Marks a request as reverting + /// @param isRevertingOnchain_ Whether the request is reverting onchain + /// @param resolvedPromise_ The resolved promise + /// @dev This function marks a request as reverting + /// @dev It cancels the request and marks the promise as onchain reverting if the request is reverting onchain + function markRevert( + PromiseReturnData memory resolvedPromise_, + bool isRevertingOnchain_ + ) external onlyWatcher { + // Get payload params from Watcher + bytes32 payloadId = resolvedPromise_.payloadId; + PayloadParams memory payloadParams = watcher__.getPayloadParams(payloadId); + if (payloadParams.deadline > block.timestamp) revert DeadlineNotPassedForOnChainRevert(); + + // marks the request as cancelled and settles the fees + requestHandler__().cancelRequestForReverts(payloadParams.requestCount); + + // marks the promise as onchain reverting if the request is reverting onchain + if (isRevertingOnchain_ && payloadParams.asyncPromise != address(0)) + IPromise(payloadParams.asyncPromise).markOnchainRevert(resolvedPromise_); + + emit MarkedRevert(payloadId, isRevertingOnchain_); + } + + /** + * @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. + * @param token_ The address of the token contract. + * @param rescueTo_ The address where rescued tokens need to be sent. + * @param amount_ The amount of tokens to be rescued. + */ + function rescueFunds(address token_, address rescueTo_, uint256 amount_) external onlyWatcher { + RescueFundsLib._rescueFunds(token_, rescueTo_, amount_); + } +} diff --git a/contracts/evmx/watcher/RequestHandler.sol b/contracts/evmx/watcher/RequestHandler.sol new file mode 100644 index 00000000..f11ca0bf --- /dev/null +++ b/contracts/evmx/watcher/RequestHandler.sol @@ -0,0 +1,442 @@ +// SPDX-License-Identifier: GPL-3.0-only +pragma solidity ^0.8.21; + +import "solady/utils/Initializable.sol"; +import "solady/auth/Ownable.sol"; +import "../helpers/AddressResolverUtil.sol"; +import "../../utils/common/Errors.sol"; +import "../../utils/common/Constants.sol"; +import "../../utils/common/IdUtils.sol"; +import "../interfaces/IAppGateway.sol"; +import "../interfaces/IPromise.sol"; +import "../interfaces/IRequestHandler.sol"; +import "../../utils/RescueFundsLib.sol"; + +abstract contract RequestHandlerStorage is IRequestHandler { + // slots [0-49] reserved for gap + uint256[50] _gap_before; + + // slot 50 (40 + 40 + 40 + 128) + /// @notice Counter for tracking request counts + uint40 public nextRequestCount = 1; + + /// @notice Counter for tracking payload _requests + uint40 public payloadCounter; + + /// @notice Counter for tracking batch counts + uint40 public nextBatchCount; + + /// @notice max number of payloads in single request + uint128 requestPayloadCountLimit; + + // slot 51 + /// @notice Mapping to store the precompiles for each call type + mapping(bytes4 => IPrecompile) public precompiles; + + // slot 52 + /// @notice Mapping to store the list of payload IDs for each batch + mapping(uint40 => bytes32[]) internal _batchPayloadIds; + + // slot 53 + /// @notice Mapping to store the batch IDs for each request + mapping(uint40 => uint40[]) internal _requestBatchIds; + + // queue => update to payloadParams, assign id, store in payloadParams map + // slot 54 + /// @notice Mapping to store the payload parameters for each payload ID + mapping(bytes32 => PayloadParams) internal _payloads; + + // slot 55 + /// @notice The metadata for a request + mapping(uint40 => RequestParams) internal _requests; + + // slots [56-105] reserved for gap + uint256[50] _gap_after; + + // slots [106-155] 50 slots reserved for address resolver util +} + +/// @title RequestHandler +/// @notice Contract that handles request processing and management, including request submission, batch processing, and request lifecycle management +/// @dev Handles request submission, batch processing, transmitter assignment, request cancellation and settlement +contract RequestHandler is RequestHandlerStorage, Initializable, Ownable, AddressResolverUtil { + error InsufficientMaxFees(); + + event RequestSubmitted( + bool hasWrite, + uint40 requestCount, + uint256 totalEstimatedWatcherFees, + RequestParams requestParams, + PayloadParams[] payloadParamsArray + ); + + event FeesIncreased(uint40 requestCount, uint256 newMaxFees); + event RequestSettled(uint40 requestCount, address winner); + event RequestCompletedWithErrors(uint40 requestCount); + event RequestCancelled(uint40 requestCount); + + modifier isRequestCancelled(uint40 requestCount_) { + if (_requests[requestCount_].requestTrackingParams.isRequestCancelled) + revert RequestAlreadyCancelled(); + _; + } + + modifier onlyPromiseResolver() { + if (msg.sender != address(watcher__().promiseResolver__())) revert NotPromiseResolver(); + _; + } + + constructor() { + _disableInitializers(); // disable for implementation + } + + function initialize(address owner_, address addressResolver_) external reinitializer(1) { + requestPayloadCountLimit = 100; + _initializeOwner(owner_); + _setAddressResolver(addressResolver_); + } + + function setPrecompile(bytes4 callType_, IPrecompile precompile_) external onlyOwner { + precompiles[callType_] = precompile_; + } + + function setRequestPayloadCountLimit(uint128 requestPayloadCountLimit_) external onlyOwner { + requestPayloadCountLimit = requestPayloadCountLimit_; + } + + function getPrecompileFees( + bytes4 callType_, + bytes memory precompileData_ + ) external view returns (uint256) { + return precompiles[callType_].getPrecompileFees(precompileData_); + } + + function getRequestBatchIds(uint40 requestCount_) external view returns (uint40[] memory) { + return _requestBatchIds[requestCount_]; + } + + function getBatchPayloadIds(uint40 batchCount_) external view returns (bytes32[] memory) { + return _batchPayloadIds[batchCount_]; + } + + function getRequest(uint40 requestCount_) external view returns (RequestParams memory) { + return _requests[requestCount_]; + } + + function getPayload(bytes32 payloadId_) external view returns (PayloadParams memory) { + return _payloads[payloadId_]; + } + + function submitRequest( + uint256 maxFees_, + address auctionManager_, + address consumeFrom_, + address appGateway_, + QueueParams[] calldata queueParams_, + bytes memory onCompleteData_ + ) external onlyWatcher returns (uint40 requestCount, address[] memory promiseList) { + if (queueParams_.length == 0) return (0, new address[](0)); + if (queueParams_.length > requestPayloadCountLimit) + revert RequestPayloadCountLimitExceeded(); + + if (!feesManager__().isCreditSpendable(consumeFrom_, appGateway_, maxFees_)) + revert InsufficientFees(); + + requestCount = nextRequestCount++; + uint40 currentBatch = nextBatchCount; + + RequestParams storage r = _requests[requestCount]; + r.requestTrackingParams.payloadsRemaining = queueParams_.length; + r.requestFeesDetails.maxFees = maxFees_; + r.requestFeesDetails.consumeFrom = consumeFrom_; + r.auctionManager = _getAuctionManager(auctionManager_); + r.appGateway = appGateway_; + r.onCompleteData = onCompleteData_; + + CreateRequestResult memory result = _createRequest(queueParams_, appGateway_, requestCount); + + // initialize tracking params + r.requestTrackingParams.currentBatch = currentBatch; + r.requestTrackingParams.currentBatchPayloadsLeft = _batchPayloadIds[currentBatch].length; + + r.writeCount = result.writeCount; + promiseList = result.promiseList; + + if (result.totalEstimatedWatcherFees > maxFees_) revert InsufficientMaxFees(); + if (r.writeCount == 0) _processBatch(currentBatch, r); + + emit RequestSubmitted( + r.writeCount > 0, + requestCount, + result.totalEstimatedWatcherFees, + r, + result.payloadParams + ); + } + + // called by auction manager when a auction ends or a new transmitter is assigned (bid expiry) + function assignTransmitter( + uint40 requestCount_, + Bid memory bid_ + ) external isRequestCancelled(requestCount_) { + RequestParams storage r = _requests[requestCount_]; + if (r.auctionManager != msg.sender) revert InvalidCaller(); + if (r.requestTrackingParams.isRequestExecuted) revert RequestAlreadySettled(); + + if (r.writeCount == 0) revert NoWriteRequest(); + + // If same transmitter is reassigned, revert + if (r.requestFeesDetails.winningBid.transmitter == bid_.transmitter) + revert AlreadyAssigned(); + + // If a transmitter was already assigned previously, unblock the credits + if (r.requestFeesDetails.winningBid.transmitter != address(0)) { + feesManager__().unblockCredits(requestCount_); + } + + r.requestFeesDetails.winningBid = bid_; + + // If a transmitter changed to address(0), return after unblocking the credits + if (bid_.transmitter == address(0)) return; + + // Block the credits for the new transmitter + feesManager__().blockCredits(requestCount_, r.requestFeesDetails.consumeFrom, bid_.fee); + + // re-process current batch again or process the batch for the first time + _processBatch(r.requestTrackingParams.currentBatch, r); + } + + function _createRequest( + QueueParams[] calldata queueParams_, + address appGateway_, + uint40 requestCount_ + ) internal returns (CreateRequestResult memory result) { + // push first batch count + _requestBatchIds[requestCount_].push(nextBatchCount); + + result.promiseList = new address[](queueParams_.length); + result.payloadParams = new PayloadParams[](queueParams_.length); + for (uint256 i = 0; i < queueParams_.length; i++) { + QueueParams calldata queuePayloadParam = queueParams_[i]; + bytes4 callType = queuePayloadParam.overrideParams.callType; + if (callType == WRITE) result.writeCount++; + + // decide batch count + if (i > 0 && queueParams_[i].overrideParams.isParallelCall != Parallel.ON) { + nextBatchCount++; + _requestBatchIds[requestCount_].push(nextBatchCount); + } + + // get the switchboard address from the configurations + // returns address(0) for schedule precompile and reads if sb type not set + address switchboard = watcher__().configurations__().switchboards( + queuePayloadParam.transaction.chainSlug, + queuePayloadParam.switchboardType + ); + + // process payload data and store + (bytes memory precompileData, uint256 estimatedFees) = _validateAndGetPrecompileData( + queuePayloadParam, + appGateway_, + callType + ); + result.totalEstimatedWatcherFees += estimatedFees; + + // create payload id + uint40 payloadCount = payloadCounter++; + bytes32 payloadId = createPayloadId( + requestCount_, + nextBatchCount, + payloadCount, + switchboard, + // todo: add evmx chain slug if schedule or read? + queuePayloadParam.transaction.chainSlug + ); + _batchPayloadIds[nextBatchCount].push(payloadId); + + // create prev digest hash + PayloadParams memory p; + p.requestCount = requestCount_; + p.batchCount = nextBatchCount; + p.payloadCount = payloadCount; + p.callType = callType; + p.asyncPromise = queueParams_[i].asyncPromise; + p.appGateway = appGateway_; + p.payloadId = payloadId; + p.precompileData = precompileData; + + result.promiseList[i] = queueParams_[i].asyncPromise; + result.payloadParams[i] = p; + _payloads[payloadId] = p; + } + + nextBatchCount++; + } + + function _validateAndGetPrecompileData( + QueueParams calldata payloadParams_, + address appGateway_, + bytes4 callType_ + ) internal view returns (bytes memory precompileData, uint256 estimatedFees) { + if (address(precompiles[callType_]) == address(0)) revert InvalidCallType(); + return + IPrecompile(precompiles[callType_]).validateAndGetPrecompileData( + payloadParams_, + appGateway_ + ); + } + + function _getAuctionManager(address auctionManager_) internal view returns (address) { + return + auctionManager_ == address(0) + ? addressResolver__.defaultAuctionManager() + : auctionManager_; + } + + // called when processing batch first time or being retried + function _processBatch(uint40 batchCount_, RequestParams storage r) internal { + bytes32[] memory payloadIds = _batchPayloadIds[batchCount_]; + + uint256 totalFees = 0; + for (uint40 i = 0; i < payloadIds.length; i++) { + bytes32 payloadId = payloadIds[i]; + + // check needed for re-process, in case a payload is already executed by last transmitter + if (_isPromiseResolved(_payloads[payloadId].asyncPromise)) continue; + PayloadParams storage payloadParams = _payloads[payloadId]; + + (uint256 fees, uint256 deadline, bytes memory precompileData) = IPrecompile( + precompiles[payloadParams.callType] + ).handlePayload(r.requestFeesDetails.winningBid.transmitter, payloadParams); + + totalFees += fees; + payloadParams.deadline = deadline; + payloadParams.precompileData = precompileData; + } + + address watcherFeesPayer = r.requestFeesDetails.winningBid.transmitter == address(0) + ? r.requestFeesDetails.consumeFrom + : r.requestFeesDetails.winningBid.transmitter; + feesManager__().transferCredits(watcherFeesPayer, address(this), totalFees); + } + + /// @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_, + address appGateway_ + ) external onlyWatcher isRequestCancelled(requestCount_) { + RequestParams storage r = _requests[requestCount_]; + if (r.requestTrackingParams.isRequestExecuted) revert RequestAlreadySettled(); + + if (appGateway_ != r.appGateway) revert OnlyAppGateway(); + if (r.requestFeesDetails.maxFees >= newMaxFees_) + revert NewMaxFeesLowerThanCurrent(r.requestFeesDetails.maxFees, newMaxFees_); + + if ( + !IFeesManager(feesManager__()).isCreditSpendable( + r.requestFeesDetails.consumeFrom, + appGateway_, + newMaxFees_ + ) + ) revert InsufficientFees(); + + r.requestFeesDetails.maxFees = newMaxFees_; + + // indexed by transmitter and watcher to start bidding or re-processing the request + emit FeesIncreased(requestCount_, newMaxFees_); + } + + function updateRequestAndProcessBatch( + uint40 requestCount_, + bytes32 payloadId_ + ) external onlyPromiseResolver isRequestCancelled(requestCount_) { + RequestParams storage r = _requests[requestCount_]; + + PayloadParams storage payloadParams = _payloads[payloadId_]; + payloadParams.resolvedAt = block.timestamp; + + RequestTrackingParams storage trackingParams = r.requestTrackingParams; + trackingParams.currentBatchPayloadsLeft--; + trackingParams.payloadsRemaining--; + + IPrecompile(precompiles[payloadParams.callType]).resolvePayload(payloadParams); + + if (trackingParams.currentBatchPayloadsLeft != 0) return; + if (trackingParams.payloadsRemaining == 0) { + trackingParams.isRequestExecuted = true; + _settleRequest(requestCount_, r); + } else { + uint40 currentBatch = ++trackingParams.currentBatch; + trackingParams.currentBatchPayloadsLeft = _batchPayloadIds[currentBatch].length; + _processBatch(currentBatch, r); + } + } + + function _isPromiseResolved(address promise_) internal view returns (bool) { + return IPromise(promise_).state() == AsyncPromiseState.RESOLVED; + } + + /// @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 cancelRequestForReverts(uint40 requestCount) external onlyPromiseResolver { + _cancelRequest(requestCount, _requests[requestCount]); + } + + /// @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, address appGateway_) external onlyWatcher { + RequestParams storage r = _requests[requestCount]; + if (appGateway_ != r.appGateway) revert InvalidCaller(); + _cancelRequest(requestCount, r); + } + + function handleRevert(uint40 requestCount) external onlyPromiseResolver { + _cancelRequest(requestCount, _requests[requestCount]); + } + + function _cancelRequest( + uint40 requestCount_, + RequestParams storage r + ) internal isRequestCancelled(requestCount_) { + if (r.requestTrackingParams.isRequestExecuted) revert RequestAlreadySettled(); + + r.requestTrackingParams.isRequestCancelled = true; + _settleRequest(requestCount_, r); + emit RequestCancelled(requestCount_); + } + + function _settleRequest(uint40 requestCount_, RequestParams storage r) internal { + feesManager__().unblockAndAssignCredits( + requestCount_, + r.requestFeesDetails.winningBid.transmitter + ); + + if (r.onCompleteData.length > 0) { + try + IAppGateway(r.appGateway).onRequestComplete(requestCount_, r.onCompleteData) + {} catch { + emit RequestCompletedWithErrors(requestCount_); + } + } + + emit RequestSettled(requestCount_, r.requestFeesDetails.winningBid.transmitter); + } + + /** + * @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. + * @param token_ The address of the token contract. + * @param rescueTo_ The address where rescued tokens need to be sent. + * @param amount_ The amount of tokens to be rescued. + */ + function rescueFunds(address token_, address rescueTo_, uint256 amount_) external onlyWatcher { + RescueFundsLib._rescueFunds(token_, rescueTo_, amount_); + } +} diff --git a/contracts/evmx/watcher/Trigger.sol b/contracts/evmx/watcher/Trigger.sol new file mode 100644 index 00000000..c72ff27b --- /dev/null +++ b/contracts/evmx/watcher/Trigger.sol @@ -0,0 +1,57 @@ +// SPDX-License-Identifier: GPL-3.0-only +pragma solidity ^0.8.21; + +import {LibCall} from "solady/utils/LibCall.sol"; +import "./WatcherStorage.sol"; +import {decodeAppGatewayId} from "../../utils/common/IdUtils.sol"; + +/// @title Trigger +/// @notice Contract that handles trigger validation and execution logic +abstract contract Trigger is WatcherStorage, AddressResolverUtil { + using LibCall for address; + + event TriggerFeesSet(uint256 triggerFees); + event TriggerFailed(bytes32 triggerId); + event TriggerSucceeded(bytes32 triggerId); + + /// @notice Sets the trigger fees + /// @param triggerFees_ The amount of fees to set + function _setTriggerFees(uint256 triggerFees_) internal { + triggerFees = triggerFees_; + emit TriggerFeesSet(triggerFees_); + } + + /// @notice Calls app gateways with the specified parameters + /// @param params_ Array of call from chain parameters + /// @dev This function calls app gateways with the specified parameters + /// @dev This function cannot be retried even if it fails + /// @dev Call can fail due to gas limit but watcher is a trusted entity + function _callAppGateways(TriggerParams memory params_) internal { + if (isAppGatewayCalled[params_.triggerId]) revert AppGatewayAlreadyCalled(); + + address appGateway = decodeAppGatewayId(params_.appGatewayId); + if (!configurations__.isValidPlug(appGateway, params_.chainSlug, params_.plug)) + revert InvalidCallerTriggered(); + + feesManager__().transferCredits(appGateway, address(this), triggerFees); + + triggerFromChainSlug = params_.chainSlug; + triggerFromPlug = params_.plug; + isAppGatewayCalled[params_.triggerId] = true; + (bool success, , ) = appGateway.tryCall( + 0, + gasleft(), + 0, // setting max_copy_bytes to 0 as not using returnData right now + params_.payload + ); + + if (!success) { + emit TriggerFailed(params_.triggerId); + } else { + emit TriggerSucceeded(params_.triggerId); + } + + triggerFromChainSlug = 0; + triggerFromPlug = address(0); + } +} diff --git a/contracts/evmx/watcher/Watcher.sol b/contracts/evmx/watcher/Watcher.sol new file mode 100644 index 00000000..7dbaa28b --- /dev/null +++ b/contracts/evmx/watcher/Watcher.sol @@ -0,0 +1,252 @@ +// SPDX-License-Identifier: GPL-3.0-only +pragma solidity ^0.8.21; + +import "./Trigger.sol"; +import "../interfaces/IPromise.sol"; + +contract Watcher is Trigger { + constructor() { + _disableInitializers(); // disable for implementation + } + + function initialize( + uint32 evmxSlug_, + uint256 triggerFees_, + address owner_, + address addressResolver_ + ) public reinitializer(1) { + evmxSlug = evmxSlug_; + triggerFees = triggerFees_; + _initializeOwner(owner_); + _setAddressResolver(addressResolver_); + } + + function setCoreContracts( + address requestHandler_, + address configManager_, + address promiseResolver_ + ) external onlyOwner { + requestHandler__ = IRequestHandler(requestHandler_); + configurations__ = IConfigurations(configManager_); + promiseResolver__ = IPromiseResolver(promiseResolver_); + } + + function isWatcher(address account_) public view override returns (bool) { + return + account_ == address(requestHandler__) || + account_ == address(configurations__) || + account_ == address(promiseResolver__); + } + + // can be called to submit single payload request without any callback + function queueAndSubmit( + QueueParams memory queue_, + uint256 maxFees, + address auctionManager, + address consumeFrom, + bytes memory onCompleteData + ) external returns (uint40 requestCount, address[] memory promises) { + _queue(queue_, msg.sender); + return _submitRequest(maxFees, auctionManager, consumeFrom, onCompleteData); + } + + /// @notice Queues a new payload + /// @param queue_ The call parameters + function queue( + QueueParams memory queue_, + address appGateway_ + ) external returns (address, uint40) { + return _queue(queue_, appGateway_); + } + + function _queue( + QueueParams memory queue_, + address appGateway_ + ) internal returns (address, uint40) { + // checks if app gateway passed by forwarder is coming from same core app gateway group + if (appGatewayTemp != address(0)) + if (appGatewayTemp != appGateway_ || appGateway_ == address(0)) + revert InvalidAppGateway(); + + uint40 requestCount = getCurrentRequestCount(); + // Deploy a new async promise contract. + latestAsyncPromise = asyncDeployer__().deployAsyncPromiseContract( + appGateway_, + requestCount + ); + appGatewayTemp = appGateway_; + queue_.asyncPromise = latestAsyncPromise; + + // Add the promise to the queue. + payloadQueue.push(queue_); + // return the promise and request count + return (latestAsyncPromise, requestCount); + } + + function submitRequest( + uint256 maxFees, + address auctionManager, + address consumeFrom, + bytes memory onCompleteData + ) external returns (uint40, address[] memory) { + return _submitRequest(maxFees, auctionManager, consumeFrom, onCompleteData); + } + + function _submitRequest( + uint256 maxFees, + address auctionManager, + address consumeFrom, + bytes memory onCompleteData + ) internal returns (uint40 requestCount, address[] memory promiseList) { + if (payloadQueue.length == 0) return (0, new address[](0)); + address appGateway = msg.sender; + + // this check is to verify that msg.sender (app gateway base) belongs to correct app gateway + if (appGateway != appGatewayTemp) revert InvalidAppGateway(); + latestAsyncPromise = address(0); + appGatewayTemp = address(0); + + (requestCount, promiseList) = requestHandler__.submitRequest( + maxFees, + auctionManager, + consumeFrom, + appGateway, + payloadQueue, + onCompleteData + ); + + clearQueue(); + } + + /// @notice Clears the call parameters array + function clearQueue() public { + delete payloadQueue; + } + + function callAppGateways(WatcherMultiCallParams memory params_) external { + _validateSignature(address(this), params_.data, params_.nonce, params_.signature); + TriggerParams[] memory params = abi.decode(params_.data, (TriggerParams[])); + + for (uint40 i = 0; i < params.length; i++) { + _callAppGateways(params[i]); + } + } + + function setTriggerFees( + uint256 triggerFees_, + uint256 nonce_, + bytes memory signature_ + ) external { + _validateSignature(address(this), abi.encode(triggerFees_), nonce_, signature_); + _setTriggerFees(triggerFees_); + } + + function getCurrentRequestCount() public view returns (uint40) { + return requestHandler__.nextRequestCount(); + } + + function getRequestParams(uint40 requestCount_) external view returns (RequestParams memory) { + return requestHandler__.getRequest(requestCount_); + } + + function getPayloadParams(bytes32 payloadId_) external view returns (PayloadParams memory) { + return requestHandler__.getPayload(payloadId_); + } + + function setIsValidPlug(bool isValid_, uint32 chainSlug_, address plug_) external override { + configurations__.setIsValidPlug(isValid_, chainSlug_, plug_, msg.sender); + } + + function cancelRequest(uint40 requestCount_) external override { + requestHandler__.cancelRequest(requestCount_, msg.sender); + } + + function increaseFees(uint40 requestCount_, uint256 newFees_) external override { + requestHandler__.increaseFees(requestCount_, newFees_, msg.sender); + } + + function getPrecompileFees( + bytes4 precompile_, + bytes memory precompileData_ + ) external view returns (uint256) { + return requestHandler__.getPrecompileFees(precompile_, precompileData_); + } + + // all function from watcher requiring signature + // can be also used to do msg.sender check related function in other contracts like withdraw credits from fees manager and set core app-gateways in configurations + function watcherMultiCall(WatcherMultiCallParams[] memory params_) external payable { + for (uint40 i = 0; i < params_.length; i++) { + _validateSignature( + params_[i].contractAddress, + params_[i].data, + params_[i].nonce, + params_[i].signature + ); + + // call the contract + (bool success, ) = params_[i].contractAddress.call(params_[i].data); + if (!success) revert CallFailed(); + } + } + + /// @notice Verifies that a watcher signature is valid + /// @param data_ The data to verify + /// @param nonce_ The nonce of the signature + /// @param signature_ The signature to verify + function _validateSignature( + address contractAddress_, + bytes memory data_, + uint256 nonce_, + bytes memory signature_ + ) internal { + if (contractAddress_ == address(0)) revert InvalidContract(); + if (data_.length == 0) revert InvalidData(); + if (signature_.length == 0) revert InvalidSignature(); + if (isNonceUsed[nonce_]) revert NonceUsed(); + isNonceUsed[nonce_] = true; + + bytes32 digest = keccak256( + abi.encode(address(this), evmxSlug, nonce_, contractAddress_, data_) + ); + + // check if signature is valid + if (_recoverSigner(digest, signature_) != owner()) revert InvalidSignature(); + } + + /// @notice Recovers the signer of a message + /// @param digest_ The digest of the input data + /// @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 _recoverSigner( + bytes32 digest_, + bytes memory 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_); + } + + /** + * @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. + * @param token_ The address of the token contract. + * @param rescueTo_ The address where rescued tokens need to be sent. + * @param amount_ The amount of tokens to be rescued. + */ + function rescueFunds( + address token_, + address rescueTo_, + uint256 amount_, + uint256 nonce_, + bytes memory signature_ + ) external { + _validateSignature( + address(this), + abi.encode(token_, rescueTo_, amount_), + nonce_, + signature_ + ); + RescueFundsLib._rescueFunds(token_, rescueTo_, amount_); + } +} diff --git a/contracts/evmx/watcher/WatcherBase.sol b/contracts/evmx/watcher/WatcherBase.sol new file mode 100644 index 00000000..468dcbcf --- /dev/null +++ b/contracts/evmx/watcher/WatcherBase.sol @@ -0,0 +1,55 @@ +// SPDX-License-Identifier: GPL-3.0-only +pragma solidity ^0.8.21; + +import "../interfaces/IWatcher.sol"; +import "../interfaces/IConfigurations.sol"; +import "../interfaces/IPromiseResolver.sol"; +import "../interfaces/IRequestHandler.sol"; + +/// @title WatcherBase +/// @notice Base contract for the Watcher contract +contract WatcherBase { + // slots 0 + // The address of the Watcher contract + IWatcher public watcher__; + + // Only Watcher can call functions + modifier onlyWatcher() { + require(msg.sender == address(watcher__), "Only Watcher can call"); + _; + } + + modifier onlyRequestHandler() { + require(msg.sender == address(requestHandler__()), "Only RequestHandler can call"); + _; + } + + modifier onlyPromiseResolver() { + require(msg.sender == address(promiseResolver__()), "Only PromiseResolver can call"); + _; + } + + /// @notice Sets the Watcher address + /// @param watcher_ The address of the Watcher contract + function _initializeWatcher(address watcher_) internal { + watcher__ = IWatcher(watcher_); + } + + /// @notice Returns the configurations of the Watcher contract + /// @return configurations The configurations of the Watcher contract + function configurations__() internal view returns (IConfigurations) { + return watcher__.configurations__(); + } + + /// @notice Returns the promise resolver of the Watcher contract + /// @return promiseResolver The promise resolver of the Watcher contract + function promiseResolver__() internal view returns (IPromiseResolver) { + return watcher__.promiseResolver__(); + } + + /// @notice Returns the request handler of the Watcher contract + /// @return requestHandler The request handler of the Watcher contract + function requestHandler__() internal view returns (IRequestHandler) { + return watcher__.requestHandler__(); + } +} diff --git a/contracts/evmx/watcher/WatcherStorage.sol b/contracts/evmx/watcher/WatcherStorage.sol new file mode 100644 index 00000000..9c429a5e --- /dev/null +++ b/contracts/evmx/watcher/WatcherStorage.sol @@ -0,0 +1,66 @@ +// SPDX-License-Identifier: GPL-3.0-only +pragma solidity ^0.8.21; + +import "../interfaces/IWatcher.sol"; +import "../helpers/AddressResolverUtil.sol"; +import "solady/utils/ECDSA.sol"; +import {Initializable} from "solady/utils/Initializable.sol"; +import "../../utils/RescueFundsLib.sol"; +import "solady/auth/Ownable.sol"; + +/// @title WatcherStorage +/// @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 WatcherStorage is IWatcher, Initializable, Ownable { + // slots [0-49]: gap for future storage variables + uint256[50] _gap_before; + + // slot 50 (32 + 32 + 160) + /// @notice The chain slug of the watcher precompile + uint32 public evmxSlug; + /// @notice stores temporary chainSlug of the trigger from a chain + uint32 public triggerFromChainSlug; + /// @notice stores temporary plug of the trigger from a chain + address public triggerFromPlug; + + // slot 51 + /// @notice Stores the trigger fees + uint256 public triggerFees; + + // slot 52 + IRequestHandler public override requestHandler__; + + // slot 53 + IConfigurations public override configurations__; + + // slot 54 + IPromiseResolver public override promiseResolver__; + + // slot 55 + address public latestAsyncPromise; + + // slot 56 + address public appGatewayTemp; + + // slot 57 + /// @notice The queue of payloads + QueueParams[] public payloadQueue; + + // slot 58 + /// @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 isAppGatewayCalled; + + // slot 59 + /// @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; + + // slots [60-109]: gap for future storage variables + uint256[50] _gap_after; + + // slots [110-159] 50 slots reserved for address resolver util +} diff --git a/contracts/evmx/watcher/precompiles/ReadPrecompile.sol b/contracts/evmx/watcher/precompiles/ReadPrecompile.sol new file mode 100644 index 00000000..e8c7f2e8 --- /dev/null +++ b/contracts/evmx/watcher/precompiles/ReadPrecompile.sol @@ -0,0 +1,100 @@ +// SPDX-License-Identifier: GPL-3.0-only +pragma solidity ^0.8.21; + +import "../../interfaces/IPrecompile.sol"; +import "../../../utils/common/Structs.sol"; +import "../../../utils/common/Errors.sol"; +import "../../../utils/RescueFundsLib.sol"; +import "../WatcherBase.sol"; + +/// @title Read +/// @notice Handles read precompile logic +contract ReadPrecompile is IPrecompile, WatcherBase { + /// @notice Emitted when a new read is requested + event ReadRequested(Transaction transaction, uint256 readAtBlockNumber, bytes32 payloadId); + event ExpiryTimeSet(uint256 expiryTime); + event ReadFeesSet(uint256 readFees); + + /// @notice The fees for a read and includes callback fees + uint256 public readFees; + uint256 public expiryTime; + + constructor(address watcher_, uint256 readFees_, uint256 expiryTime_) { + readFees = readFees_; + expiryTime = expiryTime_; + _initializeWatcher(watcher_); + } + + function getPrecompileFees(bytes memory) public view returns (uint256) { + return readFees; + } + + /// @notice Gets precompile data and fees for queue parameters + /// @param queueParams_ The queue parameters to process + /// @return precompileData The encoded precompile data + /// @return estimatedFees Estimated fees required for processing + function validateAndGetPrecompileData( + QueueParams calldata queueParams_, + address + ) external view returns (bytes memory precompileData, uint256 estimatedFees) { + if (queueParams_.transaction.target == address(0)) revert InvalidTarget(); + if (queueParams_.transaction.payload.length == 0) revert InvalidPayloadSize(); + + // For read precompile, encode the payload parameters + precompileData = abi.encode( + queueParams_.transaction, + queueParams_.overrideParams.readAtBlockNumber + ); + estimatedFees = getPrecompileFees(precompileData); + } + + /// @notice Handles payload processing and returns fees + /// @param payloadParams The payload parameters to handle + /// @return fees The fees required for processing + /// @return deadline The deadline for the payload + function handlePayload( + address, + PayloadParams calldata payloadParams + ) + external + onlyRequestHandler + returns (uint256 fees, uint256 deadline, bytes memory precompileData) + { + deadline = block.timestamp + expiryTime; + precompileData = payloadParams.precompileData; + fees = getPrecompileFees(payloadParams.precompileData); + + (Transaction memory transaction, uint256 readAtBlockNumber) = abi.decode( + payloadParams.precompileData, + (Transaction, uint256) + ); + emit ReadRequested(transaction, readAtBlockNumber, payloadParams.payloadId); + } + + function resolvePayload(PayloadParams calldata payloadParams_) external onlyRequestHandler {} + + function setFees(uint256 readFees_) external onlyWatcher { + readFees = readFees_; + emit ReadFeesSet(readFees_); + } + + /// @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 onlyWatcher { + expiryTime = expiryTime_; + emit ExpiryTimeSet(expiryTime_); + } + + /** + * @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. + * @param token_ The address of the token contract. + * @param rescueTo_ The address where rescued tokens need to be sent. + * @param amount_ The amount of tokens to be rescued. + */ + function rescueFunds(address token_, address rescueTo_, uint256 amount_) external onlyWatcher { + RescueFundsLib._rescueFunds(token_, rescueTo_, amount_); + } +} diff --git a/contracts/evmx/watcher/precompiles/SchedulePrecompile.sol b/contracts/evmx/watcher/precompiles/SchedulePrecompile.sol new file mode 100644 index 00000000..597928c7 --- /dev/null +++ b/contracts/evmx/watcher/precompiles/SchedulePrecompile.sol @@ -0,0 +1,152 @@ +// SPDX-License-Identifier: GPL-3.0-only +pragma solidity ^0.8.21; + +import "../../interfaces/IPrecompile.sol"; +import "../../../utils/common/Structs.sol"; +import {InvalidScheduleDelay, ResolvingScheduleTooEarly} from "../../../utils/common/Errors.sol"; +import "../../../utils/RescueFundsLib.sol"; +import "../WatcherBase.sol"; + +/// @title SchedulePrecompile +/// @notice Library that handles schedule logic for the WatcherPrecompile system +/// @dev This library contains pure functions for schedule operations +contract SchedulePrecompile is IPrecompile, WatcherBase { + // slot 52 + /// @notice The maximum delay for a schedule + /// @dev Maximum schedule delay in seconds + uint256 public maxScheduleDelayInSeconds; + + /// @notice The fees per second for a schedule + uint256 public scheduleFeesPerSecond; + /// @notice The callback fees for a schedule + uint256 public scheduleCallbackFees; + /// @notice The expiry time for a schedule + uint256 public expiryTime; + + /// @notice Emitted when the maximum schedule delay in seconds is set + event MaxScheduleDelayInSecondsSet(uint256 maxScheduleDelayInSeconds_); + /// @notice Emitted when the fees per second for a schedule is set + event ScheduleFeesPerSecondSet(uint256 scheduleFeesPerSecond_); + /// @notice Emitted when the callback fees for a schedule is set + event ScheduleCallbackFeesSet(uint256 scheduleCallbackFees_); + /// @notice Emitted when the expiry time for a schedule is set + event ExpiryTimeSet(uint256 expiryTime_); + /// @notice Emitted when a schedule is requested + event ScheduleRequested(bytes32 payloadId, uint256 executeAfter, uint256 deadline); + /// @notice Emitted when a schedule is resolved + event ScheduleResolved(bytes32 payloadId); + + constructor( + address watcher_, + uint256 maxScheduleDelayInSeconds_, + uint256 scheduleFeesPerSecond_, + uint256 scheduleCallbackFees_, + uint256 expiryTime_ + ) { + maxScheduleDelayInSeconds = maxScheduleDelayInSeconds_; + scheduleFeesPerSecond = scheduleFeesPerSecond_; + scheduleCallbackFees = scheduleCallbackFees_; + + if (maxScheduleDelayInSeconds < expiryTime) revert InvalidScheduleDelay(); + expiryTime = expiryTime_; + _initializeWatcher(watcher_); + } + + function getPrecompileFees(bytes memory precompileData_) public view returns (uint256) { + uint256 delayInSeconds = abi.decode(precompileData_, (uint256)); + return scheduleFeesPerSecond * delayInSeconds + scheduleCallbackFees; + } + + /// @notice Sets the maximum schedule delay in seconds + /// @param maxScheduleDelayInSeconds_ The maximum schedule delay in seconds + /// @dev This function sets the maximum schedule delay in seconds + /// @dev Only callable by the contract owner + function setMaxScheduleDelayInSeconds(uint256 maxScheduleDelayInSeconds_) external onlyWatcher { + if (maxScheduleDelayInSeconds < expiryTime) revert InvalidScheduleDelay(); + maxScheduleDelayInSeconds = maxScheduleDelayInSeconds_; + emit MaxScheduleDelayInSecondsSet(maxScheduleDelayInSeconds_); + } + + /// @notice Sets the fees per second for a schedule + /// @param scheduleFeesPerSecond_ The fees per second for a schedule + /// @dev This function sets the fees per second for a schedule + /// @dev Only callable by the contract owner + function setScheduleFeesPerSecond(uint256 scheduleFeesPerSecond_) external onlyWatcher { + scheduleFeesPerSecond = scheduleFeesPerSecond_; + emit ScheduleFeesPerSecondSet(scheduleFeesPerSecond_); + } + + /// @notice Sets the callback fees for a schedule + /// @param scheduleCallbackFees_ The callback fees for a schedule + /// @dev This function sets the callback fees for a schedule + /// @dev Only callable by the contract owner + function setScheduleCallbackFees(uint256 scheduleCallbackFees_) external onlyWatcher { + scheduleCallbackFees = scheduleCallbackFees_; + emit ScheduleCallbackFeesSet(scheduleCallbackFees_); + } + + /// @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 onlyWatcher { + if (maxScheduleDelayInSeconds < expiryTime) revert InvalidScheduleDelay(); + expiryTime = expiryTime_; + emit ExpiryTimeSet(expiryTime_); + } + + /// @notice Validates schedule parameters and return data with fees + /// @dev assuming that tx is executed on EVMx chain + function validateAndGetPrecompileData( + QueueParams calldata queueParams_, + address + ) external view returns (bytes memory precompileData, uint256 estimatedFees) { + if (queueParams_.overrideParams.delayInSeconds > maxScheduleDelayInSeconds) + revert InvalidScheduleDelay(); + + // For schedule precompile, encode the payload parameters + precompileData = abi.encode(queueParams_.overrideParams.delayInSeconds, 0); + estimatedFees = getPrecompileFees(precompileData); + } + + /// @notice Handles payload processing and returns fees + /// @param payloadParams The payload parameters to handle + /// @return fees The fees required for processing + function handlePayload( + address, + PayloadParams calldata payloadParams + ) + external + onlyRequestHandler + returns (uint256 fees, uint256 deadline, bytes memory precompileData) + { + (uint256 delayInSeconds, ) = abi.decode(payloadParams.precompileData, (uint256, uint256)); + + // expiryTime is very low, to account for infra delay + uint256 executeAfter = block.timestamp + delayInSeconds; + deadline = executeAfter + expiryTime; + precompileData = abi.encode(delayInSeconds, executeAfter); + fees = getPrecompileFees(precompileData); + + // emits event for watcher to track schedule and resolve when deadline is reached + emit ScheduleRequested(payloadParams.payloadId, executeAfter, deadline); + } + + function resolvePayload(PayloadParams calldata payloadParams_) external onlyRequestHandler { + (, uint256 executeAfter) = abi.decode(payloadParams_.precompileData, (uint256, uint256)); + + if (executeAfter > block.timestamp) revert ResolvingScheduleTooEarly(); + emit ScheduleResolved(payloadParams_.payloadId); + } + + /** + * @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. + * @param token_ The address of the token contract. + * @param rescueTo_ The address where rescued tokens need to be sent. + * @param amount_ The amount of tokens to be rescued. + */ + function rescueFunds(address token_, address rescueTo_, uint256 amount_) external onlyWatcher { + RescueFundsLib._rescueFunds(token_, rescueTo_, amount_); + } +} diff --git a/contracts/evmx/watcher/precompiles/WritePrecompile.sol b/contracts/evmx/watcher/precompiles/WritePrecompile.sol new file mode 100644 index 00000000..a36eaf4e --- /dev/null +++ b/contracts/evmx/watcher/precompiles/WritePrecompile.sol @@ -0,0 +1,308 @@ +// SPDX-License-Identifier: GPL-3.0-only +pragma solidity ^0.8.21; + +import "solady/utils/Initializable.sol"; +import "solady/auth/Ownable.sol"; + +import "../../interfaces/IPrecompile.sol"; +import {WRITE, PAYLOAD_SIZE_LIMIT} from "../../../utils/common/Constants.sol"; +import {InvalidIndex, MaxMsgValueLimitExceeded, InvalidPayloadSize} from "../../../utils/common/Errors.sol"; +import {encodeAppGatewayId} from "../../../utils/common/IdUtils.sol"; +import "../../../utils/RescueFundsLib.sol"; +import "../WatcherBase.sol"; + +abstract contract WritePrecompileStorage is IPrecompile { + // slots [0-49] reserved for gap + uint256[50] _gap_before; + + // slot 50 + /// @notice The fees for a write and includes callback fees + uint256 public writeFees; + + // slot 51 + uint256 public expiryTime; + + // slot 52 + /// @notice Mapping to store watcher proofs + /// @dev Maps payload ID to proof bytes + /// @dev payloadId => proof bytes + mapping(bytes32 => bytes) public watcherProofs; + + // slot 53 + /// @notice The maximum message value limit for a chain + mapping(uint32 => uint256) public chainMaxMsgValueLimit; + + // slot 54 + /// @notice The digest hash for a payload + mapping(bytes32 => bytes32) public digestHashes; + + // slot 55 + mapping(uint32 => address) public contractFactoryPlugs; + + // slots [56-105] reserved for gap + uint256[50] _gap_after; + + // 1 slot reserved for watcher base +} + +/// @title WritePrecompile +/// @notice Handles write precompile logic +contract WritePrecompile is WritePrecompileStorage, Initializable, Ownable, WatcherBase { + /// @notice Emitted when fees are set + event FeesSet(uint256 writeFees); + event ChainMaxMsgValueLimitsUpdated(uint32 chainSlug, uint256 maxMsgValueLimit); + event ContractFactoryPlugSet(uint32 chainSlug, address contractFactoryPlug); + /// @notice Emitted when a proof upload request is made + event WriteProofRequested( + address transmitter, + bytes32 digest, + bytes32 prevBatchDigestHash, + uint256 deadline, + PayloadParams payloadParams + ); + + /// @notice Emitted when a proof is uploaded + /// @param payloadId The unique identifier for the request + /// @param proof The proof from the watcher + event WriteProofUploaded(bytes32 indexed payloadId, bytes proof); + event ExpiryTimeSet(uint256 expiryTime); + + constructor() { + _disableInitializers(); // disable for implementation + } + + function initialize( + address owner_, + address watcher_, + uint256 writeFees_, + uint256 expiryTime_ + ) external reinitializer(1) { + writeFees = writeFees_; + expiryTime = expiryTime_; + _initializeOwner(owner_); + _initializeWatcher(watcher_); + } + + function getPrecompileFees(bytes memory) public view returns (uint256) { + return writeFees; + } + + /// @notice Gets precompile data and fees for queue parameters + /// @param queueParams_ The queue parameters to process + /// @return precompileData The encoded precompile data + /// @return estimatedFees Estimated fees required for processing + function validateAndGetPrecompileData( + QueueParams memory queueParams_, + address appGateway_ + ) external view override returns (bytes memory precompileData, uint256 estimatedFees) { + if ( + queueParams_.overrideParams.value > + chainMaxMsgValueLimit[queueParams_.transaction.chainSlug] + ) revert MaxMsgValueLimitExceeded(); + + if ( + queueParams_.transaction.payload.length == 0 || + queueParams_.transaction.payload.length > PAYLOAD_SIZE_LIMIT + ) { + revert InvalidPayloadSize(); + } + + if (queueParams_.transaction.target == address(0)) { + queueParams_.transaction.target = contractFactoryPlugs[ + queueParams_.transaction.chainSlug + ]; + appGateway_ = address(this); + } else { + configurations__().verifyConnections( + queueParams_.transaction.chainSlug, + queueParams_.transaction.target, + appGateway_, + queueParams_.switchboardType + ); + } + + // todo: can be changed to set the default gas limit for each chain + if (queueParams_.overrideParams.gasLimit == 0) { + queueParams_.overrideParams.gasLimit = 10000000; + } + + // For write precompile, encode the payload parameters + precompileData = abi.encode( + appGateway_, + queueParams_.transaction, + queueParams_.overrideParams.writeFinality, + queueParams_.overrideParams.gasLimit, + queueParams_.overrideParams.value, + configurations__().switchboards( + queueParams_.transaction.chainSlug, + queueParams_.switchboardType + ) + ); + + estimatedFees = getPrecompileFees(precompileData); + } + + /// @notice Handles payload processing and returns fees + /// @param payloadParams The payload parameters to handle + /// @return fees The fees required for processing + /// @return deadline The deadline for the payload + function handlePayload( + address transmitter_, + PayloadParams memory payloadParams + ) + external + onlyRequestHandler + returns (uint256 fees, uint256 deadline, bytes memory precompileData) + { + ( + address appGateway, + Transaction memory transaction, + , + uint256 gasLimit, + uint256 value, + + ) = abi.decode( + payloadParams.precompileData, + (address, Transaction, WriteFinality, uint256, uint256, address) + ); + + precompileData = payloadParams.precompileData; + deadline = block.timestamp + expiryTime; + fees = getPrecompileFees(payloadParams.precompileData); + + bytes32 prevBatchDigestHash = getPrevBatchDigestHash( + payloadParams.requestCount, + payloadParams.batchCount + ); + + // create digest + DigestParams memory digestParams_ = DigestParams( + configurations__().sockets(transaction.chainSlug), + transmitter_, + payloadParams.payloadId, + deadline, + payloadParams.callType, + gasLimit, + value, + transaction.payload, + transaction.target, + encodeAppGatewayId(appGateway), + prevBatchDigestHash, + bytes("") + ); + + // Calculate and store digest from payload parameters + bytes32 digest = getDigest(digestParams_); + digestHashes[payloadParams.payloadId] = digest; + + emit WriteProofRequested( + transmitter_, + digest, + prevBatchDigestHash, + deadline, + payloadParams + ); + } + + function getPrevBatchDigestHash( + uint40 requestCount_, + uint40 batchCount_ + ) public view returns (bytes32) { + if (batchCount_ == 0) return bytes32(0); + + // if first batch, return bytes32(0) + uint40[] memory requestBatchIds = requestHandler__().getRequestBatchIds(requestCount_); + if (requestBatchIds[0] == batchCount_) return bytes32(0); + + uint40 prevBatchCount = batchCount_ - 1; + + bytes32[] memory payloadIds = requestHandler__().getBatchPayloadIds(prevBatchCount); + bytes32 prevBatchDigestHash = bytes32(0); + for (uint40 i = 0; i < payloadIds.length; i++) { + prevBatchDigestHash = keccak256( + abi.encodePacked(prevBatchDigestHash, digestHashes[payloadIds[i]]) + ); + } + return prevBatchDigestHash; + } + + /// @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_.prevBatchDigestHash, + params_.extraData + ) + ); + } + + /// @notice Marks a write request with a proof on digest + /// @param payloadId_ The unique identifier of the request + /// @param proof_ The watcher's proof + function uploadProof(bytes32 payloadId_, bytes memory proof_) public onlyWatcher { + watcherProofs[payloadId_] = proof_; + emit WriteProofUploaded(payloadId_, proof_); + } + + /// @notice Updates the maximum message value limit for multiple chains + /// @param chainSlug_ The chain identifier + /// @param maxMsgValueLimit_ The maximum message value limit + function updateChainMaxMsgValueLimits( + uint32 chainSlug_, + uint256 maxMsgValueLimit_ + ) external onlyOwner { + chainMaxMsgValueLimit[chainSlug_] = maxMsgValueLimit_; + emit ChainMaxMsgValueLimitsUpdated(chainSlug_, maxMsgValueLimit_); + } + + function setFees(uint256 writeFees_) external onlyWatcher { + writeFees = writeFees_; + emit FeesSet(writeFees_); + } + + function setContractFactoryPlugs( + uint32 chainSlug_, + address contractFactoryPlug_ + ) external onlyOwner { + contractFactoryPlugs[chainSlug_] = contractFactoryPlug_; + emit ContractFactoryPlugSet(chainSlug_, contractFactoryPlug_); + } + + /// @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 onlyWatcher { + expiryTime = expiryTime_; + emit ExpiryTimeSet(expiryTime_); + } + + function resolvePayload( + PayloadParams calldata payloadParams_ + ) external override onlyRequestHandler {} + + /** + * @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. + * @param token_ The address of the token contract. + * @param rescueTo_ The address where rescued tokens need to be sent. + * @param amount_ The amount of tokens to be rescued. + */ + function rescueFunds(address token_, address rescueTo_, uint256 amount_) external onlyWatcher { + RescueFundsLib._rescueFunds(token_, rescueTo_, amount_); + } +} diff --git a/contracts/evmx/watcherPrecompile/PayloadHeaderDecoder.sol b/contracts/evmx/watcherPrecompile/PayloadHeaderDecoder.sol deleted file mode 100644 index 22879a1c..00000000 --- a/contracts/evmx/watcherPrecompile/PayloadHeaderDecoder.sol +++ /dev/null @@ -1,166 +0,0 @@ -// SPDX-License-Identifier: GPL-3.0-only -pragma solidity ^0.8.21; - -import {CallType, Parallel, WriteFinality} from "../../utils/common/Structs.sol"; - -library PayloadHeaderDecoder { - // Corrected mapping (most significant bits on the left): - // [256.....................................................................80][79.............................................0] - // ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ - // requestCount(40) | batchCount(40) | payloadCount(40) | chainSlug(32) | callType(8) | isParallel(8) | writeFinality(8) - // - // Bits: - // requestCount: [216..255] (shift >> 216) - // batchCount: [176..215] (shift >> 176, mask 0xFFFFFFFFFF) - // payloadCount: [136..175] - // chainSlug: [104..135] - // callType: [96..103] - // isParallel: [88..95] - // writeFinality: [80..87] - - // ------------------------------------------------------------------------- - // GETTERS - // ------------------------------------------------------------------------- - function getRequestCount(bytes32 payloadHeader_) internal pure returns (uint40) { - // Top 40 bits => shift right by 216 - return uint40(uint256(payloadHeader_) >> 216); - } - - function getBatchCount(bytes32 payloadHeader_) internal pure returns (uint40) { - return uint40((uint256(payloadHeader_) >> 176) & 0xFFFFFFFFFF); - } - - function getPayloadCount(bytes32 payloadHeader_) internal pure returns (uint40) { - return uint40((uint256(payloadHeader_) >> 136) & 0xFFFFFFFFFF); - } - - function getChainSlug(bytes32 payloadHeader_) internal pure returns (uint32) { - return uint32((uint256(payloadHeader_) >> 104) & 0xFFFFFFFF); - } - - function getCallType(bytes32 payloadHeader_) internal pure returns (CallType) { - return CallType(uint8((uint256(payloadHeader_) >> 96) & 0xFF)); - } - - function getIsParallel(bytes32 payloadHeader_) internal pure returns (Parallel) { - return Parallel(uint8((uint256(payloadHeader_) >> 88) & 0xFF)); - } - - function getWriteFinality(bytes32 payloadHeader_) internal pure returns (WriteFinality) { - return WriteFinality(uint8((uint256(payloadHeader_) >> 80) & 0xFF)); - } - - // ------------------------------------------------------------------------- - // SETTERS - // ------------------------------------------------------------------------- - - /// @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(payloadHeader_) & ~((uint256(0xFFFFFFFFFF)) << 216)) | - (uint256(requestCount_) << 216) - ); - } - - /// @notice Sets the batch count in a payloadHeader [176..215] - function setBatchCount( - bytes32 payloadHeader_, - uint40 batchCount_ - ) internal pure returns (bytes32) { - return - bytes32( - (uint256(payloadHeader_) & ~((uint256(0xFFFFFFFFFF)) << 176)) | - ((uint256(batchCount_) & 0xFFFFFFFFFF) << 176) - ); - } - - /// @notice Sets the payload count [136..175] - function setPayloadCount( - bytes32 payloadHeader_, - uint40 payloadCount_ - ) internal pure returns (bytes32) { - return - bytes32( - (uint256(payloadHeader_) & ~((uint256(0xFFFFFFFFFF)) << 136)) | - ((uint256(payloadCount_) & 0xFFFFFFFFFF) << 136) - ); - } - - /// @notice Sets the chain slug [104..135] - function setChainSlug( - bytes32 payloadHeader_, - uint32 chainSlug_ - ) internal pure returns (bytes32) { - return - bytes32( - (uint256(payloadHeader_) & ~((uint256(0xFFFFFFFF)) << 104)) | - ((uint256(chainSlug_) & 0xFFFFFFFF) << 104) - ); - } - - /// @notice Sets the call type [96..103] - function setCallType( - bytes32 payloadHeader_, - CallType callType_ - ) internal pure returns (bytes32) { - return - bytes32( - (uint256(payloadHeader_) & ~((uint256(0xFF)) << 96)) | - ((uint256(uint8(callType_)) & 0xFF) << 96) - ); - } - - /// @notice Sets the parallel flag [88..95] - function setIsParallel( - bytes32 payloadHeader_, - Parallel isParallel_ - ) internal pure returns (bytes32) { - return - bytes32( - (uint256(payloadHeader_) & ~((uint256(0xFF)) << 88)) | - ((uint256(uint8(isParallel_)) & 0xFF) << 88) - ); - } - - /// @notice Sets the write finality [80..87] - function setWriteFinality( - bytes32 payloadHeader_, - WriteFinality writeFinality_ - ) internal pure returns (bytes32) { - return - bytes32( - (uint256(payloadHeader_) & ~((uint256(0xFF)) << 80)) | - ((uint256(uint8(writeFinality_)) & 0xFF) << 80) - ); - } - - // ------------------------------------------------------------------------- - // CREATE - // ------------------------------------------------------------------------- - /// @notice Creates a new payloadHeader with all fields set - function createPayloadHeader( - uint40 requestCount_, - uint40 batchCount_, - uint40 payloadCount_, - uint32 chainSlug_, - CallType callType_, - Parallel isParallel_, - WriteFinality writeFinality_ - ) internal pure returns (bytes32) { - return - bytes32( - (uint256(requestCount_) << 216) | - ((uint256(batchCount_) & 0xFFFFFFFFFF) << 176) | - ((uint256(payloadCount_) & 0xFFFFFFFFFF) << 136) | - ((uint256(chainSlug_) & 0xFFFFFFFF) << 104) | - ((uint256(uint8(callType_)) & 0xFF) << 96) | - ((uint256(uint8(isParallel_)) & 0xFF) << 88) | - ((uint256(uint8(writeFinality_)) & 0xFF) << 80) - ); - } -} diff --git a/contracts/evmx/watcherPrecompile/WatcherPrecompileLimits.sol b/contracts/evmx/watcherPrecompile/WatcherPrecompileLimits.sol deleted file mode 100644 index 850feb16..00000000 --- a/contracts/evmx/watcherPrecompile/WatcherPrecompileLimits.sol +++ /dev/null @@ -1,81 +0,0 @@ -// SPDX-License-Identifier: GPL-3.0-only -pragma solidity ^0.8.21; - -import "solady/utils/Initializable.sol"; -import {Ownable} from "solady/auth/Ownable.sol"; -import {AddressResolverUtil} from "../AddressResolverUtil.sol"; -import "../interfaces/IWatcherPrecompileLimits.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, - Ownable, - AddressResolverUtil -{ - // 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 157: fees - uint256 public queryFees; - uint256 public finalizeFees; - uint256 public timeoutFees; - uint256 public callBackFees; - - /// @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_, address addressResolver_, uint256) public reinitializer(1) { - _setAddressResolver(addressResolver_); - _initializeOwner(owner_); - } - - 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/evmx/watcherPrecompile/core/RequestHandler.sol b/contracts/evmx/watcherPrecompile/core/RequestHandler.sol deleted file mode 100644 index d6f620eb..00000000 --- a/contracts/evmx/watcherPrecompile/core/RequestHandler.sol +++ /dev/null @@ -1,180 +0,0 @@ -// 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/evmx/watcherPrecompile/core/WatcherIdUtils.sol b/contracts/evmx/watcherPrecompile/core/WatcherIdUtils.sol deleted file mode 100644 index 1d2ed762..00000000 --- a/contracts/evmx/watcherPrecompile/core/WatcherIdUtils.sol +++ /dev/null @@ -1,32 +0,0 @@ -// 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/evmx/watcherPrecompile/core/WatcherPrecompile.sol b/contracts/evmx/watcherPrecompile/core/WatcherPrecompile.sol deleted file mode 100644 index 878ef213..00000000 --- a/contracts/evmx/watcherPrecompile/core/WatcherPrecompile.sol +++ /dev/null @@ -1,375 +0,0 @@ -// 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/evmx/watcherPrecompile/core/WatcherPrecompileCore.sol b/contracts/evmx/watcherPrecompile/core/WatcherPrecompileCore.sol deleted file mode 100644 index 9da6d736..00000000 --- a/contracts/evmx/watcherPrecompile/core/WatcherPrecompileCore.sol +++ /dev/null @@ -1,256 +0,0 @@ -// SPDX-License-Identifier: GPL-3.0-only -pragma solidity ^0.8.21; - -import {ECDSA} from "solady/utils/ECDSA.sol"; -import {Ownable} from "solady/auth/Ownable.sol"; - -import "solady/utils/Initializable.sol"; -import {AddressResolverUtil} from "../../AddressResolverUtil.sol"; -import {IFeesManager} from "../../interfaces/IFeesManager.sol"; -import "./WatcherIdUtils.sol"; -import "./WatcherPrecompileStorage.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/evmx/watcherPrecompile/core/WatcherPrecompileStorage.sol b/contracts/evmx/watcherPrecompile/core/WatcherPrecompileStorage.sol deleted file mode 100644 index 2ef880e3..00000000 --- a/contracts/evmx/watcherPrecompile/core/WatcherPrecompileStorage.sol +++ /dev/null @@ -1,111 +0,0 @@ -// SPDX-License-Identifier: GPL-3.0-only -pragma solidity ^0.8.21; - -import "../PayloadHeaderDecoder.sol"; -import "../../interfaces/IWatcherPrecompile.sol"; -import {IAppGateway} from "../../interfaces/IAppGateway.sol"; -import {IPromise} from "../../interfaces/IPromise.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, RequestMetadata} 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/contracts/protocol/Socket.sol b/contracts/protocol/Socket.sol index 2b39f518..09e06157 100644 --- a/contracts/protocol/Socket.sol +++ b/contracts/protocol/Socket.sol @@ -1,8 +1,9 @@ // SPDX-License-Identifier: GPL-3.0-only pragma solidity ^0.8.21; -import {LibCall} from "solady/utils/LibCall.sol"; import "./SocketUtils.sol"; +import {WRITE} from "../utils/common/Constants.sol"; +import {createPayloadId} from "../utils/common/IdUtils.sol"; /** * @title Socket @@ -36,22 +37,10 @@ contract Socket is SocketUtils { * @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 @@ -74,8 +63,9 @@ contract Socket is SocketUtils { ) 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(); + if (executeParams_.callType != WRITE) revert InvalidCallType(); PlugConfig memory plugConfig = _plugConfigs[executeParams_.target]; // check if the plug is disconnected @@ -84,7 +74,13 @@ contract Socket is SocketUtils { if (msg.value < executeParams_.value + transmissionParams_.socketFees) revert InsufficientMsgValue(); - bytes32 payloadId = _createPayloadId(plugConfig.switchboard, executeParams_); + bytes32 payloadId = createPayloadId( + executeParams_.requestCount, + executeParams_.batchCount, + executeParams_.payloadCount, + plugConfig.switchboard, + chainSlug + ); // validate the execution status _validateExecutionStatus(payloadId); @@ -168,6 +164,8 @@ contract Socket is SocketUtils { return (success, returnData); } + /// @notice Validates the execution status of a payload + /// @dev This function can be retried till execution status is executed function _validateExecutionStatus(bytes32 payloadId_) internal { if (payloadExecuted[payloadId_] == ExecutionStatus.Executed) revert PayloadAlreadyExecuted(payloadExecuted[payloadId_]); diff --git a/contracts/protocol/SocketBatcher.sol b/contracts/protocol/SocketBatcher.sol index e8313f08..1013af2f 100644 --- a/contracts/protocol/SocketBatcher.sol +++ b/contracts/protocol/SocketBatcher.sol @@ -49,7 +49,7 @@ contract SocketBatcher is ISocketBatcher, Ownable { TransmissionParams({ transmitterSignature: transmitterSignature_, socketFees: 0, - extraData: "", + extraData: executeParams_.extraData, refundAddress: refundAddress_ }) ); diff --git a/contracts/protocol/SocketConfig.sol b/contracts/protocol/SocketConfig.sol index e3fb0194..22528f5d 100644 --- a/contracts/protocol/SocketConfig.sol +++ b/contracts/protocol/SocketConfig.sol @@ -7,8 +7,8 @@ import {IPlug} from "./interfaces/IPlug.sol"; import "./interfaces/ISocketFeeManager.sol"; import "../utils/AccessControl.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 {PlugConfig, SwitchboardStatus, ExecutionStatus} from "../utils/common/Structs.sol"; +import "../utils/common/Errors.sol"; import {MAX_COPY_BYTES} from "../utils/common/Constants.sol"; /** @@ -30,10 +30,6 @@ abstract contract SocketConfig is ISocket, AccessControl { // @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 diff --git a/contracts/protocol/SocketUtils.sol b/contracts/protocol/SocketUtils.sol index ccd8db5d..4f96ec81 100644 --- a/contracts/protocol/SocketUtils.sol +++ b/contracts/protocol/SocketUtils.sol @@ -4,26 +4,45 @@ pragma solidity ^0.8.21; import {ECDSA} from "solady/utils/ECDSA.sol"; import "../utils/RescueFundsLib.sol"; import "./SocketConfig.sol"; +import {LibCall} from "solady/utils/LibCall.sol"; /** * @title SocketUtils * @notice Utility functions for socket */ abstract contract SocketUtils is SocketConfig { + using LibCall for address; + //////////////////////////////////////////////////////////// ////////////////////// State Vars ////////////////////////// //////////////////////////////////////////////////////////// + struct SimulateParams { + address target; + uint256 value; + uint256 gasLimit; + bytes payload; + } + + address public constant OFF_CHAIN_CALLER = address(0xDEAD); + + // Prefix for trigger ID containing chain slug and address bits + uint256 private immutable triggerPrefix; // Version string for this socket instance 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; - // @notice counter for trigger id uint64 public triggerCounter; + error OnlyOffChain(); + error SimulationFailed(); + + modifier onlyOffChain() { + if (msg.sender != OFF_CHAIN_CALLER) revert OnlyOffChain(); + _; + } + /* * @notice constructor for creating a new Socket contract instance. * @param chainSlug_ The unique identifier of the chain this socket is deployed on. @@ -65,33 +84,12 @@ abstract contract SocketUtils is SocketConfig { executeParams_.payload, executeParams_.target, appGatewayId_, - executeParams_.prevDigestsHash, + executeParams_.prevBatchDigestHash, executeParams_.extraData ) ); } - /** - * @notice creates the payload ID - * @param switchboard_ The address of the switchboard - * @param executeParams_ The parameters of the payload - */ - function _createPayloadId( - address switchboard_, - ExecuteParams calldata executeParams_ - ) internal view returns (bytes32) { - return - keccak256( - abi.encode( - executeParams_.requestCount, - executeParams_.batchCount, - executeParams_.payloadCount, - chainSlug, - switchboard_ - ) - ); - } - /** * @notice recovers the signer from the signature * @param digest_ The digest of the payload @@ -115,6 +113,27 @@ abstract contract SocketUtils is SocketConfig { return bytes32(triggerPrefix | triggerCounter++); } + struct SimulationResult { + bool success; + bytes returnData; + bool exceededMaxCopy; + } + + function simulate( + SimulateParams[] calldata params + ) external payable onlyOffChain returns (SimulationResult[] memory) { + SimulationResult[] memory results = new SimulationResult[](params.length); + + for (uint256 i = 0; i < params.length; i++) { + (bool success, bool exceededMaxCopy, bytes memory returnData) = params[i] + .target + .tryCall(params[i].value, params[i].gasLimit, maxCopyBytes, params[i].payload); + results[i] = SimulationResult(success, returnData, exceededMaxCopy); + } + + return results; + } + ////////////////////////////////////////////// //////////// Rescue role actions //////////// ///////////////////////////////////////////// diff --git a/contracts/protocol/base/PlugBase.sol b/contracts/protocol/base/PlugBase.sol index 21d22417..3bd4c160 100644 --- a/contracts/protocol/base/PlugBase.sol +++ b/contracts/protocol/base/PlugBase.sol @@ -3,7 +3,7 @@ pragma solidity ^0.8.21; import {ISocket} from "../interfaces/ISocket.sol"; import {IPlug} from "../interfaces/IPlug.sol"; -import {NotSocket} from "../../utils/common/Errors.sol"; +import {NotSocket, SocketAlreadyInitialized} from "../../utils/common/Errors.sol"; /// @title PlugBase /// @notice Abstract contract for plugs @@ -14,7 +14,6 @@ abstract contract PlugBase is IPlug { uint256 public isSocketInitialized; bytes public overrides; - error SocketAlreadyInitialized(); event ConnectorPlugDisconnected(); /// @notice Modifier to ensure only the socket can call the function diff --git a/contracts/utils/AccessControl.sol b/contracts/utils/AccessControl.sol index c25f0645..29225404 100644 --- a/contracts/utils/AccessControl.sol +++ b/contracts/utils/AccessControl.sol @@ -17,7 +17,7 @@ abstract contract AccessControl is Ownable { mapping(bytes32 => mapping(address => bool)) private _permits; // slots 1-50: gap for future storage variables - uint256[50] _gap_access_control; + uint256[49] _gap_access_control; /** * @dev Emitted when a role is granted to an address. diff --git a/contracts/utils/common/AccessRoles.sol b/contracts/utils/common/AccessRoles.sol index e1406a49..1ca5ab61 100644 --- a/contracts/utils/common/AccessRoles.sol +++ b/contracts/utils/common/AccessRoles.sol @@ -12,3 +12,5 @@ bytes32 constant TRANSMITTER_ROLE = keccak256("TRANSMITTER_ROLE"); bytes32 constant WATCHER_ROLE = keccak256("WATCHER_ROLE"); // used to disable switchboard bytes32 constant SWITCHBOARD_DISABLER_ROLE = keccak256("SWITCHBOARD_DISABLER_ROLE"); +// used by fees manager to withdraw native tokens +bytes32 constant FEE_MANAGER_ROLE = keccak256("FEE_MANAGER_ROLE"); diff --git a/contracts/utils/common/Constants.sol b/contracts/utils/common/Constants.sol index 7b17f483..2afa6479 100644 --- a/contracts/utils/common/Constants.sol +++ b/contracts/utils/common/Constants.sol @@ -6,11 +6,13 @@ address constant ETH_ADDRESS = address(0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEe bytes32 constant FORWARD_CALL = keccak256("FORWARD_CALL"); bytes32 constant DISTRIBUTE_FEE = keccak256("DISTRIBUTE_FEE"); bytes32 constant DEPLOY = keccak256("DEPLOY"); -bytes32 constant QUERY = keccak256("QUERY"); -bytes32 constant FINALIZE = keccak256("FINALIZE"); -bytes32 constant SCHEDULE = keccak256("SCHEDULE"); + +bytes4 constant READ = bytes4(keccak256("READ")); +bytes4 constant WRITE = bytes4(keccak256("WRITE")); +bytes4 constant SCHEDULE = bytes4(keccak256("SCHEDULE")); + bytes32 constant CALLBACK = keccak256("CALLBACK"); bytes32 constant FAST = keccak256("FAST"); -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/utils/common/Errors.sol b/contracts/utils/common/Errors.sol index ebfe9dc6..40cfcf80 100644 --- a/contracts/utils/common/Errors.sol +++ b/contracts/utils/common/Errors.sol @@ -1,43 +1,76 @@ // SPDX-License-Identifier: GPL-3.0-only pragma solidity ^0.8.21; -error NotSocket(); error ZeroAddress(); -error TimeoutDelayTooLarge(); -error TimeoutAlreadyResolved(); -error ResolvingTimeoutTooEarly(); -error LimitReached(); -error FeesAlreadyPaid(); -error NotAuctionManager(); -error CallFailed(); +error InvalidTransmitter(); +error InvalidTokenAddress(); +error InvalidSwitchboard(); +error SocketAlreadyInitialized(); + +// Socket +error NotSocket(); error PlugNotFound(); + +// EVMx +error ResolvingScheduleTooEarly(); +error CallFailed(); error InvalidAppGateway(); error AppGatewayAlreadyCalled(); -error InvalidInboxCaller(); error InvalidCallerTriggered(); -error PromisesNotResolved(); error InvalidPromise(); -error InvalidTransmitter(); -error FeesNotSet(); -error InvalidTokenAddress(); error InvalidWatcherSignature(); error NonceUsed(); +error AsyncModifierNotSet(); +error WatcherNotSet(); +error InvalidTarget(); +error InvalidIndex(); +error InvalidChainSlug(); +error InvalidPayloadSize(); +error InvalidOnChainAddress(); +error InvalidScheduleDelay(); /// @notice Error thrown when trying to start or bid a closed auction error AuctionClosed(); -/// @notice Error thrown when trying to start an ongoing auction -error AuctionAlreadyStarted(); +/// @notice Error thrown when trying to start or bid an auction that is not open +error AuctionNotOpen(); /// @notice Error thrown if fees exceed the maximum set fees 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(); + +error InvalidAmount(); +error InsufficientCreditsAvailable(); +error InsufficientBalance(); +/// @notice Error thrown when a caller is invalid +error InvalidCaller(); + +/// @notice Error thrown when a gateway is invalid +error InvalidGateway(); +/// @notice Error thrown when a request is already cancelled +error RequestAlreadyCancelled(); +error DeadlineNotPassedForOnChainRevert(); + +error InvalidBid(); +error MaxReAuctionCountReached(); +error MaxMsgValueLimitExceeded(); +/// @notice Error thrown when an invalid address attempts to call the Watcher only function +error OnlyWatcherAllowed(); +error InvalidPrecompileData(); +error InvalidCallType(); +error NotRequestHandler(); +error NotInvoker(); +error NotPromiseResolver(); +error RequestPayloadCountLimitExceeded(); +error InsufficientFees(); +error RequestAlreadySettled(); +error NoWriteRequest(); +error AlreadyAssigned(); + +error OnlyAppGateway(); +error NewMaxFeesLowerThanCurrent(uint256 currentMaxFees, uint256 newMaxFees); +error InvalidContract(); +error InvalidData(); +error InvalidSignature(); + +error DeadlinePassed(); diff --git a/contracts/utils/common/IdUtils.sol b/contracts/utils/common/IdUtils.sol new file mode 100644 index 00000000..899abc5e --- /dev/null +++ b/contracts/utils/common/IdUtils.sol @@ -0,0 +1,28 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.22; + +function encodeAppGatewayId(address appGateway_) pure returns (bytes32) { + return bytes32(uint256(uint160(appGateway_))); +} + +function decodeAppGatewayId(bytes32 appGatewayId_) 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_ +) pure returns (bytes32) { + return + keccak256(abi.encode(requestCount_, batchCount_, payloadCount_, chainSlug_, switchboard_)); +} diff --git a/contracts/utils/common/Structs.sol b/contracts/utils/common/Structs.sol index 2f93bf17..80943216 100644 --- a/contracts/utils/common/Structs.sol +++ b/contracts/utils/common/Structs.sol @@ -2,12 +2,6 @@ pragma solidity ^0.8.21; //// ENUMS //// -enum CallType { - READ, - WRITE, - DEPLOY -} - enum IsPlug { YES, NO @@ -37,7 +31,7 @@ enum SwitchboardStatus { /// @notice The state of the async promise enum AsyncPromiseState { - WAITING_FOR_SET_CALLBACK_SELECTOR, + WAITING_FOR_CALLBACK_SELECTOR, WAITING_FOR_CALLBACK_EXECUTION, CALLBACK_REVERTING, ONCHAIN_REVERTING, @@ -50,41 +44,15 @@ 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 { +struct AppGatewayApprovals { address appGateway; - bool isApproved; + bool approval; } //// STRUCTS //// -// plug: -struct LimitParams { - uint256 lastUpdateTimestamp; - uint256 ratePerSecond; - uint256 maxLimit; - uint256 lastUpdateLimit; -} -struct UpdateLimitParams { - bytes32 limitType; - address appGateway; - uint256 maxLimit; - uint256 ratePerSecond; -} - struct AppGatewayConfig { + PlugConfig plugConfig; address plug; - bytes32 appGatewayId; - address switchboard; uint32 chainSlug; } // Plug config: @@ -101,42 +69,52 @@ struct TriggerParams { bytes overrides; bytes payload; } -// timeout: -struct TimeoutRequest { - address target; - uint256 delayInSeconds; - uint256 executeAt; - uint256 executedAt; - bool isResolved; - bytes payload; -} -struct ResolvedPromises { +struct PromiseReturnData { + bool exceededMaxCopy; bytes32 payloadId; bytes returnData; } - // AM -struct Bid { - uint256 fee; - address transmitter; +struct ExecuteParams { + bytes4 callType; + uint40 requestCount; + uint40 batchCount; + uint40 payloadCount; + uint256 deadline; + uint256 gasLimit; + uint256 value; + bytes32 prevBatchDigestHash; + address target; + bytes payload; bytes extraData; } -struct OnChainFees { - uint32 chainSlug; - address token; - uint256 amount; +struct TransmissionParams { + uint256 socketFees; + address refundAddress; + bytes extraData; + bytes transmitterSignature; } -// App gateway base: -struct OverrideParams { - Read isReadCall; - Parallel isParallelCall; - WriteFinality writeFinality; - uint256 gasLimit; - uint256 value; - uint256 readAt; +struct WatcherMultiCallParams { + address contractAddress; + bytes data; + uint256 nonce; + bytes signature; +} + +struct CreateRequestResult { + uint256 totalEstimatedWatcherFees; + uint256 writeCount; + address[] promiseList; + PayloadParams[] payloadParams; +} + +struct Bid { + uint256 fee; + address transmitter; + bytes extraData; } struct UserCredits { @@ -150,125 +128,74 @@ struct DigestParams { address transmitter; bytes32 payloadId; uint256 deadline; - CallType callType; + bytes4 callType; uint256 gasLimit; uint256 value; bytes payload; address target; bytes32 appGatewayId; - bytes32 prevDigestsHash; + bytes32 prevBatchDigestHash; + bytes extraData; } -struct QueuePayloadParams { - uint32 chainSlug; - CallType callType; - Parallel isParallel; - IsPlug isPlug; +// App gateway base: +struct OverrideParams { + bytes4 callType; + Parallel isParallelCall; WriteFinality writeFinality; - address asyncPromise; - address switchboard; - address target; - address appGateway; uint256 gasLimit; uint256 value; - uint256 readAt; - bytes payload; - bytes initCallData; + uint256 readAtBlockNumber; + uint256 delayInSeconds; } -struct PayloadSubmitParams { - uint256 levelNumber; +// payload +struct Transaction { uint32 chainSlug; - CallType callType; - Parallel isParallel; - WriteFinality writeFinality; - address asyncPromise; - address switchboard; address target; - address appGateway; - uint256 gasLimit; - uint256 value; - uint256 readAt; bytes payload; } +struct QueueParams { + OverrideParams overrideParams; + Transaction transaction; + address asyncPromise; + bytes32 switchboardType; +} + struct PayloadParams { - // uint40 requestCount + uint40 batchCount + uint40 payloadCount + uint32 chainSlug - // CallType callType + Parallel isParallel + WriteFinality writeFinality - bytes32 payloadHeader; - // uint40 requestCount; - // uint40 batchCount; - // uint40 payloadCount; - // uint32 chainSlug; - // CallType callType; - // Parallel isParallel; - // WriteFinality writeFinality; + uint40 requestCount; + uint40 batchCount; + uint40 payloadCount; + bytes4 callType; address asyncPromise; - address switchboard; - address target; address appGateway; bytes32 payloadId; - bytes32 prevDigestsHash; - uint256 gasLimit; - uint256 value; - uint256 readAt; + uint256 resolvedAt; uint256 deadline; - bytes payload; - address finalizedTransmitter; + bytes precompileData; } -struct RequestParams { +// request +struct RequestTrackingParams { bool isRequestCancelled; + bool isRequestExecuted; 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; +struct RequestFeesDetails { uint256 maxFees; - uint256 queryCount; - uint256 finalizeCount; + address consumeFrom; Bid winningBid; - bytes onCompleteData; } -struct ExecuteParams { - CallType callType; - uint40 requestCount; - uint40 batchCount; - uint40 payloadCount; - 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; - uint32 chainSlug; - address switchboard; +struct RequestParams { + RequestTrackingParams requestTrackingParams; + RequestFeesDetails requestFeesDetails; + address appGateway; + address auctionManager; + uint256 writeCount; + bytes onCompleteData; } diff --git a/deployments/dev_addresses.json b/deployments/dev_addresses.json index 72af1b71..df55a5d3 100644 --- a/deployments/dev_addresses.json +++ b/deployments/dev_addresses.json @@ -1,38 +1,44 @@ { "421614": { - "ContractFactoryPlug": "0x9dEa956E863a44a24c1CE068460E6190EA438602", - "FastSwitchboard": "0xb4C5A231614639801F421Ca386388f65576F3c81", - "FeesPlug": "0x2fc70A464588b3f692D5C500Bfe3A2F2165911aD", - "Socket": "0xBd0436A6E0dee11e9359767142Ed6bD7B48ba258", - "SocketBatcher": "0x1683c3AB4954852f71f400AbDeF98112a066ee44", - "startBlock": 148461456, - "TestUSDC": "0x3f134B047620590a0b7a2f98d5F9B07D32d4D927" + "ContractFactoryPlug": "0x7b9928b01272b915050aDfcba7e0a11b22271BAd", + "FastSwitchboard": "0x2974E94c0d1323D3A24f7B4F924fbdB325Be1aa3", + "FeesPlug": "0x6FdF04Cbcbd40414BF12e0b4Ce0e331e4657EB03", + "Socket": "0xb7378ae43b135988C8a83dfD1AcD71Ff39381396", + "SocketBatcher": "0x60541d31Fda60163480CAb486be3762b5793B650", + "startBlock": 159641867 }, "7625382": { - "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" + "AddressResolver": "0x8161cDBa2d2fCE66307254AAC1d42966D4F5353E", + "AddressResolverImpl": "0x91e548d87768313C03da8405D01171b83912c430", + "AsyncDeployer": "0x025b308371dC1C5e337527f96BE46Ba6A12c774A", + "AsyncDeployerImpl": "0x80CFbD3B6134Fb2D2B7d21FC132a9F7c115e7B72", + "AuctionManager": "0xA40aFA1632328D84226084a4539B3869D2B68e28", + "AuctionManagerImpl": "0x42109F6212765ABeb589f9b2c14Bee4b8DB3e638", + "Configurations": "0x60185198097df249B504D5A164323eBF42B3764d", + "ConfigurationsImpl": "0x0d2646fC08af29A7799Af435c5ABBA1b020C4dC7", + "DeployForwarder": "0xdC51D652B8c3cCB3cAAB9C1E2704fD4D62E76433", + "DeployForwarderImpl": "0xCe95fca954a0BF43c299c79d5152f2c164C02b7A", + "ERC1967Factory": "0xb0364Fd8f158071831ac87E7EE2C792Ab509a524", + "FeesManager": "0x09F824Eae77f71279d73Ae24FEb2163FCe88B25D", + "FeesManagerImpl": "0x6975302A1B7aF61d89F85a13855B66D15221Cf8D", + "FeesPool": "0xc20Be67ef742202dc93A78aa741E7C3715eA1DFd", + "PromiseResolver": "0xcfFda1dF8668266E6A77809EcA9CCA8A632ecaF3", + "ReadPrecompile": "0x254Dc9e0623426A79F02D2001E367cd32B50aaaA", + "RequestHandler": "0x1FE7527a8620374B3Fdb101bA1D56eC46EC9a24A", + "RequestHandlerImpl": "0x3d9578B252ed1F5A66348Cc40E482dacc32Ae790", + "SchedulePrecompile": "0x7D6F2A4aDf7e5Cfcf9627CC7FCA1d39fD19C07fc", + "startBlock": 8355289, + "Watcher": "0xD5b30DC89D96ee7303Dc2726491996B46089F693", + "WatcherImpl": "0x872bb254118a2210e3C491918133F2ab4D7Bc362", + "WritePrecompile": "0x10eaDbd1a2787ebbF4Abe9b6D79e669C0c8E8B26", + "WritePrecompileImpl": "0xD3aEb53da0a72788C16eAf5a23a5aBae6708C073" }, "11155420": { - "ContractFactoryPlug": "0x95Be4D8500e3e5C970802c64b0755027d4Fc5C9F", - "FastSwitchboard": "0xe7858f1dc202f5E9C9B3ee6db052F45164a88534", - "FeesPlug": "0x29b77ecEf2D163528d1F4A235c0795daDD2DA1Bf", - "Socket": "0xE09CC429e77EE5DBeF68f3796b2A33BBDF39C03C", - "SocketBatcher": "0x9175d90706a2b17f0aE025ce5A6C76e64850c2f5", - "startBlock": 27158986, - "TestUSDC": "0x3D7515519beab77B541497626CFB7E764F6887CD" + "ContractFactoryPlug": "0x0279A18d5FC235A92fB4ABd5F7e9258e78E27948", + "FastSwitchboard": "0x6b4EF1452265193798bfa3ef6D29421da9e7E222", + "FeesPlug": "0x99f7441292EB7f0b127Db204ba269Abd9F912d4C", + "Socket": "0xB260A4DD0952e9A5b5F6652019469F05Fb137dC5", + "SocketBatcher": "0xc320FC7b06D4491A9E7e6fa55a3305b12548519e", + "startBlock": 28568337 } } diff --git a/deployments/dev_verification.json b/deployments/dev_verification.json index cb1975f3..7726efac 100644 --- a/deployments/dev_verification.json +++ b/deployments/dev_verification.json @@ -1,254 +1,306 @@ { - "84532": [ + "421614": [ [ - "0xFEFdbD9CB5bdC58Aa8B6B455923454275392838c", + "0x7b9928b01272b915050aDfcba7e0a11b22271BAd", "ContractFactoryPlug", - "contracts/protocol/payload-delivery/ContractFactoryPlug.sol", + "contracts/evmx/plugs/ContractFactoryPlug.sol", [ - "0x89e66357C5F101C56b4F9F97cf378Cc32A21438a", + "0xb7378ae43b135988C8a83dfD1AcD71Ff39381396", "0x3339Cf48f1F9cf31b6F8c2664d144c7444eBBB18" ] ], [ - "0xBBff19fBEAd971f39F1B298De985D7ADfb57b784", + "0x6FdF04Cbcbd40414BF12e0b4Ce0e331e4657EB03", "FeesPlug", - "contracts/protocol/payload-delivery/FeesPlug.sol", + "contracts/evmx/plugs/FeesPlug.sol", [ - "0x89e66357C5F101C56b4F9F97cf378Cc32A21438a", + "0xb7378ae43b135988C8a83dfD1AcD71Ff39381396", "0x3339Cf48f1F9cf31b6F8c2664d144c7444eBBB18" ] ], [ - "0xCc9f995DAE2D3Cf3C0763e353986E97ab78801b4", + "0x2974E94c0d1323D3A24f7B4F924fbdB325Be1aa3", "FastSwitchboard", - "contracts/protocol/socket/switchboard/FastSwitchboard.sol", + "contracts/protocol/switchboard/FastSwitchboard.sol", [ - 84532, - "0x89e66357C5F101C56b4F9F97cf378Cc32A21438a", + 421614, + "0xb7378ae43b135988C8a83dfD1AcD71Ff39381396", "0x3339Cf48f1F9cf31b6F8c2664d144c7444eBBB18" ] ], [ - "0x3bABE35428D9E2B87d678289f837F805dAB0db3A", + "0x60541d31Fda60163480CAb486be3762b5793B650", "SocketBatcher", - "contracts/protocol/socket/SocketBatcher.sol", + "contracts/protocol/SocketBatcher.sol", [ "0x3339Cf48f1F9cf31b6F8c2664d144c7444eBBB18", - "0x89e66357C5F101C56b4F9F97cf378Cc32A21438a" + "0xb7378ae43b135988C8a83dfD1AcD71Ff39381396" ] ], [ - "0x89e66357C5F101C56b4F9F97cf378Cc32A21438a", + "0xb7378ae43b135988C8a83dfD1AcD71Ff39381396", "Socket", - "contracts/protocol/socket/Socket.sol", - [84532, "0x3339Cf48f1F9cf31b6F8c2664d144c7444eBBB18", "EVMX"] + "contracts/protocol/Socket.sol", + [421614, "0x3339Cf48f1F9cf31b6F8c2664d144c7444eBBB18", "EVMX"] ] ], - "421614": [ + "7625382": [ [ - "0x092194e4Cd90d950ED91bD216472A18cCA7cd8F7", - "FeesPlug", - "contracts/protocol/payload-delivery/FeesPlug.sol", + "0x872bb254118a2210e3C491918133F2ab4D7Bc362", + "Watcher", + "contracts/evmx/watcher/Watcher.sol", + [] + ], + [ + "0x7D6F2A4aDf7e5Cfcf9627CC7FCA1d39fD19C07fc", + "SchedulePrecompile", + "contracts/evmx/watcher/precompiles/SchedulePrecompile.sol", [ - "0xBd0436A6E0dee11e9359767142Ed6bD7B48ba258", - "0x3339Cf48f1F9cf31b6F8c2664d144c7444eBBB18" + "0xD5b30DC89D96ee7303Dc2726491996B46089F693", + 86400, + { + "type": "BigNumber", + "hex": "0xe8d4a51000" + }, + { + "type": "BigNumber", + "hex": "0xe8d4a51000" + }, + 300 ] ], [ - "0x44f182553Ccdd82f95b592a935Bd7Dd90FF7F292", - "FeesPlug", - "contracts/protocol/payload-delivery/FeesPlug.sol", + "0x254Dc9e0623426A79F02D2001E367cd32B50aaaA", + "ReadPrecompile", + "contracts/evmx/watcher/precompiles/ReadPrecompile.sol", [ - "0xBd0436A6E0dee11e9359767142Ed6bD7B48ba258", - "0x3339Cf48f1F9cf31b6F8c2664d144c7444eBBB18" + "0xD5b30DC89D96ee7303Dc2726491996B46089F693", + { + "type": "BigNumber", + "hex": "0xe8d4a51000" + }, + 300 ] - ] - ], - "7625382": [ + ], [ - "0xe1CA4da421C52161B4EecCE6F5Cb2937554e2958", - "WatcherPrecompile", - "contracts/protocol/watcherPrecompile/core/WatcherPrecompile.sol", + "0xD3aEb53da0a72788C16eAf5a23a5aBae6708C073", + "WritePrecompile", + "contracts/evmx/watcher/precompiles/WritePrecompile.sol", [] ], [ - "0x23364acdd298EBB3Dfd3c1835C5ACd7f77E3E2bD", - "AuctionManager", - "contracts/protocol/payload-delivery/AuctionManager.sol", + "0xcfFda1dF8668266E6A77809EcA9CCA8A632ecaF3", + "PromiseResolver", + "contracts/evmx/watcher/PromiseResolver.sol", + ["0xD5b30DC89D96ee7303Dc2726491996B46089F693"] + ], + [ + "0x3d9578B252ed1F5A66348Cc40E482dacc32Ae790", + "RequestHandler", + "contracts/evmx/watcher/RequestHandler.sol", [] ], [ - "0x915eA695e03265998cd455B4df2A1EAeb1b61e74", - "DeliveryHelper", - "contracts/protocol/payload-delivery/app-gateway/DeliveryHelper.sol", + "0x0d2646fC08af29A7799Af435c5ABBA1b020C4dC7", + "Configurations", + "contracts/evmx/watcher/Configurations.sol", [] ], [ - "0xB577c29F7Cbb1bBB314dD8E74059Aa5BF72838b0", - "FeesManager", - "contracts/protocol/payload-delivery/FeesManager.sol", + "0xCe95fca954a0BF43c299c79d5152f2c164C02b7A", + "DeployForwarder", + "contracts/evmx/helpers/DeployForwarder.sol", + [] + ], + [ + "0x42109F6212765ABeb589f9b2c14Bee4b8DB3e638", + "AuctionManager", + "contracts/evmx/AuctionManager.sol", [] ], [ - "0x1BCe40d84499Db8E7Bc65277A32f0abd56588CC7", - "WatcherPrecompile", - "contracts/protocol/watcherPrecompile/core/WatcherPrecompile.sol", + "0xFa9B9271A4153eEABa76ae10bfc4F128651c25D7", + "Watcher", + "contracts/evmx/watcher/Watcher.sol", [] ], [ - "0x060b4a50EcCC9Cf329005c94406dd2886676F759", - "WatcherPrecompileConfig", - "contracts/protocol/watcherPrecompile/WatcherPrecompileConfig.sol", + "0x80CFbD3B6134Fb2D2B7d21FC132a9F7c115e7B72", + "AsyncDeployer", + "contracts/evmx/helpers/AsyncDeployer.sol", [] ], [ - "0x4Fd04B0D4903e630F169BB228be52750E6B5331a", - "WatcherPrecompileLimits", - "contracts/protocol/watcherPrecompile/WatcherPrecompileLimits.sol", + "0x6975302A1B7aF61d89F85a13855B66D15221Cf8D", + "FeesManager", + "contracts/evmx/fees/FeesManager.sol", [] ], [ - "0x4b067469E7865d9959E0E658BA77ec4b0F28FB15", + "0x91e548d87768313C03da8405D01171b83912c430", "AddressResolver", - "contracts/protocol/AddressResolver.sol", + "contracts/evmx/helpers/AddressResolver.sol", [] ], [ - "0xb30eBbA571721923175751AE7aF5aC90cfb1E892", + "0xb0364Fd8f158071831ac87E7EE2C792Ab509a524", "ERC1967Factory", "lib/solady/src/utils/ERC1967Factory.sol", [] ], [ - "0x2c50B6e519e0705A396E5c8652E5D447F37f9796", - "FeesManager", - "contracts/protocol/payload-delivery/FeesManager.sol", + "0x09A1A0A7BB8266171855871c4c0Af200a30922BE", + "WritePrecompile", + "contracts/evmx/watcher/precompiles/WritePrecompile.sol", [] ], [ - "0x233539d6BBf231660652AF00B5d6E850E892946a", - "WatcherPrecompile", - "contracts/protocol/watcherPrecompile/WatcherPrecompile.sol", + "0xDa60303321dc6aA8AeF32557bDe914008a3196eC", + "SchedulePrecompile", + "contracts/evmx/watcher/precompiles/SchedulePrecompile.sol", + [ + "0x60005b459Dc46D9a63bcb61D01Ad002130644a4F", + 86400, + { + "type": "BigNumber", + "hex": "0xe8d4a51000" + }, + { + "type": "BigNumber", + "hex": "0xe8d4a51000" + }, + 300 + ] + ], + [ + "0xddeEDDD5F156123fDbf77B86A66A043568AEfcda", + "ReadPrecompile", + "contracts/evmx/watcher/precompiles/ReadPrecompile.sol", + [ + "0x60005b459Dc46D9a63bcb61D01Ad002130644a4F", + { + "type": "BigNumber", + "hex": "0xe8d4a51000" + }, + 300 + ] + ], + [ + "0xb64D07B0dDb23cc54eE5EDe294E0cD15f08CC971", + "WritePrecompile", + "contracts/evmx/watcher/precompiles/WritePrecompile.sol", [] ], [ - "0x4D0FA91403b4902B56Ad650aF2BA9D4fcA46aB0B", - "WatcherPrecompile", - "contracts/protocol/watcherPrecompile/WatcherPrecompile.sol", + "0xF24B41A8C9F814d70FAD9E617CE32C74EcCB1A25", + "PromiseResolver", + "contracts/evmx/watcher/PromiseResolver.sol", + ["0x60005b459Dc46D9a63bcb61D01Ad002130644a4F"] + ], + [ + "0x1fa5D12C7dC1F3615c28B842A6053f5f151230F8", + "RequestHandler", + "contracts/evmx/watcher/RequestHandler.sol", [] ], [ - "0xaE4624b006D7730f22De1F7df5b1C0b960262AE3", - "AuctionManager", - "contracts/protocol/payload-delivery/AuctionManager.sol", + "0x8d41aBDbE6Bbd208D68Dd4E671c4B72474525C7B", + "Configurations", + "contracts/evmx/watcher/Configurations.sol", [] ], [ - "0xE693bEc40e39223749AC351156E713b7256541B0", - "DeliveryHelper", - "contracts/protocol/payload-delivery/app-gateway/DeliveryHelper.sol", + "0xD9CF47342c8a256fcD8B51aaABA810cd5D05F726", + "DeployForwarder", + "contracts/evmx/helpers/DeployForwarder.sol", [] ], [ - "0xc2Ca571f4d4C2008Da4Bd750BaD3d50A5705ffF8", - "FeesManager", - "contracts/protocol/payload-delivery/FeesManager.sol", + "0x1C40B5d5f6E9be1Ca4557d92cb7dba35e721a322", + "AuctionManager", + "contracts/evmx/AuctionManager.sol", [] ], [ - "0x4894D35BF607BB47099172d42133ffe411a1d4B8", - "WatcherPrecompile", - "contracts/protocol/watcherPrecompile/WatcherPrecompile.sol", + "0x812EcdF5366036B045e41Fe2dD95B72ecc26d8f3", + "Watcher", + "contracts/evmx/watcher/Watcher.sol", [] ], [ - "0xE4a2eBcE3Bdcaa1861c01ddc6465b90311B749e4", - "WatcherPrecompileConfig", - "contracts/protocol/watcherPrecompile/WatcherPrecompileConfig.sol", + "0xF4f5606189Bc091528680b2ca1c82167641d3cAf", + "AsyncDeployer", + "contracts/evmx/helpers/AsyncDeployer.sol", [] ], [ - "0xdfeA8cb793b84b3f046d2426259e0eC237Ec9bF3", - "WatcherPrecompileLimits", - "contracts/protocol/watcherPrecompile/WatcherPrecompileLimits.sol", + "0x255fBaD02F8f2Aae0Faf7074249a8701371890D2", + "FeesManager", + "contracts/evmx/fees/FeesManager.sol", [] ], [ - "0x2736Fc0AA0fa5Ca595Ae5F27Ec807B912A5b1D0f", + "0x865A24Cf706027639124489e0dA2A28766efB96A", "AddressResolver", - "contracts/protocol/AddressResolver.sol", + "contracts/evmx/helpers/AddressResolver.sol", [] ], [ - "0x122beAFCfc2E99D825322a78EAFD8a11fa2d9E0b", + "0xc20Be67ef742202dc93A78aa741E7C3715eA1DFd", + "FeesPool", + "contracts/evmx/fees/FeesPool.sol", + ["0xb62505feacC486e809392c65614Ce4d7b051923b"] + ], + [ + "0x1b0F1aA38F8BBbe779A6C1fCe7e3Ff00380fa9CE", "ERC1967Factory", "lib/solady/src/utils/ERC1967Factory.sol", [] ] ], - "11155111": [ + "11155420": [ [ - "0xdd7b56968a3505D1A10181F2880cAA65a89E4750", + "0x0279A18d5FC235A92fB4ABd5F7e9258e78E27948", "ContractFactoryPlug", - "contracts/protocol/payload-delivery/ContractFactoryPlug.sol", + "contracts/evmx/plugs/ContractFactoryPlug.sol", [ - "0xF86B89B5c689c170BfD2734254228D6d2db5a672", + "0xB260A4DD0952e9A5b5F6652019469F05Fb137dC5", "0x3339Cf48f1F9cf31b6F8c2664d144c7444eBBB18" ] ], [ - "0xfBa932AaE9Ae2777aEC77832FC0C2D9059C8E905", + "0x99f7441292EB7f0b127Db204ba269Abd9F912d4C", "FeesPlug", - "contracts/protocol/payload-delivery/FeesPlug.sol", + "contracts/evmx/plugs/FeesPlug.sol", [ - "0xF86B89B5c689c170BfD2734254228D6d2db5a672", + "0xB260A4DD0952e9A5b5F6652019469F05Fb137dC5", "0x3339Cf48f1F9cf31b6F8c2664d144c7444eBBB18" ] ], [ - "0xb34DB19C7FAeECf14B3D0336C6E34f7dc1336968", + "0x6b4EF1452265193798bfa3ef6D29421da9e7E222", "FastSwitchboard", - "contracts/protocol/socket/switchboard/FastSwitchboard.sol", + "contracts/protocol/switchboard/FastSwitchboard.sol", [ - 11155111, - "0xF86B89B5c689c170BfD2734254228D6d2db5a672", + 11155420, + "0xB260A4DD0952e9A5b5F6652019469F05Fb137dC5", "0x3339Cf48f1F9cf31b6F8c2664d144c7444eBBB18" ] ], [ - "0x466b51bcc1799A4cb8F629C46640ab3aA2608F75", + "0xc320FC7b06D4491A9E7e6fa55a3305b12548519e", "SocketBatcher", - "contracts/protocol/socket/SocketBatcher.sol", + "contracts/protocol/SocketBatcher.sol", [ "0x3339Cf48f1F9cf31b6F8c2664d144c7444eBBB18", - "0xF86B89B5c689c170BfD2734254228D6d2db5a672" + "0xB260A4DD0952e9A5b5F6652019469F05Fb137dC5" ] ], [ - "0xF86B89B5c689c170BfD2734254228D6d2db5a672", + "0xB260A4DD0952e9A5b5F6652019469F05Fb137dC5", "Socket", - "contracts/protocol/socket/Socket.sol", - [11155111, "0x3339Cf48f1F9cf31b6F8c2664d144c7444eBBB18", "EVMX"] - ] - ], - "11155420": [ - [ - "0x78775DCd56fBdb903e4d83AE51924797a74AD49d", - "FeesPlug", - "contracts/protocol/payload-delivery/FeesPlug.sol", - [ - "0xE09CC429e77EE5DBeF68f3796b2A33BBDF39C03C", - "0x3339Cf48f1F9cf31b6F8c2664d144c7444eBBB18" - ] - ], - [ - "0x8eD0cebe57236Bf85b0a74951CeE74357B1d381D", - "FeesPlug", - "contracts/protocol/payload-delivery/FeesPlug.sol", - [ - "0xE09CC429e77EE5DBeF68f3796b2A33BBDF39C03C", - "0x3339Cf48f1F9cf31b6F8c2664d144c7444eBBB18" - ] + "contracts/protocol/Socket.sol", + [11155420, "0x3339Cf48f1F9cf31b6F8c2664d144c7444eBBB18", "EVMX"] ] ] } diff --git a/deployments/stage_addresses.json b/deployments/stage_addresses.json index d950e823..10006872 100644 --- a/deployments/stage_addresses.json +++ b/deployments/stage_addresses.json @@ -1,54 +1,73 @@ { + "10": { + "ContractFactoryPlug": "0xee1Aef0b06f63Aa1c881838794Dd0876462c2B0d", + "FastSwitchboard": "0xbDE0D2da12F703Ccd275d721214745BccDCAD124", + "FeesPlug": "0x5F77550E3072c913A20B2fbdAb14026fe0E8B450", + "Socket": "0x5e1641B190B71ECCc85b1ECe934F31cD9b3dcF7a", + "SocketBatcher": "0xaC61f5696e0E2636dA7bD69827380f2Ab41A3C38", + "startBlock": 136685079 + }, "43": { - "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" + "AddressResolver": "0x935b06902cA5C8bb4C76e18738561c294D377A93", + "AddressResolverImpl": "0xD1586EaaA0d473E6655c11A927cE4FbED648F3BF", + "AsyncDeployer": "0xECa623A443F21B705714D4A0f810d0A5D135FF6F", + "AsyncDeployerImpl": "0x1B0ea1b79B526dD3d5889Bb33Dbd24f790C23102", + "AuctionManager": "0xD044f27A9c5EE4f92EF0e38685276adFDF13E90E", + "AuctionManagerImpl": "0xC72BE9e639DA23570fa1eF2fF2cb7901a081916F", + "Configurations": "0x72f4C225B4B4f0F9608a50aEe17dA9e11dcb94b2", + "ConfigurationsImpl": "0x351De7e4275dA7f49F75363e4E7ea86Dfe050501", + "DeployForwarder": "0x5E9d1072B60D6c752B1593F5937393413372E5eF", + "DeployForwarderImpl": "0x1b7752F0039E80Aa38f7CF8b5d18798dD2ac1597", + "ERC1967Factory": "0x526796AC60e45CBB9b17c654C9447Baf160C084d", + "FeesManager": "0xA07208F9e7aE243F922317ab6604DC9F86822406", + "FeesManagerImpl": "0xbD22EDD6559B28614f44D1c768EC26491CDE1cDD", + "FeesPool": "0xe2054B575664dfDBD7a7FbAf2B12420ae88DE0FF", + "PromiseResolver": "0x38e24A2F157817b830F36A35b862F24B1494d1aD", + "ReadPrecompile": "0x39b5D3FBBa1BC28438e25955aaB412C7576eCd61", + "RequestHandler": "0x2E928C000bdC1f90716B05cE2D7182C9FA081d31", + "RequestHandlerImpl": "0xD38ae1a6C410c7681ac464bd60009198406035Ed", + "SchedulePrecompile": "0xb14a7763f09eCbd47bC5230D6170547a22834a82", + "startBlock": 8240883, + "Watcher": "0x4C846eCa55ad8cF19B9D5d906225da7b565174C1", + "WatcherImpl": "0x2920F4FB50343EF2b33096650cE234E8aF9E8556", + "WritePrecompile": "0x393007B660a00970b25E34FEd6506CE96120f8e2", + "WritePrecompileImpl": "0x0026c4736E57fE2817b53f6df1E0808c3a61984d" + }, + "8453": { + "ContractFactoryPlug": "0x3aac37DC85C522c09A3DDdA44D181E6aCCD2f9F0", + "FastSwitchboard": "0xa33ACE59E4b0d9a45Cd4a3F0DBAB86D87BDd67e2", + "FeesPlug": "0xfE34ACE07836F7F05f485EAc7122D0CD58BAC047", + "Socket": "0xee1Aef0b06f63Aa1c881838794Dd0876462c2B0d", + "SocketBatcher": "0x9EDfb162b725CF6d628D68af200cAe8b624111eD", + "startBlock": 31089766 + }, + "42161": { + "ContractFactoryPlug": "0x5F77550E3072c913A20B2fbdAb14026fe0E8B450", + "FastSwitchboard": "0xbDE0D2da12F703Ccd275d721214745BccDCAD124", + "FeesPlug": "0xee1Aef0b06f63Aa1c881838794Dd0876462c2B0d", + "Socket": "0x5e1641B190B71ECCc85b1ECe934F31cD9b3dcF7a", + "SocketBatcher": "0xaC61f5696e0E2636dA7bD69827380f2Ab41A3C38", + "startBlock": 343531414 }, "84532": { - "ContractFactoryPlug": "0x8e51D99Bf353Ebba50080D0452546fd2EBAd86A4", - "FastSwitchboard": "0x4C114115755c39dB81a06fBfEb4b08302Abe7beE", - "FeesPlug": "0x9161a99deD597fe519E03D319053CA1669118dDA", - "Socket": "0x36Ae239a92faee6aFF4df9749d592dA7c00717Be", - "SocketBatcher": "0x8fa361816874a11a66D02EC84b28E1A931B4035e", - "startBlock": 25218634, - "TestUSDC": "0xfD51918C0572512901fFA79F822c99A475d22BB4" + "ContractFactoryPlug": "0x87cC19AedD434ebD3B74FfdC073CAeC7dC1E92EA", + "FastSwitchboard": "0x5aA84ffE5eCCB5263d1AE6aEd5682EAb39Bc7036", + "Socket": "0xa09217Cfc47F399C382E982778f6128685e13aD4", + "SocketBatcher": "0x80568677f2B092bd974657FE47Fc8531bfE5DBDC", + "startBlock": 26600215 }, "421614": { - "ContractFactoryPlug": "0x65C066BE05CB4622393fADc1Bf3dE8eEdEcB3817", - "FastSwitchboard": "0xF121f4B97F7C902eeD4b188B08052Da9A1FD5aBe", - "FeesPlug": "0xDfE94B9b14de382Ed13C8A7F387884808D0f7E0b", - "Socket": "0xDAB25fB82cc1b1611Fb9016FB50222dBFcD1BCf5", - "SocketBatcher": "0x4e7163Ce9F7F335138fB32827d6f99f174060897", - "startBlock": 148801970, - "TestUSDC": "0xa03Cbf13f331aF7c0fD7F2E28E6Cbc13F879E3F3" - }, - "11155111": { - "FastSwitchboard": "0x1eFD3AF2317B9E6E7492718878f69De747C9e7c3", - "FeesPlug": "0xfE555AD869ac24305471F0755976c556425E8D23", - "Socket": "0xae59BA0Bd0D92232B3B6304185448C9Fe5445f4d", - "SocketBatcher": "0xdaE4538FbbEf41B2Feb5c79DD2fFC9720AF13d7b", - "TestUSDC": "0xbcaDE56f86a819994d0F66b98e921C484bE6FE4e" + "ContractFactoryPlug": "0xe2904171afCeC319236cc051c81202677F7Aac1B", + "FastSwitchboard": "0x82833e5ac997F8f9c426949595d49702E3b08414", + "Socket": "0x468cA4bB968FD86eD752A7bD453c6869E27204f0", + "SocketBatcher": "0x977B8aB88A7159130457adA4b7078208Ab4fB111", + "startBlock": 159757112 }, "11155420": { - "ContractFactoryPlug": "0x469B536c5Df15948c8759FEEE5DB1c17790d4152", - "FastSwitchboard": "0xd8bCd4b4Bc4b0f5cb279B6FAdCEd733614f34F51", - "FeesPlug": "0x6734a30B8f2d210faefa5aeD4E11b674C59641F1", - "Socket": "0x11fbd3a7031b28607973fc44d4d24B26DEfac886", - "SocketBatcher": "0x2c2060f5586751676fC2Af96cc8bE9BF0c7A8770", - "startBlock": 27201458, - "TestUSDC": "0xa0E1738a9Fc0698789866e09d7A335d30128C5C5" + "ContractFactoryPlug": "0x705A4DD80D7203BF78AcAf3BA1851D1A80fA3d89", + "FastSwitchboard": "0x74388051BcCfA2D28690a98242A259aD94f2B1f3", + "Socket": "0x790E894C59d6275503e2Ff4ba95A42E38c071195", + "SocketBatcher": "0xa13B9b5e797e13316B23EfC01E506c8c0c2BFeF2", + "startBlock": 28583052 } } diff --git a/deployments/stage_verification.json b/deployments/stage_verification.json index 88f93e4b..2b0fd6f8 100644 --- a/deployments/stage_verification.json +++ b/deployments/stage_verification.json @@ -1,5 +1,165 @@ { + "10": [], "43": [ + [ + "0x0026c4736E57fE2817b53f6df1E0808c3a61984d", + "WritePrecompile", + "contracts/evmx/watcher/precompiles/WritePrecompile.sol", + [] + ], + [ + "0x38e24A2F157817b830F36A35b862F24B1494d1aD", + "PromiseResolver", + "contracts/evmx/watcher/PromiseResolver.sol", + [ + "0x4C846eCa55ad8cF19B9D5d906225da7b565174C1" + ] + ], + [ + "0xD38ae1a6C410c7681ac464bd60009198406035Ed", + "RequestHandler", + "contracts/evmx/watcher/RequestHandler.sol", + [] + ], + [ + "0x351De7e4275dA7f49F75363e4E7ea86Dfe050501", + "Configurations", + "contracts/evmx/watcher/Configurations.sol", + [] + ], + [ + "0x1b7752F0039E80Aa38f7CF8b5d18798dD2ac1597", + "DeployForwarder", + "contracts/evmx/helpers/DeployForwarder.sol", + [] + ], + [ + "0xC72BE9e639DA23570fa1eF2fF2cb7901a081916F", + "AuctionManager", + "contracts/evmx/AuctionManager.sol", + [] + ], + [ + "0x2920F4FB50343EF2b33096650cE234E8aF9E8556", + "Watcher", + "contracts/evmx/watcher/Watcher.sol", + [] + ], + [ + "0x1B0ea1b79B526dD3d5889Bb33Dbd24f790C23102", + "AsyncDeployer", + "contracts/evmx/helpers/AsyncDeployer.sol", + [] + ], + [ + "0xbD22EDD6559B28614f44D1c768EC26491CDE1cDD", + "FeesManager", + "contracts/evmx/fees/FeesManager.sol", + [] + ], + [ + "0xD1586EaaA0d473E6655c11A927cE4FbED648F3BF", + "AddressResolver", + "contracts/evmx/helpers/AddressResolver.sol", + [] + ], + [ + "0xe2054B575664dfDBD7a7FbAf2B12420ae88DE0FF", + "FeesPool", + "contracts/evmx/fees/FeesPool.sol", + [ + "0xb62505feacC486e809392c65614Ce4d7b051923b" + ] + ], + [ + "0x526796AC60e45CBB9b17c654C9447Baf160C084d", + "ERC1967Factory", + "lib/solady/src/utils/ERC1967Factory.sol", + [] + ], + [ + "0xFaa00117ED72CAE3399669a1E3FEedaF93020853", + "SchedulePrecompile", + "contracts/evmx/watcher/precompiles/SchedulePrecompile.sol", + [ + "0x03Aa399188E2741f89cc4265493DC5b544C52134", + 86400, + { + "type": "BigNumber", + "hex": "0x02540be400" + }, + { + "type": "BigNumber", + "hex": "0xe8d4a51000" + }, + 300 + ] + ], + [ + "0xD38d0Dfd8e7d61B45Cce6Ec58E1Ec4c514c00e7F", + "ReadPrecompile", + "contracts/evmx/watcher/precompiles/ReadPrecompile.sol", + [ + "0x03Aa399188E2741f89cc4265493DC5b544C52134", + { + "type": "BigNumber", + "hex": "0xe8d4a51000" + }, + 300 + ] + ], + [ + "0x702EfE1DfABc3963114E2356aFaF36c8b67CA961", + "WritePrecompile", + "contracts/evmx/watcher/precompiles/WritePrecompile.sol", + [] + ], + [ + "0xd0bd7837E66eEd7Be04C88354e75F5bA3cd19959", + "PromiseResolver", + "contracts/evmx/watcher/PromiseResolver.sol", + [ + "0x03Aa399188E2741f89cc4265493DC5b544C52134" + ] + ], + [ + "0x446C6B4086d1888cB15cF62735Bf57A4647E31A4", + "RequestHandler", + "contracts/evmx/watcher/RequestHandler.sol", + [] + ], + [ + "0x2dc671B87d1A9dc7C1cf06C74C2db06673b31FFf", + "Configurations", + "contracts/evmx/watcher/Configurations.sol", + [] + ], + [ + "0x4ab75b62c0E2A09E428Ce73043C36d54c78C8CFd", + "DeployForwarder", + "contracts/evmx/helpers/DeployForwarder.sol", + [] + ], + [ + "0xed8255097DFB0BB2135870bb342335dCC0C30e21", + "Watcher", + "contracts/evmx/watcher/Watcher.sol", + [] + ], + [ + "0xF946503b3bF14b39468AEce46E7Ce1A08404D109", + "AsyncDeployer", + "contracts/evmx/helpers/AsyncDeployer.sol", + [] + ], + [ + "0x69DD00B8a250e0A1bFF1b59db2EA99792faAbC66", + "FeesPool", + "contracts/evmx/fees/FeesPool.sol", + [ + "0xb62505feacC486e809392c65614Ce4d7b051923b" + ] + ], [ "0xfddb38811a0774E66ABD5F3Ae960bFB7E7415029", "AuctionManager", @@ -49,8 +209,9 @@ [] ] ], + "8453": [], + "42161": [], "84532": [], "421614": [], - "11155111": [], "11155420": [] } diff --git a/hardhat-scripts/admin/disconnect.ts b/hardhat-scripts/admin/disconnect.ts new file mode 100644 index 00000000..b5fb69d9 --- /dev/null +++ b/hardhat-scripts/admin/disconnect.ts @@ -0,0 +1,177 @@ +import { constants, Wallet } from "ethers"; +import { ChainAddressesObj, ChainSlug, Contracts } from "../../src"; +import { chains, EVMX_CHAIN_ID, getFeesPlugChains, mode } from "../config"; +import { + AppGatewayConfig, + DeploymentAddresses, + ZERO_APP_GATEWAY_ID, +} from "../constants"; +import { + checkIfAddressExists, + getAddresses, + getInstance, + getSocketSigner, +} from "../utils"; +import { getWatcherSigner, sendWatcherMultiCallWithNonce } from "../utils/sign"; +import { isConfigSetOnEVMx, isConfigSetOnSocket } from "../deploy/6.connect"; + +// update this map to disconnect plugs from chains not in this list +const feesPlugChains = getFeesPlugChains(); + +export const main = async () => { + try { + await disconnectPlugsOnSocket(); + await updateConfigEVMx(); + } catch (error) { + console.log("Error while sending transaction", error); + } +}; + +// Connect a single plug contract to its app gateway and switchboard +async function disconnectPlug( + chain: number, + plugContract: string, + socketSigner: Wallet, + addr: ChainAddressesObj +) { + console.log(`Disconnecting ${plugContract} on ${chain}`); + + // Get contract instances + const plug = (await getInstance(plugContract, addr[plugContract])).connect( + socketSigner + ); + const socket = ( + await getInstance(Contracts.Socket, addr[Contracts.Socket]) + ).connect(socketSigner); + + // Get switchboard and app gateway addresses + const switchboard = addr[Contracts.FastSwitchboard]; + checkIfAddressExists(switchboard, "Switchboard"); + + // Check if config is already set + if ( + await isConfigSetOnSocket(plug, socket, ZERO_APP_GATEWAY_ID, switchboard) + ) { + console.log(`${plugContract} Socket Config on ${chain} already set!`); + return; + } + + // Connect the plug + const tx = await plug.functions["connectSocket"]( + ZERO_APP_GATEWAY_ID, + socket.address, + switchboard + ); + console.log( + `Connecting ${plugContract} on ${chain} to ${ZERO_APP_GATEWAY_ID} tx hash: ${tx.hash}` + ); + await tx.wait(); +} + +export const disconnectPlugsOnSocket = async () => { + console.log("Disconnecting plugs"); + const addresses = getAddresses(mode) as unknown as DeploymentAddresses; + // Disconnect plugs on each chain + await Promise.all( + chains.map(async (chain) => { + // skip if chain is in feesPlugChains or not in addresses + if (feesPlugChains.includes(chain) || !addresses[chain]) return; + + const socketSigner = getSocketSigner(chain as ChainSlug); + const addr = addresses[chain]!; + if (addr[Contracts.FeesPlug]) { + await disconnectPlug(chain, Contracts.FeesPlug, socketSigner, addr); + } + }) + ); +}; + +// Configure plugs on the Watcher VM +export const updateConfigEVMx = async () => { + try { + console.log("Disconnecting plugs on EVMx"); + const addresses = getAddresses(mode) as unknown as DeploymentAddresses; + const appConfigs: AppGatewayConfig[] = []; + + // Set up Watcher contract + const signer = getWatcherSigner(); + const EVMxAddresses = addresses[EVMX_CHAIN_ID]!; + const configurationsContract = ( + await getInstance( + Contracts.Configurations, + EVMxAddresses[Contracts.Configurations] + ) + ).connect(signer); + + // Collect configs for each chain and plug + await Promise.all( + chains.map(async (chain) => { + // skip if chain is in feesPlugChains or not in addresses + if (feesPlugChains.includes(chain) || !addresses[chain]) return; + const addr = addresses[chain]!; + + const appGatewayId = ZERO_APP_GATEWAY_ID; + const switchboard = constants.AddressZero; + const plugContract = Contracts.FeesPlug; + + if (!addr[plugContract]) return; + + if ( + await isConfigSetOnEVMx( + configurationsContract, + chain, + addr[plugContract], + appGatewayId, + switchboard + ) + ) { + console.log(`Config already set on ${chain} for ${plugContract}`); + return; + } + appConfigs.push({ + plugConfig: { + appGatewayId: appGatewayId, + switchboard: switchboard, + }, + plug: addr[plugContract], + chainSlug: chain, + }); + + // update fees manager + const feesManager = ( + await getInstance(Contracts.FeesManager, addr[Contracts.FeesManager]) + ).connect(signer); + + const tx = await feesManager.functions["setFeesPlug"]( + chain, + constants.AddressZero + ); + console.log(`Updating Fees Manager tx hash: ${tx.hash}`); + }) + ); + + // Update configs if any changes needed + if (appConfigs.length > 0) { + console.log({ appConfigs }); + const calldata = configurationsContract.interface.encodeFunctionData( + "setAppGatewayConfigs", + [appConfigs] + ); + const tx = await sendWatcherMultiCallWithNonce( + configurationsContract.address, + calldata + ); + console.log(`Updating EVMx Config tx hash: ${tx.hash}`); + await tx.wait(); + } + } catch (error) { + console.log("Error while sending transaction", error); + } +}; + +main() + .then(() => process.exit(0)) + .catch((error: Error) => { + console.error(error); + process.exit(1); + }); diff --git a/hardhat-scripts/admin/rescue.ts b/hardhat-scripts/admin/rescue.ts new file mode 100644 index 00000000..66e5bb17 --- /dev/null +++ b/hardhat-scripts/admin/rescue.ts @@ -0,0 +1,165 @@ +import { config as dotenvConfig } from "dotenv"; + +dotenvConfig(); + +import { formatEther } from "ethers/lib/utils"; +import { Contract, ethers } from "ethers"; +import { mainnetChains, mode, testnetChains } from "../config"; +import { getAddresses, getSocketSigner, overrides } from "../utils"; +import { DeploymentAddresses } from "../constants"; +import { ChainAddressesObj, ChainSlug } from "../../src"; + +/** + * Usable flags + * --sendtx Send rescue tx along with checking balance. + * Default is only check balance. + * Eg. npx --sendtx ts-node scripts/admin/rescueFunds.ts + * + * --amount Specify amount to rescue, can be used only with --sendtx + * If this much is not available then less is rescued. + * Full amount is rescued if not mentioned. + * Eg. npx --chains=2999 --sendtx --amount=0.2 ts-node scripts/admin/rescueFunds.ts + * + * --chains Run only for specified chains. + * Default is all chains. + * Eg. npx --chains=10,2999 ts-node scripts/admin/rescueFunds.ts + * + * --testnets Run for testnets. + * Default is false. + */ + +const addresses: DeploymentAddresses = getAddresses( + mode +) as unknown as DeploymentAddresses; + +const testnets = process.env.npm_config_testnets == "true"; +let activeChainSlugs: string[]; +if (testnets) + activeChainSlugs = Object.keys(addresses).filter((c) => + testnetChains.includes(parseInt(c) as ChainSlug) + ); +else + activeChainSlugs = Object.keys(addresses).filter((c) => + mainnetChains.includes(parseInt(c) as ChainSlug) + ); + +const sendTx = process.env.npm_config_sendtx == "true"; +const filterChains = process.env.npm_config_chains + ? process.env.npm_config_chains.split(",") + : activeChainSlugs; +const maxRescueAmount = ethers.utils.parseEther( + process.env.npm_config_amount || "0" +); + +const ETH_ADDRESS = "0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE"; +const rescueFundsABI = [ + { + inputs: [ + { + internalType: "address", + name: "token_", + type: "address", + }, + { + internalType: "address", + name: "rescueTo_", + type: "address", + }, + { + internalType: "uint256", + name: "amount_", + type: "uint256", + }, + ], + name: "rescueFunds", + outputs: [], + stateMutability: "nonpayable", + type: "function", + }, +]; + +const createContractAddrArray = (chainSlug: number): string[] => { + let chainAddresses = addresses[chainSlug] as unknown as ChainAddressesObj; + if (!chainAddresses) { + console.log("addresses not found for ", chainSlug, chainAddresses); + return []; + } + + let addressArray: string[] = []; + if (chainAddresses.SocketFeesManager) + addressArray.push(chainAddresses.SocketFeesManager); + if (chainAddresses.FeesPlug) addressArray.push(chainAddresses.FeesPlug); + addressArray.push(chainAddresses.Socket); + addressArray.push(chainAddresses.SocketBatcher); + addressArray.push(chainAddresses.FastSwitchboard); + addressArray.push(chainAddresses.ContractFactoryPlug); + return addressArray; +}; + +export const main = async () => { + // parallelize chains + await Promise.all( + activeChainSlugs + .filter((c) => filterChains.includes(c)) + .map(async (chainSlug) => { + const signer = await getSocketSigner(parseInt(chainSlug)); + const contractAddr = createContractAddrArray(parseInt(chainSlug)); + + for (let index = 0; index < contractAddr.length; index++) { + const rescueableAmount = await signer.provider.getBalance( + contractAddr[index] + ); + const fundingAmount = await signer.provider.getBalance( + "0x0240c3151FE3e5bdBB1894F59C5Ed9fE71ba0a5E" + ); + console.log( + `rescueableAmount on ${chainSlug} : ${formatEther( + rescueableAmount + )}` + ); + console.log( + `fundingAmount on ${chainSlug}: ${formatEther(fundingAmount)}` + ); + + const rescueAmount = + maxRescueAmount.eq(0) || rescueableAmount.lt(maxRescueAmount) + ? rescueableAmount + : maxRescueAmount; + if (rescueAmount.toString() === "0") continue; + + const contractInstance: Contract = new ethers.Contract( + contractAddr[index], + rescueFundsABI, + signer + ); + + if (sendTx) { + try { + const tx = await contractInstance.rescueFunds( + ETH_ADDRESS, + signer.address, + rescueAmount, + { ...(await overrides(parseInt(chainSlug))) } + ); + console.log( + `Rescuing ${rescueAmount} from ${contractAddr[index]} on ${chainSlug}: ${tx.hash}` + ); + + await tx.wait(); + } catch (e) { + console.log( + `Error while rescuing ${rescueAmount} from ${contractAddr[index]} on ${chainSlug}` + ); + } + } + } + }) + ); +}; + +main() + .then(() => process.exit(0)) + .catch((error: Error) => { + console.error(error); + process.exit(1); + }); diff --git a/hardhat-scripts/config/config.ts b/hardhat-scripts/config/config.ts index e0964075..7ba92e79 100644 --- a/hardhat-scripts/config/config.ts +++ b/hardhat-scripts/config/config.ts @@ -31,6 +31,9 @@ export const getChains = () => { return [ChainSlug.ARBITRUM_SEPOLIA, ChainSlug.OPTIMISM_SEPOLIA]; case DeploymentMode.STAGE: return [ + ChainSlug.BASE, + ChainSlug.ARBITRUM, + ChainSlug.OPTIMISM, ChainSlug.OPTIMISM_SEPOLIA, ChainSlug.ARBITRUM_SEPOLIA, ChainSlug.BASE_SEPOLIA, @@ -47,6 +50,32 @@ export const getChains = () => { } }; +export const getFeesPlugChains = (): Array => { + switch (mode) { + case DeploymentMode.LOCAL: + return getChains(); + case DeploymentMode.DEV: + return getChains(); + case DeploymentMode.STAGE: + return [ChainSlug.OPTIMISM, ChainSlug.ARBITRUM, ChainSlug.BASE]; + case DeploymentMode.PROD: + return getChains(); + default: + throw new Error(`Invalid deployment mode: ${mode}`); + } +}; + +export const testnetChains: Array = [ + ChainSlug.OPTIMISM_SEPOLIA, + ChainSlug.ARBITRUM_SEPOLIA, + ChainSlug.BASE_SEPOLIA, +]; +export const mainnetChains: Array = [ + ChainSlug.OPTIMISM, + ChainSlug.ARBITRUM, + ChainSlug.BASE, +]; + export const chains: Array = getChains(); export const EVM_CHAIN_ID_MAP: Record = { [DeploymentMode.LOCAL]: 7625382, @@ -54,34 +83,36 @@ export const EVM_CHAIN_ID_MAP: Record = { [DeploymentMode.STAGE]: 43, [DeploymentMode.PROD]: 3605, }; + // Addresses export const watcher = "0xb62505feacC486e809392c65614Ce4d7b051923b"; export const transmitter = "0x138e9840861C983DC0BB9b3e941FB7C0e9Ade320"; // Chain config export const EVMX_CHAIN_ID = EVM_CHAIN_ID_MAP[mode]; -export const MAX_FEES = ethers.utils.parseEther("0.001"); +export const MAX_MSG_VALUE_LIMIT = ethers.utils.parseEther("0.001"); // Auction parameters -export const auctionEndDelaySeconds = 0; +export const AUCTION_END_DELAY_SECONDS = 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; + +// Fees Pool Funding Amount +export const FEES_POOL_FUNDING_AMOUNT_THRESHOLD = + ethers.utils.parseEther("1000"); // 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"); +export const READ_FEES = utils.parseEther("0.000001"); +export const TRIGGER_FEES = utils.parseEther("0.000001"); +export const WRITE_FEES = utils.parseEther("0.000001"); +export const SCHEDULE_FEES_PER_SECOND = utils.parseEther("0.00000001"); +export const SCHEDULE_CALLBACK_FEES = utils.parseEther("0.000001"); +export const MAX_SCHEDULE_DELAY_SECONDS = 60 * 60 * 24; // Other constants -export const DEFAULT_MAX_LIMIT = 100; export const UPGRADE_VERSION = 1; + +// Transmitter constants +export const TRANSMITTER_CREDIT_THRESHOLD = ethers.utils.parseEther("100"); // 100 ETH threshold +export const TRANSMITTER_NATIVE_THRESHOLD = ethers.utils.parseEther("100"); // 100 ETH threshold diff --git a/hardhat-scripts/constants/constants.ts b/hardhat-scripts/constants/constants.ts index bd93d8d3..71aa09e1 100644 --- a/hardhat-scripts/constants/constants.ts +++ b/hardhat-scripts/constants/constants.ts @@ -1,4 +1,5 @@ -import { keccak256, id } from "ethers/lib/utils"; +import { constants, ethers } from "ethers"; +import { id } from "ethers/lib/utils"; export const ETH_ADDRESS = "0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE"; @@ -6,3 +7,8 @@ export const IMPLEMENTATION_SLOT = "0x360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc"; export const FAST_SWITCHBOARD_TYPE = id("FAST"); + +export const ZERO_APP_GATEWAY_ID = ethers.utils.hexZeroPad( + constants.AddressZero, + 32 +); diff --git a/hardhat-scripts/constants/enums.ts b/hardhat-scripts/constants/enums.ts deleted file mode 100644 index 011570b2..00000000 --- a/hardhat-scripts/constants/enums.ts +++ /dev/null @@ -1,21 +0,0 @@ -export enum CORE_CONTRACTS { - Socket = "Socket", - SocketBatcher = "SocketBatcher", - FastSwitchboard = "FastSwitchboard", - FeesPlug = "FeesPlug", - ContractFactoryPlug = "ContractFactoryPlug", - TestUSDC = "TestUSDC", - SocketFeeManager = "SocketFeeManager", -} - -export enum EVMxCoreContracts { - WatcherPrecompile = "WatcherPrecompile", - WatcherPrecompileLimits = "WatcherPrecompileLimits", - WatcherPrecompileConfig = "WatcherPrecompileConfig", - AuctionManager = "AuctionManager", - FeesManager = "FeesManager", - DeliveryHelper = "DeliveryHelper", - Forwarder = "Forwarder", - AsyncPromise = "AsyncPromise", - AddressResolver = "AddressResolver", -} diff --git a/hardhat-scripts/constants/feeConstants.ts b/hardhat-scripts/constants/feeConstants.ts new file mode 100644 index 00000000..0c610e3e --- /dev/null +++ b/hardhat-scripts/constants/feeConstants.ts @@ -0,0 +1,30 @@ +import { DeploymentMode } from "../../src"; +import { TokenMap } from "./types"; + +const tokens: TokenMap = { + [DeploymentMode.DEV]: { + 421614: ["0x2321BF7AdFaf49b1338F1Cd474859dBc0D8dfA96"], + 11155420: ["0x15dbE4B96306Cc9Eba15D834d6c1a895cF4e1697"], + }, + [DeploymentMode.STAGE]: { + 8453: ["0x833589fcd6edb6e08f4c7c32d4f71b54bda02913"], + 42161: ["0xaf88d065e77c8cc2239327c5edb3a432268e5831"], + 10: ["0x0b2c639c533813f4aa9d7837caf62653d097ff85"], + }, +}; + +const feePools: { [key: string]: string } = { + [DeploymentMode.DEV]: "0xc20Be67ef742202dc93A78aa741E7C3715eA1DFd", + [DeploymentMode.STAGE]: "0xe2054B575664dfDBD7a7FbAf2B12420ae88DE0FF", +}; + +export const getFeeTokens = ( + mode: DeploymentMode, + chainSlug: number +): string[] => { + return tokens[mode][chainSlug] || []; +}; + +export const getFeePool = (mode: DeploymentMode): string => { + return feePools[mode]; +}; diff --git a/hardhat-scripts/constants/index.ts b/hardhat-scripts/constants/index.ts index e42be831..fb57bb11 100644 --- a/hardhat-scripts/constants/index.ts +++ b/hardhat-scripts/constants/index.ts @@ -1,3 +1,3 @@ export * from "./constants"; -export * from "./enums"; +export * from "./feeConstants"; export * from "./types"; diff --git a/hardhat-scripts/constants/roles.ts b/hardhat-scripts/constants/roles.ts index ec875446..31a4493b 100644 --- a/hardhat-scripts/constants/roles.ts +++ b/hardhat-scripts/constants/roles.ts @@ -5,4 +5,5 @@ export enum ROLES { WATCHER_ROLE = "WATCHER_ROLE", TRANSMITTER_ROLE = "TRANSMITTER_ROLE", SWITCHBOARD_DISABLER_ROLE = "SWITCHBOARD_DISABLER_ROLE", + FEE_MANAGER_ROLE = "FEE_MANAGER_ROLE", } diff --git a/hardhat-scripts/constants/types.ts b/hardhat-scripts/constants/types.ts index 0cd19bef..3e41d235 100644 --- a/hardhat-scripts/constants/types.ts +++ b/hardhat-scripts/constants/types.ts @@ -3,3 +3,21 @@ import { ChainAddressesObj, ChainSlug } from "../../src"; export type DeploymentAddresses = { [chainSlug in ChainSlug]?: ChainAddressesObj; }; + +export type TokenMap = { [key: string]: { [chainSlug: number]: string[] } }; + +export interface WatcherMultiCallParams { + contractAddress: string; + data: string; + nonce: number; + signature: string; +} + +export type AppGatewayConfig = { + plugConfig: { + appGatewayId: string; + switchboard: string; + }; + plug: string; + chainSlug: number; +}; diff --git a/hardhat-scripts/deploy/1.deploy.ts b/hardhat-scripts/deploy/1.deploy.ts index fdb51e5a..60581ec7 100644 --- a/hardhat-scripts/deploy/1.deploy.ts +++ b/hardhat-scripts/deploy/1.deploy.ts @@ -1,44 +1,38 @@ -import { ChainAddressesObj, ChainSlug } from "../../src"; import { config } from "dotenv"; -import { BigNumber, Contract, Signer, utils, Wallet } from "ethers"; +import { Contract, utils, Wallet } from "ethers"; import { formatEther } from "ethers/lib/utils"; import { ethers } from "hardhat"; +import { ChainAddressesObj, ChainSlug, Contracts } from "../../src"; +import { + AUCTION_END_DELAY_SECONDS, + BID_TIMEOUT, + chains, + EVMX_CHAIN_ID, + EXPIRY_TIME, + getFeesPlugChains, + logConfig, + MAX_RE_AUCTION_COUNT, + MAX_SCHEDULE_DELAY_SECONDS, + mode, + READ_FEES, + SCHEDULE_CALLBACK_FEES, + SCHEDULE_FEES_PER_SECOND, + TRIGGER_FEES, + WRITE_FEES, +} from "../config/config"; import { - CORE_CONTRACTS, DeploymentAddresses, - ETH_ADDRESS, - EVMxCoreContracts, FAST_SWITCHBOARD_TYPE, + getFeePool, IMPLEMENTATION_SLOT, } from "../constants"; import { DeployParams, getAddresses, - getInstance, getOrDeploy, storeAddresses, } from "../utils"; import { getSocketSigner, getWatcherSigner } from "../utils/sign"; -import { - auctionEndDelaySeconds, - BID_TIMEOUT, - chains, - EVMX_CHAIN_ID, - EXPIRY_TIME, - logConfig, - 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(); let EVMxOwner: string; @@ -111,169 +105,150 @@ const deployEVMxContracts = async () => { ); deployUtils.addresses[contractName] = proxyFactory.address; + const feePool = getFeePool(mode); + if (feePool.length == 0) { + const feesPool = await getOrDeploy( + Contracts.FeesPool, + Contracts.FeesPool, + "contracts/evmx/fees/FeesPool.sol", + [EVMxOwner], + deployUtils + ); + deployUtils.addresses[Contracts.FeesPool] = feesPool.address; + } else { + deployUtils.addresses[Contracts.FeesPool] = feePool; + } + deployUtils = await deployContractWithProxy( - EVMxCoreContracts.AddressResolver, - `contracts/protocol/AddressResolver.sol`, + Contracts.AddressResolver, + `contracts/evmx/helpers/AddressResolver.sol`, [EVMxOwner], proxyFactory, deployUtils ); const addressResolver = await ethers.getContractAt( - EVMxCoreContracts.AddressResolver, - deployUtils.addresses[EVMxCoreContracts.AddressResolver] + Contracts.AddressResolver, + deployUtils.addresses[Contracts.AddressResolver] ); deployUtils = await deployContractWithProxy( - EVMxCoreContracts.WatcherPrecompileLimits, - `contracts/protocol/watcherPrecompile/WatcherPrecompileLimits.sol`, - [EVMxOwner, addressResolver.address, DEFAULT_MAX_LIMIT], + Contracts.FeesManager, + `contracts/evmx/fees/FeesManager.sol`, + [ + EVMX_CHAIN_ID, + addressResolver.address, + deployUtils.addresses[Contracts.FeesPool], + EVMxOwner, + FAST_SWITCHBOARD_TYPE, + ], proxyFactory, deployUtils ); deployUtils = await deployContractWithProxy( - EVMxCoreContracts.WatcherPrecompileConfig, - `contracts/protocol/watcherPrecompile/WatcherPrecompileConfig.sol`, - [EVMxOwner, addressResolver.address, EVMX_CHAIN_ID], + Contracts.AsyncDeployer, + `contracts/evmx/helpers/AsyncDeployer.sol`, + [EVMxOwner, addressResolver.address], proxyFactory, deployUtils ); deployUtils = await deployContractWithProxy( - EVMxCoreContracts.WatcherPrecompile, - `contracts/protocol/watcherPrecompile/core/WatcherPrecompile.sol`, - [ - EVMxOwner, - addressResolver.address, - EXPIRY_TIME, - EVMX_CHAIN_ID, - deployUtils.addresses[EVMxCoreContracts.WatcherPrecompileLimits], - deployUtils.addresses[EVMxCoreContracts.WatcherPrecompileConfig], - ], + Contracts.Watcher, + `contracts/evmx/watcher/Watcher.sol`, + [EVMX_CHAIN_ID, TRIGGER_FEES, EVMxOwner, addressResolver.address], proxyFactory, deployUtils ); deployUtils = await deployContractWithProxy( - EVMxCoreContracts.FeesManager, - `contracts/protocol/payload-delivery/FeesManager.sol`, + Contracts.AuctionManager, + `contracts/evmx/AuctionManager.sol`, [ + EVMX_CHAIN_ID, + BID_TIMEOUT, + MAX_RE_AUCTION_COUNT, + AUCTION_END_DELAY_SECONDS, addressResolver.address, EVMxOwner, - EVMX_CHAIN_ID, - FAST_SWITCHBOARD_TYPE, ], proxyFactory, deployUtils ); - const feesManagerAddress = - deployUtils.addresses[EVMxCoreContracts.FeesManager]; - - console.log("Deploying DeliveryHelper"); deployUtils = await deployContractWithProxy( - EVMxCoreContracts.DeliveryHelper, - `contracts/protocol/payload-delivery/app-gateway/DeliveryHelper.sol`, - [addressResolver.address, EVMxOwner, BID_TIMEOUT], + Contracts.DeployForwarder, + `contracts/evmx/helpers/DeployForwarder.sol`, + [EVMxOwner, addressResolver.address, FAST_SWITCHBOARD_TYPE], proxyFactory, deployUtils ); deployUtils = await deployContractWithProxy( - EVMxCoreContracts.AuctionManager, - `contracts/protocol/payload-delivery/AuctionManager.sol`, - [ - EVMX_CHAIN_ID, - auctionEndDelaySeconds, - addressResolver.address, - EVMxOwner, - MAX_RE_AUCTION_COUNT, - ], + Contracts.Configurations, + `contracts/evmx/watcher/Configurations.sol`, + [deployUtils.addresses[Contracts.Watcher], EVMxOwner], proxyFactory, deployUtils ); - await updateContractSettings( - addressResolver, - "deliveryHelper", - "setDeliveryHelper", - deployUtils.addresses[EVMxCoreContracts.DeliveryHelper], - deployUtils.signer - ); - - await updateContractSettings( - addressResolver, - "feesManager", - "setFeesManager", - feesManagerAddress, - deployUtils.signer - ); - - await updateContractSettings( - addressResolver, - "defaultAuctionManager", - "setDefaultAuctionManager", - deployUtils.addresses[EVMxCoreContracts.AuctionManager], - deployUtils.signer - ); - - await updateContractSettings( - addressResolver, - "watcherPrecompile__", - "setWatcherPrecompile", - deployUtils.addresses[EVMxCoreContracts.WatcherPrecompile], - deployUtils.signer + deployUtils = await deployContractWithProxy( + Contracts.RequestHandler, + `contracts/evmx/watcher/RequestHandler.sol`, + [EVMxOwner, addressResolver.address], + proxyFactory, + deployUtils ); - let watcherPrecompileLimits = await getInstance( - EVMxCoreContracts.WatcherPrecompileLimits, - deployUtils.addresses[EVMxCoreContracts.WatcherPrecompileLimits] - ); - watcherPrecompileLimits = watcherPrecompileLimits.connect( - deployUtils.signer - ); - await updateContractSettings( - watcherPrecompileLimits, - "queryFees", - "setQueryFees", - QUERY_FEES, - deployUtils.signer + const promiseResolver = await getOrDeploy( + Contracts.PromiseResolver, + Contracts.PromiseResolver, + "contracts/evmx/watcher/PromiseResolver.sol", + [deployUtils.addresses[Contracts.Watcher]], + deployUtils ); + deployUtils.addresses[Contracts.PromiseResolver] = + promiseResolver.address; - await updateContractSettings( - watcherPrecompileLimits, - "finalizeFees", - "setFinalizeFees", - FINALIZE_FEES, - deployUtils.signer + deployUtils = await deployContractWithProxy( + Contracts.WritePrecompile, + `contracts/evmx/watcher/precompiles/WritePrecompile.sol`, + [ + EVMxOwner, + deployUtils.addresses[Contracts.Watcher], + WRITE_FEES, + EXPIRY_TIME, + ], + proxyFactory, + deployUtils ); - await updateContractSettings( - watcherPrecompileLimits, - "timeoutFees", - "setTimeoutFees", - TIMEOUT_FEES, - deployUtils.signer + const readPrecompile = await getOrDeploy( + Contracts.ReadPrecompile, + Contracts.ReadPrecompile, + "contracts/evmx/watcher/precompiles/ReadPrecompile.sol", + [deployUtils.addresses[Contracts.Watcher], READ_FEES, EXPIRY_TIME], + deployUtils ); + deployUtils.addresses[Contracts.ReadPrecompile] = readPrecompile.address; - await updateContractSettings( - watcherPrecompileLimits, - "callBackFees", - "setCallBackFees", - CALLBACK_FEES, - deployUtils.signer + const schedulePrecompile = await getOrDeploy( + Contracts.SchedulePrecompile, + Contracts.SchedulePrecompile, + "contracts/evmx/watcher/precompiles/SchedulePrecompile.sol", + [ + deployUtils.addresses[Contracts.Watcher], + MAX_SCHEDULE_DELAY_SECONDS, + SCHEDULE_FEES_PER_SECOND, + SCHEDULE_CALLBACK_FEES, + EXPIRY_TIME, + ], + deployUtils ); + deployUtils.addresses[Contracts.SchedulePrecompile] = + schedulePrecompile.address; - 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 @@ -289,56 +264,6 @@ 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; @@ -367,70 +292,53 @@ const deploySocketContracts = async () => { currentChainSlug: chain as ChainSlug, }; - let contractName = CORE_CONTRACTS.Socket; + let contractName = Contracts.Socket; const socket: Contract = await getOrDeploy( contractName, contractName, - `contracts/protocol/socket/${contractName}.sol`, + `contracts/protocol/${contractName}.sol`, [chain as ChainSlug, socketOwner, "EVMX"], deployUtils ); deployUtils.addresses[contractName] = socket.address; - contractName = CORE_CONTRACTS.SocketBatcher; + contractName = Contracts.SocketBatcher; const batcher: Contract = await getOrDeploy( contractName, contractName, - `contracts/protocol/socket/${contractName}.sol`, + `contracts/protocol/${contractName}.sol`, [socketOwner, socket.address], deployUtils ); deployUtils.addresses[contractName] = batcher.address; - contractName = CORE_CONTRACTS.FastSwitchboard; + contractName = Contracts.FastSwitchboard; const sb: Contract = await getOrDeploy( contractName, contractName, - `contracts/protocol/socket/switchboard/${contractName}.sol`, + `contracts/protocol/switchboard/${contractName}.sol`, [chain as ChainSlug, socket.address, socketOwner], deployUtils ); 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, - contractName, - `contracts/protocol/payload-delivery/${contractName}.sol`, - [socket.address, socketOwner], - deployUtils - ); - deployUtils.addresses[contractName] = feesPlug.address; - - await whitelistToken(feesPlug, testUSDC.address, deployUtils.signer); - - contractName = CORE_CONTRACTS.ContractFactoryPlug; + if (getFeesPlugChains().includes(chain as ChainSlug)) { + contractName = Contracts.FeesPlug; + const feesPlug: Contract = await getOrDeploy( + contractName, + contractName, + `contracts/evmx/plugs/${contractName}.sol`, + [socket.address, socketOwner], + deployUtils + ); + deployUtils.addresses[contractName] = feesPlug.address; + } + + contractName = Contracts.ContractFactoryPlug; const contractFactoryPlug: Contract = await getOrDeploy( contractName, contractName, - `contracts/protocol/payload-delivery/${contractName}.sol`, + `contracts/evmx/plugs/${contractName}.sol`, [socket.address, socketOwner], deployUtils ); @@ -456,50 +364,6 @@ const deploySocketContracts = async () => { } }; -async function whitelistToken( - feesPlug: Contract, - tokenAddress: string, - signer: Signer -) { - 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(); - } -} - -async function updateContractSettings( - contract: Contract, - getterMethod: string, - setterMethod: string, - requiredValue: string | BigNumber, - signer: Signer -) { - const currentValue = await contract.connect(signer)[getterMethod](); - - if ( - (typeof currentValue === "string" && - currentValue.toLowerCase() !== String(requiredValue).toLowerCase()) || - (BigNumber.isBigNumber(currentValue) && - currentValue.toString() !== requiredValue.toString()) - ) { - console.log({ - setterMethod, - current: currentValue, - required: requiredValue, - }); - const tx = await contract.connect(signer)[setterMethod](requiredValue); - console.log(`Setting ${getterMethod} for ${contract.address} to`, tx.hash); - await tx.wait(); - } -} - /** * @notice Deploys a contract implementation and its transparent proxy, then initializes it * @param contractName The name of the contract to deploy @@ -532,19 +396,24 @@ const deployContractWithProxy = async ( ); const newImplementation = implementation.address; - console.log("Current implementation:", currentImplAddress); - console.log("New implementation:", newImplementation); + console.log( + "Current implementation for", + contractName, + ":", + currentImplAddress + ); + console.log("New implementation for", contractName, ":", newImplementation); if (currentImplAddress.toLowerCase() === newImplementation.toLowerCase()) return deployUtils; - console.log("Upgrading contract"); + console.log("Upgrading contract: ", contractName); const tx = await proxyFactory .connect(deployUtils.signer) .upgrade(deployUtils.addresses[contractName], newImplementation); - console.log("Upgraded contract", tx.hash); + console.log("Upgraded contract", contractName, tx.hash); await tx.wait(); diff --git a/hardhat-scripts/deploy/2.roles.ts b/hardhat-scripts/deploy/2.roles.ts index 8ef83acc..79723cec 100644 --- a/hardhat-scripts/deploy/2.roles.ts +++ b/hardhat-scripts/deploy/2.roles.ts @@ -3,34 +3,31 @@ dotenvConfig(); import { Wallet } from "ethers"; import { chains, EVMX_CHAIN_ID, mode, watcher, transmitter } from "../config"; -import { - CORE_CONTRACTS, - DeploymentAddresses, - EVMxCoreContracts, -} from "../constants"; -import { - getAddresses, - getInstance, - getProviderFromChainSlug, - getRoleHash, - overrides, -} from "../utils"; -import { ChainAddressesObj, ChainSlug } from "../../src"; +import { DeploymentAddresses } from "../constants"; +import { getAddresses, getInstance, getRoleHash, overrides } from "../utils"; +import { ChainAddressesObj, ChainSlug, Contracts } from "../../src"; 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, - ROLES.SWITCHBOARD_DISABLER_ROLE, - ], - FeesPlug: [ROLES.RESCUE_ROLE], - ContractFactoryPlug: [ROLES.RESCUE_ROLE], + EVMx: { + AuctionManager: [ROLES.TRANSMITTER_ROLE], + FeesPool: [ROLES.FEE_MANAGER_ROLE], + }, + Chain: { + FastSwitchboard: [ROLES.WATCHER_ROLE, ROLES.RESCUE_ROLE], + Socket: [ + ROLES.GOVERNANCE_ROLE, + ROLES.RESCUE_ROLE, + ROLES.SWITCHBOARD_DISABLER_ROLE, + ], + FeesPlug: [ROLES.RESCUE_ROLE], + ContractFactoryPlug: [ROLES.RESCUE_ROLE], + }, }; async function setRoleForContract( - contractName: CORE_CONTRACTS | EVMxCoreContracts, + contractName: Contracts, contractAddress: string | number, targetAddress: string, roleName: string, @@ -52,7 +49,7 @@ async function setRoleForContract( if (!hasRole) { let tx = await contract.grantRole(roleHash, targetAddress, { - ...overrides(chain), + ...(await overrides(chain as ChainSlug)), }); console.log( `granting ${roleName} role to ${targetAddress} for ${contractName}`, @@ -71,28 +68,25 @@ async function getSigner(chain: number, isWatcher: boolean = false) { return signer; } -async function setRolesForOnChain( - chain: number, - addresses: DeploymentAddresses -) { +async function setRolesOnChain(chain: number, addresses: DeploymentAddresses) { const chainAddresses: ChainAddressesObj = (addresses[chain] ?? {}) as ChainAddressesObj; const signer = await getSigner(chain); - for (const [contractName, roles] of Object.entries(REQUIRED_ROLES)) { + for (const [contractName, roles] of Object.entries(REQUIRED_ROLES["Chain"])) { const contractAddress = chainAddresses[contractName as keyof ChainAddressesObj]; if (!contractAddress) continue; for (const roleName of roles) { const targetAddress = - contractName === CORE_CONTRACTS.FastSwitchboard && + contractName === Contracts.FastSwitchboard && roleName === ROLES.WATCHER_ROLE ? watcher : signer.address; await setRoleForContract( - contractName as CORE_CONTRACTS, + contractName as Contracts, contractAddress, targetAddress, roleName, @@ -108,17 +102,23 @@ async function setRolesForEVMx(addresses: DeploymentAddresses) { {}) as ChainAddressesObj; const signer = await getSigner(EVMX_CHAIN_ID, true); - const contractAddress = chainAddresses[EVMxCoreContracts.WatcherPrecompile]; - if (!contractAddress) return; - await setRoleForContract( - EVMxCoreContracts.AuctionManager, - chainAddresses[EVMxCoreContracts.AuctionManager], + Contracts.AuctionManager, + chainAddresses[Contracts.AuctionManager], transmitter, ROLES.TRANSMITTER_ROLE, signer, EVMX_CHAIN_ID ); + + await setRoleForContract( + Contracts.FeesPool, + chainAddresses[Contracts.FeesPool], + chainAddresses[Contracts.FeesManager], + ROLES.FEE_MANAGER_ROLE, + signer, + EVMX_CHAIN_ID + ); } export const main = async () => { @@ -127,7 +127,7 @@ export const main = async () => { const addresses = getAddresses(mode) as unknown as DeploymentAddresses; console.log("Setting Roles for On Chain"); for (const chain of chains) { - await setRolesForOnChain(chain, addresses); + await setRolesOnChain(chain, addresses); } await setRolesForEVMx(addresses); } catch (error) { diff --git a/hardhat-scripts/deploy/3.configureChains.ts b/hardhat-scripts/deploy/3.configureChains.ts new file mode 100644 index 00000000..54a3cb11 --- /dev/null +++ b/hardhat-scripts/deploy/3.configureChains.ts @@ -0,0 +1,198 @@ +import { config as dotenvConfig } from "dotenv"; +dotenvConfig(); + +import { Contract, Signer, Wallet } from "ethers"; +import { ChainAddressesObj, ChainSlug, Contracts } from "../../src"; +import { chains, EVMX_CHAIN_ID, MAX_MSG_VALUE_LIMIT, mode } from "../config"; +import { + DeploymentAddresses, + FAST_SWITCHBOARD_TYPE, + getFeeTokens, +} from "../constants"; +import { + getAddresses, + getInstance, + getSocketSigner, + getWatcherSigner, + overrides, + updateContractSettings, +} from "../utils"; + +export const main = async () => { + let addresses: DeploymentAddresses; + try { + console.log("Configuring chain contracts"); + addresses = getAddresses(mode) as unknown as DeploymentAddresses; + await configureChains(addresses); + } catch (error) { + console.log("Error:", error); + } +}; + +export const configureChains = async (addresses: DeploymentAddresses) => { + for (const chain of chains) { + let chainAddresses: ChainAddressesObj = addresses[chain] + ? (addresses[chain] as ChainAddressesObj) + : ({} as ChainAddressesObj); + + const signer: Wallet = getSocketSigner(chain as ChainSlug); + + const socketContract = ( + await getInstance(Contracts.Socket, chainAddresses[Contracts.Socket]) + ).connect(signer); + + await registerSb( + chain, + chainAddresses[Contracts.FastSwitchboard], + signer, + socketContract + ); + + if (chainAddresses[Contracts.FeesPlug]) { + await whitelistToken(chain, chainAddresses[Contracts.FeesPlug], signer); + } + + await setMaxMsgValueLimit(chain); + + await setOnchainContracts(chain, addresses); + } +}; + +export const setMaxMsgValueLimit = async (chain: number) => { + console.log("Setting max msg value limit"); + const signer: Wallet = getWatcherSigner(); + await updateContractSettings( + EVMX_CHAIN_ID, + Contracts.WritePrecompile, + "chainMaxMsgValueLimit", + [chain], + MAX_MSG_VALUE_LIMIT, + "updateChainMaxMsgValueLimits", + [chain, MAX_MSG_VALUE_LIMIT], + signer + ); +}; + +async function setOnchainContracts( + chain: number, + addresses: DeploymentAddresses +) { + console.log("Setting onchain contracts"); + const signer: Wallet = getWatcherSigner(); + const chainAddresses = addresses[chain] as ChainAddressesObj; + + await updateContractSettings( + EVMX_CHAIN_ID, + Contracts.Configurations, + "switchboards", + [chain, FAST_SWITCHBOARD_TYPE], + chainAddresses[Contracts.FastSwitchboard], + "setSwitchboard", + [chain, FAST_SWITCHBOARD_TYPE, chainAddresses[Contracts.FastSwitchboard]], + signer + ); + await updateContractSettings( + EVMX_CHAIN_ID, + Contracts.Configurations, + "sockets", + [chain], + chainAddresses[Contracts.Socket], + "setSocket", + [chain, chainAddresses[Contracts.Socket]], + signer + ); + + if (chainAddresses[Contracts.FeesPlug]) + await updateContractSettings( + EVMX_CHAIN_ID, + Contracts.FeesManager, + "feesPlugs", + [chain], + chainAddresses[Contracts.FeesPlug], + "setFeesPlug", + [chain, chainAddresses[Contracts.FeesPlug]], + signer + ); + + await updateContractSettings( + EVMX_CHAIN_ID, + Contracts.WritePrecompile, + "contractFactoryPlugs", + [chain], + chainAddresses[Contracts.ContractFactoryPlug], + "setContractFactoryPlugs", + [chain, chainAddresses[Contracts.ContractFactoryPlug]], + signer + ); +} + +const registerSb = async ( + chain: number, + sbAddress: string, + signer: Wallet, + socket: Contract +) => { + try { + console.log("Registering switchboard"); + // used fast switchboard here as all have same function signature + const switchboard = ( + await getInstance(Contracts.FastSwitchboard, sbAddress) + ).connect(signer); + + // send overrides while reading capacitor to avoid errors on mantle chain + // some chains give balance error if gas price is used with from address as zero + // therefore override from address as well + let sb = await socket.isValidSwitchboard(sbAddress, { + from: signer.address, + }); + + if (Number(sb) == 0) { + const registerTx = await switchboard.registerSwitchboard({ + ...(await overrides(chain)), + }); + console.log(`Registering Switchboard ${sbAddress}: ${registerTx.hash}`); + await registerTx.wait(); + } + } catch (error) { + throw error; + } +}; + +export const whitelistToken = async ( + chain: number, + feesPlugAddress: string, + signer: Signer +) => { + console.log("Whitelisting token"); + + const feesPlugContract = ( + await getInstance(Contracts.FeesPlug, feesPlugAddress) + ).connect(signer); + + const tokens = getFeeTokens(mode, chain); + if (tokens.length == 0) return; + + for (const token of tokens) { + const isWhitelisted = await feesPlugContract.whitelistedTokens(token); + + if (!isWhitelisted) { + const tx = await feesPlugContract.whitelistToken(token, { + ...(await overrides(chain)), + }); + console.log( + `Whitelisting token ${token} for ${feesPlugContract.address}`, + tx.hash + ); + await tx.wait(); + } else { + console.log(`Token ${token} is already whitelisted`); + } + } +}; + +main() + .then(() => process.exit(0)) + .catch((error: Error) => { + console.error(error); + process.exit(1); + }); diff --git a/hardhat-scripts/deploy/3.upgradeManagers.ts b/hardhat-scripts/deploy/3.upgradeManagers.ts deleted file mode 100644 index 2c3a261c..00000000 --- a/hardhat-scripts/deploy/3.upgradeManagers.ts +++ /dev/null @@ -1,143 +0,0 @@ -import { ChainAddressesObj, ChainSlug, EVMxAddressesObj } from "../../src"; - -import { config as dotenvConfig } from "dotenv"; -dotenvConfig(); - -import { Wallet } from "ethers"; -import { chains, EVMX_CHAIN_ID, mode } from "../config"; -import { - CORE_CONTRACTS, - DeploymentAddresses, - EVMxCoreContracts, - FAST_SWITCHBOARD_TYPE, -} from "../constants"; -import { - getAddresses, - getInstance, - getSocketSigner, - getWatcherSigner, - storeAddresses, -} from "../utils"; - -export const main = async () => { - let addresses: DeploymentAddresses; - try { - console.log("Upgrading Managers"); - addresses = getAddresses(mode) as unknown as DeploymentAddresses; - - for (const chain of chains) { - let chainAddresses: ChainAddressesObj = addresses[chain] - ? (addresses[chain] as ChainAddressesObj) - : ({} as ChainAddressesObj); - - const signer: Wallet = getSocketSigner(chain as ChainSlug); - - const socketContract = ( - await getInstance( - CORE_CONTRACTS.Socket, - chainAddresses[CORE_CONTRACTS.Socket] - ) - ).connect(signer); - - await registerSb( - chainAddresses[CORE_CONTRACTS.FastSwitchboard], - signer, - socketContract - ); - - await setOnchainContracts(chain, addresses); - - await storeAddresses(chainAddresses, chain, mode); - } - } catch (error) { - console.log("Error:", error); - } -}; - -async function setOnchainContracts(chain: number, addresses) { - const signer: Wallet = getWatcherSigner(); - const EVMxAddresses = addresses[EVMX_CHAIN_ID] as EVMxAddressesObj; - const chainAddresses = addresses[chain] as ChainAddressesObj; - const watcherPrecompileConfig = ( - await getInstance( - EVMxCoreContracts.WatcherPrecompileConfig, - EVMxAddresses[EVMxCoreContracts.WatcherPrecompileConfig] - ) - ).connect(signer); - - const sbAddress = chainAddresses[CORE_CONTRACTS.FastSwitchboard]; - const socketAddress = chainAddresses[CORE_CONTRACTS.Socket]; - const contractFactoryPlugAddress = - chainAddresses[CORE_CONTRACTS.ContractFactoryPlug]; - const feesPlugAddress = chainAddresses[CORE_CONTRACTS.FeesPlug]; - - const currentSbAddress = await watcherPrecompileConfig.switchboards( - chain, - FAST_SWITCHBOARD_TYPE - ); - const currentSocket = await watcherPrecompileConfig.sockets(chain); - const currentContractFactoryPlug = - await watcherPrecompileConfig.contractFactoryPlug(chain); - const currentFeesPlug = await watcherPrecompileConfig.feesPlug(chain); - - console.log("Setting onchain contracts for", chain); - if ( - currentSocket.toLowerCase() !== socketAddress.toLowerCase() || - currentContractFactoryPlug.toLowerCase() !== - contractFactoryPlugAddress.toLowerCase() || - currentFeesPlug.toLowerCase() !== feesPlugAddress.toLowerCase() - ) { - const tx = await watcherPrecompileConfig - .connect(signer) - .setOnChainContracts( - chain, - socketAddress, - contractFactoryPlugAddress, - feesPlugAddress - ); - - console.log(`Setting onchain contracts for ${chain}, txHash: `, tx.hash); - await tx.wait(); - } - - console.log("Setting switchboard for", chain); - if (currentSbAddress.toLowerCase() !== sbAddress.toLowerCase()) { - const tx = await watcherPrecompileConfig - .connect(signer) - .setSwitchboard(chain, FAST_SWITCHBOARD_TYPE, sbAddress); - - console.log(`Setting switchboard for ${chain}, txHash: `, tx.hash); - await tx.wait(); - } -} - -const registerSb = async (sbAddress, signer, socket) => { - try { - // used fast switchboard here as all have same function signature - const switchboard = ( - await getInstance(CORE_CONTRACTS.FastSwitchboard, sbAddress) - ).connect(signer); - - // send overrides while reading capacitor to avoid errors on mantle chain - // some chains give balance error if gas price is used with from address as zero - // therefore override from address as well - let sb = await socket.isValidSwitchboard(sbAddress, { - from: signer.address, - }); - - if (Number(sb) == 0) { - const registerTx = await switchboard.registerSwitchboard(); - console.log(`Registering Switchboard ${sbAddress}: ${registerTx.hash}`); - await registerTx.wait(); - } - } catch (error) { - throw error; - } -}; - -main() - .then(() => process.exit(0)) - .catch((error: Error) => { - console.error(error); - process.exit(1); - }); diff --git a/hardhat-scripts/deploy/4.configureEVMx.ts b/hardhat-scripts/deploy/4.configureEVMx.ts new file mode 100644 index 00000000..1b05f542 --- /dev/null +++ b/hardhat-scripts/deploy/4.configureEVMx.ts @@ -0,0 +1,161 @@ +import { config as dotenvConfig } from "dotenv"; +dotenvConfig(); + +import { Contracts, EVMxAddressesObj, READ, SCHEDULE, WRITE } from "../../src"; +import { Wallet } from "ethers"; +import { EVMX_CHAIN_ID, mode } from "../config"; +import { DeploymentAddresses } from "../constants"; +import { + getAddresses, + getInstance, + getWatcherSigner, + overrides, + updateContractSettings, +} from "../utils"; + +export const main = async () => { + let addresses: DeploymentAddresses; + try { + console.log("Configuring EVMx contracts"); + addresses = getAddresses(mode) as unknown as DeploymentAddresses; + const evmxAddresses = addresses[EVMX_CHAIN_ID] as EVMxAddressesObj; + + await configureEVMx(evmxAddresses); + } catch (error) { + console.log("Error:", error); + } +}; + +export const configureEVMx = async (evmxAddresses: EVMxAddressesObj) => { + const signer: Wallet = getWatcherSigner(); + + await updateContractSettings( + EVMX_CHAIN_ID, + Contracts.AddressResolver, + "asyncDeployer__", + [], + evmxAddresses[Contracts.AsyncDeployer], + "setAsyncDeployer", + [evmxAddresses[Contracts.AsyncDeployer]], + signer + ); + + await updateContractSettings( + EVMX_CHAIN_ID, + Contracts.AddressResolver, + "feesManager__", + [], + evmxAddresses[Contracts.FeesManager], + "setFeesManager", + [evmxAddresses[Contracts.FeesManager]], + signer + ); + + await updateContractSettings( + EVMX_CHAIN_ID, + Contracts.AddressResolver, + "defaultAuctionManager", + [], + evmxAddresses[Contracts.AuctionManager], + "setDefaultAuctionManager", + [evmxAddresses[Contracts.AuctionManager]], + signer + ); + + await updateContractSettings( + EVMX_CHAIN_ID, + Contracts.AddressResolver, + "watcher__", + [], + evmxAddresses[Contracts.Watcher], + "setWatcher", + [evmxAddresses[Contracts.Watcher]], + signer + ); + + await updateContractSettings( + EVMX_CHAIN_ID, + Contracts.AddressResolver, + "deployForwarder__", + [], + evmxAddresses[Contracts.DeployForwarder], + "setDeployForwarder", + [evmxAddresses[Contracts.DeployForwarder]], + signer + ); + + await updateContractSettings( + EVMX_CHAIN_ID, + Contracts.RequestHandler, + "precompiles", + [READ], + evmxAddresses[Contracts.ReadPrecompile], + "setPrecompile", + [READ, evmxAddresses[Contracts.ReadPrecompile]], + signer + ); + + await updateContractSettings( + EVMX_CHAIN_ID, + Contracts.RequestHandler, + "precompiles", + [WRITE], + evmxAddresses[Contracts.WritePrecompile], + "setPrecompile", + [WRITE, evmxAddresses[Contracts.WritePrecompile]], + signer + ); + + await updateContractSettings( + EVMX_CHAIN_ID, + Contracts.RequestHandler, + "precompiles", + [SCHEDULE], + evmxAddresses[Contracts.SchedulePrecompile], + "setPrecompile", + [SCHEDULE, evmxAddresses[Contracts.SchedulePrecompile]], + signer + ); + + await setWatcherCoreContracts(evmxAddresses); +}; + +export const setWatcherCoreContracts = async ( + evmxAddresses: EVMxAddressesObj +) => { + const watcherContract = ( + await getInstance(Contracts.Watcher, evmxAddresses[Contracts.Watcher]) + ).connect(getWatcherSigner()); + + const requestHandlerSet = await watcherContract.requestHandler__(); + const PromiseResolverSet = await watcherContract.promiseResolver__(); + const ConfigurationsSet = await watcherContract.configurations__(); + + if ( + requestHandlerSet.toLowerCase() !== + evmxAddresses[Contracts.RequestHandler].toLowerCase() || + PromiseResolverSet.toLowerCase() !== + evmxAddresses[Contracts.PromiseResolver].toLowerCase() || + ConfigurationsSet.toLowerCase() !== + evmxAddresses[Contracts.Configurations].toLowerCase() + ) { + console.log("Setting watcher core contracts"); + const tx = await watcherContract.setCoreContracts( + evmxAddresses[Contracts.RequestHandler], + evmxAddresses[Contracts.Configurations], + evmxAddresses[Contracts.PromiseResolver], + { ...(await overrides(EVMX_CHAIN_ID)) } + ); + console.log("Watcher core contracts set tx: ", tx.hash); + await tx.wait(); + } else { + console.log("Watcher core contracts are already set"); + } +}; + +main() + .then(() => process.exit(0)) + .catch((error: Error) => { + console.error(error); + process.exit(1); + }); diff --git a/hardhat-scripts/deploy/5.fundTransfers.ts b/hardhat-scripts/deploy/5.fundTransfers.ts new file mode 100644 index 00000000..584e2ef2 --- /dev/null +++ b/hardhat-scripts/deploy/5.fundTransfers.ts @@ -0,0 +1,47 @@ +import { config } from "dotenv"; +import { Signer } from "ethers"; +import { Contracts } from "../../src"; +import { + EVMX_CHAIN_ID, + FEES_POOL_FUNDING_AMOUNT_THRESHOLD, + mode, +} from "../config/config"; +import { getAddresses, getWatcherSigner } from "../utils"; +config(); + +export const fundFeesPool = async (watcherSigner: Signer) => { + const addresses = getAddresses(mode); + const feesPoolAddress = addresses[EVMX_CHAIN_ID][Contracts.FeesPool]; + const feesPoolBalance = await watcherSigner.provider!.getBalance( + feesPoolAddress + ); + console.log({ + feesPoolAddress, + feesPoolBalance, + FEES_POOL_FUNDING_AMOUNT_THRESHOLD, + }); + if (feesPoolBalance.gte(FEES_POOL_FUNDING_AMOUNT_THRESHOLD)) { + console.log( + `Fees pool ${feesPoolAddress} already has sufficient balance, skipping funding` + ); + return; + } + + const tx = await watcherSigner.sendTransaction({ + to: feesPoolAddress, + value: FEES_POOL_FUNDING_AMOUNT_THRESHOLD, + }); + console.log( + `Funding fees pool ${feesPoolAddress} with ${FEES_POOL_FUNDING_AMOUNT_THRESHOLD} ETH, txHash: `, + tx.hash + ); + await tx.wait(); +}; + +const main = async () => { + console.log("Fund transfers"); + const watcherSigner = getWatcherSigner(); + await fundFeesPool(watcherSigner); +}; + +main(); diff --git a/hardhat-scripts/deploy/4.connect.ts b/hardhat-scripts/deploy/6.connect.ts similarity index 60% rename from hardhat-scripts/deploy/4.connect.ts rename to hardhat-scripts/deploy/6.connect.ts index 71223808..cd9fcf62 100644 --- a/hardhat-scripts/deploy/4.connect.ts +++ b/hardhat-scripts/deploy/6.connect.ts @@ -1,69 +1,28 @@ -import { constants, Contract, ethers, Wallet } from "ethers"; -import { ChainAddressesObj, ChainSlug } from "../../src"; +import { Contract, Wallet } from "ethers"; +import { ChainAddressesObj, ChainSlug, Contracts } from "../../src"; import { chains, EVMX_CHAIN_ID, mode } from "../config"; +import { AppGatewayConfig, DeploymentAddresses } from "../constants"; import { - CORE_CONTRACTS, - DeploymentAddresses, - EVMxCoreContracts, -} from "../constants"; -import { + checkIfAddressExists, + checkIfAppGatewayIdExists, getAddresses, + getAppGatewayId, getInstance, getSocketSigner, overrides, } from "../utils"; -import { getWatcherSigner, signWatcherMessage } from "../utils/sign"; -const plugs = [CORE_CONTRACTS.ContractFactoryPlug, CORE_CONTRACTS.FeesPlug]; -export type AppGatewayConfig = { - plug: string; - appGatewayId: string; - switchboard: string; - chainSlug: number; -}; -// Maps plug contracts to their corresponding app gateways -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 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 ethers.utils.hexZeroPad(address, 32); - default: - throw new Error(`Unknown plug: ${plug}`); - } -}; +import { getWatcherSigner, sendWatcherMultiCallWithNonce } from "../utils/sign"; -export const checkIfAddressExists = (address: string, name: string) => { - if ( - address == "0x0000000000000000000000000000000000000000" || - !address || - address == "0x" || - address.length != 42 - ) { - 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}`); +const plugs = [Contracts.ContractFactoryPlug, Contracts.FeesPlug]; + +// Main function to connect plugs on all chains +export const main = async () => { + try { + await connectPlugsOnSocket(); + await updateConfigEVMx(); + } catch (error) { + console.log("Error while sending transaction", error); } - return appGatewayId; }; export const isConfigSetOnSocket = async ( @@ -95,11 +54,11 @@ async function connectPlug( socketSigner ); const socket = ( - await getInstance(CORE_CONTRACTS.Socket, addr[CORE_CONTRACTS.Socket]) + await getInstance(Contracts.Socket, addr[Contracts.Socket]) ).connect(socketSigner); // Get switchboard and app gateway addresses - const switchboard = addr[CORE_CONTRACTS.FastSwitchboard]; + const switchboard = addr[Contracts.FastSwitchboard]; checkIfAddressExists(switchboard, "Switchboard"); const appGatewayId = getAppGatewayId(plugContract, addresses); checkIfAppGatewayIdExists(appGatewayId, "AppGatewayId"); @@ -113,7 +72,8 @@ async function connectPlug( const tx = await plug.functions["connectSocket"]( appGatewayId, socket.address, - switchboard + switchboard, + { ...(await overrides(chain)) } ); console.log( `Connecting ${plugContract} on ${chain} to ${appGatewayId} tx hash: ${tx.hash}` @@ -133,7 +93,9 @@ export const connectPlugsOnSocket = async () => { const addr = addresses[chain]!; // Connect each plug contract for (const plugContract of plugs) { - await connectPlug(chain, plugContract, socketSigner, addresses, addr); + if (addr[plugContract]) { + await connectPlug(chain, plugContract, socketSigner, addresses, addr); + } } }) ); @@ -163,10 +125,10 @@ export const updateConfigEVMx = async () => { // Set up Watcher contract const signer = getWatcherSigner(); const EVMxAddresses = addresses[EVMX_CHAIN_ID]!; - const watcherPrecompileConfig = ( + const configurationsContract = ( await getInstance( - EVMxCoreContracts.WatcherPrecompileConfig, - EVMxAddresses[EVMxCoreContracts.WatcherPrecompileConfig] + Contracts.Configurations, + EVMxAddresses[Contracts.Configurations] ) ).connect(signer); @@ -178,13 +140,18 @@ export const updateConfigEVMx = async () => { for (const plugContract of plugs) { const appGatewayId = getAppGatewayId(plugContract, addresses); - const switchboard = addr[CORE_CONTRACTS.FastSwitchboard]; + const switchboard = addr[Contracts.FastSwitchboard]; checkIfAddressExists(switchboard, "Switchboard"); checkIfAppGatewayIdExists(appGatewayId, "AppGatewayId"); + if (!addr[plugContract]) { + console.log(`${plugContract} not found on ${chain}`); + continue; + } + if ( await isConfigSetOnEVMx( - watcherPrecompileConfig, + configurationsContract, chain, addr[plugContract], appGatewayId, @@ -195,9 +162,11 @@ export const updateConfigEVMx = async () => { continue; } appConfigs.push({ + plugConfig: { + appGatewayId: appGatewayId, + switchboard: switchboard, + }, plug: addr[plugContract], - appGatewayId: appGatewayId, - switchboard: addr[CORE_CONTRACTS.FastSwitchboard], chainSlug: chain, }); } @@ -207,25 +176,13 @@ export const updateConfigEVMx = async () => { // Update configs if any changes needed if (appConfigs.length > 0) { console.log({ appConfigs }); - const encodedMessage = ethers.utils.defaultAbiCoder.encode( - [ - "bytes4", - "tuple(address plug,bytes32 appGatewayId,address switchboard,uint32 chainSlug)[]", - ], - [ - watcherPrecompileConfig.interface.getSighash("setAppGateways"), - appConfigs, - ] - ); - const { nonce, signature } = await signWatcherMessage( - encodedMessage, - watcherPrecompileConfig.address + const calldata = configurationsContract.interface.encodeFunctionData( + "setAppGatewayConfigs", + [appConfigs] ); - const tx = await watcherPrecompileConfig.setAppGateways( - appConfigs, - nonce, - signature, - { ...overrides(EVMX_CHAIN_ID) } + const tx = await sendWatcherMultiCallWithNonce( + configurationsContract.address, + calldata ); console.log(`Updating EVMx Config tx hash: ${tx.hash}`); await tx.wait(); @@ -235,16 +192,6 @@ export const updateConfigEVMx = async () => { } }; -// Main function to connect plugs on all chains -export const main = async () => { - try { - await connectPlugsOnSocket(); - await updateConfigEVMx(); - } catch (error) { - console.log("Error while sending transaction", error); - } -}; - main() .then(() => process.exit(0)) .catch((error: Error) => { diff --git a/hardhat-scripts/deploy/5.upload.ts b/hardhat-scripts/deploy/7.upload.ts similarity index 100% rename from hardhat-scripts/deploy/5.upload.ts rename to hardhat-scripts/deploy/7.upload.ts diff --git a/hardhat-scripts/deploy/6.setupEnv.ts b/hardhat-scripts/deploy/8.setupEnv.ts similarity index 59% rename from hardhat-scripts/deploy/6.setupEnv.ts rename to hardhat-scripts/deploy/8.setupEnv.ts index 1fe5cc72..d8bc7ead 100644 --- a/hardhat-scripts/deploy/6.setupEnv.ts +++ b/hardhat-scripts/deploy/8.setupEnv.ts @@ -1,8 +1,9 @@ -import { ChainSlug } from "../../src"; +import { ChainSlug, Contracts } from "../../src"; import fs from "fs"; import path from "path"; import { EVMX_CHAIN_ID, mode } from "../config/config"; import { getAddresses } from "../utils"; +import { getFeeTokens } from "../constants"; const envFilePath = path.join(__dirname, "../../.env"); const encoding = "utf8"; @@ -20,29 +21,36 @@ const latestEVMxAddresses = latestAddresses[EVMX_CHAIN_ID]; // Create a new array to hold the updated lines const updatedLines = lines.map((line) => { if (line.startsWith("ADDRESS_RESOLVER=")) { - return `ADDRESS_RESOLVER=${latestEVMxAddresses["AddressResolver"]}`; - } else if (line.startsWith("WATCHER_PRECOMPILE=")) { - return `WATCHER_PRECOMPILE=${latestEVMxAddresses["WatcherPrecompile"]}`; + return `ADDRESS_RESOLVER=${latestEVMxAddresses[Contracts.AddressResolver]}`; + } else if (line.startsWith("WATCHER=")) { + return `WATCHER=${latestEVMxAddresses[Contracts.Watcher]}`; } else if (line.startsWith("AUCTION_MANAGER=")) { - return `AUCTION_MANAGER=${latestEVMxAddresses["AuctionManager"]}`; + return `AUCTION_MANAGER=${latestEVMxAddresses[Contracts.AuctionManager]}`; } else if (line.startsWith("FEES_MANAGER=")) { - return `FEES_MANAGER=${latestEVMxAddresses["FeesManager"]}`; + return `FEES_MANAGER=${latestEVMxAddresses[Contracts.FeesManager]}`; } else if (line.startsWith("ARBITRUM_SOCKET=")) { return `ARBITRUM_SOCKET=${ - latestAddresses[ChainSlug.ARBITRUM_SEPOLIA]["Socket"] + latestAddresses[ChainSlug.ARBITRUM_SEPOLIA][Contracts.Socket] }`; } else if (line.startsWith("ARBITRUM_SWITCHBOARD=")) { return `ARBITRUM_SWITCHBOARD=${ - latestAddresses[ChainSlug.ARBITRUM_SEPOLIA]["FastSwitchboard"] + latestAddresses[ChainSlug.ARBITRUM_SEPOLIA][Contracts.FastSwitchboard] }`; } else if (line.startsWith("ARBITRUM_FEES_PLUG=")) { - return `ARBITRUM_FEES_PLUG=${ - latestAddresses[ChainSlug.ARBITRUM_SEPOLIA]["FeesPlug"] - }`; + const feesPlug = + latestAddresses[ChainSlug.ARBITRUM_SEPOLIA][Contracts.FeesPlug]; + if (feesPlug) { + return `ARBITRUM_FEES_PLUG=${feesPlug}`; + } else { + return line; + } } else if (line.startsWith("ARBITRUM_TEST_USDC=")) { - return `ARBITRUM_TEST_USDC=${ - latestAddresses[ChainSlug.ARBITRUM_SEPOLIA]["TestUSDC"] - }`; + const testUSDC = getFeeTokens(mode, ChainSlug.ARBITRUM_SEPOLIA)[0]; + if (testUSDC) { + return `ARBITRUM_TEST_USDC=${testUSDC}`; + } else { + return line; + } } return line; // Return the line unchanged if it doesn't match any of the above }); diff --git a/hardhat-scripts/deploy/9.setupTransmitter.ts b/hardhat-scripts/deploy/9.setupTransmitter.ts new file mode 100644 index 00000000..279be8b1 --- /dev/null +++ b/hardhat-scripts/deploy/9.setupTransmitter.ts @@ -0,0 +1,110 @@ +import { Contract, Wallet } from "ethers"; +import { ChainSlug, Contracts, EVMxAddressesObj } from "../../src"; +import { + EVMX_CHAIN_ID, + mode, + TRANSMITTER_CREDIT_THRESHOLD, + TRANSMITTER_NATIVE_THRESHOLD, +} from "../config/config"; +import { getAddresses } from "../utils/address"; +import { getInstance } from "../utils/deployUtils"; +import { overrides } from "../utils/overrides"; +import { getTransmitterSigner, getWatcherSigner } from "../utils/sign"; +import { SignerWithAddress } from "@nomiclabs/hardhat-ethers/signers"; + +let evmxAddresses: EVMxAddressesObj; +let feesManagerContract: Contract; +let transmitterSigner: SignerWithAddress | Wallet; +let transmitterAddress: string; + +export const main = async () => { + console.log("Setting up transmitter..."); + await init(); + await approveAuctionManager(); + await checkAndDepositCredits(); + + console.log("Transmitter setup complete!"); +}; + +export const init = async () => { + const addresses = getAddresses(mode); + evmxAddresses = addresses[EVMX_CHAIN_ID] as EVMxAddressesObj; + feesManagerContract = await getInstance( + Contracts.FeesManager, + evmxAddresses[Contracts.FeesManager] + ); + transmitterSigner = getTransmitterSigner(EVMX_CHAIN_ID as ChainSlug); + transmitterAddress = await transmitterSigner.getAddress(); +}; + +export const approveAuctionManager = async () => { + console.log("Approving auction manager"); + const auctionManagerAddress = evmxAddresses[Contracts.AuctionManager]; + const isAlreadyApproved = await feesManagerContract + .connect(transmitterSigner) + .isApproved(transmitterAddress, auctionManagerAddress); + + if (!isAlreadyApproved) { + console.log("Approving auction manager"); + const tx = await feesManagerContract + .connect(transmitterSigner) + .approveAppGateways( + [ + { + appGateway: auctionManagerAddress, + approval: true, + }, + ], + await overrides(EVMX_CHAIN_ID as ChainSlug) + ); + console.log("Auction manager approval tx hash:", tx.hash); + await tx.wait(); + console.log("Auction manager approved"); + } else { + console.log("Auction manager already approved"); + } +}; + +export const checkAndDepositCredits = async () => { + console.log("Checking and depositing credits"); + const credits = await feesManagerContract + .connect(transmitterSigner) + .getAvailableCredits(transmitterAddress); + + if (credits.lt(TRANSMITTER_CREDIT_THRESHOLD)) { + console.log("Depositing credits for transmitter..."); + const tx = await feesManagerContract + .connect(getWatcherSigner()) + .wrap(transmitterAddress, { + ...(await overrides(EVMX_CHAIN_ID as ChainSlug)), + value: TRANSMITTER_CREDIT_THRESHOLD, + }); + console.log("Credits wrap tx hash:", tx.hash); + await tx.wait(); + console.log("Credits wrapped"); + } +}; + +export const checkAndDepositNative = async () => { + console.log("Checking and depositing native"); + const nativeBalance = await transmitterSigner.provider!.getBalance( + transmitterAddress + ); + + if (nativeBalance.lt(TRANSMITTER_NATIVE_THRESHOLD)) { + console.log("Depositing native for transmitter..."); + const tx = await getWatcherSigner().sendTransaction({ + to: transmitterAddress, + value: TRANSMITTER_NATIVE_THRESHOLD, + ...(await overrides(EVMX_CHAIN_ID as ChainSlug)), + }); + console.log("Native deposit tx hash:", tx.hash); + await tx.wait(); + console.log("Native deposited"); + } +}; + +main().catch((error) => { + console.error(error); + process.exit(1); +}); diff --git a/hardhat-scripts/s3Config/buildConfig.ts b/hardhat-scripts/s3Config/buildConfig.ts index 7d51681b..73bac889 100644 --- a/hardhat-scripts/s3Config/buildConfig.ts +++ b/hardhat-scripts/s3Config/buildConfig.ts @@ -1,6 +1,12 @@ import { config as dotenvConfig } from "dotenv"; import { ChainConfig, ChainSlug, S3Config, getFinalityBlocks } from "../../src"; -import { EVMX_CHAIN_ID, chains, mode } from "../config/config"; +import { + EVMX_CHAIN_ID, + chains, + mainnetChains, + mode, + testnetChains, +} from "../config/config"; import { getAddresses } from "../utils/address"; import { getChainName, rpcKeys, wssRpcKeys } from "../utils/networks"; import { getChainType } from "./utils"; @@ -15,6 +21,8 @@ export const getS3Config = () => { supportedChainSlugs, version: version[mode], chains: {}, + testnetChainSlugs: testnetChains, + mainnetChainSlugs: mainnetChains, }; supportedChainSlugs.forEach((chainSlug) => { config.chains[chainSlug] = getChainConfig(chainSlug); diff --git a/hardhat-scripts/utils/address.ts b/hardhat-scripts/utils/address.ts index 6caf8c3f..f1476c41 100644 --- a/hardhat-scripts/utils/address.ts +++ b/hardhat-scripts/utils/address.ts @@ -20,3 +20,15 @@ export const getAddresses = ( throw new Error(`Invalid deployment mode: ${mode}`); } }; + +export const checkIfAddressExists = (address: string, name: string) => { + if ( + address == "0x0000000000000000000000000000000000000000" || + !address || + address == "0x" || + address.length != 42 + ) { + throw Error(`${name} not found : ${address}`); + } + return address; +}; diff --git a/hardhat-scripts/utils/deployUtils.ts b/hardhat-scripts/utils/deployUtils.ts index 62824dcf..dd71525e 100644 --- a/hardhat-scripts/utils/deployUtils.ts +++ b/hardhat-scripts/utils/deployUtils.ts @@ -1,4 +1,4 @@ -import { Wallet, utils } from "ethers"; +import { BigNumber, Signer, Wallet, utils } from "ethers"; import { network, ethers, run } from "hardhat"; import { ContractFactory, Contract } from "ethers"; @@ -7,9 +7,10 @@ import path from "path"; import fs from "fs"; import { ChainAddressesObj, ChainSlug, DeploymentMode } from "../../src"; import { SignerWithAddress } from "@nomiclabs/hardhat-ethers/signers"; -import { overrides } from "../utils"; +import { getAddresses, overrides } from "../utils"; import { VerifyArgs } from "../verify"; import { DeploymentAddresses } from "../constants"; +import { EVMX_CHAIN_ID, mode } from "../config"; export const deploymentsPath = path.join(__dirname, `/../../deployments/`); @@ -318,3 +319,44 @@ export function getChainSlugFromId(chainId: number) { // avoid conflict for now return parseInt(utils.id(chainId.toString()).substring(0, 10)); } + +export const updateContractSettings = async ( + chainSlug: number, + contractName: string, + getterMethod: string, + getterArgs: any[], + requiredValue: string | BigNumber, + setterMethod: string, + setterArgs: any[], + signer: SignerWithAddress | Wallet +) => { + const addresses = getAddresses(mode); + const contractAddress = addresses[chainSlug][contractName]; + const contractInstance = await getInstance(contractName, contractAddress); + const currentValue = await contractInstance + .connect(signer) + [getterMethod](...getterArgs); + + if ( + (typeof currentValue === "string" && + currentValue.toLowerCase() !== String(requiredValue).toLowerCase()) || + (BigNumber.isBigNumber(currentValue) && + currentValue.toString() !== requiredValue.toString()) + ) { + console.log({ + setterMethod, + current: currentValue, + required: requiredValue, + }); + const tx = await contractInstance + .connect(signer) + [setterMethod](...setterArgs); + console.log( + `Setting ${getterMethod} for ${contractInstance.address} to`, + tx.hash + ); + await tx.wait(); + } else { + console.log(`${getterMethod} is already set to ${requiredValue}`); + } +}; diff --git a/hardhat-scripts/utils/gatewayId.ts b/hardhat-scripts/utils/gatewayId.ts new file mode 100644 index 00000000..a24fc02d --- /dev/null +++ b/hardhat-scripts/utils/gatewayId.ts @@ -0,0 +1,38 @@ +import { constants, ethers } from "ethers"; +import { Contracts } from "../../src"; +import { EVMX_CHAIN_ID } from "../config"; +import { DeploymentAddresses } from "../constants"; + +export const getAppGatewayId = ( + plug: string, + addresses: DeploymentAddresses +) => { + let address: string = ""; + switch (plug) { + case Contracts.ContractFactoryPlug: + address = addresses?.[EVMX_CHAIN_ID]?.[Contracts.WritePrecompile]; + if (!address) throw new Error(`WritePrecompile not found on EVMX`); + return ethers.utils.hexZeroPad(address, 32); + case Contracts.FeesPlug: + address = addresses?.[EVMX_CHAIN_ID]?.[Contracts.FeesManager]; + if (!address) throw new Error(`FeesManager not found on EVMX`); + return ethers.utils.hexZeroPad(address, 32); + default: + throw new Error(`Unknown plug: ${plug}`); + } +}; + +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; +}; diff --git a/hardhat-scripts/utils/index.ts b/hardhat-scripts/utils/index.ts index e11a036e..b53a8577 100644 --- a/hardhat-scripts/utils/index.ts +++ b/hardhat-scripts/utils/index.ts @@ -4,3 +4,4 @@ export * from "./overrides"; export * from "./accounts"; export * from "./deployUtils"; export * from "./sign"; +export * from "./gatewayId"; diff --git a/hardhat-scripts/utils/overrides.ts b/hardhat-scripts/utils/overrides.ts index d4cfdafc..b81f365b 100644 --- a/hardhat-scripts/utils/overrides.ts +++ b/hardhat-scripts/utils/overrides.ts @@ -1,5 +1,5 @@ import { ChainSlug } from "../../src"; -import { BigNumber, BigNumberish, providers } from "ethers"; +import { BigNumber, BigNumberish, Contract, providers, Signer } from "ethers"; import { EVMX_CHAIN_ID } from "../config/config"; import { getProviderFromChainSlug } from "./networks"; @@ -27,6 +27,9 @@ export const chainOverrides: { // gasLimit: 1_000_000, // gasPrice: 212_000_000_000, }, + [ChainSlug.BASE]: { + gasLimit: 2_000_000, + }, [EVMX_CHAIN_ID as ChainSlug]: { type: 0, // gasLimit: 1_000_000_000, @@ -41,7 +44,10 @@ export const overrides = async ( gasLimit?: BigNumberish | undefined; gasPrice?: BigNumberish | undefined; }> => { - return await getOverrides(chain, getProviderFromChainSlug(chain)); + return await getOverrides( + chain as ChainSlug, + getProviderFromChainSlug(chain) + ); }; export const getOverrides = async ( diff --git a/hardhat-scripts/utils/sign.ts b/hardhat-scripts/utils/sign.ts index 5f7ed311..00afdcb4 100644 --- a/hardhat-scripts/utils/sign.ts +++ b/hardhat-scripts/utils/sign.ts @@ -1,23 +1,12 @@ import { ethers } from "ethers"; -import { ChainSlug } from "../../src"; -import { EVMX_CHAIN_ID } from "../config/config"; +import { ChainSlug, Contracts } from "../../src"; +import { EVMX_CHAIN_ID, mode } from "../config/config"; import { getProviderFromChainSlug } from "./networks"; - -export const signWatcherMessage = async ( - encodedMessage: string, - watcherContractAddress: string -) => { - const signatureNonce = Date.now(); - const signer = getWatcherSigner(); - const digest = ethers.utils.keccak256( - ethers.utils.defaultAbiCoder.encode( - ["address", "uint32", "uint256", "bytes"], - [watcherContractAddress, EVMX_CHAIN_ID, signatureNonce, encodedMessage] - ) - ); - const signature = await signer.signMessage(ethers.utils.arrayify(digest)); - return { nonce: signatureNonce, signature }; -}; +import { signWatcherMultiCallMessage } from "../../src/signer"; +import { getAddresses } from "./address"; +import { overrides } from "./overrides"; +import { getInstance } from "./deployUtils"; +import { WatcherMultiCallParams } from "../constants/types"; export const getWatcherSigner = () => { const provider = getProviderFromChainSlug(EVMX_CHAIN_ID as ChainSlug); @@ -28,3 +17,53 @@ export const getSocketSigner = (chainSlug: ChainSlug) => { const provider = getProviderFromChainSlug(chainSlug); return new ethers.Wallet(process.env.SOCKET_SIGNER_KEY as string, provider); }; + +export const getTransmitterSigner = (chainSlug: ChainSlug) => { + const provider = getProviderFromChainSlug(chainSlug); + return new ethers.Wallet( + process.env.TRANSMITTER_PRIVATE_KEY as string, + provider + ); +}; + +export const signWatcherMessage = async ( + targetContractAddress: string, + calldata: string +) => { + const addresses = getAddresses(mode); + return await signWatcherMultiCallMessage( + addresses[EVMX_CHAIN_ID][Contracts.Watcher], + EVMX_CHAIN_ID, + targetContractAddress, + calldata, + getWatcherSigner() + ); +}; + +export const sendWatcherMultiCallWithNonce = async ( + targetContractAddress: string, + calldata: string +) => { + const addresses = getAddresses(mode); + const watcherContract = ( + await getInstance( + Contracts.Watcher, + addresses[EVMX_CHAIN_ID][Contracts.Watcher] + ) + ).connect(getWatcherSigner()); + const { nonce, signature } = await signWatcherMessage( + targetContractAddress, + calldata + ); + + const params: WatcherMultiCallParams = { + contractAddress: targetContractAddress, + data: calldata, + nonce, + signature, + }; + // Call watcherMultiCall function with single call data + return await watcherContract.watcherMultiCall([params], { + ...(await overrides(EVMX_CHAIN_ID as ChainSlug)), + }); +}; diff --git a/hardhat-scripts/verify/verify.ts b/hardhat-scripts/verify/verify.ts index 2835a150..38140e02 100644 --- a/hardhat-scripts/verify/verify.ts +++ b/hardhat-scripts/verify/verify.ts @@ -43,7 +43,6 @@ export const main = async () => { for (let chainIndex = 0; chainIndex < chains.length; chainIndex++) { const chain = parseInt(chains[chainIndex]) as ChainSlug; let chainName: string; - console.log({ chain }); if (chain == (EVMX_CHAIN_ID as ChainSlug)) { chainName = "EVMX"; } else { @@ -51,10 +50,10 @@ export const main = async () => { } console.log({ chainName }); hre.changeNetwork(chainName); - console.log(chainName); const chainParams: VerifyArgs[] = verificationParams[chain]; const unverifiedChainParams: VerifyArgs[] = []; + if (chainParams.length) { const len = chainParams.length; for (let index = 0; index < len!; index++) { diff --git a/hardhat.config.ts b/hardhat.config.ts index ada0e153..74627321 100644 --- a/hardhat.config.ts +++ b/hardhat.config.ts @@ -61,6 +61,9 @@ let liveNetworks = { ), [HardhatChainName.SEPOLIA]: getChainConfig(ChainSlug.SEPOLIA), [HardhatChainName.BASE_SEPOLIA]: getChainConfig(ChainSlug.BASE_SEPOLIA), + [HardhatChainName.BASE]: getChainConfig(ChainSlug.BASE), + [HardhatChainName.ARBITRUM]: getChainConfig(ChainSlug.ARBITRUM), + [HardhatChainName.OPTIMISM]: getChainConfig(ChainSlug.OPTIMISM), EVMX: { accounts: [`0x${privateKey}`], chainId: EVMX_CHAIN_ID, @@ -90,6 +93,7 @@ const config: HardhatUserConfig = { arbitrumOne: process.env.ARBISCAN_API_KEY || "", arbitrumTestnet: process.env.ARBISCAN_API_KEY || "", baseTestnet: process.env.BASESCAN_API_KEY || "", + base: process.env.BASESCAN_API_KEY || "", bsc: process.env.BSCSCAN_API_KEY || "", bscTestnet: process.env.BSCSCAN_API_KEY || "", goerli: process.env.ETHERSCAN_API_KEY || "", @@ -97,7 +101,7 @@ const config: HardhatUserConfig = { sepolia: process.env.ETHERSCAN_API_KEY || "", optimisticEthereum: process.env.OPTIMISM_API_KEY || "", optimisticTestnet: process.env.OPTIMISM_API_KEY || "", - evmx: "none", + EVMX: "none", }, customChains: [ { @@ -125,7 +129,15 @@ const config: HardhatUserConfig = { }, }, { - network: "evmx", + network: "base", + chainId: ChainId.BASE, + urls: { + apiURL: "https://api.basescan.org/api", + browserURL: "https://basescan.org/", + }, + }, + { + network: "EVMX", chainId: EVMX_CHAIN_ID, urls: { apiURL: "https://evmx.cloud.blockscout.com/api", @@ -134,11 +146,6 @@ const config: HardhatUserConfig = { }, ], }, - sourcify: { - // Disabled by default - // Doesn't need an API key - enabled: true, - }, // This fully resolves paths for imports in the ./lib directory for Hardhat preprocess: { eachLine: (hre) => ({ diff --git a/lib.tsconfig.json b/lib.tsconfig.json index ba2f6b86..e0ae1226 100644 --- a/lib.tsconfig.json +++ b/lib.tsconfig.json @@ -10,6 +10,6 @@ "moduleResolution": "node", "allowJs": true }, - "include": ["src/**/*.ts", "lib/**/*.ts", "deployments/"], + "include": ["src/**/*.ts"], "exclude": ["node_modules", "dist"] } diff --git a/package.json b/package.json index 86bf9215..a30b0517 100644 --- a/package.json +++ b/package.json @@ -9,7 +9,7 @@ "publishConfig": { "access": "public" }, - "version": "1.1.15", + "version": "1.1.21", "description": "socket protocol", "scripts": { "build": "yarn abi && tsc --project lib.tsconfig.json", @@ -38,6 +38,7 @@ "hardhat": "2.12.2", "hardhat-abi-exporter": "2.10.1", "hardhat-change-network": "^0.0.7", + "hardhat-contract-sizer": "^2.10.0", "hardhat-deploy": "0.11.20", "hardhat-preprocessor": "0.1.4", "http-server": "^14.1.1", @@ -50,7 +51,5 @@ "typechain": "^8.0.0", "typescript": "^4.6.4" }, - "dependencies": { - "hardhat-contract-sizer": "^2.10.0" - } + "dependencies": {} } diff --git a/script/admin/RescueFunds.s.sol b/script/admin/RescueFunds.s.sol index d1268058..a880b61c 100644 --- a/script/admin/RescueFunds.s.sol +++ b/script/admin/RescueFunds.s.sol @@ -3,7 +3,7 @@ pragma solidity ^0.8.19; import {Script} from "forge-std/Script.sol"; import {console} from "forge-std/console.sol"; -import {FeesPlug} from "../../contracts/evmx/payload-delivery/FeesPlug.sol"; +import {FeesPlug} from "../../contracts/evmx/plugs/FeesPlug.sol"; contract RescueFundsScript is Script { address constant NATIVE_TOKEN = 0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE; diff --git a/script/admin/mock/DeployEVMx.s.sol b/script/admin/mock/DeployEVMx.s.sol index c268e42c..294f2a99 100644 --- a/script/admin/mock/DeployEVMx.s.sol +++ b/script/admin/mock/DeployEVMx.s.sol @@ -11,7 +11,7 @@ contract DeployEVMx is Script { vm.createSelectFork(rpc); uint256 deployerPrivateKey = vm.envUint("PRIVATE_KEY"); vm.startBroadcast(deployerPrivateKey); - MockWatcherPrecompile watcher = new MockWatcherPrecompile(address(0), address(0)); + MockWatcherPrecompile watcher = new MockWatcherPrecompile(); console.log("MockWatcherPrecompile:", address(watcher)); } } diff --git a/script/counter/DeployEVMxCounterApp.s.sol b/script/counter/DeployEVMxCounterApp.s.sol index 79c916c8..96111337 100644 --- a/script/counter/DeployEVMxCounterApp.s.sol +++ b/script/counter/DeployEVMxCounterApp.s.sol @@ -4,7 +4,6 @@ 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 {ETH_ADDRESS} from "../../contracts/utils/common/Constants.sol"; // source .env && forge script script/counter/deployEVMxCounterApp.s.sol --broadcast --skip-simulation --legacy --gas-price 0 contract CounterDeploy is Script { diff --git a/script/counter/DeployOnchainCounters.s.sol b/script/counter/DeployOnchainCounters.s.sol index 3edb7b78..a6a30757 100644 --- a/script/counter/DeployOnchainCounters.s.sol +++ b/script/counter/DeployOnchainCounters.s.sol @@ -3,7 +3,6 @@ pragma solidity ^0.8.21; import {Script} from "forge-std/Script.sol"; import {console} from "forge-std/console.sol"; -import {ETH_ADDRESS} from "../../contracts/utils/common/Constants.sol"; import {CounterAppGateway} from "../../test/apps/app-gateways/counter/CounterAppGateway.sol"; // source .env && forge script script/counter/DeployCounterOnchain.s.sol --broadcast --skip-simulation --legacy --gas-price 0 diff --git a/script/counter/SetFees.s.sol b/script/counter/SetFees.s.sol index b365f64b..758ff54a 100644 --- a/script/counter/SetFees.s.sol +++ b/script/counter/SetFees.s.sol @@ -3,7 +3,6 @@ pragma solidity ^0.8.21; import {Script} from "forge-std/Script.sol"; import {console} from "forge-std/console.sol"; -import {ETH_ADDRESS} from "../../contracts/utils/common/Constants.sol"; import {CounterAppGateway} from "../../test/apps/app-gateways/counter/CounterAppGateway.sol"; // source .env && forge script script/counter/DeployCounterOnchain.s.sol --broadcast --skip-simulation --legacy --gas-price 0 @@ -21,7 +20,7 @@ contract CounterSetFees is Script { console.log("Setting fees..."); // Setting fee payment on Arbitrum Sepolia - uint256 fees = 0.00001 ether; + // uint256 fees = 0.00001 ether; // appGateway.setFees(fees); } } diff --git a/script/counter/WithdrawFeesArbitrumFeesPlug.s.sol b/script/counter/WithdrawFeesArbitrumFeesPlug.s.sol index 57ad117e..2d329a2f 100644 --- a/script/counter/WithdrawFeesArbitrumFeesPlug.s.sol +++ b/script/counter/WithdrawFeesArbitrumFeesPlug.s.sol @@ -3,8 +3,7 @@ pragma solidity ^0.8.21; import {Script} from "forge-std/Script.sol"; import {console} from "forge-std/console.sol"; -import {FeesManager} from "../../contracts/evmx/payload-delivery/FeesManager.sol"; -import {ETH_ADDRESS} from "../../contracts/utils/common/Constants.sol"; +import {FeesManager} from "../../contracts/evmx/fees/FeesManager.sol"; import {CounterAppGateway} from "../../test/apps/app-gateways/counter/CounterAppGateway.sol"; // @notice This script is used to withdraw fees from EVMX to Arbitrum Sepolia @@ -15,9 +14,10 @@ contract WithdrawFees is Script { vm.createSelectFork(vm.envString("EVMX_RPC")); FeesManager feesManager = FeesManager(payable(vm.envAddress("FEES_MANAGER"))); address appGatewayAddress = vm.envAddress("APP_GATEWAY"); + address token = vm.envAddress("USDC"); CounterAppGateway appGateway = CounterAppGateway(appGatewayAddress); - uint256 availableFees = feesManager.getMaxCreditsAvailableForWithdraw(appGatewayAddress); + uint256 availableFees = feesManager.getAvailableCredits(appGatewayAddress); console.log("Available fees:", availableFees); if (availableFees > 0) { @@ -45,7 +45,7 @@ contract WithdrawFees is Script { vm.createSelectFork(vm.envString("EVMX_RPC")); vm.startBroadcast(privateKey); console.log("Withdrawing amount:", amountToWithdraw); - appGateway.withdrawFeeTokens(421614, ETH_ADDRESS, amountToWithdraw, sender); + appGateway.withdrawCredits(421614, token, amountToWithdraw, sender); vm.stopBroadcast(); // Switch back to Arbitrum Sepolia to check final balance diff --git a/script/helpers/AppGatewayFeeBalance.s.sol b/script/helpers/CheckDepositedCredits.s.sol similarity index 81% rename from script/helpers/AppGatewayFeeBalance.s.sol rename to script/helpers/CheckDepositedCredits.s.sol index 2911c527..ad9bca71 100644 --- a/script/helpers/AppGatewayFeeBalance.s.sol +++ b/script/helpers/CheckDepositedCredits.s.sol @@ -3,10 +3,9 @@ pragma solidity ^0.8.21; import {Script} from "forge-std/Script.sol"; import {console} from "forge-std/console.sol"; -import {FeesManager} from "../../contracts/evmx/payload-delivery/FeesManager.sol"; -import {ETH_ADDRESS} from "../../contracts/utils/common/Constants.sol"; +import {FeesManager} from "../../contracts/evmx/fees/FeesManager.sol"; -contract CheckDepositedFees is Script { +contract CheckDepositedCredits is Script { function run() external { vm.createSelectFork(vm.envString("EVMX_RPC")); FeesManager feesManager = FeesManager(payable(vm.envAddress("FEES_MANAGER"))); diff --git a/script/helpers/PayFeesInArbitrumTestUSDC.s.sol b/script/helpers/DepositCredit.s.sol similarity index 67% rename from script/helpers/PayFeesInArbitrumTestUSDC.s.sol rename to script/helpers/DepositCredit.s.sol index daf9b08e..767d5aac 100644 --- a/script/helpers/PayFeesInArbitrumTestUSDC.s.sol +++ b/script/helpers/DepositCredit.s.sol @@ -3,16 +3,16 @@ pragma solidity ^0.8.21; import {Script} from "forge-std/Script.sol"; import {console} from "forge-std/console.sol"; -import {FeesPlug} from "../../contracts/evmx/payload-delivery/FeesPlug.sol"; -import {ETH_ADDRESS} from "../../contracts/utils/common/Constants.sol"; -import {TestUSDC} from "../../contracts/evmx/helpers/TestUSDC.sol"; -// source .env && forge script script/helpers/PayFeesInArbitrumETH.s.sol --broadcast --skip-simulation -contract DepositFees is Script { +import {FeesPlug} from "../../contracts/evmx/plugs/FeesPlug.sol"; +import {TestUSDC} from "../../contracts/evmx/mocks/TestUSDC.sol"; + +// source .env && forge script script/helpers/DepositCreditAndNative.s.sol --broadcast --skip-simulation +contract DepositCredit is Script { function run() external { - uint256 feesAmount = 100000000; + uint256 feesAmount = 100000000; // 100 USDC vm.createSelectFork(vm.envString("ARBITRUM_SEPOLIA_RPC")); - uint256 privateKey = vm.envUint("SPONSOR_KEY"); + uint256 privateKey = vm.envUint("PRIVATE_KEY"); vm.startBroadcast(privateKey); FeesPlug feesPlug = FeesPlug(payable(vm.envAddress("ARBITRUM_FEES_PLUG"))); address appGateway = vm.envAddress("APP_GATEWAY"); @@ -30,6 +30,6 @@ contract DepositFees is Script { console.log("App Gateway:", appGateway); console.log("Fees Plug:", address(feesPlug)); console.log("Fees Amount:", feesAmount); - feesPlug.depositToFeeAndNative(address(testUSDCContract), appGateway, feesAmount); + feesPlug.depositCredit(address(testUSDCContract), appGateway, feesAmount); } } diff --git a/script/helpers/DepositCreditAndNative.s.sol b/script/helpers/DepositCreditAndNative.s.sol new file mode 100644 index 00000000..629a3998 --- /dev/null +++ b/script/helpers/DepositCreditAndNative.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/evmx/plugs/FeesPlug.sol"; +import {TestUSDC} from "../../contracts/evmx/mocks/TestUSDC.sol"; + +// source .env && forge script script/helpers/DepositCreditAndNative.s.sol --broadcast --skip-simulation +contract DepositCreditAndNative is Script { + function run() external { + uint256 feesAmount = 100000000; // 100 USDC + vm.createSelectFork(vm.envString("ARBITRUM_SEPOLIA_RPC")); + + uint256 privateKey = vm.envUint("PRIVATE_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.depositCreditAndNative(address(testUSDCContract), appGateway, feesAmount); + } +} diff --git a/script/helpers/PayFeesInArbitrumETH.s.sol b/script/helpers/PayFeesInArbitrumETH.s.sol deleted file mode 100644 index b7d5b887..00000000 --- a/script/helpers/PayFeesInArbitrumETH.s.sol +++ /dev/null @@ -1,28 +0,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/evmx/payload-delivery/FeesPlug.sol"; -import {ETH_ADDRESS} from "../../contracts/utils/common/Constants.sol"; - -// source .env && forge script script/helpers/PayFeesInArbitrumETH.s.sol --broadcast --skip-simulation -contract DepositFees is Script { - function run() external { - vm.createSelectFork(vm.envString("ARBITRUM_SEPOLIA_RPC")); - - uint256 privateKey = vm.envUint("PRIVATE_KEY"); - vm.startBroadcast(privateKey); - FeesPlug feesPlug = FeesPlug(payable(vm.envAddress("ARBITRUM_FEES_PLUG"))); - address appGateway = vm.envAddress("APP_GATEWAY"); - - address sender = vm.addr(privateKey); - console.log("Sender address:", sender); - uint256 balance = sender.balance; - console.log("Sender balance in wei:", balance); - console.log("App Gateway:", appGateway); - console.log("Fees Plug:", address(feesPlug)); - uint feesAmount = 0.001 ether; - feesPlug.depositToFeeAndNative(ETH_ADDRESS, appGateway, feesAmount); - } -} diff --git a/setupInfraContracts.sh b/setupInfraContracts.sh index 9d916681..989eabaa 100644 --- a/setupInfraContracts.sh +++ b/setupInfraContracts.sh @@ -4,10 +4,13 @@ else time npx hardhat run hardhat-scripts/deploy/1.deploy.ts fi time npx hardhat run hardhat-scripts/deploy/2.roles.ts --no-compile -time npx hardhat run hardhat-scripts/deploy/3.upgradeManagers.ts --no-compile -time npx hardhat run hardhat-scripts/deploy/4.connect.ts --no-compile -time npx ts-node hardhat-scripts/deploy/5.upload.ts --resolveJsonModule -time npx hardhat run hardhat-scripts/deploy/6.setupEnv.ts --no-compile +time npx hardhat run hardhat-scripts/deploy/3.configureChains.ts --no-compile +time npx hardhat run hardhat-scripts/deploy/4.configureEVMx.ts --no-compile +time npx hardhat run hardhat-scripts/deploy/5.fundTransfers.ts --no-compile +time npx hardhat run hardhat-scripts/deploy/6.connect.ts --no-compile +time npx hardhat run hardhat-scripts/deploy/7.upload.ts --no-compile +time npx hardhat run hardhat-scripts/deploy/8.setupEnv.ts --no-compile +time npx hardhat run hardhat-scripts/deploy/9.setupTransmitter.ts --no-compile 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 diff --git a/src/constants.ts b/src/constants.ts new file mode 100644 index 00000000..6e5164bb --- /dev/null +++ b/src/constants.ts @@ -0,0 +1,5 @@ +import { keccak256 } from "ethers/lib/utils"; + +export const READ = keccak256(Buffer.from("READ")).substring(0, 10); +export const WRITE = keccak256(Buffer.from("WRITE")).substring(0, 10); +export const SCHEDULE = keccak256(Buffer.from("SCHEDULE")).substring(0, 10); diff --git a/src/enums.ts b/src/enums.ts index 689543ce..cb7af5e4 100644 --- a/src/enums.ts +++ b/src/enums.ts @@ -11,55 +11,66 @@ export enum Events { ExecutionFailed = "ExecutionFailed", PlugConnected = "PlugConnected", AppGatewayCallRequested = "AppGatewayCallRequested", - AppGatewayCallFailed = "AppGatewayCallFailed", // FeesPlug FeesDeposited = "FeesDeposited", - // WatcherPrecompile + // Watcher CalledAppGateway = "CalledAppGateway", - QueryRequested = "QueryRequested", - FinalizeRequested = "FinalizeRequested", + AppGatewayCallFailed = "AppGatewayCallFailed", + + // PromiseResolver PromiseResolved = "PromiseResolved", PromiseNotResolved = "PromiseNotResolved", - TimeoutRequested = "TimeoutRequested", - TimeoutResolved = "TimeoutResolved", - RequestSubmitted = "RequestSubmitted", - Finalized = "Finalized", MarkedRevert = "MarkedRevert", + + // RequestHandler + RequestSubmitted = "RequestSubmitted", + RequestCancelled = "RequestCancelled", + FeesIncreased = "FeesIncreased", + + // WritePrecompile + WriteProofRequested = "WriteProofRequested", + WriteProofUploaded = "WriteProofUploaded", + + // ReadPrecompile + ReadRequested = "ReadRequested", + + // SchedulePrecompile + ScheduleRequested = "ScheduleRequested", + ScheduleResolved = "ScheduleResolved", + // AuctionManager AuctionEnded = "AuctionEnded", AuctionRestarted = "AuctionRestarted", - - // DeliveryHelper - PayloadSubmitted = "PayloadSubmitted", - PayloadAsyncRequested = "PayloadAsyncRequested", - FeesIncreased = "FeesIncreased", - RequestCancelled = "RequestCancelled", } export enum Contracts { Socket = "Socket", FeesPlug = "FeesPlug", - WatcherPrecompile = "WatcherPrecompile", - WatcherPrecompileLimits = "WatcherPrecompileLimits", - WatcherPrecompileConfig = "WatcherPrecompileConfig", + ContractFactoryPlug = "ContractFactoryPlug", + FastSwitchboard = "FastSwitchboard", + SocketBatcher = "SocketBatcher", + SocketFeeManager = "SocketFeeManager", + AddressResolver = "AddressResolver", + Watcher = "Watcher", + RequestHandler = "RequestHandler", + Configurations = "Configurations", + PromiseResolver = "PromiseResolver", AuctionManager = "AuctionManager", - DeliveryHelper = "DeliveryHelper", -} - -export enum CallType { - READ, - WRITE, - DEPLOY, - WITHDRAW, + FeesManager = "FeesManager", + WritePrecompile = "WritePrecompile", + ReadPrecompile = "ReadPrecompile", + SchedulePrecompile = "SchedulePrecompile", + FeesPool = "FeesPool", + AsyncDeployer = "AsyncDeployer", + DeployForwarder = "DeployForwarder", } export enum CallTypeNames { READ = "READ", WRITE = "WRITE", - DEPLOY = "DEPLOY", - WITHDRAW = "WITHDRAW", + SCHEDULE = "SCHEDULE", } export enum FinalityBucket { diff --git a/src/events.ts b/src/events.ts index b12fa610..c115e6bf 100644 --- a/src/events.ts +++ b/src/events.ts @@ -9,26 +9,35 @@ export const socketEvents = [ export const feesPlugEvents = [Events.FeesDeposited]; -export const watcherPrecompileEvents = [ +export const watcherEvents = [ Events.CalledAppGateway, Events.AppGatewayCallFailed, - Events.RequestSubmitted, - Events.QueryRequested, - Events.FinalizeRequested, - Events.Finalized, +]; + +export const promiseResolverEvents = [ Events.PromiseResolved, Events.PromiseNotResolved, - Events.TimeoutRequested, - Events.TimeoutResolved, Events.MarkedRevert, ]; -export const deliveryHelperEvents = [ - Events.PayloadSubmitted, +export const requestHandlerEvents = [ + Events.RequestSubmitted, Events.FeesIncreased, Events.RequestCancelled, ]; +export const writePrecompileEvents = [ + Events.WriteProofRequested, + Events.WriteProofUploaded, +]; + +export const readPrecompileEvents = [Events.ReadRequested]; + +export const schedulePrecompileEvents = [ + Events.ScheduleRequested, + Events.ScheduleResolved, +]; + export const auctionManagerEvents = [ Events.AuctionEnded, Events.AuctionRestarted, diff --git a/src/index.ts b/src/index.ts index 24e89c47..39a65132 100644 --- a/src/index.ts +++ b/src/index.ts @@ -3,3 +3,5 @@ export * from "./enums"; export * from "./events"; export * from "./finality"; export * from "./types"; +export * from "./constants"; +export * from "./signer"; diff --git a/src/signer.ts b/src/signer.ts new file mode 100644 index 00000000..9ec27359 --- /dev/null +++ b/src/signer.ts @@ -0,0 +1,25 @@ +import { ethers } from "ethers"; + +export const signWatcherMultiCallMessage = async ( + watcherContractAddress: string, + evmxChainId: number, + targetContractAddress: string, + calldata: string, + signer: ethers.Signer +) => { + const signatureNonce = Date.now(); + const digest = ethers.utils.keccak256( + ethers.utils.defaultAbiCoder.encode( + ["address", "uint32", "uint256", "address", "bytes"], + [ + watcherContractAddress, + evmxChainId, + signatureNonce, + targetContractAddress, + calldata, + ] + ) + ); + const signature = await signer.signMessage(ethers.utils.arrayify(digest)); + return { nonce: signatureNonce, signature }; +}; diff --git a/src/types.ts b/src/types.ts index 8a738fbd..1a047bcc 100644 --- a/src/types.ts +++ b/src/types.ts @@ -19,19 +19,27 @@ export type ChainAddressesObj = { Socket: string; SocketBatcher: string; FastSwitchboard: string; - FeesPlug: string; ContractFactoryPlug: string; + SocketFeesManager?: string; + FeesPlug?: string; startBlock: number; }; export type EVMxAddressesObj = { AddressResolver: string; - WatcherPrecompile: string; - WatcherPrecompileLimits: string; - WatcherPrecompileConfig: string; + AsyncDeployer: string; AuctionManager: string; + Configurations: string; + DeployForwarder: string; FeesManager: string; - DeliveryHelper: string; + FeesPool: string; + PromiseResolver: string; + ReadPrecompile: string; + RequestHandler: string; + SchedulePrecompile: string; + Watcher: string; + WritePrecompile: string; + ERC1967Factory: string; startBlock: number; }; @@ -39,6 +47,8 @@ export type S3Config = { version: string; chains: { [chainSlug: number]: ChainConfig }; supportedChainSlugs: number[]; + testnetChainSlugs: number[]; + mainnetChainSlugs: number[]; }; export type ChainConfig = { diff --git a/test/DeliveryHelper.t.sol b/test/DeliveryHelper.t.sol deleted file mode 100644 index a14321b7..00000000 --- a/test/DeliveryHelper.t.sol +++ /dev/null @@ -1,439 +0,0 @@ -// SPDX-License-Identifier: GPL-3.0-only -pragma solidity ^0.8.21; - -import "../contracts/evmx/payload-delivery/app-gateway/DeliveryHelper.sol"; -import "../contracts/evmx/payload-delivery/FeesManager.sol"; -import "../contracts/evmx/payload-delivery/AuctionManager.sol"; - -import "../contracts/evmx/Forwarder.sol"; -import "../contracts/evmx/interfaces/IAppGateway.sol"; - -import "./SetupTest.t.sol"; - -interface IAppGatewayDeployer { - function deployContracts(uint32 chainSlug_) external; -} - -contract DeliveryHelperTest is SetupTest { - uint256 public maxFees = 0.0001 ether; - uint256 public bidAmount = maxFees / 100; - uint256 public deployCounter; - uint256 public asyncPromiseCounterLocal = 0; - uint256 public asyncCounterTest; - uint256 public auctionEndDelaySeconds = 0; - uint256 public bidTimeout = 86400; - - DeliveryHelper deliveryHelper; - FeesManager feesManager; - AuctionManager auctionManager; - - event PayloadSubmitted( - uint40 indexed requestCount, - address indexed appGateway, - PayloadSubmitParams[] payloadSubmitParams, - uint256 maxFees, - address auctionManager, - bool onlyReadRequests - ); - event BidPlaced(uint40 requestCount, Bid bid); - event AuctionEnded(uint40 indexed requestCount, Bid winningBid); - event RequestCancelled(uint40 indexed requestCount); - event QueryRequested(uint32 chainSlug, address targetAddress, bytes32 payloadId, bytes payload); - - //////////////////////////////////// Setup //////////////////////////////////// - function setUpDeliveryHelper() internal { - // core - deployEVMxCore(); - // Deploy implementations - FeesManager feesManagerImpl = new FeesManager(); - DeliveryHelper deliveryHelperImpl = new DeliveryHelper(); - AuctionManager auctionManagerImpl = new AuctionManager(); - - // Deploy and initialize proxies - bytes memory feesManagerData = abi.encodeWithSelector( - FeesManager.initialize.selector, - address(addressResolver), - watcherEOA, - evmxSlug, - FAST - ); - - vm.expectEmit(true, true, true, false); - emit Initialized(version); - address feesManagerProxy = proxyFactory.deployAndCall( - address(feesManagerImpl), - watcherEOA, - feesManagerData - ); - - bytes memory auctionManagerData = abi.encodeWithSelector( - AuctionManager.initialize.selector, - evmxSlug, - auctionEndDelaySeconds, - address(addressResolver), - owner, - maxReAuctionCount - ); - vm.expectEmit(true, true, true, false); - emit Initialized(version); - address auctionManagerProxy = proxyFactory.deployAndCall( - address(auctionManagerImpl), - watcherEOA, - auctionManagerData - ); - - bytes memory deliveryHelperData = abi.encodeWithSelector( - DeliveryHelper.initialize.selector, - address(addressResolver), - owner, - bidTimeout - ); - - vm.expectEmit(true, true, true, false); - emit Initialized(version); - address deliveryHelperProxy = proxyFactory.deployAndCall( - address(deliveryHelperImpl), - watcherEOA, - deliveryHelperData - ); - - // Assign proxy addresses to contract variables - feesManager = FeesManager(address(feesManagerProxy)); - deliveryHelper = DeliveryHelper(address(deliveryHelperProxy)); - auctionManager = AuctionManager(address(auctionManagerProxy)); - - vm.startPrank(watcherEOA); - addressResolver.setDeliveryHelper(address(deliveryHelper)); - addressResolver.setDefaultAuctionManager(address(auctionManager)); - addressResolver.setFeesManager(address(feesManager)); - vm.stopPrank(); - - hoax(owner); - auctionManager.grantRole(TRANSMITTER_ROLE, transmitterEOA); - - // chain core contracts - 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( - _encodeAppGatewayId(address(deliveryHelper)), - address(arbConfig.socket), - address(arbConfig.switchboard) - ); - optConfig.contractFactoryPlug.initSocket( - _encodeAppGatewayId(address(deliveryHelper)), - address(optConfig.socket), - address(optConfig.switchboard) - ); - - arbConfig.feesPlug.initSocket( - _encodeAppGatewayId(address(feesManager)), - address(arbConfig.socket), - address(arbConfig.switchboard) - ); - optConfig.feesPlug.initSocket( - _encodeAppGatewayId(address(feesManager)), - address(optConfig.socket), - address(optConfig.switchboard) - ); - vm.stopPrank(); - - AppGatewayConfig[] memory gateways = new AppGatewayConfig[](4); - gateways[0] = AppGatewayConfig({ - plug: address(arbConfig.contractFactoryPlug), - chainSlug: arbChainSlug, - appGatewayId: _encodeAppGatewayId(address(deliveryHelper)), - switchboard: address(arbConfig.switchboard) - }); - gateways[1] = AppGatewayConfig({ - plug: address(optConfig.contractFactoryPlug), - chainSlug: optChainSlug, - appGatewayId: _encodeAppGatewayId(address(deliveryHelper)), - switchboard: address(optConfig.switchboard) - }); - gateways[2] = AppGatewayConfig({ - plug: address(arbConfig.feesPlug), - chainSlug: arbChainSlug, - appGatewayId: _encodeAppGatewayId(address(feesManager)), - switchboard: address(arbConfig.switchboard) - }); - gateways[3] = AppGatewayConfig({ - plug: address(optConfig.feesPlug), - chainSlug: optChainSlug, - appGatewayId: _encodeAppGatewayId(address(feesManager)), - switchboard: address(optConfig.switchboard) - }); - - bytes memory watcherSignature = _createWatcherSignature( - address(watcherPrecompileConfig), - abi.encode(IWatcherPrecompileConfig.setAppGateways.selector, gateways) - ); - watcherPrecompileConfig.setAppGateways(gateways, signatureNonce++, watcherSignature); - } - - //////////////////////////////////// Fees //////////////////////////////////// - - 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 - ) - ); - - feesManager.depositCredits{value: fees_.amount}( - appGateway_, - 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, - 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 //////////////////////////////////// - function _deploy( - uint32 chainSlug_, - IAppGateway appGateway_, - bytes32[] memory contractIds_ - ) internal returns (uint40 requestCount) { - requestCount = watcherPrecompile.nextRequestCount(); - IAppGatewayDeployer(address(appGateway_)).deployContracts(chainSlug_); - - finalizeRequest(requestCount, new bytes[](0)); - setupGatewayAndPlugs(chainSlug_, appGateway_, contractIds_); - } - - function finalizeRequest(uint40 requestCount_, bytes[] memory readReturnData_) internal { - uint40[] memory batches = watcherPrecompile.getBatches(requestCount_); - - bool onlyReads = _checkIfOnlyReads(batches[0]); - if (!(onlyReads && batches.length == 1)) { - bidAndEndAuction(requestCount_); - } - - uint256 readCount = 0; - for (uint i = 0; i < batches.length; i++) { - bool hasMoreBatches = i < batches.length - 1; - readCount = _finalizeBatch(batches[i], readReturnData_, readCount, hasMoreBatches); - } - } - - function executeRequest(bytes[] memory readReturnData_) internal { - uint40 requestCount = watcherPrecompile.nextRequestCount(); - requestCount = requestCount == 0 ? 0 : requestCount - 1; - finalizeRequest(requestCount, readReturnData_); - } - - function setupGatewayAndPlugs( - uint32 chainSlug_, - IAppGateway appGateway_, - bytes32[] memory contractIds_ - ) internal { - AppGatewayConfig[] memory gateways = new AppGatewayConfig[](contractIds_.length); - - SocketContracts memory socketConfig = getSocketConfig(chainSlug_); - for (uint i = 0; i < contractIds_.length; i++) { - address plug = appGateway_.getOnChainAddress(contractIds_[i], chainSlug_); - - gateways[i] = AppGatewayConfig({ - plug: plug, - chainSlug: chainSlug_, - appGatewayId: _encodeAppGatewayId(address(appGateway_)), - switchboard: address(socketConfig.switchboard) - }); - } - - bytes memory watcherSignature = _createWatcherSignature( - address(watcherPrecompileConfig), - abi.encode(IWatcherPrecompileConfig.setAppGateways.selector, gateways) - ); - watcherPrecompileConfig.setAppGateways(gateways, signatureNonce++, watcherSignature); - } - - //////////////////////////////////// Auction //////////////////////////////////// - function placeBid(uint40 requestCount) internal { - bytes memory transmitterSignature = _createSignature( - keccak256(abi.encode(address(auctionManager), evmxSlug, requestCount, bidAmount, "")), - transmitterPrivateKey - ); - - vm.expectEmit(false, false, false, false); - emit BidPlaced( - requestCount, - Bid({transmitter: transmitterEOA, fee: bidAmount, extraData: bytes("")}) - ); - auctionManager.bid(requestCount, bidAmount, transmitterSignature, bytes("")); - } - - function endAuction(uint40 requestCount_) internal { - if (auctionEndDelaySeconds == 0) return; - bytes32 timeoutId = _encodeTimeoutId( - evmxSlug, - address(watcherPrecompile), - timeoutIdCounter++ - ); - - bytes memory watcherSignature = _createWatcherSignature( - address(watcherPrecompile), - abi.encode(IWatcherPrecompile.resolveTimeout.selector, timeoutId) - ); - - vm.expectEmit(true, true, true, true); - emit AuctionEnded( - requestCount_, - Bid({fee: bidAmount, transmitter: transmitterEOA, extraData: ""}) - ); - watcherPrecompile.resolveTimeout(timeoutId, signatureNonce++, watcherSignature); - } - - function bidAndEndAuction(uint40 requestCount) internal { - placeBid(requestCount); - endAuction(requestCount); - } - - //////////////////////////////////// Utils /////////////////////////////////// - function _encodeTimeoutId( - uint32 chainSlug_, - address sbOrWatcher_, - uint256 counter_ - ) internal pure returns (bytes32) { - return - bytes32( - (uint256(chainSlug_) << 224) | (uint256(uint160(sbOrWatcher_)) << 64) | counter_ - ); - } - - function getOnChainAndForwarderAddresses( - uint32 chainSlug_, - bytes32 contractId_, - IAppGateway appGateway_ - ) internal view returns (address, address) { - address app = appGateway_.getOnChainAddress(contractId_, chainSlug_); - address forwarder = appGateway_.forwarderAddresses(contractId_, chainSlug_); - return (app, forwarder); - } - - function getContractFactoryPlug(uint32 chainSlug_) internal view returns (address) { - return address(getSocketConfig(chainSlug_).contractFactoryPlug); - } - - //////////////////////////////////// Helpers //////////////////////////////////// - function predictAsyncPromiseAddress( - address invoker_, - address forwarder_ - ) internal returns (address) { - bytes memory constructorArgs = abi.encode(invoker_, forwarder_, address(addressResolver)); - bytes memory combinedBytecode = abi.encodePacked(asyncPromiseBytecode, constructorArgs); - - bytes32 salt = keccak256(abi.encodePacked(constructorArgs, asyncPromiseCounterLocal++)); - - bytes32 hash = keccak256( - abi.encodePacked( - bytes1(0xff), - address(addressResolver), - salt, - keccak256(combinedBytecode) - ) - ); - - return address(uint160(uint256(hash))); - } - - //////////////////////////////////// Validators //////////////////////////////////// - - function checkPayloadRequestAndDetails( - PayloadSubmitParams[] memory payloadSubmitParams, - uint40 requestCount, - address appGateway_ - ) internal view { - for (uint i = 0; i < payloadSubmitParams.length; i++) { - PayloadSubmitParams memory payloadSubmitParam = payloadSubmitParams[i]; - - assertEq( - payloadSubmitParam.chainSlug, - payloadSubmitParams[i].chainSlug, - "ChainSlug mismatch" - ); - // todo - assertEq(payloadSubmitParam.target, payloadSubmitParams[i].target, "Target mismatch"); - assertEq( - keccak256(payloadSubmitParam.payload), - keccak256(payloadSubmitParams[i].payload), - "Payload mismatch" - ); - assertEq( - uint(payloadSubmitParam.callType), - uint(payloadSubmitParams[i].callType), - "CallType mismatch" - ); - assertEq( - payloadSubmitParam.gasLimit, - payloadSubmitParams[i].gasLimit, - "gasLimit mismatch" - ); - } - - RequestMetadata memory payloadRequest = deliveryHelper.getRequestMetadata(requestCount); - - assertEq(payloadRequest.appGateway, appGateway_, "AppGateway mismatch"); - assertEq(payloadRequest.auctionManager, address(auctionManager), "AuctionManager mismatch"); - assertEq(payloadRequest.winningBid.fee, bidAmount, "WinningBid mismatch"); - assertEq( - payloadRequest.winningBid.transmitter, - transmitterEOA, - "WinningBid transmitter mismatch" - ); - } -} diff --git a/test/FeesTest.t.sol b/test/FeesTest.t.sol index 1f6a0ba0..73016fdc 100644 --- a/test/FeesTest.t.sol +++ b/test/FeesTest.t.sol @@ -1,120 +1,84 @@ // 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"; -import {CounterAppGateway} from "./apps/app-gateways/counter/CounterAppGateway.sol"; +import "./apps/Counter.t.sol"; +import "./SetupTest.t.sol"; -contract FeesTest is DeliveryHelperTest { +contract FeesTest is AppGatewayBaseSetup { + uint32 feesChainSlug = arbChainSlug; 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; + SocketContracts feesConfig; CounterAppGateway counterGateway; function setUp() public { - setUpDeliveryHelper(); - feesConfig = getSocketConfig(feesChainSlug); + deploy(); + feesConfig = getSocketConfig(feesChainSlug); counterGateway = new CounterAppGateway(address(addressResolver), feesAmount); - depositUSDCFees( - address(counterGateway), - OnChainFees({ - chainSlug: feesChainSlug, - token: address(feesConfig.feesTokenUSDC), - amount: depositAmount - }) - ); + depositNativeAndCredits(feesChainSlug, 100 ether, 0, address(counterGateway)); bytes32[] memory contractIds = new bytes32[](1); contractIds[0] = counterGateway.counter(); - _deploy(feesChainSlug, IAppGateway(counterGateway), contractIds); + + // deploy counter app gateway + counterGateway.deployContracts(feesChainSlug); + executeDeploy(IAppGateway(counterGateway), feesChainSlug, contractIds); } - function testWithdrawTransmitterFees() public { - uint256 initialFeesPlugBalance = feesConfig.feesTokenUSDC.balanceOf( - address(feesConfig.feesPlug) + function withdrawCredits(address from, uint256 withdrawAmount) public { + approveAppGateway(address(feesManager), from); + hoax(from); + feesManager.withdrawCredits( + feesChainSlug, + address(feesConfig.testUSDC), + withdrawAmount, + feesAmount, + address(receiver) ); + executeRequest(); + } - assertEq( - initialFeesPlugBalance, - feesConfig.feesTokenUSDC.balanceOf(address(feesConfig.feesPlug)), - "FeesPlug Balance should be correct" - ); + function testWithdrawTransmitterFees() public { + uint256 transmitterReceiverBalanceBefore = feesConfig.testUSDC.balanceOf(receiver); + uint256 withdrawAmount = feesManager.getAvailableCredits(transmitterEOA); + withdrawAmount = withdrawAmount - feesAmount; + withdrawCredits(transmitterEOA, withdrawAmount); - uint256 transmitterReceiverBalanceBefore = feesConfig.feesTokenUSDC.balanceOf(receiver); - uint256 withdrawAmount = feesManager.getMaxCreditsAvailableForWithdraw(transmitterEOA); - vm.startPrank(transmitterEOA); - uint40 requestCount = deliveryHelper.withdrawTransmitterFees( - feesChainSlug, - address(feesConfig.feesTokenUSDC), - address(receiver), - withdrawAmount - ); - vm.stopPrank(); - uint40[] memory batches = watcherPrecompile.getBatches(requestCount); - _finalizeBatch(batches[0], new bytes[](0), 0, false); assertEq( transmitterReceiverBalanceBefore + withdrawAmount, - feesConfig.feesTokenUSDC.balanceOf(receiver), + feesConfig.testUSDC.balanceOf(receiver), "Transmitter Balance should be correct" ); - assertEq( - initialFeesPlugBalance - withdrawAmount, - feesConfig.feesTokenUSDC.balanceOf(address(feesConfig.feesPlug)), - "FeesPlug Balance should be correct" - ); } function testWithdrawFeeTokensAppGateway() public { - uint256 receiverBalanceBefore = feesConfig.feesTokenUSDC.balanceOf(receiver); + uint256 receiverBalanceBefore = feesConfig.testUSDC.balanceOf(receiver); uint256 withdrawAmount = 0.5 ether; - counterGateway.withdrawFeeTokens( - feesChainSlug, - address(feesConfig.feesTokenUSDC), - withdrawAmount, - receiver - ); - executeRequest(new bytes[](0)); + withdrawCredits(address(counterGateway), withdrawAmount); assertEq( receiverBalanceBefore + withdrawAmount, - feesConfig.feesTokenUSDC.balanceOf(receiver), + feesConfig.testUSDC.balanceOf(receiver), "Receiver Balance should be correct" ); } function testWithdrawFeeTokensUser() public { - depositUSDCFees( - user, - OnChainFees({ - chainSlug: feesChainSlug, - token: address(feesConfig.feesTokenUSDC), - amount: depositAmount - }) - ); + depositNativeAndCredits(feesChainSlug, 1 ether, 0, user); - uint256 receiverBalanceBefore = feesConfig.feesTokenUSDC.balanceOf(user); + uint256 receiverBalanceBefore = feesConfig.testUSDC.balanceOf(receiver); uint256 withdrawAmount = 0.5 ether; - - vm.prank(user); - deliveryHelper.withdrawTo( - feesChainSlug, - address(feesConfig.feesTokenUSDC), - withdrawAmount, - user, - address(auctionManager), - maxFees - ); - executeRequest(new bytes[](0)); + withdrawCredits(user, withdrawAmount); assertEq( receiverBalanceBefore + withdrawAmount, - feesConfig.feesTokenUSDC.balanceOf(user), + feesConfig.testUSDC.balanceOf(receiver), "Receiver Balance should be correct" ); } diff --git a/test/Migration.t.sol b/test/Migration.t.sol deleted file mode 100644 index fa7b05cb..00000000 --- a/test/Migration.t.sol +++ /dev/null @@ -1,329 +0,0 @@ -// SPDX-License-Identifier: GPL-3.0-only -pragma solidity ^0.8.21; - -import "./SetupTest.t.sol"; -import "../contracts/evmx/AddressResolver.sol"; -import "../contracts/evmx/watcherPrecompile/core/WatcherPrecompile.sol"; -import "../contracts/evmx/watcherPrecompile/WatcherPrecompileLimits.sol"; -import "../contracts/evmx/watcherPrecompile/WatcherPrecompileConfig.sol"; -import "../contracts/evmx/Forwarder.sol"; -import "../contracts/evmx/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.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" - ); - } - - 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/ProxyMigration.t.sol b/test/ProxyMigration.t.sol new file mode 100644 index 00000000..ad35df2a --- /dev/null +++ b/test/ProxyMigration.t.sol @@ -0,0 +1,257 @@ +// SPDX-License-Identifier: GPL-3.0-only +pragma solidity ^0.8.21; + +import "./ProxyStorage.t.sol"; +import "./mock/MockWatcherPrecompile.sol"; + +contract MigrationTest is ProxyStorageAssertions { + // 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 { + deploy(); + } + + 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 upgradeAndCall(address proxy, address newImpl, bytes memory data) internal { + address oldImpl = getImplementation(proxy); + + hoax(watcherEOA); + vm.expectEmit(true, true, true, true, address(proxyFactory)); + emit Upgraded(proxy, address(newImpl)); + proxyFactory.upgradeAndCall(proxy, address(newImpl), data); + + // Verify upgrade was successful + address newImplAddr = getImplementation(address(proxy)); + assertNotEq(oldImpl, newImplAddr, "Implementation should have changed"); + assertEq(newImplAddr, address(newImpl), "New implementation not set correctly"); + } + + function testFeesManagerUpgrade() public { + FeesManager newImpl = new FeesManager(); + upgradeAndCall(address(feesManager), address(newImpl), ""); + assertFeesManagerSlot(); + } + + function testAddressResolverUpgrade() public { + AddressResolver newImpl = new AddressResolver(); + upgradeAndCall(address(addressResolver), address(newImpl), ""); + assertAddressResolverSlot(); + } + + function testAsyncDeployerUpgrade() public { + AsyncDeployer newImpl = new AsyncDeployer(); + upgradeAndCall(address(asyncDeployer), address(newImpl), ""); + assertAsyncDeployerSlot(); + } + + function testWatcherUpgrade() public { + Watcher newImpl = new Watcher(); + upgradeAndCall(address(watcher), address(newImpl), ""); + assertWatcherSlot(); + } + + function testAuctionManagerUpgrade() public { + AuctionManager newImpl = new AuctionManager(); + upgradeAndCall(address(auctionManager), address(newImpl), ""); + assertAuctionManagerSlot(); + } + + function testDeployForwarderUpgrade() public { + DeployForwarder newImpl = new DeployForwarder(); + upgradeAndCall(address(deployForwarder), address(newImpl), ""); + assertDeployForwarderSlot(); + } + + function testConfigurationsUpgrade() public { + Configurations newImpl = new Configurations(); + upgradeAndCall(address(configurations), address(newImpl), ""); + assertConfigurationsSlot(); + } + + function testRequestHandlerUpgrade() public { + RequestHandler newImpl = new RequestHandler(); + upgradeAndCall(address(requestHandler), address(newImpl), ""); + assertRequestHandlerSlot(); + } + + function testWritePrecompileUpgrade() public { + WritePrecompile newImpl = new WritePrecompile(); + upgradeAndCall(address(writePrecompile), address(newImpl), ""); + assertWritePrecompileSlot(); + } + + function testUpgradeWithInitializationData() public { + // Deploy new implementation + MockWatcherPrecompile newImpl = new MockWatcherPrecompile(); + + // Prepare initialization data with new defaultLimit + uint256 newValue = 2000; + bytes memory initData = abi.encodeWithSelector( + MockWatcherPrecompile.initialize.selector, + newValue + ); + + upgradeAndCall(address(watcher), address(newImpl), initData); + assertWatcherSlot(); + + // Verify new value is set + bytes32 slotValue = vm.load(address(watcher), bytes32(uint256(160))); + assertEq(uint256(slotValue), newValue, "newValue mismatch"); + } + + function testUnauthorizedUpgrade() public { + // Deploy new implementation + Watcher newImpl = new Watcher(); + address oldImpl = getImplementation(address(watcher)); + + // Try to upgrade from unauthorized account + address unauthorizedUser = address(0xBEEF); + vm.startPrank(unauthorizedUser); + vm.expectRevert(UNAUTHORIZED_SELECTOR); + proxyFactory.upgradeAndCall(address(watcher), address(newImpl), ""); + vm.stopPrank(); + + // Verify implementation was not changed + assertEq( + getImplementation(address(watcher)), + oldImpl, + "Implementation should not have changed" + ); + } + + function testForwarderBeaconUpgrade() public { + // Deploy new implementation + Forwarder newImpl = new Forwarder(); + + // Get current implementation from beacon + address oldImpl = getBeaconImplementation(address(asyncDeployer.forwarderBeacon())); + + // Upgrade beacon to new implementation + vm.startPrank(watcherEOA); + vm.expectEmit(true, true, true, true, address(asyncDeployer)); + emit ImplementationUpdated("Forwarder", address(newImpl)); + asyncDeployer.setForwarderImplementation(address(newImpl)); + vm.stopPrank(); + + // Verify upgrade was successful + address newImplAddr = getBeaconImplementation(address(asyncDeployer.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 = asyncDeployer.getOrDeployForwarderContract(address(0x123), 1); + address beacon = getBeacon(newForwarder); + assertEq( + beacon, + address(asyncDeployer.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(asyncDeployer.asyncPromiseBeacon())); + + // Upgrade beacon to new implementation + hoax(watcherEOA); + vm.expectEmit(true, true, true, false); + emit ImplementationUpdated("AsyncPromise", address(newImpl)); + asyncDeployer.setAsyncPromiseImplementation(address(newImpl)); + + // Verify upgrade was successful + address newImplAddr = getBeaconImplementation(address(asyncDeployer.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 + hoax(address(watcher)); + address newPromise = asyncDeployer.deployAsyncPromiseContract(address(this), 1); + address beacon = getBeacon(newPromise); + assertEq( + beacon, + address(asyncDeployer.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)); + asyncDeployer.setForwarderImplementation(address(newForwarderImpl)); + + // Try upgrading async promise beacon + vm.expectRevert(abi.encodeWithSelector(Ownable.Unauthorized.selector)); + asyncDeployer.setAsyncPromiseImplementation(address(newAsyncPromiseImpl)); + + vm.stopPrank(); + + // Verify implementations were not changed + assertNotEq( + getBeaconImplementation(address(asyncDeployer.forwarderBeacon())), + address(newForwarderImpl), + "Forwarder implementation should not have changed" + ); + assertNotEq( + getBeaconImplementation(address(asyncDeployer.asyncPromiseBeacon())), + address(newAsyncPromiseImpl), + "AsyncPromise implementation should not have changed" + ); + } +} diff --git a/test/ProxyStorage.t.sol b/test/ProxyStorage.t.sol new file mode 100644 index 00000000..284b84cc --- /dev/null +++ b/test/ProxyStorage.t.sol @@ -0,0 +1,296 @@ +// SPDX-License-Identifier: GPL-3.0-only +pragma solidity ^0.8.21; + +import "./SetupTest.t.sol"; + +contract ProxyStorageAssertions is AppGatewayBaseSetup { + uint256 public constant FIRST_SLOT = 50; + uint256 public constant ADDRESS_RESOLVER_SLOT = 59; + uint256 public constant WATCHER_SLOT = 52; + + function assertAddressResolverUtilSlot(uint256 slot_, address contract_) internal view { + bytes32 slotValue = vm.load(address(contract_), bytes32(uint256(slot_))); + assertEq( + address(uint160(uint256(slotValue))), + address(addressResolver), + "address resolver mismatch" + ); + } + + function assertAccessControlSlot(uint256 slot_, address contract_) internal { + bytes32 role = keccak256("ADMIN_ROLE"); + address account = address(0xBEEF); + bool value = true; + + // Compute the slot for _permits[role][account] + bytes32 outerSlot = keccak256(abi.encode(role, uint256(slot_))); + bytes32 mappingSlot = keccak256(abi.encode(account, outerSlot)); + + // Store the value + vm.store(address(contract_), mappingSlot, bytes32(uint256(value ? 1 : 0))); + + // Read back and assert + bytes32 slotValue = vm.load(address(contract_), mappingSlot); + assertEq(uint256(slotValue), value ? 1 : 0, "_permits mapping slot value mismatch"); + } + + function assertFeesManagerSlot() internal { + // first + bytes32 slotValue = vm.load(address(feesManager), bytes32(uint256(FIRST_SLOT))); + assertEq(uint32(uint256(slotValue)), evmxSlug, "evmxSlug mismatch"); + + // last + hoax(watcherEOA); + feesManager.setFeesPlug(evmxSlug, address(addressResolver)); + bytes32 mappingSlot = keccak256(abi.encode(uint256(evmxSlug), uint256(57))); + slotValue = vm.load(address(feesManager), mappingSlot); + assertEq( + address(uint160(uint256(slotValue))), + address(addressResolver), + "fees plug mismatch" + ); + + // address resolver util slot + assertAddressResolverUtilSlot(108, address(feesManager)); + } + + function assertAddressResolverSlot() internal { + // first + bytes32 slotValue = vm.load(address(addressResolver), bytes32(uint256(FIRST_SLOT))); + assertEq(address(uint160(uint256(slotValue))), address(watcher), "watcher mismatch"); + + // last + hoax(watcherEOA); + addressResolver.setContractAddress(keccak256("auctionManager"), address(auctionManager)); + bytes32 mappingSlot = keccak256(abi.encode(keccak256("auctionManager"), uint256(55))); + slotValue = vm.load(address(addressResolver), mappingSlot); + assertEq( + address(uint160(uint256(slotValue))), + address(auctionManager), + "auctionManager mismatch" + ); + } + + function assertAsyncDeployerSlot() internal { + // first + bytes32 slotValue = vm.load(address(asyncDeployer), bytes32(uint256(FIRST_SLOT))); + assertEq( + address(uint160(uint256(slotValue))), + address(asyncDeployer.forwarderBeacon()), + "forwarderBeacon mismatch" + ); + + // last + hoax(address(watcher)); + asyncDeployer.deployAsyncPromiseContract(address(this), 1); + slotValue = vm.load(address(asyncDeployer), bytes32(uint256(54))); + assertEq(uint256(slotValue), 1, "asyncPromiseCounter mismatch"); + + // address resolver util slot + assertAddressResolverUtilSlot(105, address(asyncDeployer)); + } + + function assertWatcherSlot() internal { + // first + bytes32 slotValue = vm.load(address(watcher), bytes32(uint256(FIRST_SLOT))); + assertEq(uint32(uint256(slotValue)), evmxSlug, "evmxSlug mismatch"); + + // last + uint256 nonce = watcherNonce; + watcherMultiCall( + address(writePrecompile), + abi.encodeWithSelector(WritePrecompile.uploadProof.selector, bytes32(0), bytes32(0)) + ); + bytes32 mappingSlot = keccak256(abi.encode(uint256(nonce), uint256(59))); + slotValue = vm.load(address(watcher), mappingSlot); + assertEq(uint256(slotValue), 1, "isNonceUsed mismatch"); + + // address resolver util slot + assertAddressResolverUtilSlot(110, address(watcher)); + } + + function assertAuctionManagerSlot() internal { + // first + bytes32 slotValue = vm.load(address(auctionManager), bytes32(uint256(FIRST_SLOT))); + assertEq(uint32(uint256(slotValue)), evmxSlug, "evmxSlug mismatch"); + + // last + uint40 requestCount = 1; + uint256 reAuctionCount = 100; + bytes32 mappingSlot = keccak256(abi.encode(uint256(requestCount), uint256(54))); + vm.store(address(auctionManager), mappingSlot, bytes32(uint256(reAuctionCount))); + slotValue = vm.load(address(auctionManager), mappingSlot); + assertEq(uint256(slotValue), reAuctionCount, "reAuctionCount mismatch"); + + // address resolver util slot + assertAddressResolverUtilSlot(106, address(auctionManager)); + + // access control slot + assertAccessControlSlot(165, address(auctionManager)); + } + + function assertDeployForwarderSlot() internal view { + // address resolver util slot + assertAddressResolverUtilSlot(0, address(deployForwarder)); + + // first + bytes32 slotValue = vm.load(address(deployForwarder), bytes32(uint256(100))); + assertEq(uint32(uint256(slotValue)), 0, "saltCounter mismatch"); + + slotValue = vm.load(address(deployForwarder), bytes32(uint256(101))); + assertEq(bytes32(uint256(slotValue)), FAST, "deployerSwitchboardType mismatch"); + } + + function assertConfigurationsSlot() internal { + // first + uint32 chainSlug = 123; + address plug = address(0xBEEF); + uint256 testValue = 42; + // Compute the slot for _plugConfigs[chainSlug][plug] + bytes32 outerSlot = keccak256(abi.encode(uint256(chainSlug), uint256(50))); + bytes32 mappingSlot = keccak256(abi.encode(plug, outerSlot)); + vm.store(address(configurations), mappingSlot, bytes32(testValue)); + bytes32 slotValue = vm.load(address(configurations), mappingSlot); + assertEq(uint256(slotValue), testValue, "_plugConfigs mapping slot value mismatch"); + + // last + address appGateway = address(0xCAFE); + bool value = true; + // Compute the slot for isValidPlug[appGateway][chainSlug][plug] + bytes32 outerSlot1 = keccak256(abi.encode(appGateway, uint256(53))); + bytes32 outerSlot2 = keccak256(abi.encode(uint256(chainSlug), outerSlot1)); + mappingSlot = keccak256(abi.encode(plug, outerSlot2)); + vm.store(address(configurations), mappingSlot, bytes32(uint256(value ? 1 : 0))); + slotValue = vm.load(address(configurations), mappingSlot); + assertEq(uint256(slotValue), value ? 1 : 0, "isValidPlug mapping slot value mismatch"); + + // watcher base slot + slotValue = vm.load(address(configurations), bytes32(uint256(104))); + assertEq(address(uint160(uint256(slotValue))), address(watcher), "watcher mismatch"); + } + + function assertRequestHandlerSlot() internal { + // first + bytes32 slotValue = vm.load(address(requestHandler), bytes32(uint256(FIRST_SLOT))); + assertEq( + uint40(uint256(slotValue)), + requestHandler.nextRequestCount(), + "nextRequestCount mismatch" + ); + + // last + uint40 requestCount = 1; + uint256 testValue = 42; + // Compute the slot for _requests[requestCount] + bytes32 mappingSlot = keccak256(abi.encode(uint256(requestCount), uint256(55))); + vm.store(address(requestHandler), mappingSlot, bytes32(testValue)); + slotValue = vm.load(address(requestHandler), mappingSlot); + assertEq(uint256(slotValue), testValue, "_requests mapping slot value mismatch"); + + // address resolver util slot + assertAddressResolverUtilSlot(106, address(requestHandler)); + } + + function assertWritePrecompileSlot() internal view { + // first + bytes32 slotValue = vm.load(address(writePrecompile), bytes32(uint256(FIRST_SLOT))); + assertEq(uint256(slotValue), writeFees, "writeFees mismatch"); + + // last + bytes32 mappingSlot = keccak256(abi.encode(uint256(arbChainSlug), uint256(55))); + slotValue = vm.load(address(writePrecompile), mappingSlot); + assertEq( + address(uint160(uint256(slotValue))), + address(arbConfig.contractFactoryPlug), + "contractFactoryPlugs mismatch" + ); + + // watcher base slot + slotValue = vm.load(address(writePrecompile), bytes32(uint256(106))); + assertEq(address(uint160(uint256(slotValue))), address(watcher), "watcher mismatch"); + } + + function assertForwarderSlot() internal { + address forwarder = asyncDeployer.getOrDeployForwarderContract(address(this), evmxSlug); + + // first + bytes32 slotValue = vm.load(address(forwarder), bytes32(uint256(FIRST_SLOT))); + assertEq(uint32(uint256(slotValue)), evmxSlug); + + assertAddressResolverUtilSlot(101, address(forwarder)); + } + + function assertAsyncPromiseSlot() internal { + hoax(address(watcher)); + address asyncPromise = asyncDeployer.deployAsyncPromiseContract(address(this), 100); + + // first + bytes32 slotValue = vm.load(address(asyncPromise), bytes32(uint256(FIRST_SLOT))); + assertEq( + bytes4(uint32(uint256(slotValue))), + bytes4(AsyncPromise(asyncPromise).callbackSelector()), + "callbackSelector mismatch" + ); + + // last + slotValue = vm.load(address(asyncPromise), bytes32(uint256(52))); + assertEq( + bytes32(uint256(slotValue)), + bytes32(AsyncPromise(asyncPromise).callbackData()), + "callbackData mismatch" + ); + + // address resolver util slot + assertAddressResolverUtilSlot(103, address(asyncPromise)); + } +} + +contract ProxyTest is ProxyStorageAssertions { + function setUp() public { + deploy(); + } + + function testFeesManagerSlot() public { + assertFeesManagerSlot(); + } + + function testAddressResolverSlot() public { + assertAddressResolverSlot(); + } + + function testAsyncDeployerSlot() public { + assertAsyncDeployerSlot(); + } + + function testWatcherSlot() public { + assertWatcherSlot(); + } + + function testAuctionManagerSlot() public { + assertAuctionManagerSlot(); + } + + function testDeployForwarderSlot() public view { + assertDeployForwarderSlot(); + } + + function testConfigurationsSlot() public { + assertConfigurationsSlot(); + } + + function testRequestHandlerSlot() public { + assertRequestHandlerSlot(); + } + + function testWritePrecompileSlot() public view { + assertWritePrecompileSlot(); + } + + function testForwarderSlot() public { + assertForwarderSlot(); + } + + function testAsyncPromiseSlot() public { + assertAsyncPromiseSlot(); + } +} diff --git a/test/SetupTest.t.sol b/test/SetupTest.t.sol index 8153cd27..03ed46b2 100644 --- a/test/SetupTest.t.sol +++ b/test/SetupTest.t.sol @@ -5,52 +5,69 @@ import "forge-std/Test.sol"; import "../contracts/utils/common/Structs.sol"; import "../contracts/utils/common/Errors.sol"; import "../contracts/utils/common/Constants.sol"; -import "../contracts/evmx/watcherPrecompile/core/WatcherPrecompile.sol"; -import "../contracts/evmx/watcherPrecompile/WatcherPrecompileConfig.sol"; -import "../contracts/evmx/watcherPrecompile/WatcherPrecompileLimits.sol"; -import "../contracts/evmx/watcherPrecompile/PayloadHeaderDecoder.sol"; -import "../contracts/evmx/interfaces/IForwarder.sol"; import "../contracts/utils/common/AccessRoles.sol"; -import {Socket} from "../contracts/protocol/Socket.sol"; +import "../contracts/utils/common/IdUtils.sol"; + +import "../contracts/evmx/interfaces/IForwarder.sol"; + +import "../contracts/protocol/Socket.sol"; import "../contracts/protocol/switchboard/FastSwitchboard.sol"; import "../contracts/protocol/SocketBatcher.sol"; -import "../contracts/evmx/AddressResolver.sol"; -import {ContractFactoryPlug} from "../contracts/evmx/payload-delivery/ContractFactoryPlug.sol"; -import {FeesPlug} from "../contracts/evmx/payload-delivery/FeesPlug.sol"; -import {SocketFeeManager} from "../contracts/protocol/SocketFeeManager.sol"; -import {ETH_ADDRESS} from "../contracts/utils/common/Constants.sol"; -import {ResolvedPromises, OnChainFees} from "../contracts/utils/common/Structs.sol"; +import "../contracts/protocol/SocketFeeManager.sol"; + +import "../contracts/evmx/watcher/Watcher.sol"; +import "../contracts/evmx/watcher/Configurations.sol"; +import "../contracts/evmx/watcher/RequestHandler.sol"; +import "../contracts/evmx/watcher/PromiseResolver.sol"; +import "../contracts/evmx/watcher/precompiles/WritePrecompile.sol"; +import "../contracts/evmx/watcher/precompiles/ReadPrecompile.sol"; +import "../contracts/evmx/watcher/precompiles/SchedulePrecompile.sol"; + +import "../contracts/evmx/helpers/AddressResolver.sol"; +import "../contracts/evmx/helpers/AsyncDeployer.sol"; +import "../contracts/evmx/helpers/DeployForwarder.sol"; +import "../contracts/evmx/plugs/ContractFactoryPlug.sol"; +import "../contracts/evmx/fees/FeesManager.sol"; +import "../contracts/evmx/fees/FeesPool.sol"; +import "../contracts/evmx/plugs/FeesPlug.sol"; +import "../contracts/evmx/AuctionManager.sol"; +import "../contracts/evmx/mocks/TestUSDC.sol"; import "solady/utils/ERC1967Factory.sol"; -import "./apps/app-gateways/USDC.sol"; -contract SetupTest is Test { - using PayloadHeaderDecoder for bytes32; - uint public c = 1; - address owner = address(uint160(c++)); +contract SetupStore is Test { + uint256 c = 1; + uint64 version = 1; uint256 watcherPrivateKey = 0xac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80; uint256 transmitterPrivateKey = 0x59c6995e998f97a5a0044966f0945389dc9e86dae88c7a8412f4603b6b78690d; - address watcherEOA = 0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266; address transmitterEOA = 0x70997970C51812dc3A010C7d01b50e0d17dc79C8; + address socketOwner = address(uint160(c++)); uint32 arbChainSlug = 421614; uint32 optChainSlug = 11155420; uint32 evmxSlug = 1; - uint256 expiryTime = 10000000; + + uint256 expiryTime = 864000; + uint256 bidTimeout = 86400; uint256 maxReAuctionCount = 10; - uint256 socketFees = 0.01 ether; - uint256 public signatureNonce; - uint256 public payloadIdCounter; - uint256 public timeoutIdCounter; - uint256 public triggerCounter; - uint256 public defaultLimit = 1000; + uint256 auctionEndDelaySeconds = 0; + uint256 maxScheduleDelayInSeconds = 86400; + uint256 maxMsgValueLimit = 1 ether; - bytes public asyncPromiseBytecode = type(AsyncPromise).creationCode; - uint64 public version = 1; + uint256 writeFees = 10000; + uint256 readFees = 10000; + uint256 scheduleCallbackFees = 10000; + uint256 scheduleFeesPerSecond = 10000; + uint256 triggerFees = 10000; + uint256 socketFees = 0; + uint256 public watcherNonce; + uint256 public payloadIdCounter; + uint256 public triggerCounter; + uint256 public asyncPromiseCounter; struct SocketContracts { uint32 chainSlug; Socket socket; @@ -59,373 +76,1035 @@ contract SetupTest is Test { SocketBatcher socketBatcher; ContractFactoryPlug contractFactoryPlug; FeesPlug feesPlug; - ERC20 feesTokenUSDC; + TestUSDC testUSDC; } - - AddressResolver public addressResolver; - WatcherPrecompile public watcherPrecompile; - WatcherPrecompileConfig public watcherPrecompileConfig; - WatcherPrecompileLimits public watcherPrecompileLimits; - SocketContracts public arbConfig; SocketContracts public optConfig; - // Add new variables for proxy admin and implementation contracts - WatcherPrecompile public watcherPrecompileImpl; - WatcherPrecompileConfig public watcherPrecompileConfigImpl; - WatcherPrecompileLimits public watcherPrecompileLimitsImpl; - AddressResolver public addressResolverImpl; + FeesManager feesManagerImpl; + AddressResolver addressResolverImpl; + AsyncDeployer asyncDeployerImpl; + Watcher watcherImpl; + AuctionManager auctionManagerImpl; + DeployForwarder deployForwarderImpl; + Configurations configurationsImpl; + RequestHandler requestHandlerImpl; + WritePrecompile writePrecompileImpl; + ERC1967Factory public proxyFactory; + FeesManager feesManager; + FeesPool feesPool; + AddressResolver public addressResolver; + AsyncDeployer public asyncDeployer; + DeployForwarder public deployForwarder; + AuctionManager auctionManager; + + Watcher public watcher; + Configurations public configurations; + RequestHandler public requestHandler; + PromiseResolver public promiseResolver; + WritePrecompile public writePrecompile; + ReadPrecompile public readPrecompile; + SchedulePrecompile public schedulePrecompile; +} +contract DeploySetup is SetupStore { event Initialized(uint64 version); - event FinalizeRequested(bytes32 digest, PayloadParams payloadParams); //////////////////////////////////// Setup //////////////////////////////////// + function _deploy() internal { + _deployEVMxCore(); - function deploySocket(uint32 chainSlug_) internal returns (SocketContracts memory) { - Socket socket = new Socket(chainSlug_, owner, "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)); + // chain core contracts + arbConfig = _deploySocket(arbChainSlug); + _configureChain(arbChainSlug); + optConfig = _deploySocket(optChainSlug); + _configureChain(optChainSlug); - // switchboard - switchboard.registerSwitchboard(); - switchboard.grantRole(WATCHER_ROLE, watcherEOA); + // transfer eth to fees pool for native fee payouts + vm.deal(address(feesPool), 100000 ether); + + vm.startPrank(watcherEOA); + auctionManager.grantRole(TRANSMITTER_ROLE, transmitterEOA); + feesPool.grantRole(FEE_MANAGER_ROLE, address(feesManager)); + + // setup address resolver + addressResolver.setWatcher(address(watcher)); + addressResolver.setAsyncDeployer(address(asyncDeployer)); + addressResolver.setDefaultAuctionManager(address(auctionManager)); + addressResolver.setFeesManager(address(feesManager)); + addressResolver.setDeployForwarder(address(deployForwarder)); + + requestHandler.setPrecompile(WRITE, writePrecompile); + requestHandler.setPrecompile(READ, readPrecompile); + requestHandler.setPrecompile(SCHEDULE, schedulePrecompile); + + watcher.setCoreContracts( + address(requestHandler), + address(configurations), + address(promiseResolver) + ); vm.stopPrank(); - hoax(watcherEOA); - watcherPrecompileConfig.setOnChainContracts( - chainSlug_, - address(socket), - address(contractFactoryPlug), - address(feesPlug) + _connectCorePlugs(); + + vm.startPrank(transmitterEOA); + arbConfig.testUSDC.mint(address(transmitterEOA), 100 ether); + arbConfig.testUSDC.approve(address(arbConfig.feesPlug), 100 ether); + + arbConfig.feesPlug.depositCreditAndNative( + address(arbConfig.testUSDC), + address(transmitterEOA), + 100 ether + ); + + AppGatewayApprovals[] memory approvals = new AppGatewayApprovals[](1); + approvals[0] = AppGatewayApprovals({appGateway: address(auctionManager), approval: true}); + feesManager.approveAppGateways(approvals); + vm.stopPrank(); + } + + function _connectCorePlugs() internal { + AppGatewayConfig[] memory configs = new AppGatewayConfig[](4); + configs[0] = AppGatewayConfig({ + chainSlug: arbChainSlug, + plug: address(arbConfig.feesPlug), + plugConfig: PlugConfig({ + appGatewayId: encodeAppGatewayId(address(feesManager)), + switchboard: address(arbConfig.switchboard) + }) + }); + configs[1] = AppGatewayConfig({ + chainSlug: optChainSlug, + plug: address(optConfig.feesPlug), + plugConfig: PlugConfig({ + appGatewayId: encodeAppGatewayId(address(feesManager)), + switchboard: address(optConfig.switchboard) + }) + }); + configs[2] = AppGatewayConfig({ + chainSlug: arbChainSlug, + plug: address(arbConfig.contractFactoryPlug), + plugConfig: PlugConfig({ + appGatewayId: encodeAppGatewayId(address(writePrecompile)), + switchboard: address(arbConfig.switchboard) + }) + }); + configs[3] = AppGatewayConfig({ + chainSlug: optChainSlug, + plug: address(optConfig.contractFactoryPlug), + plugConfig: PlugConfig({ + appGatewayId: encodeAppGatewayId(address(writePrecompile)), + switchboard: address(optConfig.switchboard) + }) + }); + + watcherMultiCall( + address(configurations), + abi.encodeWithSelector(Configurations.setAppGatewayConfigs.selector, configs) ); - SocketFeeManager socketFeeManager = new SocketFeeManager(owner, socketFees); - hoax(watcherEOA); - watcherPrecompileConfig.setSwitchboard(chainSlug_, FAST, address(switchboard)); + } + + function _deploySocket(uint32 chainSlug_) internal returns (SocketContracts memory) { + // socket + Socket socket = new Socket(chainSlug_, socketOwner, "test"); return SocketContracts({ chainSlug: chainSlug_, socket: socket, - socketFeeManager: socketFeeManager, - switchboard: switchboard, - socketBatcher: socketBatcher, - contractFactoryPlug: contractFactoryPlug, - feesPlug: feesPlug, - feesTokenUSDC: feesTokenUSDC + socketFeeManager: new SocketFeeManager(socketOwner, socketFees), + switchboard: new FastSwitchboard(chainSlug_, socket, socketOwner), + socketBatcher: new SocketBatcher(socketOwner, socket), + contractFactoryPlug: new ContractFactoryPlug(address(socket), socketOwner), + feesPlug: new FeesPlug(address(socket), socketOwner), + testUSDC: new TestUSDC("USDC", "USDC", 6, socketOwner, 1000000000000000000000000) }); } - function deployEVMxCore() internal { - // Deploy implementations - addressResolverImpl = new AddressResolver(); - watcherPrecompileImpl = new WatcherPrecompile(); - watcherPrecompileConfigImpl = new WatcherPrecompileConfig(); - watcherPrecompileLimitsImpl = new WatcherPrecompileLimits(); + function _configureChain(uint32 chainSlug_) internal { + SocketContracts memory socketConfig = getSocketConfig(chainSlug_); + Socket socket = socketConfig.socket; + FastSwitchboard switchboard = socketConfig.switchboard; + FeesPlug feesPlug = socketConfig.feesPlug; + ContractFactoryPlug contractFactoryPlug = socketConfig.contractFactoryPlug; + + vm.startPrank(socketOwner); + // socket + socket.grantRole(GOVERNANCE_ROLE, address(socketOwner)); + socket.grantRole(RESCUE_ROLE, address(socketOwner)); + socket.grantRole(SWITCHBOARD_DISABLER_ROLE, address(socketOwner)); + + // switchboard + switchboard.registerSwitchboard(); + switchboard.grantRole(WATCHER_ROLE, watcherEOA); + switchboard.grantRole(RESCUE_ROLE, address(socketOwner)); + + feesPlug.grantRole(RESCUE_ROLE, address(socketOwner)); + feesPlug.whitelistToken(address(socketConfig.testUSDC)); + feesPlug.connectSocket( + encodeAppGatewayId(address(feesManager)), + address(socket), + address(switchboard) + ); + + contractFactoryPlug.grantRole(RESCUE_ROLE, address(socketOwner)); + contractFactoryPlug.connectSocket( + encodeAppGatewayId(address(writePrecompile)), + address(socket), + address(switchboard) + ); + + vm.stopPrank(); + + vm.startPrank(watcherEOA); + configurations.setSocket(chainSlug_, address(socket)); + configurations.setSwitchboard(chainSlug_, FAST, address(switchboard)); + + // plugs + feesManager.setFeesPlug(chainSlug_, address(feesPlug)); + + // precompiles + writePrecompile.updateChainMaxMsgValueLimits(chainSlug_, maxMsgValueLimit); + writePrecompile.setContractFactoryPlugs(chainSlug_, address(contractFactoryPlug)); + + vm.stopPrank(); + } + + function _deployEVMxCore() internal { proxyFactory = new ERC1967Factory(); + feesPool = new FeesPool(watcherEOA); + + // Deploy implementations for upgradeable contracts + feesManagerImpl = new FeesManager(); + addressResolverImpl = new AddressResolver(); + asyncDeployerImpl = new AsyncDeployer(); + watcherImpl = new Watcher(); + auctionManagerImpl = new AuctionManager(); + deployForwarderImpl = new DeployForwarder(); + configurationsImpl = new Configurations(); + requestHandlerImpl = new RequestHandler(); + writePrecompileImpl = new WritePrecompile(); // Deploy and initialize proxies - bytes memory addressResolverData = abi.encodeWithSelector( - AddressResolver.initialize.selector, - watcherEOA - ); - vm.expectEmit(true, true, true, false); - emit Initialized(version); - address addressResolverProxy = proxyFactory.deployAndCall( + address addressResolverProxy = _deployAndVerifyProxy( address(addressResolverImpl), watcherEOA, - addressResolverData + abi.encodeWithSelector(AddressResolver.initialize.selector, watcherEOA) ); + addressResolver = AddressResolver(addressResolverProxy); - bytes memory watcherPrecompileLimitsData = abi.encodeWithSelector( - WatcherPrecompileLimits.initialize.selector, + address feesManagerProxy = _deployAndVerifyProxy( + address(feesManagerImpl), watcherEOA, - address(addressResolverProxy), - defaultLimit + abi.encodeWithSelector( + FeesManager.initialize.selector, + evmxSlug, + address(addressResolver), + address(feesPool), + watcherEOA, + FAST + ) ); - vm.expectEmit(true, true, true, false); - emit Initialized(version); - address watcherPrecompileLimitsProxy = proxyFactory.deployAndCall( - address(watcherPrecompileLimitsImpl), + feesManager = FeesManager(feesManagerProxy); + + address asyncDeployerProxy = _deployAndVerifyProxy( + address(asyncDeployerImpl), watcherEOA, - watcherPrecompileLimitsData + abi.encodeWithSelector( + AsyncDeployer.initialize.selector, + watcherEOA, + address(addressResolver) + ) ); + asyncDeployer = AsyncDeployer(asyncDeployerProxy); - bytes memory watcherPrecompileConfigData = abi.encodeWithSelector( - WatcherPrecompileConfig.initialize.selector, + address auctionManagerProxy = _deployAndVerifyProxy( + address(auctionManagerImpl), watcherEOA, - address(addressResolverProxy), - evmxSlug + abi.encodeWithSelector( + AuctionManager.initialize.selector, + evmxSlug, + uint128(bidTimeout), + maxReAuctionCount, + auctionEndDelaySeconds, + address(addressResolver), + watcherEOA + ) ); - vm.expectEmit(true, true, true, false); - emit Initialized(version); - address watcherPrecompileConfigProxy = proxyFactory.deployAndCall( - address(watcherPrecompileConfigImpl), + auctionManager = AuctionManager(auctionManagerProxy); + + address deployForwarderProxy = _deployAndVerifyProxy( + address(deployForwarderImpl), + watcherEOA, + abi.encodeWithSelector( + DeployForwarder.initialize.selector, + watcherEOA, + address(addressResolver), + FAST + ) + ); + deployForwarder = DeployForwarder(deployForwarderProxy); + + address watcherProxy = _deployAndVerifyProxy( + address(watcherImpl), watcherEOA, - watcherPrecompileConfigData + abi.encodeWithSelector( + Watcher.initialize.selector, + evmxSlug, + triggerFees, + watcherEOA, + address(addressResolver) + ) ); + watcher = Watcher(watcherProxy); - bytes memory watcherPrecompileData = abi.encodeWithSelector( - WatcherPrecompile.initialize.selector, + address requestHandlerProxy = _deployAndVerifyProxy( + address(requestHandlerImpl), watcherEOA, - address(addressResolverProxy), - expiryTime, - evmxSlug, - address(watcherPrecompileLimitsProxy), - address(watcherPrecompileConfigProxy) + abi.encodeWithSelector( + RequestHandler.initialize.selector, + watcherEOA, + address(addressResolver) + ) + ); + requestHandler = RequestHandler(requestHandlerProxy); + + address configurationsProxy = _deployAndVerifyProxy( + address(configurationsImpl), + watcherEOA, + abi.encodeWithSelector(Configurations.initialize.selector, address(watcher), watcherEOA) + ); + configurations = Configurations(configurationsProxy); + + address writePrecompileProxy = _deployAndVerifyProxy( + address(writePrecompileImpl), + watcherEOA, + abi.encodeWithSelector( + WritePrecompile.initialize.selector, + watcherEOA, + address(watcher), + writeFees, + expiryTime + ) + ); + writePrecompile = WritePrecompile(writePrecompileProxy); + + // non proxy contracts + promiseResolver = new PromiseResolver(address(watcher)); + readPrecompile = new ReadPrecompile(address(watcher), readFees, expiryTime); + schedulePrecompile = new SchedulePrecompile( + address(watcher), + maxScheduleDelayInSeconds, + scheduleFeesPerSecond, + scheduleCallbackFees, + expiryTime ); + } + + function _deployAndVerifyProxy( + address implementation_, + address owner_, + bytes memory data_ + ) internal returns (address) { vm.expectEmit(true, true, true, false); emit Initialized(version); - address watcherPrecompileProxy = proxyFactory.deployAndCall( - address(watcherPrecompileImpl), - watcherEOA, - watcherPrecompileData + return address(proxyFactory.deployAndCall(implementation_, owner_, data_)); + } + + function getSocketConfig(uint32 chainSlug_) internal view returns (SocketContracts memory) { + return chainSlug_ == arbChainSlug ? arbConfig : optConfig; + } + + function watcherMultiCall(address contractAddress_, bytes memory data_) internal { + WatcherMultiCallParams[] memory params = new WatcherMultiCallParams[](1); + params[0] = WatcherMultiCallParams({ + contractAddress: contractAddress_, + data: data_, + nonce: watcherNonce, + signature: _createWatcherSignature(contractAddress_, data_) + }); + watcherNonce++; + watcher.watcherMultiCall(params); + } + + function _createWatcherSignature( + address contractAddress_, + bytes memory data_ + ) internal view returns (bytes memory) { + bytes32 digest = keccak256( + abi.encode(address(watcher), evmxSlug, watcherNonce, contractAddress_, data_) + ); + return _createSignature(digest, watcherPrivateKey); + } + + function _createSignature( + bytes32 digest_, + uint256 privateKey_ + ) internal pure returns (bytes memory sig) { + bytes32 digest = keccak256(abi.encodePacked("\x19Ethereum Signed Message:\n32", digest_)); + (uint8 sigV, bytes32 sigR, bytes32 sigS) = vm.sign(privateKey_, digest); + sig = new bytes(65); + bytes1 v32 = bytes1(sigV); + assembly { + mstore(add(sig, 96), v32) + mstore(add(sig, 32), sigR) + mstore(add(sig, 64), sigS) + } + } + + function predictAsyncPromiseAddress( + address invoker_, + address forwarder_ + ) internal returns (address) { + bytes memory asyncPromiseBytecode = type(AsyncPromise).creationCode; + bytes memory constructorArgs = abi.encode(invoker_, forwarder_, address(addressResolver)); + bytes memory combinedBytecode = abi.encodePacked(asyncPromiseBytecode, constructorArgs); + + bytes32 salt = keccak256(abi.encodePacked(constructorArgs, asyncPromiseCounter++)); + + bytes32 hash = keccak256( + abi.encodePacked( + bytes1(0xff), + address(addressResolver), + salt, + keccak256(combinedBytecode) + ) ); - // Assign proxy addresses to public variables - addressResolver = AddressResolver(address(addressResolverProxy)); - watcherPrecompile = WatcherPrecompile(address(watcherPrecompileProxy)); - watcherPrecompileConfig = WatcherPrecompileConfig(address(watcherPrecompileConfigProxy)); - watcherPrecompileLimits = WatcherPrecompileLimits(address(watcherPrecompileLimitsProxy)); + return address(uint160(uint256(hash))); + } +} - vm.startPrank(watcherEOA); - addressResolver.setWatcherPrecompile(address(watcherPrecompile)); - watcherPrecompileLimits.setCallBackFees(1); - watcherPrecompileLimits.setFinalizeFees(1); - watcherPrecompileLimits.setQueryFees(1); - watcherPrecompileLimits.setTimeoutFees(1); +contract FeesSetup is DeploySetup { + event Deposited( + uint32 indexed chainSlug, + address indexed token, + address indexed appGateway, + uint256 creditAmount, + uint256 nativeAmount + ); + event CreditsWrapped(address indexed consumeFrom, uint256 amount); + event CreditsUnwrapped(address indexed consumeFrom, uint256 amount); + event CreditsTransferred(address indexed from, address indexed to, uint256 amount); + + function deploy() internal { + _deploy(); + + depositNativeAndCredits(arbChainSlug, 100 ether, 100 ether, address(transmitterEOA)); + approveAppGateway(address(auctionManager), address(transmitterEOA)); + } + + // mints test token and deposits the given native and credits to given `user_` + function depositNativeAndCredits( + uint32 chainSlug_, + uint256 credits_, + uint256 native_, + address user_ + ) internal { + SocketContracts memory socketConfig = getSocketConfig(chainSlug_); + TestUSDC token = socketConfig.testUSDC; + + uint256 userBalance = token.balanceOf(user_); + uint256 feesPlugBalance = token.balanceOf(address(socketConfig.feesPlug)); + token.mint(address(user_), 100 ether); + assertEq( + token.balanceOf(user_), + userBalance + 100 ether, + "User should have 100 more test tokens" + ); + + vm.startPrank(user_); + token.approve(address(socketConfig.feesPlug), 100 ether); + socketConfig.feesPlug.depositCreditAndNative(address(token), address(user_), 100 ether); vm.stopPrank(); + + assertEq( + token.balanceOf(address(socketConfig.feesPlug)), + feesPlugBalance + 100 ether, + "Fees plug should have 100 more test tokens" + ); + + uint256 currentCredits = feesManager.getAvailableCredits(user_); + uint256 currentNative = address(user_).balance; + + vm.expectEmit(true, true, true, false); + emit Deposited(chainSlug_, address(token), user_, credits_, native_); + + watcherMultiCall( + address(feesManager), + abi.encodeWithSelector( + Credit.deposit.selector, + chainSlug_, + address(token), + user_, + native_, + credits_ + ) + ); + + assertEq( + feesManager.getAvailableCredits(user_), + currentCredits + credits_, + "User should have more credits" + ); + assertEq(address(user_).balance, currentNative + native_, "User should have more native"); } - //////////////////////////////////// Watcher precompiles //////////////////////////////////// + function approveAppGateway(address appGateway_, address user_) internal { + bool approval = feesManager.isApproved(user_, appGateway_); + if (approval) return; - function _checkIfOnlyReads(uint40 batchCount_) internal view returns (bool) { - bytes32[] memory payloadIds = watcherPrecompile.getBatchPayloadIds(batchCount_); + AppGatewayApprovals[] memory approvals = new AppGatewayApprovals[](1); + approvals[0] = AppGatewayApprovals({appGateway: appGateway_, approval: true}); - for (uint i = 0; i < payloadIds.length; i++) { - PayloadParams memory payloadParams = watcherPrecompile.getPayloadParams(payloadIds[i]); - if (payloadParams.payloadHeader.getCallType() != CallType.READ) { - return false; - } - } + hoax(user_); + feesManager.approveAppGateways(approvals); - return true; + assertEq( + feesManager.isApproved(user_, appGateway_), + true, + "App gateway should be approved" + ); } - function _resolveAndExpectFinalizeRequested( - bytes32 payloadId_, - PayloadParams memory payloadParams, - bytes memory returnData, - bool isLastPayload + function approveAppGatewayWithSignature( + address appGateway_, + address user_, + uint256 userPrivateKey_ ) internal { - if (isLastPayload) { - vm.expectEmit(false, false, false, false); - emit FinalizeRequested(payloadId_, payloadParams); + bool approval = feesManager.isApproved(user_, appGateway_); + if (approval) return; + + // Create fee approval data with signature + bytes32 digest = keccak256( + abi.encode(address(feesManager), evmxSlug, appGateway_, block.timestamp, true) + ); + + // Sign with consumeFrom's private key + bytes memory signature = _createSignature(digest, userPrivateKey_); + + // Encode approval data + bytes memory feeApprovalData = abi.encode(appGateway_, true, block.timestamp, signature); + + // Call whitelistAppGatewayWithSignature with approval data + feesManager.approveAppGatewayWithSignature(feeApprovalData); + assertEq( + feesManager.isApproved(user_, appGateway_), + true, + "App gateway should be approved" + ); + } +} + +contract AuctionSetup is FeesSetup { + event BidPlaced(uint40 requestCount, Bid bid); + event AuctionStarted(uint40 requestCount); + event AuctionEnded(uint40 requestCount, Bid winningBid); + + function getBidAmount(uint40 requestCount) internal view returns (uint256) { + return watcher.getRequestParams(requestCount).requestFeesDetails.maxFees / 2; + } + + function placeBid(uint40 requestCount) internal { + uint256 bidAmount = getBidAmount(requestCount); + + bytes memory transmitterSignature = _createSignature( + keccak256(abi.encode(address(auctionManager), evmxSlug, requestCount, bidAmount, "")), + transmitterPrivateKey + ); + + if (auctionEndDelaySeconds == 0) { + vm.expectEmit(true, true, true, false); + emit AuctionEnded( + requestCount, + Bid({fee: bidAmount, transmitter: transmitterEOA, extraData: bytes("")}) + ); + } else { + vm.expectEmit(true, true, true, false); + emit AuctionStarted(requestCount); } - _resolvePromise(payloadId_, returnData); + vm.expectEmit(true, true, true, false); + emit BidPlaced( + requestCount, + Bid({transmitter: transmitterEOA, fee: bidAmount, extraData: bytes("")}) + ); + auctionManager.bid(requestCount, bidAmount, transmitterSignature, bytes("")); } - function _finalizeBatch( - uint40 batchCount_, - bytes[] memory readReturnData_, - uint256 readCount_, - bool hasMoreBatches - ) internal returns (uint256) { - bytes32[] memory payloadIds = watcherPrecompile.getBatchPayloadIds(batchCount_); + function endAuction(uint40 requestCount_) internal { + if (auctionEndDelaySeconds == 0) return; - for (uint i = 0; i < payloadIds.length; i++) { - PayloadParams memory payloadParams = watcherPrecompile.getPayloadParams(payloadIds[i]); - bool isLastPayload = i == payloadIds.length - 1 && hasMoreBatches; - - if (payloadParams.payloadHeader.getCallType() == CallType.READ) { - _resolveAndExpectFinalizeRequested( - payloadParams.payloadId, - payloadParams, - readReturnData_[readCount_++], - isLastPayload - ); - } else { - (, bytes memory returnData) = _uploadProofAndExecute(payloadParams); - _resolveAndExpectFinalizeRequested( - payloadParams.payloadId, - payloadParams, - returnData, - isLastPayload - ); - } + // todo: handle other cases + + uint256 bidAmount = getBidAmount(requestCount_); + // bytes memory watcherSignature = _createSignature( + // keccak256(abi.encode(address(watcher), evmxSlug, requestCount_, bidAmount, "")), + // watcherPrivateKey + // ); + + vm.expectEmit(true, true, true, true); + emit AuctionEnded( + requestCount_, + Bid({fee: bidAmount, transmitter: transmitterEOA, extraData: ""}) + ); + + // promiseResolver.resolvePromises(); + } + + function bidAndEndAuction(uint40 requestCount) internal { + placeBid(requestCount); + endAuction(requestCount); + } + + // tests: + // bid and end auction with delay + // bid and end auction with delay and expire bid +} + +contract WatcherSetup is AuctionSetup { + event ReadRequested(Transaction transaction, uint256 readAtBlockNumber, bytes32 payloadId); + event ScheduleRequested(bytes32 payloadId, uint256 deadline); + event ScheduleResolved(bytes32 payloadId); + event WriteProofRequested( + address transmitter, + bytes32 digest, + bytes32 prevBatchDigestHash, + uint256 deadline, + PayloadParams payloadParams + ); + event WriteProofUploaded(bytes32 indexed payloadId, bytes proof); + + function executeDeployMultiChain( + IAppGateway appGateway_, + uint32[] memory chainSlugs_, + bytes32[] memory contractIds_ + ) internal returns (uint40 requestCount) { + return _executeDeploy(appGateway_, chainSlugs_, contractIds_); + } + + function executeDeploy( + IAppGateway appGateway_, + uint32 chainSlug_, + bytes32[] memory contractIds_ + ) internal returns (uint40 requestCount) { + uint32[] memory chainSlugs = new uint32[](1); + chainSlugs[0] = chainSlug_; + return _executeDeploy(appGateway_, chainSlugs, contractIds_); + } + + function _executeDeploy( + IAppGateway appGateway_, + uint32[] memory chainSlugs_, + bytes32[] memory contractIds_ + ) internal returns (uint40 requestCount) { + requestCount = executeRequest(); + for (uint i = 0; i < chainSlugs_.length; i++) { + setupGatewayAndPlugs(chainSlugs_[i], appGateway_, contractIds_); } - return readCount_; } - function _uploadProofAndExecute( - PayloadParams memory payloadParams - ) internal returns (bool, bytes memory) { - (bytes memory watcherProof, bytes32 digest) = _generateWatcherProof(payloadParams); - _writeProof(payloadParams.payloadId, watcherProof); + function executeRequest() internal returns (uint40 requestCount) { + requestCount = watcher.getCurrentRequestCount(); + requestCount = requestCount == 0 ? 0 : requestCount - 1; - ( - ExecuteParams memory params, - SocketBatcher socketBatcher, - , - bytes memory transmitterSig, - address refundAddress - ) = _getExecuteParams(payloadParams); + RequestParams memory requestParams = requestHandler.getRequest(requestCount); + uint40[] memory batches = requestHandler.getRequestBatchIds(requestCount); - return - socketBatcher.attestAndExecute( - params, - payloadParams.switchboard, - digest, - watcherProof, - transmitterSig, - refundAddress - ); + // bids and executes schedule request if created for endAuction + if (requestParams.writeCount != 0) bidAndEndAuction(requestCount); + + for (uint i = 0; i < batches.length; i++) _processBatch(batches[i]); + requestParams = requestHandler.getRequest(requestCount); + assertEq(requestParams.requestTrackingParams.isRequestExecuted, true); } - function resolvePromises(bytes32[] memory payloadIds, bytes[] memory returnData) internal { + function _processBatch(uint40 batchCount_) internal { + bytes32[] memory payloadIds = requestHandler.getBatchPayloadIds(batchCount_); + + PromiseReturnData[] memory promiseReturnData = new PromiseReturnData[](1); + bool success; for (uint i = 0; i < payloadIds.length; i++) { - _resolvePromise(payloadIds[i], returnData[i]); + PayloadParams memory payloadParams = watcher.getPayloadParams(payloadIds[i]); + + if (payloadParams.callType == READ) { + (success, promiseReturnData[0]) = _processRead(payloadParams); + } else if (payloadParams.callType == WRITE) { + (success, promiseReturnData[0]) = _processWrite(payloadParams); + } else if (payloadParams.callType == SCHEDULE) { + vm.warp(payloadParams.deadline - expiryTime); + promiseReturnData[0] = PromiseReturnData({ + exceededMaxCopy: false, + payloadId: payloadParams.payloadId, + returnData: bytes("") + }); + success = true; + } + + if (success) { + _resolvePromise(promiseReturnData); + } else { + vm.warp(payloadParams.deadline); + _markRevert(promiseReturnData[0], true); + } } } - //////////////////////////////////// Helpers //////////////////////////////////// - function getSocketConfig(uint32 chainSlug_) internal view returns (SocketContracts memory) { - return chainSlug_ == arbChainSlug ? arbConfig : optConfig; + function _processRead( + PayloadParams memory payloadParams + ) internal returns (bool success, PromiseReturnData memory promiseReturnData) { + (Transaction memory transaction, ) = abi.decode( + payloadParams.precompileData, + (Transaction, uint256) + ); + + bytes memory returnData; + (success, returnData) = transaction.target.call(transaction.payload); + promiseReturnData = PromiseReturnData({ + exceededMaxCopy: false, + payloadId: payloadParams.payloadId, + returnData: returnData + }); } - function _generateWatcherProof( - PayloadParams memory params_ - ) internal view returns (bytes memory, bytes32) { - SocketContracts memory socketConfig = getSocketConfig(params_.payloadHeader.getChainSlug()); - DigestParams memory digestParams_ = DigestParams( - address(socketConfig.socket), - transmitterEOA, - params_.payloadId, - params_.deadline, - params_.payloadHeader.getCallType(), - params_.gasLimit, - params_.value, - params_.payload, - params_.target, - _encodeAppGatewayId(params_.appGateway), - params_.prevDigestsHash - ); - bytes32 digest = watcherPrecompile.getDigest(digestParams_); + function _processWrite( + PayloadParams memory payloadParams + ) internal returns (bool success, PromiseReturnData memory promiseReturnData) { + bytes32 payloadId = payloadParams.payloadId; - bytes32 sigDigest = keccak256( - abi.encode(address(socketConfig.switchboard), socketConfig.chainSlug, digest) - ); - bytes memory proof = _createSignature(sigDigest, watcherPrivateKey); - return (proof, digest); + ( + uint32 chainSlug, + address switchboard, + bytes32 digest, + DigestParams memory digestParams + ) = _validateAndGetDigest(payloadParams); + + bytes memory watcherProof = _uploadProof(payloadId, digest, switchboard, chainSlug); + + return + _executeWrite( + chainSlug, + switchboard, + digest, + digestParams, + payloadParams, + watcherProof + ); } - function _writeProof(bytes32 payloadId_, bytes memory watcherProof_) internal { - bytes memory bytesInput = abi.encode( - IWatcherPrecompile.finalized.selector, - payloadId_, - watcherProof_ + function _uploadProof( + bytes32 payloadId, + bytes32 digest, + address switchboard, + uint32 chainSlug + ) internal returns (bytes memory proof) { + proof = _createSignature( + keccak256(abi.encode(address(switchboard), chainSlug, digest)), + watcherPrivateKey ); - bytes memory watcherSignature = _createWatcherSignature( - address(watcherPrecompile), - bytesInput + + vm.expectEmit(true, true, true, false); + emit WriteProofUploaded(payloadId, proof); + watcherMultiCall( + address(writePrecompile), + abi.encodeWithSelector(WritePrecompile.uploadProof.selector, payloadId, proof) ); - watcherPrecompile.finalized(payloadId_, watcherProof_, signatureNonce++, watcherSignature); - assertEq(watcherPrecompile.watcherProofs(payloadId_), watcherProof_); + assertEq(writePrecompile.watcherProofs(payloadId), proof); } - function _getExecuteParams( + function _validateAndGetDigest( PayloadParams memory payloadParams ) internal view returns ( - ExecuteParams memory params, - SocketBatcher socketBatcher, - uint256 value, - bytes memory transmitterSig, - address refundAddress + uint32 chainSlug, + address switchboard, + bytes32 digest, + DigestParams memory digestParams ) { - 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, - gasLimit: payloadParams.gasLimit, - value: payloadParams.value, - payload: payloadParams.payload, - target: payloadParams.target, - requestCount: payloadParams.payloadHeader.getRequestCount(), - batchCount: payloadParams.payloadHeader.getBatchCount(), - payloadCount: payloadParams.payloadHeader.getPayloadCount(), - prevDigestsHash: payloadParams.prevDigestsHash, - extraData: bytes("") - }); + ( + address appGateway, + Transaction memory transaction, + , + uint256 gasLimit, + uint256 value, + address switchboard_ + ) = abi.decode( + payloadParams.precompileData, + (address, Transaction, WriteFinality, uint256, uint256, address) + ); - value = payloadParams.value; - socketBatcher = socketConfig.socketBatcher; - refundAddress = transmitterEOA; + chainSlug = transaction.chainSlug; + switchboard = switchboard_; + + bytes32 prevBatchDigestHash = writePrecompile.getPrevBatchDigestHash( + payloadParams.requestCount, + payloadParams.batchCount + ); + digestParams = DigestParams( + address(getSocketConfig(transaction.chainSlug).socket), + transmitterEOA, + payloadParams.payloadId, + payloadParams.deadline, + payloadParams.callType, + gasLimit, + value, + transaction.payload, + transaction.target, + encodeAppGatewayId(appGateway), + prevBatchDigestHash, + bytes("") + ); + + digest = writePrecompile.getDigest(digestParams); + assertEq(writePrecompile.digestHashes(payloadParams.payloadId), digest); } - function _resolvePromise(bytes32 payloadId, bytes memory returnData) internal { - ResolvedPromises[] memory resolvedPromises = new ResolvedPromises[](1); - resolvedPromises[0] = ResolvedPromises({payloadId: payloadId, returnData: returnData}); + function _executeWrite( + uint32 chainSlug, + address switchboard, + bytes32 digest, + DigestParams memory digestParams, + PayloadParams memory payloadParams, + bytes memory watcherProof + ) internal returns (bool success, PromiseReturnData memory promiseReturnData) { + bytes memory transmitterSig = _createSignature( + keccak256( + abi.encode(address(getSocketConfig(chainSlug).socket), payloadParams.payloadId) + ), + transmitterPrivateKey + ); + bytes memory returnData; + (success, returnData) = getSocketConfig(chainSlug).socketBatcher.attestAndExecute( + ExecuteParams({ + callType: digestParams.callType, + deadline: digestParams.deadline, + gasLimit: digestParams.gasLimit, + value: digestParams.value, + payload: digestParams.payload, + target: digestParams.target, + requestCount: payloadParams.requestCount, + batchCount: payloadParams.batchCount, + payloadCount: payloadParams.payloadCount, + prevBatchDigestHash: digestParams.prevBatchDigestHash, + extraData: digestParams.extraData + }), + switchboard, + digest, + watcherProof, + transmitterSig, + transmitterEOA + ); + + promiseReturnData = PromiseReturnData({ + exceededMaxCopy: false, + payloadId: payloadParams.payloadId, + returnData: returnData + }); + } - bytes memory watcherSignature = _createWatcherSignature( - address(watcherPrecompile), - abi.encode(WatcherPrecompile.resolvePromises.selector, resolvedPromises) + function _resolvePromise(PromiseReturnData[] memory promiseReturnData) internal { + watcherMultiCall( + address(promiseResolver), + abi.encodeWithSelector(PromiseResolver.resolvePromises.selector, promiseReturnData) ); - watcherPrecompile.resolvePromises(resolvedPromises, signatureNonce++, watcherSignature); } - function _createWatcherSignature( - address watcherPrecompile_, - bytes memory params_ - ) internal view returns (bytes memory) { - bytes32 digest = keccak256( - abi.encode(watcherPrecompile_, evmxSlug, signatureNonce, params_) + function _markRevert( + PromiseReturnData memory promiseReturnData, + bool isRevertingOnchain_ + ) internal { + watcherMultiCall( + address(promiseResolver), + abi.encodeWithSelector( + PromiseResolver.markRevert.selector, + promiseReturnData, + isRevertingOnchain_ + ) ); - return _createSignature(digest, watcherPrivateKey); } - function _createSignature( - bytes32 digest_, - uint256 privateKey_ - ) internal pure returns (bytes memory sig) { - bytes32 digest = keccak256(abi.encodePacked("\x19Ethereum Signed Message:\n32", digest_)); - (uint8 sigV, bytes32 sigR, bytes32 sigS) = vm.sign(privateKey_, digest); - sig = new bytes(65); - bytes1 v32 = bytes1(sigV); + function setupGatewayAndPlugs( + uint32 chainSlug_, + IAppGateway appGateway_, + bytes32[] memory contractIds_ + ) internal { + AppGatewayConfig[] memory configs = new AppGatewayConfig[](contractIds_.length); - assembly { - mstore(add(sig, 96), v32) - mstore(add(sig, 32), sigR) - mstore(add(sig, 64), sigS) + SocketContracts memory socketConfig = getSocketConfig(chainSlug_); + for (uint i = 0; i < contractIds_.length; i++) { + address plug = appGateway_.getOnChainAddress(contractIds_[i], chainSlug_); + + configs[i] = AppGatewayConfig({ + plug: plug, + chainSlug: chainSlug_, + plugConfig: PlugConfig({ + appGatewayId: encodeAppGatewayId(address(appGateway_)), + switchboard: address(socketConfig.switchboard) + }) + }); } + watcherMultiCall( + address(configurations), + abi.encodeWithSelector(Configurations.setAppGatewayConfigs.selector, configs) + ); + } +} + +contract AppGatewayBaseSetup is WatcherSetup { + function getOnChainAndForwarderAddresses( + uint32 chainSlug_, + bytes32 contractId_, + IAppGateway appGateway_ + ) internal view returns (address, address) { + address app = appGateway_.getOnChainAddress(contractId_, chainSlug_); + address forwarder = appGateway_.forwarderAddresses(contractId_, chainSlug_); + return (app, forwarder); } - function _encodeAppGatewayId(address appGateway_) internal pure returns (bytes32) { - return bytes32(uint256(uint160(appGateway_))); + // todo: add checks for request params and payload params created to match what is expected + + function checkRequestParams( + uint40 requestCount, + RequestParams memory expectedRequest + ) internal view { + RequestParams memory actualRequest = watcher.getRequestParams(requestCount); + // RequestParams checks + assertEq( + actualRequest.appGateway, + expectedRequest.appGateway, + "Request: appGateway mismatch" + ); + assertEq( + actualRequest.auctionManager, + expectedRequest.auctionManager, + "Request: auctionManager mismatch" + ); + assertEq( + actualRequest.writeCount, + expectedRequest.writeCount, + "Request: writeCount mismatch" + ); + assertEq( + keccak256(actualRequest.onCompleteData), + keccak256(expectedRequest.onCompleteData), + "Request: onCompleteData mismatch" + ); + // Nested struct checks (RequestTrackingParams) + assertEq( + actualRequest.requestTrackingParams.isRequestCancelled, + expectedRequest.requestTrackingParams.isRequestCancelled, + "RequestTrackingParams: isRequestCancelled mismatch" + ); + assertEq( + actualRequest.requestTrackingParams.isRequestExecuted, + expectedRequest.requestTrackingParams.isRequestExecuted, + "RequestTrackingParams: isRequestExecuted mismatch" + ); + assertEq( + actualRequest.requestTrackingParams.currentBatch, + expectedRequest.requestTrackingParams.currentBatch, + "RequestTrackingParams: currentBatch mismatch" + ); + assertEq( + actualRequest.requestTrackingParams.currentBatchPayloadsLeft, + expectedRequest.requestTrackingParams.currentBatchPayloadsLeft, + "RequestTrackingParams: currentBatchPayloadsLeft mismatch" + ); + assertEq( + actualRequest.requestTrackingParams.payloadsRemaining, + expectedRequest.requestTrackingParams.payloadsRemaining, + "RequestTrackingParams: payloadsRemaining mismatch" + ); + // Nested struct checks (RequestFeesDetails) + assertEq( + actualRequest.requestFeesDetails.maxFees, + expectedRequest.requestFeesDetails.maxFees, + "RequestFeesDetails: maxFees mismatch" + ); + assertEq( + actualRequest.requestFeesDetails.consumeFrom, + expectedRequest.requestFeesDetails.consumeFrom, + "RequestFeesDetails: consumeFrom mismatch" + ); + assertEq( + actualRequest.requestFeesDetails.winningBid.fee, + expectedRequest.requestFeesDetails.winningBid.fee, + "RequestFeesDetails: winningBid.fee mismatch" + ); + assertEq( + actualRequest.requestFeesDetails.winningBid.transmitter, + expectedRequest.requestFeesDetails.winningBid.transmitter, + "RequestFeesDetails: winningBid.transmitter mismatch" + ); + assertEq( + keccak256(actualRequest.requestFeesDetails.winningBid.extraData), + keccak256(expectedRequest.requestFeesDetails.winningBid.extraData), + "RequestFeesDetails: winningBid.extraData mismatch" + ); } - function _decodeAppGatewayId(bytes32 appGatewayId_) internal pure returns (address) { - return address(uint160(uint256(appGatewayId_))); + function checkPayloadParams(PayloadParams[] memory expectedPayloads) internal view { + for (uint i = 0; i < expectedPayloads.length; i++) { + PayloadParams memory expectedPayload = expectedPayloads[i]; + PayloadParams memory actualPayload = watcher.getPayloadParams( + expectedPayload.payloadId + ); + // PayloadParams checks + assertEq( + actualPayload.requestCount, + expectedPayload.requestCount, + "Payload: requestCount mismatch" + ); + assertEq( + actualPayload.batchCount, + expectedPayload.batchCount, + "Payload: batchCount mismatch" + ); + assertEq( + actualPayload.payloadCount, + expectedPayload.payloadCount, + "Payload: payloadCount mismatch" + ); + assertEq( + actualPayload.callType, + expectedPayload.callType, + "Payload: callType mismatch" + ); + assertEq( + actualPayload.asyncPromise, + expectedPayload.asyncPromise, + "Payload: asyncPromise mismatch" + ); + assertEq( + actualPayload.appGateway, + expectedPayload.appGateway, + "Payload: appGateway mismatch" + ); + assertEq( + actualPayload.payloadId, + expectedPayload.payloadId, + "Payload: payloadId mismatch" + ); + assertEq( + actualPayload.resolvedAt, + expectedPayload.resolvedAt, + "Payload: resolvedAt mismatch" + ); + assertEq( + actualPayload.deadline, + expectedPayload.deadline, + "Payload: deadline mismatch" + ); + assertEq( + keccak256(actualPayload.precompileData), + keccak256(expectedPayload.precompileData), + "Payload: precompileData mismatch" + ); + } } } diff --git a/test/SocketFeeManager.t.sol b/test/SocketFeeManager.t.sol index e81132ca..8809d11e 100644 --- a/test/SocketFeeManager.t.sol +++ b/test/SocketFeeManager.t.sol @@ -1,32 +1,34 @@ // 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/SocketFeeManager.sol"; import {MockFastSwitchboard} from "./mock/MockFastSwitchboard.sol"; -import {ExecuteParams, TransmissionParams, CallType} from "../contracts/utils/common/Structs.sol"; -import {GOVERNANCE_ROLE, RESCUE_ROLE} from "../contracts/utils/common/AccessRoles.sol"; -import {Test} from "forge-std/Test.sol"; +import "./SetupTest.t.sol"; +import "./apps/Counter.t.sol"; -contract SocketFeeManagerTest is SetupTest { +contract SocketFeeManagerTest is AppGatewayBaseSetup { Counter public counter; - address public gateway = address(5); + + address public owner = address(uint160(c++)); + address public gateway = address(uint160(c++)); + MockFastSwitchboard public mockSwitchboard; Socket public socket; SocketFeeManager public socketFeeManager; function setUp() public { + socketFees = 0.001 ether; + 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); + counter = new Counter(); + mockSwitchboard.registerSwitchboard(); + counter.initSocket(encodeAppGatewayId(gateway), address(socket), address(mockSwitchboard)); - counter = new Counter(); - counter.initSocket(_encodeAppGatewayId(gateway), address(socket), address(mockSwitchboard)); + vm.prank(owner); + socket.grantRole(GOVERNANCE_ROLE, address(owner)); } function testSuccessfulExecutionWithFeeManagerNotSet() public { @@ -126,7 +128,7 @@ contract SocketFeeManagerTest is SetupTest { bytes memory payload ) internal view returns (ExecuteParams memory, TransmissionParams memory) { ExecuteParams memory executeParams = ExecuteParams({ - callType: CallType.WRITE, + callType: WRITE, deadline: block.timestamp + 1 days, gasLimit: 100000, value: 0, @@ -135,7 +137,7 @@ contract SocketFeeManagerTest is SetupTest { requestCount: 0, batchCount: 0, payloadCount: 0, - prevDigestsHash: bytes32(0), + prevBatchDigestHash: bytes32(0), extraData: bytes("") }); diff --git a/test/Storage.t.sol b/test/Storage.t.sol deleted file mode 100644 index 523d7834..00000000 --- a/test/Storage.t.sol +++ /dev/null @@ -1,79 +0,0 @@ -// SPDX-License-Identifier: GPL-3.0-only -pragma solidity ^0.8.21; - -import "./DeliveryHelper.t.sol"; - -// contract StorageTest is DeliveryHelperTest { -// DeliveryHelper public deliveryHelperImpl; - -// function setUp() public { -// setUpDeliveryHelper(); -// } - -// function testAddressResolverSlot() public view { -// // Test AddressResolver version at slot 59 -// bytes32 versionSlot = vm.load(address(addressResolver), bytes32(uint256(59))); -// assertEq(uint64(uint256(versionSlot)), 1); - -// // Test auction manager address at slot 61 in AddressResolver -// bytes32 slotValue = vm.load(address(addressResolver), bytes32(uint256(61))); -// assertEq(address(uint160(uint256(slotValue))), address(auctionManager)); -// } - -// function testWatcherPrecompileSlot() public view { -// // Test AddressResolver address at slot 109 in WatcherPrecompile -// bytes32 slotValue = vm.load(address(watcherPrecompile), bytes32(uint256(52))); -// assertEq(uint256(slotValue), evmxSlug); - -// slotValue = vm.load(address(watcherPrecompile), bytes32(uint256(220))); -// assertEq(address(uint160(uint256(slotValue))), address(addressResolver)); -// } - -// function testFeesManagerSlot() public view { -// bytes32 slotValue = vm.load(address(feesManager), bytes32(uint256(51))); -// assertEq(uint32(uint256(slotValue)), evmxSlug); - -// slotValue = vm.load(address(feesManager), bytes32(uint256(106))); -// assertEq(address(uint160(uint256(slotValue))), address(addressResolver)); -// } - -// function testAuctionManagerSlot() public view { -// bytes32 slotValue = vm.load(address(auctionManager), bytes32(uint256(50))); -// assertEq(uint32(uint256(slotValue)), evmxSlug); - -// slotValue = vm.load(address(auctionManager), bytes32(uint256(105))); -// assertEq(address(uint160(uint256(slotValue))), address(addressResolver)); -// } - -// function testForwarderSlot() public { -// address forwarder = addressResolver.getOrDeployForwarderContract( -// address(this), -// address(this), -// evmxSlug -// ); - -// bytes32 slotValue = vm.load(address(forwarder), bytes32(uint256(50))); -// assertEq(uint32(uint256(slotValue)), evmxSlug); - -// slotValue = vm.load(address(forwarder), bytes32(uint256(53))); -// assertEq(address(uint160(uint256(slotValue))), address(0)); -// } - -// function testAsyncPromiseSlot() public { -// address asyncPromise = addressResolver.deployAsyncPromiseContract(address(this)); - -// bytes32 slotValue = vm.load(address(asyncPromise), bytes32(uint256(51))); -// assertEq(address(uint160(uint256(slotValue))), address(this)); - -// slotValue = vm.load(address(asyncPromise), bytes32(uint256(103))); -// assertEq(address(uint160(uint256(slotValue))), address(addressResolver)); -// } - -// function testDeliveryHelperSlot() public view { -// bytes32 slotValue = vm.load(address(deliveryHelper), bytes32(uint256(50))); -// assertEq(uint256(uint256(slotValue)), 0); - -// slotValue = vm.load(address(deliveryHelper), bytes32(uint256(109))); -// assertEq(address(uint160(uint256(slotValue))), address(addressResolver)); -// } -// } diff --git a/test/Inbox.t.sol b/test/TriggerTest.t.sol similarity index 51% rename from test/Inbox.t.sol rename to test/TriggerTest.t.sol index c9306913..0ebed42f 100644 --- a/test/Inbox.t.sol +++ b/test/TriggerTest.t.sol @@ -1,11 +1,11 @@ // 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"; +import {CounterAppGateway} from "./apps/Counter.t.sol"; +import {Counter} from "./apps/Counter.t.sol"; +import "./SetupTest.t.sol"; -contract TriggerTest is DeliveryHelperTest { +contract TriggerTest is AppGatewayBaseSetup { uint256 constant feesAmount = 0.01 ether; CounterAppGateway public gateway; Counter public counter; @@ -21,53 +21,33 @@ contract TriggerTest is DeliveryHelperTest { function setUp() public { // Setup core test infrastructure - setUpDeliveryHelper(); - - // Deploy the counter contract - counter = new Counter(); + deploy(); // Deploy the gateway with fees gateway = new CounterAppGateway(address(addressResolver), feesAmount); - gateway.setIsValidPlug(arbChainSlug, address(counter)); + depositNativeAndCredits(arbChainSlug, 1 ether, 0, address(gateway)); - // Connect the counter to the gateway and socket - counter.initSocket( - _encodeAppGatewayId(address(gateway)), - address(arbConfig.socket), - address(arbConfig.switchboard) - ); + bytes32[] memory contractIds = new bytes32[](1); + bytes32 counterId = gateway.counter(); + contractIds[0] = counterId; - // Setup gateway config for the watcher - AppGatewayConfig[] memory gateways = new AppGatewayConfig[](1); - gateways[0] = AppGatewayConfig({ - plug: address(counter), - chainSlug: arbChainSlug, - appGatewayId: _encodeAppGatewayId(address(gateway)), - switchboard: address(arbConfig.switchboard) - }); + // Deploy the counter contract + gateway.deployContracts(arbChainSlug); + executeDeploy(gateway, arbChainSlug, contractIds); - bytes memory watcherSignature = _createWatcherSignature( - address(watcherPrecompileConfig), - abi.encode(IWatcherPrecompileConfig.setAppGateways.selector, gateways) + (address counterAddress, ) = getOnChainAndForwarderAddresses( + arbChainSlug, + counterId, + gateway ); - - watcherPrecompileConfig.setAppGateways(gateways, signatureNonce++, watcherSignature); - - hoax(watcherEOA); - watcherPrecompileConfig.setIsValidPlug(arbChainSlug, address(counter), true); + counter = Counter(counterAddress); + gateway.setIsValidPlug(arbChainSlug, counterId); } 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); @@ -79,7 +59,7 @@ contract TriggerTest is DeliveryHelperTest { vm.expectEmit(true, true, true, true); emit AppGatewayCallRequested( triggerId, - _encodeAppGatewayId(address(gateway)), + encodeAppGatewayId(address(gateway)), address(arbConfig.switchboard), address(counter), bytes(""), @@ -91,17 +71,23 @@ contract TriggerTest is DeliveryHelperTest { params[0] = TriggerParams({ triggerId: triggerId, chainSlug: arbChainSlug, - appGatewayId: _encodeAppGatewayId(address(gateway)), + appGatewayId: encodeAppGatewayId(address(gateway)), plug: address(counter), payload: payload, overrides: bytes("") }); + bytes memory data = abi.encode(params); + + WatcherMultiCallParams memory watcherParams; + watcherParams = WatcherMultiCallParams({ + contractAddress: address(watcher), + data: data, + nonce: watcherNonce, + signature: _createWatcherSignature(address(watcher), data) + }); + watcherNonce++; + watcher.callAppGateways(watcherParams); - bytes memory watcherSignature = _createWatcherSignature( - address(watcherPrecompile), - abi.encode(IWatcherPrecompile.callAppGateways.selector, params) - ); - watcherPrecompile.callAppGateways(params, signatureNonce++, watcherSignature); // Check counter was incremented assertEq(gateway.counterVal(), incrementValue, "Gateway counter should be incremented"); } diff --git a/test/Watcher.t.sol b/test/Watcher.t.sol new file mode 100644 index 00000000..bc8eb07b --- /dev/null +++ b/test/Watcher.t.sol @@ -0,0 +1,16 @@ +// SPDX-License-Identifier: GPL-3.0-only +pragma solidity ^0.8.21; + +import "./SetupTest.t.sol"; + +contract WatcherTest is AppGatewayBaseSetup { + function testWatcherDeployment() public { + deploy(); + + vm.assertEq(address(arbConfig.feesPlug.socket__()), address(arbConfig.socket)); + vm.assertEq(address(optConfig.feesPlug.socket__()), address(optConfig.socket)); + + vm.assertEq(address(arbConfig.contractFactoryPlug.socket__()), address(arbConfig.socket)); + vm.assertEq(address(optConfig.contractFactoryPlug.socket__()), address(optConfig.socket)); + } +} diff --git a/test/apps/Counter.t.sol b/test/apps/Counter.t.sol index f2c58c2c..e0949f07 100644 --- a/test/apps/Counter.t.sol +++ b/test/apps/Counter.t.sol @@ -3,39 +3,32 @@ pragma solidity ^0.8.21; import {CounterAppGateway} from "./app-gateways/counter/CounterAppGateway.sol"; import {Counter} from "./app-gateways/counter/Counter.sol"; -import "../DeliveryHelper.t.sol"; +import "../SetupTest.t.sol"; -contract CounterTest is DeliveryHelperTest { +contract CounterTest is AppGatewayBaseSetup { uint256 feesAmount = 0.01 ether; bytes32 counterId; bytes32[] contractIds = new bytes32[](1); - CounterAppGateway counterGateway; - function deploySetup() internal { - setUpDeliveryHelper(); + event CounterScheduleResolved(uint256 creationTimestamp, uint256 executionTimestamp); - counterGateway = new CounterAppGateway(address(addressResolver), feesAmount); - depositUSDCFees( - address(counterGateway), - OnChainFees({ - chainSlug: arbChainSlug, - token: address(arbConfig.feesTokenUSDC), - amount: 1 ether - }) - ); + function setUp() public { + deploy(); + counterGateway = new CounterAppGateway(address(addressResolver), feesAmount); + depositNativeAndCredits(arbChainSlug, 1 ether, 0, address(counterGateway)); counterId = counterGateway.counter(); contractIds[0] = counterId; } function deployCounterApp(uint32 chainSlug) internal returns (uint40 requestCount) { - requestCount = _deploy(chainSlug, counterGateway, contractIds); + counterGateway.deployContracts(chainSlug); + requestCount = executeDeploy(counterGateway, chainSlug, contractIds); } function testCounterDeployment() external { - deploySetup(); deployCounterApp(arbChainSlug); (address onChain, address forwarder) = getOnChainAndForwarderAddresses( @@ -57,14 +50,11 @@ contract CounterTest is DeliveryHelperTest { } function testCounterDeploymentWithoutAsync() external { - deploySetup(); - - vm.expectRevert(abi.encodeWithSelector(AsyncModifierNotUsed.selector)); + vm.expectRevert(abi.encodeWithSelector(AsyncModifierNotSet.selector)); counterGateway.deployContractsWithoutAsync(arbChainSlug); } function testCounterIncrement() external { - deploySetup(); deployCounterApp(arbChainSlug); (address arbCounter, address arbCounterForwarder) = getOnChainAndForwarderAddresses( @@ -78,13 +68,12 @@ contract CounterTest is DeliveryHelperTest { address[] memory instances = new address[](1); instances[0] = arbCounterForwarder; counterGateway.incrementCounters(instances); - executeRequest(new bytes[](0)); + executeRequest(); assertEq(Counter(arbCounter).counter(), arbCounterBefore + 1); } function testCounterIncrementMultipleChains() public { - deploySetup(); deployCounterApp(arbChainSlug); deployCounterApp(optChainSlug); @@ -111,7 +100,7 @@ contract CounterTest is DeliveryHelperTest { chains[0] = arbChainSlug; chains[1] = optChainSlug; - executeRequest(new bytes[](0)); + executeRequest(); assertEq(Counter(arbCounter).counter(), arbCounterBefore + 1); assertEq(Counter(optCounter).counter(), optCounterBefore + 1); } @@ -135,11 +124,19 @@ contract CounterTest is DeliveryHelperTest { instances[1] = optCounterForwarder; counterGateway.readCounters(instances); + executeRequest(); + } + + function testCounterSchedule() external { + deployCounterApp(arbChainSlug); + + uint256 creationTimestamp = block.timestamp; + counterGateway.setSchedule(100); - bytes[] memory readReturnData = new bytes[](2); - readReturnData[0] = abi.encode(10); - readReturnData[1] = abi.encode(10); + vm.expectEmit(true, true, true, false); + emit CounterScheduleResolved(creationTimestamp, block.timestamp); + executeRequest(); - executeRequest(readReturnData); + assertLe(block.timestamp, creationTimestamp + 100 + expiryTime); } } diff --git a/test/apps/ParallelCounter.t.sol b/test/apps/ParallelCounter.t.sol index 5e7e193f..fda14b52 100644 --- a/test/apps/ParallelCounter.t.sol +++ b/test/apps/ParallelCounter.t.sol @@ -2,9 +2,10 @@ pragma solidity ^0.8.21; import {CounterAppGateway} from "./app-gateways/counter/CounterAppGateway.sol"; -import "../DeliveryHelper.t.sol"; +import {Counter} from "./app-gateways/counter/Counter.sol"; +import "../SetupTest.t.sol"; -contract ParallelCounterTest is DeliveryHelperTest { +contract ParallelCounterTest is AppGatewayBaseSetup { uint256 feesAmount = 0.01 ether; bytes32 counterId1; @@ -13,35 +14,28 @@ contract ParallelCounterTest is DeliveryHelperTest { CounterAppGateway parallelCounterGateway; - function deploySetup() internal { - setUpDeliveryHelper(); + function setUp() public { + deploy(); parallelCounterGateway = new CounterAppGateway(address(addressResolver), feesAmount); - depositUSDCFees( - address(parallelCounterGateway), - OnChainFees({ - chainSlug: arbChainSlug, - token: address(arbConfig.feesTokenUSDC), - amount: 1 ether - }) - ); - counterId1 = parallelCounterGateway.counter1(); + depositNativeAndCredits(arbChainSlug, 1 ether, 0, address(parallelCounterGateway)); + counterId2 = parallelCounterGateway.counter(); + counterId1 = parallelCounterGateway.counter1(); contractIds[0] = counterId1; contractIds[1] = counterId2; } - function deployCounterApps(uint32[] memory chainSlugs) internal { + function deployCounterApp(uint32[] memory chainSlugs) internal { parallelCounterGateway.deployMultiChainContracts(chainSlugs); - executeRequest(new bytes[](0)); + executeDeployMultiChain(parallelCounterGateway, chainSlugs, contractIds); } function testParallelCounterDeployment() external { - deploySetup(); uint32[] memory chainSlugs = new uint32[](2); chainSlugs[0] = arbChainSlug; chainSlugs[1] = optChainSlug; - deployCounterApps(chainSlugs); + deployCounterApp(chainSlugs); (address onChainArb1, address forwarderArb1) = getOnChainAndForwarderAddresses( arbChainSlug, @@ -107,75 +101,55 @@ contract ParallelCounterTest is DeliveryHelperTest { ); } - function testAsyncModifierNotSet() external { - deploySetup(); + function testCounterIncrement() external { uint32[] memory chainSlugs = new uint32[](1); chainSlugs[0] = arbChainSlug; - deployCounterApps(chainSlugs); + deployCounterApp(chainSlugs); - (, address arbCounterForwarder) = getOnChainAndForwarderAddresses( + (address arbCounter, address arbCounterForwarder) = getOnChainAndForwarderAddresses( arbChainSlug, counterId1, parallelCounterGateway ); + uint256 arbCounterBefore = Counter(arbCounter).counter(); + address[] memory instances = new address[](1); instances[0] = arbCounterForwarder; + parallelCounterGateway.incrementCounters(instances); - // Should revert with AsyncModifierNotUsed error - vm.expectRevert(abi.encodeWithSignature("AsyncModifierNotUsed()")); - parallelCounterGateway.incrementCountersWithoutAsync(instances); + executeRequest(); + assertEq(Counter(arbCounter).counter(), arbCounterBefore + 1); } - // function testCounterIncrement() external { - // deploySetup(); - // deployCounterApp(arbChainSlug); - - // (address arbCounter, address arbCounterForwarder) = getOnChainAndForwarderAddresses( - // arbChainSlug, - // counterId, - // counterDeployer - // ); - - // uint256 arbCounterBefore = Counter(arbCounter).counter(); - - // address[] memory instances = new address[](1); - // instances[0] = arbCounterForwarder; - // counterGateway.incrementCounters(instances); - - // _executeRequestSingleChain(arbChainSlug, 1); - // assertEq(Counter(arbCounter).counter(), arbCounterBefore + 1); - // } - - // function testCounterIncrementMultipleChains() external { - // deploySetup(); - // deployCounterApp(arbChainSlug); - // deployCounterApp(optChainSlug); - - // (address arbCounter, address arbCounterForwarder) = getOnChainAndForwarderAddresses( - // arbChainSlug, - // counterId, - // counterDeployer - // ); - // (address optCounter, address optCounterForwarder) = getOnChainAndForwarderAddresses( - // optChainSlug, - // counterId, - // counterDeployer - // ); - - // uint256 arbCounterBefore = Counter(arbCounter).counter(); - // uint256 optCounterBefore = Counter(optCounter).counter(); - - // address[] memory instances = new address[](2); - // instances[0] = arbCounterForwarder; - // instances[1] = optCounterForwarder; - // counterGateway.incrementCounters(instances); - - // uint32[] memory chains = new uint32[](2); - // chains[0] = arbChainSlug; - // chains[1] = optChainSlug; - // _executeRequestMultiChain(chains); - // assertEq(Counter(arbCounter).counter(), arbCounterBefore + 1); - // assertEq(Counter(optCounter).counter(), optCounterBefore + 1); - // } + function testCounterIncrementMultipleChains() external { + uint32[] memory chainSlugs = new uint32[](2); + chainSlugs[0] = arbChainSlug; + chainSlugs[1] = optChainSlug; + deployCounterApp(chainSlugs); + + (address arbCounter, address arbCounterForwarder) = getOnChainAndForwarderAddresses( + arbChainSlug, + counterId1, + parallelCounterGateway + ); + (address optCounter, address optCounterForwarder) = getOnChainAndForwarderAddresses( + optChainSlug, + counterId1, + parallelCounterGateway + ); + + uint256 arbCounterBefore = Counter(arbCounter).counter(); + uint256 optCounterBefore = Counter(optCounter).counter(); + + address[] memory instances = new address[](2); + instances[0] = arbCounterForwarder; + instances[1] = optCounterForwarder; + parallelCounterGateway.incrementCounters(instances); + + executeRequest(); + + assertEq(Counter(arbCounter).counter(), arbCounterBefore + 1); + assertEq(Counter(optCounter).counter(), optCounterBefore + 1); + } } diff --git a/test/apps/SuperToken.t.sol b/test/apps/SuperToken.t.sol index 9a5a4897..0314b353 100644 --- a/test/apps/SuperToken.t.sol +++ b/test/apps/SuperToken.t.sol @@ -3,14 +3,13 @@ pragma solidity ^0.8.21; import {SuperTokenAppGateway} from "./app-gateways/super-token/SuperTokenAppGateway.sol"; import {SuperToken} from "./app-gateways/super-token/SuperToken.sol"; -import "../DeliveryHelper.t.sol"; -import {QUERY, FINALIZE, SCHEDULE} from "../../contracts/utils/common/Constants.sol"; +import "../SetupTest.t.sol"; /** * @title SuperToken Test * @notice Test contract for verifying the functionality of the SuperToken system, which enables * multi-chain token bridging capabilities. - * @dev Inherits from DeliveryHelperTest to utilize multi-chain messaging infrastructure + * @dev Inherits from AppGatewayBaseSetup to utilize multi-chain messaging infrastructure * * The test suite validates: * - Contract deployment across different chains @@ -18,7 +17,7 @@ import {QUERY, FINALIZE, SCHEDULE} from "../../contracts/utils/common/Constants. * - Proper balance updates * - Integration with the delivery and auction system */ -contract SuperTokenTest is DeliveryHelperTest { +contract SuperTokenTest is AppGatewayBaseSetup { /** * @notice Groups the main contracts needed for SuperToken functionality * @param superTokenApp The gateway contract that handles multi-chain token operations @@ -29,6 +28,8 @@ contract SuperTokenTest is DeliveryHelperTest { SuperTokenAppGateway superTokenApp; bytes32 superToken; } + address owner = address(uint160(c++)); + uint256 maxFees = 0.01 ether; /// @dev Main contracts used throughout the tests AppContracts appContracts; @@ -49,19 +50,8 @@ contract SuperTokenTest is DeliveryHelperTest { * 3. Initializes contract IDs array */ function setUp() public { - setUpDeliveryHelper(); - deploySuperTokenApp(); - contractIds[0] = appContracts.superToken; - } + deploy(); - /** - * @notice Deploys the SuperToken application and its components - * @dev Creates both the deployer and gateway contracts with initial configuration - * - Sets up SuperToken with "SUPER TOKEN" name and "SUPER" symbol - * - Configures initial supply of 1 billion tokens - * - Sets up fee structure and auction manager integration - */ - function deploySuperTokenApp() internal { SuperTokenAppGateway superTokenApp = new SuperTokenAppGateway( address(addressResolver), owner, @@ -74,21 +64,29 @@ contract SuperTokenTest is DeliveryHelperTest { initialSupply_: 1000000000 ether }) ); + // 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 - depositUSDCFees( - address(superTokenApp), - OnChainFees({ - chainSlug: arbChainSlug, - token: address(arbConfig.feesTokenUSDC), - amount: 1 ether - }) - ); + depositNativeAndCredits(arbChainSlug, 1 ether, 0, address(superTokenApp)); appContracts = AppContracts({ superTokenApp: superTokenApp, superToken: superTokenApp.superToken() }); + + contractIds[0] = appContracts.superToken; + } + + /** + * @notice Deploys the SuperToken application and its components + * @dev Creates both the deployer and gateway contracts with initial configuration + * - Sets up SuperToken with "SUPER TOKEN" name and "SUPER" symbol + * - Configures initial supply of 1 billion tokens + * - Sets up fee structure and auction manager integration + */ + function deploySuperToken(uint32 chainSlug) internal { + appContracts.superTokenApp.deployContracts(chainSlug); + executeDeploy(IAppGateway(appContracts.superTokenApp), chainSlug, contractIds); } /** @@ -99,7 +97,7 @@ contract SuperTokenTest is DeliveryHelperTest { * - Correct setup of forwarder contracts for multi-chain communication */ function testContractDeployment() public { - _deploy(arbChainSlug, IAppGateway(appContracts.superTokenApp), contractIds); + deploySuperToken(arbChainSlug); (address onChain, address forwarder) = getOnChainAndForwarderAddresses( arbChainSlug, @@ -112,11 +110,13 @@ contract SuperTokenTest is DeliveryHelperTest { "SUPER TOKEN", "OnChain SuperToken name should be SUPER TOKEN" ); + assertEq( IForwarder(forwarder).getChainSlug(), arbChainSlug, "Forwarder SuperToken chainSlug should be arbChainSlug" ); + assertEq( IForwarder(forwarder).getOnChainAddress(), onChain, @@ -125,15 +125,6 @@ contract SuperTokenTest is DeliveryHelperTest { assertEq(SuperToken(onChain).owner(), owner, "SuperToken owner should be correct"); } - /** - * @notice Helper function to prepare the environment for transfer tests - * @dev Deploys necessary contracts on both Arbitrum and Optimism chains - */ - function beforeTransfer() internal { - _deploy(arbChainSlug, IAppGateway(appContracts.superTokenApp), contractIds); - _deploy(optChainSlug, IAppGateway(appContracts.superTokenApp), contractIds); - } - /** * @notice Tests multi-chain token transfers * @dev Verifies: @@ -143,7 +134,8 @@ contract SuperTokenTest is DeliveryHelperTest { * - Proper execution of multi-chain messaging */ function testTransfer() public { - beforeTransfer(); + deploySuperToken(arbChainSlug); + deploySuperToken(optChainSlug); (address onChainArb, address forwarderArb) = getOnChainAndForwarderAddresses( arbChainSlug, @@ -170,7 +162,7 @@ contract SuperTokenTest is DeliveryHelperTest { bytes memory encodedOrder = abi.encode(transferOrder); appContracts.superTokenApp.transfer(encodedOrder); - executeRequest(new bytes[](0)); + executeRequest(); assertEq( SuperToken(onChainArb).balanceOf(owner), diff --git a/test/apps/app-gateways/USDC.sol b/test/apps/app-gateways/USDC.sol deleted file mode 100644 index df08d360..00000000 --- a/test/apps/app-gateways/USDC.sol +++ /dev/null @@ -1,41 +0,0 @@ -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/CounterAppGateway.sol b/test/apps/app-gateways/counter/CounterAppGateway.sol index eecc2c79..41666c9b 100644 --- a/test/apps/app-gateways/counter/CounterAppGateway.sol +++ b/test/apps/app-gateways/counter/CounterAppGateway.sol @@ -2,8 +2,6 @@ pragma solidity ^0.8.21; import "../../../../contracts/evmx/base/AppGatewayBase.sol"; -import "../../../../contracts/evmx/interfaces/IForwarder.sol"; -import "../../../../contracts/evmx/interfaces/IPromise.sol"; import "./Counter.sol"; import "./ICounter.sol"; @@ -12,20 +10,21 @@ contract CounterAppGateway is AppGatewayBase, Ownable { bytes32 public counter1 = _createContractId("counter1"); uint256 public counterVal; - uint256 public arbCounter; uint256 public optCounter; - event TimeoutResolved(uint256 creationTimestamp, uint256 executionTimestamp); - constructor(address addressResolver_, uint256 fees_) AppGatewayBase(addressResolver_) { + event CounterScheduleResolved(uint256 creationTimestamp, uint256 executionTimestamp); + + constructor(address addressResolver_, uint256 fees_) { creationCodeWithArgs[counter] = abi.encodePacked(type(Counter).creationCode); creationCodeWithArgs[counter1] = abi.encodePacked(type(Counter).creationCode); _setMaxFees(fees_); _initializeOwner(msg.sender); + _initializeAppGateway(addressResolver_); } // deploy contracts - function deployContracts(uint32 chainSlug_) external async(bytes("")) { + function deployContracts(uint32 chainSlug_) external async { _deploy(counter, chainSlug_, IsPlug.YES); } @@ -33,14 +32,14 @@ contract CounterAppGateway is AppGatewayBase, Ownable { _deploy(counter, chainSlug_, IsPlug.YES); } - function deployParallelContracts(uint32 chainSlug_) external async(bytes("")) { + function deployParallelContracts(uint32 chainSlug_) external async { _setOverrides(Parallel.ON); _deploy(counter, chainSlug_, IsPlug.YES); _deploy(counter1, chainSlug_, IsPlug.YES); _setOverrides(Parallel.OFF); } - function deployMultiChainContracts(uint32[] memory chainSlugs_) external async(bytes("")) { + function deployMultiChainContracts(uint32[] memory chainSlugs_) external async { _setOverrides(Parallel.ON); for (uint32 i = 0; i < chainSlugs_.length; i++) { _deploy(counter, chainSlugs_[i], IsPlug.YES); @@ -49,13 +48,12 @@ contract CounterAppGateway is AppGatewayBase, Ownable { _setOverrides(Parallel.OFF); } - function initialize(uint32) public pure override { + function initializeOnChain(uint32) public pure override { return; } - function incrementCounters(address[] memory instances_) public async(bytes("")) { + function incrementCounters(address[] memory instances_) public async { // the increase function is called on given list of instances - // this for (uint256 i = 0; i < instances_.length; i++) { ICounter(instances_[i]).increase(); } @@ -69,22 +67,22 @@ contract CounterAppGateway is AppGatewayBase, Ownable { } } - function readCounters(address[] memory instances_) public async(bytes("")) { + function readCounters(address[] memory instances_) public async { // the increase function is called on given list of instances _setOverrides(Read.ON, Parallel.ON); for (uint256 i = 0; i < instances_.length; i++) { uint32 chainSlug = IForwarder(instances_[i]).getChainSlug(); ICounter(instances_[i]).getCounter(); - IPromise(instances_[i]).then(this.setCounterValues.selector, abi.encode(chainSlug)); + then(this.setCounterValues.selector, abi.encode(chainSlug)); } _setOverrides(Read.OFF, Parallel.OFF); } - function readCounterAtBlock(address instance_, uint256 blockNumber_) public async(bytes("")) { + function readCounterAtBlock(address instance_, uint256 blockNumber_) public async { uint32 chainSlug = IForwarder(instance_).getChainSlug(); _setOverrides(Read.ON, Parallel.ON, blockNumber_); ICounter(instance_).getCounter(); - IPromise(instance_).then(this.setCounterValues.selector, abi.encode(chainSlug)); + then(this.setCounterValues.selector, abi.encode(chainSlug)); } function setCounterValues(bytes memory data, bytes memory returnData) external onlyPromises { @@ -98,25 +96,22 @@ contract CounterAppGateway is AppGatewayBase, Ownable { } // trigger from a chain - function setIsValidPlug(uint32 chainSlug_, address plug_) public { - watcherPrecompileConfig().setIsValidPlug(chainSlug_, plug_, true); + function setIsValidPlug(uint32 chainSlug_, bytes32 contractId_) public { + _setValidPlug(true, chainSlug_, contractId_); } - function increase(uint256 value_) external onlyWatcherPrecompile { + function increase(uint256 value_) external onlyWatcher { counterVal += value_; } - // TIMEOUT - function setTimeout(uint256 delayInSeconds_) public { - bytes memory payload = abi.encodeWithSelector( - this.resolveTimeout.selector, - block.timestamp - ); - watcherPrecompile__().setTimeout(delayInSeconds_, payload); + // Schedule + function setSchedule(uint256 delayInSeconds_) public async { + _setSchedule(delayInSeconds_); + then(this.resolveSchedule.selector, abi.encode(block.timestamp)); } - function resolveTimeout(uint256 creationTimestamp_) external onlyWatcherPrecompile { - emit TimeoutResolved(creationTimestamp_, block.timestamp); + function resolveSchedule(uint256 creationTimestamp_) external onlyPromises { + emit CounterScheduleResolved(creationTimestamp_, block.timestamp); } // UTILS @@ -124,27 +119,27 @@ contract CounterAppGateway is AppGatewayBase, Ownable { maxFees = fees_; } - function withdrawFeeTokens( + function withdrawCredits( uint32 chainSlug_, address token_, uint256 amount_, address receiver_ - ) external returns (uint40) { - return _withdrawFeeTokens(chainSlug_, token_, amount_, receiver_); + ) external { + _withdrawCredits(chainSlug_, token_, amount_, receiver_); } - function testOnChainRevert(uint32 chainSlug) public async(bytes("")) { + function testOnChainRevert(uint32 chainSlug) public async { address instance = forwarderAddresses[counter][chainSlug]; ICounter(instance).wrongFunction(); } - function testCallBackRevert(uint32 chainSlug) public async(bytes("")) { + function testCallBackRevert(uint32 chainSlug) public async { // the increase function is called on given list of instances _setOverrides(Read.ON, Parallel.ON); address instance = forwarderAddresses[counter][chainSlug]; ICounter(instance).getCounter(); // wrong function call in callback so it reverts - IPromise(instance).then(this.withdrawFeeTokens.selector, abi.encode(chainSlug)); + then(this.withdrawCredits.selector, abi.encode(chainSlug)); _setOverrides(Read.OFF, Parallel.OFF); } diff --git a/test/apps/app-gateways/super-token/SuperTokenAppGateway.sol b/test/apps/app-gateways/super-token/SuperTokenAppGateway.sol index aa88756f..0e835b7e 100644 --- a/test/apps/app-gateways/super-token/SuperTokenAppGateway.sol +++ b/test/apps/app-gateways/super-token/SuperTokenAppGateway.sol @@ -31,7 +31,7 @@ contract SuperTokenAppGateway is AppGatewayBase, Ownable { address owner_, uint256 fees_, ConstructorParams memory params_ - ) AppGatewayBase(addressResolver_) { + ) { creationCodeWithArgs[superToken] = abi.encodePacked( type(SuperToken).creationCode, abi.encode( @@ -47,24 +47,25 @@ contract SuperTokenAppGateway is AppGatewayBase, Ownable { // they can be updated for each transfer as well _setMaxFees(fees_); _initializeOwner(owner_); + _initializeAppGateway(addressResolver_); } - function deployContracts(uint32 chainSlug_) external async(bytes("")) { + function deployContracts(uint32 chainSlug_) external async { bytes memory initData = abi.encodeWithSelector(SuperToken.setOwner.selector, owner()); _deploy(superToken, chainSlug_, IsPlug.YES, initData); } // no need to call this directly, will be called automatically after all contracts are deployed. // check AppGatewayBase._deploy and AppGatewayBase.onRequestComplete - function initialize(uint32) public pure override { + function initializeOnChain(uint32) public pure override { return; } - function transfer(bytes memory order_) external async(bytes("")) { + function transfer(bytes memory order_) external async { TransferOrder memory order = abi.decode(order_, (TransferOrder)); ISuperToken(order.srcToken).burn(order.user, order.srcAmount); ISuperToken(order.dstToken).mint(order.user, order.srcAmount); - emit Transferred(_getCurrentAsyncId()); + emit Transferred(_getCurrentRequestCount()); } } diff --git a/test/mock/MockFastSwitchboard.sol b/test/mock/MockFastSwitchboard.sol index e516fe3d..284e88ca 100644 --- a/test/mock/MockFastSwitchboard.sol +++ b/test/mock/MockFastSwitchboard.sol @@ -22,9 +22,7 @@ contract MockFastSwitchboard is ISwitchboard { owner = owner_; } - function attest(bytes32, bytes calldata) external { - // TODO: implement - } + function attest(bytes32, bytes calldata) external {} function allowPayload(bytes32, bytes32) external pure returns (bool) { // digest has enough attestations diff --git a/test/mock/MockSocket.sol b/test/mock/MockSocket.sol index 027d3c27..fbe737a8 100644 --- a/test/mock/MockSocket.sol +++ b/test/mock/MockSocket.sol @@ -1,9 +1,9 @@ // SPDX-License-Identifier: GPL-3.0-only pragma solidity ^0.8.21; -import {InvalidAppGateway} from "../../contracts/utils/common/Errors.sol"; -import "../../contracts/protocol/interfaces/ISwitchboard.sol"; +import "../../contracts/utils/common/Errors.sol"; import "../../contracts/protocol/interfaces/ISocket.sol"; +import "../../contracts/protocol/interfaces/ISwitchboard.sol"; /** * @title SocketDst diff --git a/test/mock/MockWatcherPrecompile.sol b/test/mock/MockWatcherPrecompile.sol index ee89876b..7ee24975 100644 --- a/test/mock/MockWatcherPrecompile.sol +++ b/test/mock/MockWatcherPrecompile.sol @@ -1,195 +1,68 @@ // SPDX-License-Identifier: GPL-3.0-only pragma solidity ^0.8.21; -import "../../contracts/evmx/interfaces/IAppGateway.sol"; -import "../../contracts/evmx/interfaces/IWatcherPrecompile.sol"; -import "../../contracts/evmx/interfaces/IPromise.sol"; - -import {TimeoutRequest, TriggerParams, PlugConfig, ResolvedPromises, AppGatewayConfig} from "../../contracts/utils/common/Structs.sol"; -import {QUERY, FINALIZE, SCHEDULE} from "../../contracts/utils/common/Constants.sol"; -import {TimeoutDelayTooLarge, TimeoutAlreadyResolved, ResolvingTimeoutTooEarly, CallFailed, AppGatewayAlreadyCalled} from "../../contracts/utils/common/Errors.sol"; -import "solady/utils/ERC1967Factory.sol"; +import "../../contracts/evmx/watcher/Trigger.sol"; /// @title WatcherPrecompile /// @notice Contract that handles payload verification, execution and app configurations -contract MockWatcherPrecompile { - uint256 public maxTimeoutDelayInSeconds = 24 * 60 * 60; // 24 hours - /// @notice Counter for tracking payload execution requests - uint256 public payloadCounter; - /// @notice Mapping to store timeout requests - /// @dev timeoutId => TimeoutRequest struct - mapping(bytes32 => TimeoutRequest) public timeoutRequests; - - mapping(uint32 => mapping(address => PlugConfig)) internal _plugConfigs; - - /// @notice Error thrown when an invalid chain slug is provided - error InvalidChainSlug(); - - event CalledAppGateway(bytes32 triggerId); - - /// @notice Emitted when a new query is requested - /// @param chainSlug The identifier of the destination chain - /// @param targetAddress The address of the target contract - /// @param payloadId The unique identifier for the query - /// @param payload The query data - event QueryRequested(uint32 chainSlug, address targetAddress, bytes32 payloadId, bytes payload); - - /// @notice Emitted when a finalize request is made - event FinalizeRequested(bytes32 digest, PayloadParams params); - - /// @notice Emitted when a request is finalized - /// @param payloadId The unique identifier for the request - /// @param proof The proof from the watcher - event Finalized(bytes32 indexed payloadId, bytes proof); - - /// @notice Emitted when a promise is resolved - /// @param payloadId The unique identifier for the resolved promise - event PromiseResolved(bytes32 indexed payloadId); - - event TimeoutRequested( - bytes32 timeoutId, - address target, - bytes payload, - uint256 executeAt // Epoch time when the task should execute - ); - - /// @notice Emitted when a timeout is resolved - /// @param timeoutId The unique identifier for the timeout - /// @param target The target address for the timeout - /// @param payload The payload data - /// @param executedAt The epoch time when the task was executed - event TimeoutResolved(bytes32 timeoutId, address target, bytes payload, uint256 executedAt); - - /// @notice Contract constructor - /// @param _owner Address of the contract owner - constructor(address _owner, address addressResolver_) {} - - // ================== Timeout functions ================== - - /// @notice Sets a timeout for a payload execution on app gateway - /// @param payload_ The payload data - /// @param delayInSeconds_ The delay in seconds - function setTimeout(bytes calldata payload_, uint256 delayInSeconds_) external { - uint256 executeAt = block.timestamp + delayInSeconds_; - bytes32 timeoutId = _encodeTimeoutId(); - timeoutRequests[timeoutId] = TimeoutRequest( - msg.sender, - delayInSeconds_, - executeAt, - 0, - false, - payload_ - ); - emit TimeoutRequested(timeoutId, msg.sender, payload_, executeAt); - } +contract MockWatcherPrecompile is Trigger { + uint256 public newValue; - /// @notice Ends the timeouts and calls the target address with the callback payload - /// @param timeoutId The unique identifier for the timeout - /// @dev Only callable by the contract owner - function resolveTimeout(bytes32 timeoutId) external { - TimeoutRequest storage timeoutRequest = timeoutRequests[timeoutId]; - - (bool success, ) = address(timeoutRequest.target).call(timeoutRequest.payload); - if (!success) revert CallFailed(); - emit TimeoutResolved( - timeoutId, - timeoutRequest.target, - timeoutRequest.payload, - block.timestamp - ); + function initialize(uint256 newValue_) external reinitializer(2) { + newValue = newValue_; } - // ================== Finalize functions ================== + function getRequestParams( + uint40 requestCount_ + ) external view override returns (RequestParams memory) {} - /// @notice Finalizes a payload request, requests the watcher to release the proofs to execute on chain - /// @param params_ The finalization parameters - /// @return digest The digest of the payload parameters - function finalize(PayloadParams memory params_) external returns (bytes32 digest) { - digest = keccak256(abi.encode(block.timestamp)); - // Generate a unique payload ID by combining chain, target, and counter - emit FinalizeRequested(digest, params_); - } + function getPayloadParams( + bytes32 payloadId_ + ) external view override returns (PayloadParams memory) {} - // ================== Query functions ================== - /// @notice Creates a new query request - /// @param chainSlug The identifier of the destination chain - /// @param targetAddress The address of the target contract - /// @param payload The query payload data - /// @return payloadId The unique identifier for the query - function query( - uint32 chainSlug, - address targetAddress, - address[] memory, - bytes memory payload - ) public returns (bytes32 payloadId) { - payloadId = bytes32(payloadCounter++); - emit QueryRequested(chainSlug, targetAddress, payloadId, payload); - } + function getCurrentRequestCount() external view override returns (uint40) {} - /// @notice Marks a request as finalized with a proof - /// @param payloadId_ The unique identifier of the request - /// @param proof_ The watcher's proof - /// @dev Only callable by the contract owner - function finalized(bytes32 payloadId_, bytes calldata proof_) external { - emit Finalized(payloadId_, proof_); - } + function queue( + QueueParams calldata queueParams_, + address appGateway_ + ) external override returns (address, uint40) {} - /// @notice Resolves multiple promises with their return data - /// @param resolvedPromises_ Array of resolved promises and their return data - /// @dev Only callable by the contract owner - function resolvePromises(ResolvedPromises[] calldata resolvedPromises_) external { - for (uint256 i = 0; i < resolvedPromises_.length; i++) { - emit PromiseResolved(resolvedPromises_[i].payloadId); - } - } + function clearQueue() external override {} - // ================== On-Chain Trigger ================== + function submitRequest( + uint256 maxFees, + address auctionManager, + address consumeFrom, + bytes calldata onCompleteData + ) external override returns (uint40 requestCount, address[] memory promises) {} - function callAppGateways(TriggerParams[] calldata params_) external { - for (uint256 i = 0; i < params_.length; i++) { - emit CalledAppGateway(params_[i].triggerId); - } - } + function queueAndSubmit( + QueueParams memory queue_, + uint256 maxFees, + address auctionManager, + address consumeFrom, + bytes calldata onCompleteData + ) external override returns (uint40 requestCount, address[] memory promises) {} - /// @notice Encodes a unique payload ID from chain slug, plug address, and counter - /// @param chainSlug_ The identifier of the chain - /// @param plug_ The plug address - /// @param counter_ The current counter value - /// @return The encoded payload ID as bytes32 - /// @dev Reverts if chainSlug is 0 - function encodePayloadId( - uint32 chainSlug_, - address plug_, - uint256 counter_ - ) internal view returns (bytes32) { - if (chainSlug_ == 0) revert InvalidChainSlug(); - (, address switchboard) = getPlugConfigs(chainSlug_, plug_); - // Encode payload ID by bit-shifting and combining: - // chainSlug (32 bits) | switchboard address (160 bits) | counter (64 bits) - - return - bytes32( - (uint256(chainSlug_) << 224) | (uint256(uint160(switchboard)) << 64) | counter_ - ); - } + function getPrecompileFees( + bytes4 precompile_, + bytes memory precompileData_ + ) external view override returns (uint256) {} - function _encodeTimeoutId() internal returns (bytes32) { - // watcher address (160 bits) | counter (64 bits) - return bytes32((uint256(uint160(address(this))) << 64) | payloadCounter++); - } + function cancelRequest(uint40 requestCount_) external override {} - /// @notice Retrieves the configuration for a specific plug on a network - /// @param chainSlug_ The identifier of the network - /// @param plug_ The address of the plug - /// @return The app gateway address and switchboard address for the plug - /// @dev Returns zero addresses if configuration doesn't exist - function getPlugConfigs( + function increaseFees(uint40 requestCount_, uint256 newFees_) external override {} + + function setIsValidPlug( + bool isValid_, uint32 chainSlug_, - address plug_ - ) public view returns (bytes32, address) { - return ( - _plugConfigs[chainSlug_][plug_].appGatewayId, - _plugConfigs[chainSlug_][plug_].switchboard - ); + address onchainAddress_ + ) external override {} + + function isWatcher(address account_) external view override returns (bool) {} + + function watcherMultiCall(WatcherMultiCallParams[] memory params_) external payable { + if (isNonceUsed[params_[0].nonce]) revert NonceUsed(); + isNonceUsed[params_[0].nonce] = true; } } diff --git a/test/mock/MockWatcherPrecompileImpl.sol b/test/mock/MockWatcherPrecompileImpl.sol deleted file mode 100644 index 59cbfb87..00000000 --- a/test/mock/MockWatcherPrecompileImpl.sol +++ /dev/null @@ -1,13 +0,0 @@ -// SPDX-License-Identifier: GPL-3.0-only -pragma solidity ^0.8.21; - -import "../../contracts/evmx/watcherPrecompile/core/WatcherPrecompile.sol"; - -contract MockWatcherPrecompileImpl is WatcherPrecompile { - // Mock function to test reinitialization with version 2 - function mockReinitialize(address owner_, address addressResolver_) external reinitializer(2) { - _setAddressResolver(addressResolver_); - _initializeOwner(owner_); - maxTimeoutDelayInSeconds = 24 * 60 * 60; // 24 hours - } -}