From bdd3cce64a2e5b293e3b32b8dde064c77c4dcf72 Mon Sep 17 00:00:00 2001 From: Akash Date: Wed, 16 Apr 2025 17:52:00 +0530 Subject: [PATCH 01/21] feat: fees changes, WIP --- contracts/base/AppGatewayBase.sol | 8 +- contracts/interfaces/IFeesManager.sol | 13 +++- contracts/interfaces/IMiddleware.sol | 2 + .../interfaces/IWatcherPrecompileLimits.sol | 6 ++ .../protocol/payload-delivery/FeesManager.sol | 74 +++++++++++++++---- .../protocol/payload-delivery/FeesPlug.sol | 49 +++++++++--- .../app-gateway/DeliveryUtils.sol | 1 + .../app-gateway/RequestQueue.sol | 49 +++++++++--- contracts/protocol/utils/common/Structs.sol | 1 + .../WatcherPrecompileLimits.sol | 53 ++++++++++++- .../core/WatcherPrecompile.sol | 8 +- script/helpers/AppGatewayFeeBalance.s.sol | 2 +- 12 files changed, 222 insertions(+), 44 deletions(-) diff --git a/contracts/base/AppGatewayBase.sol b/contracts/base/AppGatewayBase.sol index 0216e44d..a040a9f2 100644 --- a/contracts/base/AppGatewayBase.sol +++ b/contracts/base/AppGatewayBase.sol @@ -26,9 +26,8 @@ abstract contract AppGatewayBase is AddressResolverUtil, IAppGateway, FeesPlugin mapping(bytes32 => bytes) public creationCodeWithArgs; /// @notice Modifier to treat functions async - modifier async() { + modifier async(address consumeFrom_) { if (fees.feePoolChain == 0) revert FeesNotSet(); - isAsyncModifierSet = true; _clearOverrides(); deliveryHelper__().clearQueue(); @@ -37,7 +36,7 @@ abstract contract AppGatewayBase is AddressResolverUtil, IAppGateway, FeesPlugin _; isAsyncModifierSet = false; - deliveryHelper__().batch(fees, auctionManager, onCompleteData); + deliveryHelper__().batch(fees, auctionManager, consumeFrom_, onCompleteData); _markValidPromises(); onCompleteData = bytes(""); } @@ -181,7 +180,8 @@ abstract contract AppGatewayBase is AddressResolverUtil, IAppGateway, FeesPlugin return address(0); } - onChainAddress = IForwarder(forwarderAddresses[contractId_][chainSlug_]).getOnChainAddress(); + onChainAddress = IForwarder(forwarderAddresses[contractId_][chainSlug_]) + .getOnChainAddress(); } //////////////////////////////////////////////////////////////////////////////////////////////// diff --git a/contracts/interfaces/IFeesManager.sol b/contracts/interfaces/IFeesManager.sol index 82f5fd8f..80953ba7 100644 --- a/contracts/interfaces/IFeesManager.sol +++ b/contracts/interfaces/IFeesManager.sol @@ -13,7 +13,11 @@ interface IFeesManager { function unblockFees(uint40 requestCount_) external; - function isFeesEnough(address appGateway_, Fees memory fees_) external view returns (bool); + function isFeesEnough( + address appGateway_, + address consumeFrom_, + Fees memory fees_ + ) external view returns (bool); function unblockAndAssignFees( uint40 requestCount_, @@ -28,4 +32,11 @@ interface IFeesManager { uint256 amount_, address receiver_ ) external; + + function assignWatcherPrecompileFees( + uint32 chainSlug_, + address token_, + uint256 amount_, + address consumeFrom_ + ) external; } diff --git a/contracts/interfaces/IMiddleware.sol b/contracts/interfaces/IMiddleware.sol index 54b50a53..a99ce076 100644 --- a/contracts/interfaces/IMiddleware.sol +++ b/contracts/interfaces/IMiddleware.sol @@ -25,11 +25,13 @@ interface IMiddleware { /// @notice Batches a request /// @param fees_ The fees for the request /// @param auctionManager_ The address of the auction manager + /// @param consumeFrom_ The address of the consumeFrom /// @param onCompleteData_ The data to be passed to the onComplete callback /// @return requestCount The request id function batch( Fees memory fees_, address auctionManager_, + address consumeFrom_, bytes memory onCompleteData_ ) external returns (uint40 requestCount); diff --git a/contracts/interfaces/IWatcherPrecompileLimits.sol b/contracts/interfaces/IWatcherPrecompileLimits.sol index c56ed635..f7db2adf 100644 --- a/contracts/interfaces/IWatcherPrecompileLimits.sol +++ b/contracts/interfaces/IWatcherPrecompileLimits.sol @@ -48,6 +48,12 @@ interface IWatcherPrecompileLimits { /// @param consumeLimit_ The amount of limit to consume function consumeLimit(address appGateway_, bytes32 limitType_, uint256 consumeLimit_) external; + function getTotalFeesRequired( + address token_, + uint queryCount, + uint finalizeCount, + uint scheduleCount + ) external view returns (uint256); /// @notice Emitted when limit parameters are updated event LimitParamsUpdated(UpdateLimitParams[] updates); diff --git a/contracts/protocol/payload-delivery/FeesManager.sol b/contracts/protocol/payload-delivery/FeesManager.sol index 8cb9ff30..300f093e 100644 --- a/contracts/protocol/payload-delivery/FeesManager.sol +++ b/contracts/protocol/payload-delivery/FeesManager.sol @@ -27,9 +27,11 @@ abstract contract FeesManagerStorage is IFeesManager { // slot 53 /// @notice Master mapping tracking all fee information - /// @dev appGateway => chainSlug => token => TokenBalance - mapping(address => mapping(uint32 => mapping(address => TokenBalance))) - public appGatewayFeeBalances; + /// @dev userAddress => chainSlug => token => TokenBalance + mapping(address => mapping(uint32 => mapping(address => TokenBalance))) public userFeeBalances; + + // userAddress => appGateway => isWhitelisted + mapping(address => mapping(address => bool)) public isAppGatewayWhitelisted; // slot 54 /// @notice Mapping to track blocked fees for each async id @@ -41,6 +43,9 @@ abstract contract FeesManagerStorage is IFeesManager { /// @dev transmitter => chainSlug => token => amount mapping(address => mapping(uint32 => mapping(address => uint256))) public transmitterFees; + // @dev chainSlug => token => amount + mapping(uint32 => mapping(address => uint256)) public watcherPrecompileFees; + // slot 56 /// @notice Mapping to track nonce to whether it has been used /// @dev signatureNonce => isNonceUsed @@ -76,7 +81,12 @@ contract FeesManager is FeesManagerStorage, Initializable, Ownable, AddressResol address indexed transmitter, uint256 amount ); - + event WatcherPrecompileFeesAssigned( + uint32 chainSlug, + address token, + uint256 amount, + address consumeFrom + ); /// @notice Emitted when fees deposited are updated /// @param chainSlug The chain identifier /// @param appGateway The app gateway address @@ -104,6 +114,12 @@ contract FeesManager is FeesManagerStorage, Initializable, Ownable, AddressResol /// @param appGateway The app gateway address event FeesUnblocked(uint40 indexed requestCount, address indexed appGateway); + /// @notice Emitted when insufficient watcher precompile fees are available + event InsufficientWatcherPrecompileFeesAvailable( + uint32 chainSlug, + address token, + address consumeFrom + ); /// @notice Error thrown when insufficient fees are available error InsufficientFeesAvailable(); /// @notice Error thrown when no fees are available for a transmitter @@ -140,10 +156,10 @@ contract FeesManager is FeesManagerStorage, Initializable, Ownable, AddressResol /// @return The available fee amount function getAvailableFees( uint32 chainSlug_, - address appGateway_, + address consumeFrom_, address token_ ) public view returns (uint256) { - TokenBalance memory tokenBalance = appGatewayFeeBalances[appGateway_][chainSlug_][token_]; + TokenBalance memory tokenBalance = userFeeBalances[consumeFrom_][chainSlug_][token_]; if (tokenBalance.deposited == 0 || tokenBalance.deposited <= tokenBalance.blocked) return 0; return tokenBalance.deposited - tokenBalance.blocked; } @@ -169,24 +185,35 @@ contract FeesManager is FeesManagerStorage, Initializable, Ownable, AddressResol address appGateway = _getCoreAppGateway(originAppGateway_); - TokenBalance storage tokenBalance = appGatewayFeeBalances[appGateway][chainSlug_][token_]; + TokenBalance storage tokenBalance = userFeeBalances[appGateway][chainSlug_][token_]; tokenBalance.deposited += amount_; emit FeesDepositedUpdated(chainSlug_, appGateway, token_, amount_); } function isFeesEnough( address originAppGateway_, + address consumeFrom_, Fees memory fees_ ) external view returns (bool) { address appGateway = _getCoreAppGateway(originAppGateway_); + address consumeFromCore = _getCoreAppGateway(consumeFrom_); + if (appGateway != consumeFromCore && !isAppGatewayWhitelisted[consumeFromCore][appGateway]) + return false; uint256 availableFees = getAvailableFees( fees_.feePoolChain, - appGateway, + consumeFrom_, fees_.feePoolToken ); return availableFees >= fees_.amount; } + /// @notice Whitelists multiple app gateways for the caller + /// @param appGateways_ Array of app gateway addresses to whitelist + function whitelistApps(address[] calldata appGateways_) external { + for (uint256 i = 0; i < appGateways_.length; i++) { + isAppGatewayWhitelisted[msg.sender][appGateways_[i]] = true; + } + } /// @notice Blocks fees for a request count /// @param originAppGateway_ The app gateway address /// @param feesGivenByApp_ The fees data struct given by the app gateway @@ -213,7 +240,7 @@ contract FeesManager is FeesManagerStorage, Initializable, Ownable, AddressResol availableFees += requestCountBlockedFees[requestCount_].amount; if (availableFees < winningBid_.fee) revert InsufficientFeesAvailable(); - TokenBalance storage tokenBalance = appGatewayFeeBalances[appGateway][ + TokenBalance storage tokenBalance = userFeeBalances[appGateway][ feesGivenByApp_.feePoolChain ][feesGivenByApp_.feePoolToken]; @@ -248,7 +275,7 @@ contract FeesManager is FeesManagerStorage, Initializable, Ownable, AddressResol if (fees.amount == 0) return; address appGateway = _getCoreAppGateway(originAppGateway_); - TokenBalance storage tokenBalance = appGatewayFeeBalances[appGateway][fees.feePoolChain][ + TokenBalance storage tokenBalance = userFeeBalances[appGateway][fees.feePoolChain][ fees.feePoolToken ]; @@ -264,6 +291,21 @@ contract FeesManager is FeesManagerStorage, Initializable, Ownable, AddressResol emit FeesUnblockedAndAssigned(requestCount_, transmitter_, fees.amount); } + function assignWatcherPrecompileFees( + uint32 chainSlug_, + address token_, + uint256 amount_, + address consumeFrom_ + ) external onlyWatcherPrecompile { + address appGateway = _getCoreAppGateway(consumeFrom_); + TokenBalance storage tokenBalance = userFeeBalances[appGateway][chainSlug_][token_]; + if (tokenBalance.deposited < amount_) + revert InsufficientWatcherPrecompileFeesAvailable(chainSlug_, token_, consumeFrom_); + tokenBalance.deposited -= amount_; + watcherPrecompileFees[chainSlug_][token_] += amount_; + emit WatcherPrecompileFeesAssigned(chainSlug_, token_, amount_, consumeFrom_); + } + function unblockFees(uint40 requestCount_) external { RequestMetadata memory requestMetadata = deliveryHelper__().getRequestMetadata( requestCount_ @@ -277,7 +319,7 @@ contract FeesManager is FeesManagerStorage, Initializable, Ownable, AddressResol Fees memory fees = requestCountBlockedFees[requestCount_]; if (fees.amount == 0) return; - TokenBalance storage tokenBalance = appGatewayFeeBalances[requestMetadata.appGateway][ + TokenBalance storage tokenBalance = userFeeBalances[requestMetadata.appGateway][ fees.feePoolChain ][fees.feePoolToken]; @@ -325,20 +367,20 @@ contract FeesManager is FeesManagerStorage, Initializable, Ownable, AddressResol /// @param amount_ The amount of tokens to withdraw /// @param receiver_ The address of the receiver function withdrawFees( - address originAppGateway_, + address originAppGatewayOrUser_, uint32 chainSlug_, address token_, uint256 amount_, address receiver_ ) public { - if (msg.sender != address(deliveryHelper__())) originAppGateway_ = msg.sender; - address appGateway = _getCoreAppGateway(originAppGateway_); + if (msg.sender != address(deliveryHelper__())) originAppGatewayOrUser_ = msg.sender; + address source = _getCoreAppGateway(originAppGatewayOrUser_); // Check if amount is available in fees plug - uint256 availableAmount = getAvailableFees(chainSlug_, appGateway, token_); + uint256 availableAmount = getAvailableFees(chainSlug_, source, token_); if (availableAmount < amount_) revert InsufficientFeesAvailable(); - TokenBalance storage tokenBalance = appGatewayFeeBalances[appGateway][chainSlug_][token_]; + TokenBalance storage tokenBalance = userFeeBalances[source][chainSlug_][token_]; tokenBalance.deposited -= amount_; // Add it to the queue and submit request diff --git a/contracts/protocol/payload-delivery/FeesPlug.sol b/contracts/protocol/payload-delivery/FeesPlug.sol index fbd9a13e..773ceb7d 100644 --- a/contracts/protocol/payload-delivery/FeesPlug.sol +++ b/contracts/protocol/payload-delivery/FeesPlug.sol @@ -30,7 +30,7 @@ contract FeesPlug is IFeesPlug, PlugBase, AccessControl { error TokenNotWhitelisted(address token_); /// @notice Event emitted when fees are deposited - event FeesDeposited(address appGateway, address token, uint256 amount); + event FeesDeposited(address receiver, address token, uint256 feeAmount, uint256 nativeAmount); /// @notice Event emitted when fees are withdrawn event FeesWithdrawn(address token, uint256 amount, address receiver); /// @notice Event emitted when a token is whitelisted @@ -88,30 +88,59 @@ contract FeesPlug is IFeesPlug, PlugBase, AccessControl { emit FeesWithdrawn(token_, amount_, receiver_); } + function depositToFee( + address token_, + uint256 amount_, + address receiver_ + ) external payable override { + _deposit(token_, receiver_, amount_, 0); + } + + function depositToFeeAndNative( + address token_, + uint256 amount_, + address receiver_ + ) external payable override { + uint256 nativeAmount_ = amount_ / 10; + uint256 feeAmount_ = amount_ - nativeAmount_; + _deposit(token_, receiver_, feeAmount_, nativeAmount_); + } + + function depositToNative( + address token_, + uint256 amount_, + address receiver_ + ) external payable override { + _deposit(token_, receiver_, 0, amount_); + } + /// @notice Deposits funds /// @param token_ The token address - /// @param amount_ The amount + /// @param feeAmount_ The amount of fees + /// @param nativeAmount_ The amount of native tokens /// @param appGateway_ The app gateway address - function deposit( + function _deposit( address token_, - address appGateway_, - uint256 amount_ - ) external payable override { + address receiver_, + uint256 feeAmount_, + uint256 nativeAmount_ + ) internal override { + uint256 totalAmount_ = feeAmount_ + nativeAmount_; if (!whitelistedTokens[token_]) revert TokenNotWhitelisted(token_); if (token_ == ETH_ADDRESS) { - if (msg.value != amount_) revert InvalidDepositAmount(); + if (msg.value != totalAmount_) revert InvalidDepositAmount(); } else { if (token_.code.length == 0) revert InvalidTokenAddress(); } - balanceOf[token_] += amount_; + balanceOf[token_] += totalAmount_; if (token_ != ETH_ADDRESS) { - SafeTransferLib.safeTransferFrom(token_, msg.sender, address(this), amount_); + SafeTransferLib.safeTransferFrom(token_, msg.sender, address(this), totalAmount_); } - emit FeesDeposited(appGateway_, token_, amount_); + emit FeesDeposited(receiver_, token_, feeAmount_, nativeAmount_); } /// @notice Transfers tokens diff --git a/contracts/protocol/payload-delivery/app-gateway/DeliveryUtils.sol b/contracts/protocol/payload-delivery/app-gateway/DeliveryUtils.sol index 0a63674b..f4d20e75 100644 --- a/contracts/protocol/payload-delivery/app-gateway/DeliveryUtils.sol +++ b/contracts/protocol/payload-delivery/app-gateway/DeliveryUtils.sol @@ -39,6 +39,7 @@ abstract contract DeliveryUtils is PayloadSubmitParams[] payloadSubmitParams, Fees fees, address auctionManager, + address consumeFrom, bool onlyReadRequests ); /// @notice Emitted when fees are increased diff --git a/contracts/protocol/payload-delivery/app-gateway/RequestQueue.sol b/contracts/protocol/payload-delivery/app-gateway/RequestQueue.sol index 419cc5c8..8843f053 100644 --- a/contracts/protocol/payload-delivery/app-gateway/RequestQueue.sol +++ b/contracts/protocol/payload-delivery/app-gateway/RequestQueue.sol @@ -26,23 +26,40 @@ abstract contract RequestQueue is DeliveryUtils { function batch( Fees memory fees_, address auctionManager_, + address consumeFrom_, bytes memory onCompleteData_ ) external returns (uint40 requestCount) { address appGateway = _getCoreAppGateway(msg.sender); - return _batch(appGateway, auctionManager_, fees_, onCompleteData_); + return _batch(appGateway, auctionManager_, consumeFrom_, fees_, onCompleteData_); } function _checkBatch( address appGateway_, - address auctionManager_, - Fees memory fees_ - ) internal view returns (address) { + address consumeFrom_, + Fees memory fees_, + uint readCount, + uint writeCount + ) internal view { if (queuePayloadParams.length > REQUEST_PAYLOAD_COUNT_LIMIT) revert RequestPayloadCountLimitExceeded(); - if (!IFeesManager(addressResolver__.feesManager()).isFeesEnough(appGateway_, fees_)) - revert InsufficientFees(); + uint256 totalWatcherPrecompileFeesRequired = watcherPrecompileLimits__.getTotalFeesRequired( + fees_.feePoolToken, + readCount, + writeCount, + 0 + ); + fees_.amount += totalWatcherPrecompileFeesRequired; + if ( + !IFeesManager(addressResolver__.feesManager()).isFeesEnough( + appGateway_, + consumeFrom_, + fees_ + ) + ) revert InsufficientFees(); + } + function _getAuctionManager(address auctionManager_) internal view returns (address) { return auctionManager_ == address(0) ? IAddressResolver(addressResolver__).defaultAuctionManager() @@ -56,21 +73,26 @@ abstract contract RequestQueue is DeliveryUtils { function _batch( address appGateway_, address auctionManager_, + address consumeFrom_, Fees memory fees_, bytes memory onCompleteData_ ) internal returns (uint40 requestCount) { if (queuePayloadParams.length == 0) return 0; - auctionManager_ = _checkBatch(appGateway_, auctionManager_, fees_); + address auctionManager = _getAuctionManager(auctionManager_); // create the payload submit params array in desired format ( PayloadSubmitParams[] memory payloadSubmitParamsArray, + uint readCount, + uint writeCount, bool onlyReadRequests ) = _createPayloadSubmitParamsArray(); + _checkBatch(appGateway_, consumeFrom_, fees_, readCount, writeCount); RequestMetadata memory requestMetadata = RequestMetadata({ appGateway: appGateway_, - auctionManager: auctionManager_, + auctionManager: auctionManager, + consumeFrom: consumeFrom_, fees: fees_, winningBid: Bid({fee: 0, transmitter: address(0), extraData: new bytes(0)}), onCompleteData: onCompleteData_, @@ -92,6 +114,7 @@ abstract contract RequestQueue is DeliveryUtils { payloadSubmitParamsArray, fees_, auctionManager_, + consumeFrom_, onlyReadRequests ); } @@ -100,7 +123,12 @@ abstract contract RequestQueue is DeliveryUtils { /// @return payloadDetailsArray An array of payload details function _createPayloadSubmitParamsArray() internal - returns (PayloadSubmitParams[] memory payloadDetailsArray, bool onlyReadRequests) + returns ( + PayloadSubmitParams[] memory payloadDetailsArray, + uint readCount, + uint writeCount, + bool onlyReadRequests + ) { payloadDetailsArray = new PayloadSubmitParams[](queuePayloadParams.length); onlyReadRequests = queuePayloadParams[0].callType == CallType.READ; @@ -109,7 +137,8 @@ abstract contract RequestQueue is DeliveryUtils { for (uint256 i = 0; i < queuePayloadParams.length; i++) { if (queuePayloadParams[i].callType != CallType.READ) { onlyReadRequests = false; - } + writeCount++; + } else readCount++; // Update level for calls if (i > 0 && queuePayloadParams[i].isParallel != Parallel.ON) { diff --git a/contracts/protocol/utils/common/Structs.sol b/contracts/protocol/utils/common/Structs.sol index 60a77fb2..4e93b06f 100644 --- a/contracts/protocol/utils/common/Structs.sol +++ b/contracts/protocol/utils/common/Structs.sol @@ -215,6 +215,7 @@ struct RequestParams { struct RequestMetadata { address appGateway; address auctionManager; + address consumeFrom; Fees fees; Bid winningBid; bytes onCompleteData; diff --git a/contracts/protocol/watcherPrecompile/WatcherPrecompileLimits.sol b/contracts/protocol/watcherPrecompile/WatcherPrecompileLimits.sol index 8303a440..c39cfd61 100644 --- a/contracts/protocol/watcherPrecompile/WatcherPrecompileLimits.sol +++ b/contracts/protocol/watcherPrecompile/WatcherPrecompileLimits.sol @@ -43,9 +43,14 @@ contract WatcherPrecompileLimits is // Mapping to track active app gateways mapping(address => bool) internal _activeAppGateways; + // token => fee amount + mapping(address => uint256) public queryFees; + mapping(address => uint256) public finalizeFees; + mapping(address => uint256) public scheduleFees; + /// @notice Emitted when the default limit and rate per second are set event DefaultLimitAndRatePerSecondSet(uint256 defaultLimit, uint256 defaultRatePerSecond); - + event WatcherFeesNotSetForToken(address token_); /// @notice Initial initialization (version 1) function initialize( address owner_, @@ -156,4 +161,50 @@ contract WatcherPrecompileLimits is emit DefaultLimitAndRatePerSecondSet(defaultLimit, defaultRatePerSecond); } + + function setQueryFees( + address[] calldata tokens_, + uint256[] calldata amounts_ + ) external onlyOwner { + require(tokens_.length == amounts_.length, "Length mismatch"); + for (uint256 i = 0; i < tokens_.length; i++) { + queryFees[tokens_[i]] = amounts_[i]; + } + } + + function setFinalizeFees( + address[] calldata tokens_, + uint256[] calldata amounts_ + ) external onlyOwner { + require(tokens_.length == amounts_.length, "Length mismatch"); + for (uint256 i = 0; i < tokens_.length; i++) { + finalizeFees[tokens_[i]] = amounts_[i]; + } + } + + function setScheduleFees( + address[] calldata tokens_, + uint256[] calldata amounts_ + ) external onlyOwner { + require(tokens_.length == amounts_.length, "Length mismatch"); + for (uint256 i = 0; i < tokens_.length; i++) { + scheduleFees[tokens_[i]] = amounts_[i]; + } + } + + function getTotalFeesRequired( + address token_, + uint queryCount, + uint finalizeCount, + uint scheduleCount + ) external view returns (uint256) { + uint256 totalFees = 0; + if (queryFees[token_] == 0 || finalizeFees[token_] == 0 || scheduleFees[token_] == 0) { + revert WatcherFeesNotSetForToken(token_); + } + totalFees += queryCount * queryFees[token_]; + totalFees += finalizeCount * finalizeFees[token_]; + totalFees += scheduleCount * scheduleFees[token_]; + return totalFees; + } } diff --git a/contracts/protocol/watcherPrecompile/core/WatcherPrecompile.sol b/contracts/protocol/watcherPrecompile/core/WatcherPrecompile.sol index 1ebd1d4d..3a91c754 100644 --- a/contracts/protocol/watcherPrecompile/core/WatcherPrecompile.sol +++ b/contracts/protocol/watcherPrecompile/core/WatcherPrecompile.sol @@ -109,9 +109,15 @@ contract WatcherPrecompile is RequestHandler { /// @return The digest hash of the finalized payload /// @dev This function finalizes a payload request and requests the watcher to release the proofs function finalize( - PayloadParams memory params_, + PayloadParams memory params_,` address transmitter_ ) external returns (bytes32) { + IFeesManager(addressResolver__.feesManager()).assignWatcherPrecompileFees( + evmxSlug, + params_.payloadHeader.getToken(), + watcherPrecompileLimits__.finalizeFees(params_.payloadHeader.getToken()), + msg.sender + ); return _finalize(params_, transmitter_); } diff --git a/script/helpers/AppGatewayFeeBalance.s.sol b/script/helpers/AppGatewayFeeBalance.s.sol index 395c542e..9bf7c2c9 100644 --- a/script/helpers/AppGatewayFeeBalance.s.sol +++ b/script/helpers/AppGatewayFeeBalance.s.sol @@ -14,7 +14,7 @@ contract CheckDepositedFees is Script { address appGateway = vm.envAddress("APP_GATEWAY"); uint32 chain = 421614; address token = ETH_ADDRESS; - (uint256 deposited, uint256 blocked) = feesManager.appGatewayFeeBalances( + (uint256 deposited, uint256 blocked) = feesManager.userFeeBalances( appGateway, chain, token From 5f293e02581c5591f376816a3527ad9c7baf4349 Mon Sep 17 00:00:00 2001 From: Ameesha Agrawal Date: Thu, 17 Apr 2025 17:11:04 +0530 Subject: [PATCH 02/21] fix: changes till auction --- contracts/base/AppGatewayBase.sol | 27 +++++++----- contracts/interfaces/IMiddleware.sol | 6 +-- .../payload-delivery/AuctionManager.sol | 28 ++++++++---- .../protocol/payload-delivery/FeesManager.sol | 12 +++-- .../app-gateway/DeliveryUtils.sol | 3 +- .../app-gateway/RequestQueue.sol | 44 +++++++------------ contracts/protocol/socket/Socket.sol | 1 + contracts/protocol/socket/SocketUtils.sol | 1 + contracts/protocol/utils/common/Structs.sol | 2 +- .../WatcherPrecompileLimits.sol | 15 ++++--- .../super-token/SuperTokenAppGateway.sol | 2 +- 11 files changed, 78 insertions(+), 63 deletions(-) diff --git a/contracts/base/AppGatewayBase.sol b/contracts/base/AppGatewayBase.sol index a040a9f2..82422aae 100644 --- a/contracts/base/AppGatewayBase.sol +++ b/contracts/base/AppGatewayBase.sol @@ -26,21 +26,29 @@ abstract contract AppGatewayBase is AddressResolverUtil, IAppGateway, FeesPlugin mapping(bytes32 => bytes) public creationCodeWithArgs; /// @notice Modifier to treat functions async - modifier async(address consumeFrom_) { - if (fees.feePoolChain == 0) revert FeesNotSet(); - isAsyncModifierSet = true; - _clearOverrides(); - deliveryHelper__().clearQueue(); - addressResolver__.clearPromises(); - + modifier async(bytes memory feesApprovalData_) { + _preAsync(); _; + _postAsync(); + } + function _postAsync() internal { isAsyncModifierSet = false; - deliveryHelper__().batch(fees, auctionManager, consumeFrom_, onCompleteData); + + // todo: cache the feesApprovalData for next async in same request + deliveryHelper__().batch(fees, auctionManager, feesApprovalData_, onCompleteData); _markValidPromises(); onCompleteData = bytes(""); } + function _preAsync() internal { + if (fees.feePoolChain == 0) revert FeesNotSet(); + isAsyncModifierSet = true; + _clearOverrides(); + deliveryHelper__().clearQueue(); + addressResolver__.clearPromises(); + } + /// @notice Modifier to ensure only valid promises can call the function /// @dev only valid promises can call the function modifier onlyPromises() { @@ -180,8 +188,7 @@ abstract contract AppGatewayBase is AddressResolverUtil, IAppGateway, FeesPlugin return address(0); } - onChainAddress = IForwarder(forwarderAddresses[contractId_][chainSlug_]) - .getOnChainAddress(); + onChainAddress = IForwarder(forwarderAddresses[contractId_][chainSlug_]).getOnChainAddress(); } //////////////////////////////////////////////////////////////////////////////////////////////// diff --git a/contracts/interfaces/IMiddleware.sol b/contracts/interfaces/IMiddleware.sol index a99ce076..ce949529 100644 --- a/contracts/interfaces/IMiddleware.sol +++ b/contracts/interfaces/IMiddleware.sol @@ -1,6 +1,6 @@ // SPDX-License-Identifier: GPL-3.0-only pragma solidity ^0.8.21; -import {PayloadSubmitParams, QueuePayloadParams, Bid, Fees, WriteFinality, CallType, Parallel, IsPlug, RequestMetadata} from "../protocol/utils/common/Structs.sol"; +import {PayloadSubmitParams, QueuePayloadParams, Bid, Fees, WriteFinality, CallType, Parallel, IsPlug, RequestMetadata, } from "../protocol/utils/common/Structs.sol"; /// @title IMiddleware /// @notice Interface for the Middleware contract @@ -25,13 +25,13 @@ interface IMiddleware { /// @notice Batches a request /// @param fees_ The fees for the request /// @param auctionManager_ The address of the auction manager - /// @param consumeFrom_ The address of the consumeFrom + /// @param feesApprovalData_ the data to be passed to the fees manager /// @param onCompleteData_ The data to be passed to the onComplete callback /// @return requestCount The request id function batch( Fees memory fees_, address auctionManager_, - address consumeFrom_, + bytes memory feesApprovalData_, bytes memory onCompleteData_ ) external returns (uint40 requestCount); diff --git a/contracts/protocol/payload-delivery/AuctionManager.sol b/contracts/protocol/payload-delivery/AuctionManager.sol index 422a3863..d310343c 100644 --- a/contracts/protocol/payload-delivery/AuctionManager.sol +++ b/contracts/protocol/payload-delivery/AuctionManager.sol @@ -112,14 +112,9 @@ contract AuctionManager is ); if (!_hasRole(TRANSMITTER_ROLE, transmitter)) revert InvalidTransmitter(); - // get the request metadata - RequestMetadata memory requestMetadata = IMiddleware(addressResolver__.deliveryHelper()) - .getRequestMetadata(requestCount_); - - // check if the bid is for this auction manager - if (requestMetadata.auctionManager != address(this)) revert InvalidBid(); - // check if the bid exceeds the max fees quoted by app gateway - if (fee > requestMetadata.fees.amount) revert BidExceedsMaxFees(); + // check if the bid exceeds the max fees quoted by app gateway subtracting the watcher fees + if (fee > getTransmitterMaxFeesRequired(requestMetadata.fees.token, requestCount_)) + revert BidExceedsMaxFees(); // check if the bid is lower than the existing bid if ( @@ -149,6 +144,7 @@ contract AuctionManager is requestMetadata.appGateway, requestMetadata.fees, newBid, + watcherPrecompile__().getTotalFeesRequired(requestMetadata.fees.token, requestCount_), requestCount_ ); @@ -211,6 +207,22 @@ contract AuctionManager is emit AuctionStarted(requestCount_); } + function getTransmitterMaxFeesRequired( + address token_, + uint40 requestCount_ + ) public view returns (uint256) { + // check if the bid is for this auction manager + if (requestMetadata.auctionManager != address(this)) revert InvalidBid(); + + // get the request metadata + RequestMetadata memory requestMetadata = IMiddleware(addressResolver__.deliveryHelper()) + .getRequestMetadata(requestCount_); + + // get the total fees required for the watcher precompile ops + uint256 watcherFees = watcherPrecompile__().getTotalFeesRequired(token_, requestCount_); + return requestMetadata.fees.amount - watcherFees; + } + function _recoverSigner( bytes32 digest_, bytes memory signature_ diff --git a/contracts/protocol/payload-delivery/FeesManager.sol b/contracts/protocol/payload-delivery/FeesManager.sol index 300f093e..f3eaf0f3 100644 --- a/contracts/protocol/payload-delivery/FeesManager.sol +++ b/contracts/protocol/payload-delivery/FeesManager.sol @@ -214,6 +214,7 @@ contract FeesManager is FeesManagerStorage, Initializable, Ownable, AddressResol isAppGatewayWhitelisted[msg.sender][appGateways_[i]] = true; } } + /// @notice Blocks fees for a request count /// @param originAppGateway_ The app gateway address /// @param feesGivenByApp_ The fees data struct given by the app gateway @@ -223,6 +224,7 @@ contract FeesManager is FeesManagerStorage, Initializable, Ownable, AddressResol address originAppGateway_, Fees memory feesGivenByApp_, Bid memory winningBid_, + uint256 watcherFees_, uint40 requestCount_ ) external { if (msg.sender != deliveryHelper__().getRequestMetadata(requestCount_).auctionManager) @@ -239,27 +241,29 @@ contract FeesManager is FeesManagerStorage, Initializable, Ownable, AddressResol if (requestCountBlockedFees[requestCount_].amount > 0) availableFees += requestCountBlockedFees[requestCount_].amount; - if (availableFees < winningBid_.fee) revert InsufficientFeesAvailable(); + uint256 feesNeeded = winningBid_.fee + watcherFees_; + if (availableFees < feesNeeded) revert InsufficientFeesAvailable(); + TokenBalance storage tokenBalance = userFeeBalances[appGateway][ feesGivenByApp_.feePoolChain ][feesGivenByApp_.feePoolToken]; tokenBalance.blocked = tokenBalance.blocked + - winningBid_.fee - + feesNeeded - requestCountBlockedFees[requestCount_].amount; requestCountBlockedFees[requestCount_] = Fees({ feePoolChain: feesGivenByApp_.feePoolChain, feePoolToken: feesGivenByApp_.feePoolToken, - amount: winningBid_.fee + amount: feesNeeded }); emit FeesBlocked( requestCount_, feesGivenByApp_.feePoolChain, feesGivenByApp_.feePoolToken, - winningBid_.fee + feesNeeded ); } diff --git a/contracts/protocol/payload-delivery/app-gateway/DeliveryUtils.sol b/contracts/protocol/payload-delivery/app-gateway/DeliveryUtils.sol index f4d20e75..8fa29c14 100644 --- a/contracts/protocol/payload-delivery/app-gateway/DeliveryUtils.sol +++ b/contracts/protocol/payload-delivery/app-gateway/DeliveryUtils.sol @@ -33,13 +33,14 @@ abstract contract DeliveryUtils is error MaxMsgValueLimitExceeded(); event BidTimeoutUpdated(uint256 newBidTimeout); + + /// @notice Emitted when a payload is submitted event PayloadSubmitted( uint40 indexed requestCount, address indexed appGateway, PayloadSubmitParams[] payloadSubmitParams, Fees fees, address auctionManager, - address consumeFrom, bool onlyReadRequests ); /// @notice Emitted when fees are increased diff --git a/contracts/protocol/payload-delivery/app-gateway/RequestQueue.sol b/contracts/protocol/payload-delivery/app-gateway/RequestQueue.sol index 8843f053..a3d7e801 100644 --- a/contracts/protocol/payload-delivery/app-gateway/RequestQueue.sol +++ b/contracts/protocol/payload-delivery/app-gateway/RequestQueue.sol @@ -26,34 +26,25 @@ abstract contract RequestQueue is DeliveryUtils { function batch( Fees memory fees_, address auctionManager_, - address consumeFrom_, + bytes memory feesApprovalData_, bytes memory onCompleteData_ ) external returns (uint40 requestCount) { address appGateway = _getCoreAppGateway(msg.sender); - return _batch(appGateway, auctionManager_, consumeFrom_, fees_, onCompleteData_); + return _batch(appGateway, auctionManager_, feesApprovalData_, onCompleteData_); } function _checkBatch( address appGateway_, - address consumeFrom_, - Fees memory fees_, - uint readCount, - uint writeCount + bytes memory feesApprovalData_, + Fees memory fees_ ) internal view { if (queuePayloadParams.length > REQUEST_PAYLOAD_COUNT_LIMIT) revert RequestPayloadCountLimitExceeded(); - uint256 totalWatcherPrecompileFeesRequired = watcherPrecompileLimits__.getTotalFeesRequired( - fees_.feePoolToken, - readCount, - writeCount, - 0 - ); - fees_.amount += totalWatcherPrecompileFeesRequired; if ( !IFeesManager(addressResolver__.feesManager()).isFeesEnough( appGateway_, - consumeFrom_, + feesApprovalData_, fees_ ) ) revert InsufficientFees(); @@ -73,8 +64,8 @@ abstract contract RequestQueue is DeliveryUtils { function _batch( address appGateway_, address auctionManager_, - address consumeFrom_, Fees memory fees_, + bytes memory feesApprovalData_, bytes memory onCompleteData_ ) internal returns (uint40 requestCount) { if (queuePayloadParams.length == 0) return 0; @@ -83,16 +74,14 @@ abstract contract RequestQueue is DeliveryUtils { // create the payload submit params array in desired format ( PayloadSubmitParams[] memory payloadSubmitParamsArray, - uint readCount, - uint writeCount, bool onlyReadRequests ) = _createPayloadSubmitParamsArray(); - _checkBatch(appGateway_, consumeFrom_, fees_, readCount, writeCount); + _checkBatch(appGateway_, feesApprovalData_, fees_); RequestMetadata memory requestMetadata = RequestMetadata({ appGateway: appGateway_, auctionManager: auctionManager, - consumeFrom: consumeFrom_, + feesApprovalData: feesApprovalData_, fees: fees_, winningBid: Bid({fee: 0, transmitter: address(0), extraData: new bytes(0)}), onCompleteData: onCompleteData_, @@ -108,13 +97,16 @@ abstract contract RequestQueue is DeliveryUtils { if (onlyReadRequests) watcherPrecompile__().startProcessingRequest(requestCount, address(0)); + // to save extra calls from transmitter + uint256 maxTransmitterFees = fees_.amount - + watcherPrecompile__().getTotalFeesRequired(fees_.token, requestCount); + emit PayloadSubmitted( requestCount, appGateway_, payloadSubmitParamsArray, - fees_, + Fees({token: fees_.token, amount: fees_.amount - maxTransmitterFees}), auctionManager_, - consumeFrom_, onlyReadRequests ); } @@ -123,12 +115,7 @@ abstract contract RequestQueue is DeliveryUtils { /// @return payloadDetailsArray An array of payload details function _createPayloadSubmitParamsArray() internal - returns ( - PayloadSubmitParams[] memory payloadDetailsArray, - uint readCount, - uint writeCount, - bool onlyReadRequests - ) + returns (PayloadSubmitParams[] memory payloadDetailsArray, bool onlyReadRequests) { payloadDetailsArray = new PayloadSubmitParams[](queuePayloadParams.length); onlyReadRequests = queuePayloadParams[0].callType == CallType.READ; @@ -137,8 +124,7 @@ abstract contract RequestQueue is DeliveryUtils { for (uint256 i = 0; i < queuePayloadParams.length; i++) { if (queuePayloadParams[i].callType != CallType.READ) { onlyReadRequests = false; - writeCount++; - } else readCount++; + } // Update level for calls if (i > 0 && queuePayloadParams[i].isParallel != Parallel.ON) { diff --git a/contracts/protocol/socket/Socket.sol b/contracts/protocol/socket/Socket.sol index aa2e0fb6..4ac83636 100644 --- a/contracts/protocol/socket/Socket.sol +++ b/contracts/protocol/socket/Socket.sol @@ -76,6 +76,7 @@ contract Socket is SocketUtils { // validate the execution status _validateExecutionStatus(payloadId); + // should we move this to a separate contract? // recover the signer address transmitter = _recoverSigner( keccak256(abi.encode(address(this), payloadId)), diff --git a/contracts/protocol/socket/SocketUtils.sol b/contracts/protocol/socket/SocketUtils.sol index 6f886c5e..eef99e2e 100644 --- a/contracts/protocol/socket/SocketUtils.sol +++ b/contracts/protocol/socket/SocketUtils.sol @@ -67,6 +67,7 @@ abstract contract SocketUtils is SocketConfig { executeParams_.target, appGateway_, executeParams_.prevDigestsHash + // can keep bytes here? ) ); } diff --git a/contracts/protocol/utils/common/Structs.sol b/contracts/protocol/utils/common/Structs.sol index 4e93b06f..32170560 100644 --- a/contracts/protocol/utils/common/Structs.sol +++ b/contracts/protocol/utils/common/Structs.sol @@ -215,11 +215,11 @@ struct RequestParams { struct RequestMetadata { address appGateway; address auctionManager; - address consumeFrom; Fees fees; Bid winningBid; bytes onCompleteData; bool onlyReadRequests; + bytes feesApprovalData; } struct ExecuteParams { diff --git a/contracts/protocol/watcherPrecompile/WatcherPrecompileLimits.sol b/contracts/protocol/watcherPrecompile/WatcherPrecompileLimits.sol index c39cfd61..1fdf374d 100644 --- a/contracts/protocol/watcherPrecompile/WatcherPrecompileLimits.sol +++ b/contracts/protocol/watcherPrecompile/WatcherPrecompileLimits.sol @@ -51,6 +51,7 @@ contract WatcherPrecompileLimits is /// @notice Emitted when the default limit and rate per second are set event DefaultLimitAndRatePerSecondSet(uint256 defaultLimit, uint256 defaultRatePerSecond); event WatcherFeesNotSetForToken(address token_); + /// @notice Initial initialization (version 1) function initialize( address owner_, @@ -124,6 +125,7 @@ contract WatcherPrecompileLimits is * @param consumeLimit_ The amount of limit to consume */ function consumeLimit( + uint40 requestCount_, address appGateway_, bytes32 limitType_, uint256 consumeLimit_ @@ -148,6 +150,8 @@ contract WatcherPrecompileLimits is } // Update the limit + precompileCount[limitType_][requestCount_] += consumeLimit_; + _consumeFullLimit(consumeLimit_ * 10 ** limitDecimals, limitParams); } @@ -194,17 +198,16 @@ contract WatcherPrecompileLimits is function getTotalFeesRequired( address token_, - uint queryCount, - uint finalizeCount, - uint scheduleCount + uint40 requestCount_ ) external view returns (uint256) { uint256 totalFees = 0; if (queryFees[token_] == 0 || finalizeFees[token_] == 0 || scheduleFees[token_] == 0) { revert WatcherFeesNotSetForToken(token_); } - totalFees += queryCount * queryFees[token_]; - totalFees += finalizeCount * finalizeFees[token_]; - totalFees += scheduleCount * scheduleFees[token_]; + + totalFees += precompileCount[QUERY][requestCount_] * queryFees[token_]; + totalFees += precompileCount[FINALIZE][requestCount_] * finalizeFees[token_]; + totalFees += precompileCount[SCHEDULE][requestCount_] * scheduleFees[token_]; return totalFees; } } diff --git a/test/apps/app-gateways/super-token/SuperTokenAppGateway.sol b/test/apps/app-gateways/super-token/SuperTokenAppGateway.sol index 3110d497..98488499 100644 --- a/test/apps/app-gateways/super-token/SuperTokenAppGateway.sol +++ b/test/apps/app-gateways/super-token/SuperTokenAppGateway.sol @@ -60,7 +60,7 @@ contract SuperTokenAppGateway is AppGatewayBase, Ownable { return; } - function transfer(bytes memory order_) external async { + function transfer(bytes memory order_) external async createFeePool { TransferOrder memory order = abi.decode(order_, (TransferOrder)); ISuperToken(order.srcToken).burn(order.user, order.srcAmount); ISuperToken(order.dstToken).mint(order.user, order.srcAmount); From aef1ebb806400b36d990d471535db02160e1aa3d Mon Sep 17 00:00:00 2001 From: Ameesha Agrawal Date: Thu, 17 Apr 2025 18:03:40 +0530 Subject: [PATCH 03/21] feat: watcher precompile fees and callback fees --- .../protocol/payload-delivery/FeesManager.sol | 28 ++++++++++++----- .../app-gateway/DeliveryHelper.sol | 3 +- .../app-gateway/RequestQueue.sol | 2 +- .../watcherPrecompile/core/RequestHandler.sol | 11 ++++--- .../core/WatcherPrecompile.sol | 31 +++++++++---------- .../core/WatcherPrecompileCore.sol | 25 +++++++++++++++ .../core/WatcherPrecompileStorage.sol | 8 +++-- 7 files changed, 75 insertions(+), 33 deletions(-) diff --git a/contracts/protocol/payload-delivery/FeesManager.sol b/contracts/protocol/payload-delivery/FeesManager.sol index f3eaf0f3..53ac31bb 100644 --- a/contracts/protocol/payload-delivery/FeesManager.sol +++ b/contracts/protocol/payload-delivery/FeesManager.sol @@ -51,6 +51,8 @@ abstract contract FeesManagerStorage is IFeesManager { /// @dev signatureNonce => isNonceUsed mapping(uint256 => bool) public isNonceUsed; + mapping(uint40 => address) public requestCountConsumeFrom; + // slots [57-106] reserved for gap uint256[50] _gap_after; @@ -299,15 +301,14 @@ contract FeesManager is FeesManagerStorage, Initializable, Ownable, AddressResol uint32 chainSlug_, address token_, uint256 amount_, - address consumeFrom_ + uint40 requestCount_ ) external onlyWatcherPrecompile { - address appGateway = _getCoreAppGateway(consumeFrom_); - TokenBalance storage tokenBalance = userFeeBalances[appGateway][chainSlug_][token_]; - if (tokenBalance.deposited < amount_) - revert InsufficientWatcherPrecompileFeesAvailable(chainSlug_, token_, consumeFrom_); - tokenBalance.deposited -= amount_; + Fees storage fees = requestCountBlockedFees[requestCount_]; + if (fees.amount == 0) revert NoFeesBlocked(); + + fees.amount -= amount_; watcherPrecompileFees[chainSlug_][token_] += amount_; - emit WatcherPrecompileFeesAssigned(chainSlug_, token_, amount_, consumeFrom_); + emit WatcherPrecompileFeesAssigned(chainSlug_, token_, amount_, requestCount_); } function unblockFees(uint40 requestCount_) external { @@ -412,7 +413,18 @@ contract FeesManager is FeesManagerStorage, Initializable, Ownable, AddressResol readAt: 0, payload: payload_ }); - requestCount = watcherPrecompile__().submitRequest(payloadSubmitParamsArray); + + RequestMetadata memory requestMetadata = RequestMetadata({ + appGateway: address(this), + auctionManager: address(0), + feesApprovalData: bytes(""), + fees: Fees({token: token_, amount: amount_}), + winningBid: Bid({transmitter: transmitter_, fee: 0, extraData: new bytes(0)}) + }); + requestCount = watcherPrecompile__().submitRequest( + payloadSubmitParamsArray, + requestMetadata + ); // same transmitter can execute requests without auction watcherPrecompile__().startProcessingRequest(requestCount, transmitter_); diff --git a/contracts/protocol/payload-delivery/app-gateway/DeliveryHelper.sol b/contracts/protocol/payload-delivery/app-gateway/DeliveryHelper.sol index 6df859ac..6c7b0c80 100644 --- a/contracts/protocol/payload-delivery/app-gateway/DeliveryHelper.sol +++ b/contracts/protocol/payload-delivery/app-gateway/DeliveryHelper.sol @@ -41,7 +41,7 @@ contract DeliveryHelper is FeesHelpers { requestMetadata_.winningBid.transmitter = winningBid_.transmitter; if (!isRestarted) { - watcherPrecompile__().startProcessingRequest(requestCount_, winningBid_.transmitter); + watcherPrecompile__().startProcessingRequest(requestCount_, winningBid_); } else { watcherPrecompile__().updateTransmitter(requestCount_, winningBid_.transmitter); } @@ -52,6 +52,7 @@ contract DeliveryHelper is FeesHelpers { function finishRequest(uint40 requestCount_) external onlyWatcherPrecompile { RequestMetadata storage requestMetadata_ = requests[requestCount_]; + // todo: move it to watcher precompile if (requestMetadata_.winningBid.transmitter != address(0)) IFeesManager(addressResolver__.feesManager()).unblockAndAssignFees( requestCount_, diff --git a/contracts/protocol/payload-delivery/app-gateway/RequestQueue.sol b/contracts/protocol/payload-delivery/app-gateway/RequestQueue.sol index a3d7e801..be9cabf7 100644 --- a/contracts/protocol/payload-delivery/app-gateway/RequestQueue.sol +++ b/contracts/protocol/payload-delivery/app-gateway/RequestQueue.sol @@ -89,7 +89,7 @@ abstract contract RequestQueue is DeliveryUtils { }); // process and submit the queue of payloads to watcher precompile - requestCount = watcherPrecompile__().submitRequest(payloadSubmitParamsArray); + requestCount = watcherPrecompile__().submitRequest(payloadSubmitParamsArray, requestMetadata); requests[requestCount] = requestMetadata; // send query directly if request contains only reads diff --git a/contracts/protocol/watcherPrecompile/core/RequestHandler.sol b/contracts/protocol/watcherPrecompile/core/RequestHandler.sol index 0d3a2851..3d00e32d 100644 --- a/contracts/protocol/watcherPrecompile/core/RequestHandler.sol +++ b/contracts/protocol/watcherPrecompile/core/RequestHandler.sol @@ -19,7 +19,8 @@ abstract contract RequestHandler is WatcherPrecompileCore { /// @dev This function processes a batch of payload requests and assigns them to batches /// @dev It also consumes limits for the app gateway based on the number of reads and writes function submitRequest( - PayloadSubmitParams[] calldata payloadSubmitParams + PayloadSubmitParams[] calldata payloadSubmitParams, + RequestMetadata calldata requestMetadata ) public returns (uint40 requestCount) { address appGateway = _checkAppGateways(payloadSubmitParams); @@ -96,6 +97,8 @@ abstract contract RequestHandler is WatcherPrecompileCore { requestParams[requestCount].payloadsRemaining = payloadSubmitParams.length; requestParams[requestCount].middleware = msg.sender; + requestMetadata[requestCount] = requestMetadata; + emit RequestSubmitted( msg.sender, requestCount, @@ -129,17 +132,17 @@ abstract contract RequestHandler is WatcherPrecompileCore { /// @notice Starts processing a request with a specified transmitter /// @param requestCount The request count to start processing - /// @param transmitter The address of the transmitter + /// @param winningBid 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 { + function startProcessingRequest(uint40 requestCount, Bid memory winningBid) 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.transmitter = winningBid.transmitter; r.currentBatch = batchCount; _processBatch(requestCount, batchCount); diff --git a/contracts/protocol/watcherPrecompile/core/WatcherPrecompile.sol b/contracts/protocol/watcherPrecompile/core/WatcherPrecompile.sol index 3a91c754..27dc496b 100644 --- a/contracts/protocol/watcherPrecompile/core/WatcherPrecompile.sol +++ b/contracts/protocol/watcherPrecompile/core/WatcherPrecompile.sol @@ -112,12 +112,6 @@ contract WatcherPrecompile is RequestHandler { PayloadParams memory params_,` address transmitter_ ) external returns (bytes32) { - IFeesManager(addressResolver__.feesManager()).assignWatcherPrecompileFees( - evmxSlug, - params_.payloadHeader.getToken(), - watcherPrecompileLimits__.finalizeFees(params_.payloadHeader.getToken()), - msg.sender - ); return _finalize(params_, transmitter_); } @@ -208,15 +202,25 @@ contract WatcherPrecompile is RequestHandler { PayloadParams memory payloadParams = payloads[resolvedPromises_[i].payloadId]; address asyncPromise = payloadParams.asyncPromise; + uint40 requestCount = payloadParams.payloadHeader.getRequestCount(); + // todo: non trusted call if (asyncPromise != address(0)) { // Resolve each promise with its corresponding return data + + uint256 initialGas = gasleft(); bool success = IPromise(asyncPromise).markResolved( - payloadParams.payloadHeader.getRequestCount(), + requestCount, resolvedPromises_[i].payloadId, resolvedPromises_[i].returnData ); + uint256 gasUsed = initialGas - gasleft(); + _consumeFees( + requestCount, + gasUsed * tx.gasprice + ); + if (!success) { emit PromiseNotResolved(resolvedPromises_[i].payloadId, asyncPromise); continue; @@ -224,9 +228,7 @@ contract WatcherPrecompile is RequestHandler { } isPromiseExecuted[resolvedPromises_[i].payloadId] = true; - RequestParams storage requestParams_ = requestParams[ - payloadParams.payloadHeader.getRequestCount() - ]; + RequestParams storage requestParams_ = requestParams[requestCount]; requestParams_.currentBatchPayloadsLeft--; requestParams_.payloadsRemaining--; @@ -234,17 +236,12 @@ contract WatcherPrecompile is RequestHandler { if ( requestParams_.currentBatchPayloadsLeft == 0 && requestParams_.payloadsRemaining > 0 ) { - _processBatch( - payloadParams.payloadHeader.getRequestCount(), - ++requestParams_.currentBatch - ); + _processBatch(requestCount, ++requestParams_.currentBatch); } // if all payloads of a request are executed, finish the request if (requestParams_.payloadsRemaining == 0) { - IMiddleware(requestParams_.middleware).finishRequest( - payloadParams.payloadHeader.getRequestCount() - ); + IMiddleware(requestParams_.middleware).finishRequest(requestCount); } emit PromiseResolved(resolvedPromises_[i].payloadId, asyncPromise); } diff --git a/contracts/protocol/watcherPrecompile/core/WatcherPrecompileCore.sol b/contracts/protocol/watcherPrecompile/core/WatcherPrecompileCore.sol index 63e32618..70b109ad 100644 --- a/contracts/protocol/watcherPrecompile/core/WatcherPrecompileCore.sol +++ b/contracts/protocol/watcherPrecompile/core/WatcherPrecompileCore.sol @@ -33,6 +33,11 @@ abstract contract WatcherPrecompileCore is ) internal returns (bytes32 timeoutId) { if (delayInSeconds_ > maxTimeoutDelayInSeconds) revert TimeoutDelayTooLarge(); + _consumeFees( + requestCount_, + watcherPrecompileLimits__.scheduleFees(requestMetadata_.fees.feePoolToken) + ); + uint256 executeAt = block.timestamp + delayInSeconds_; timeoutId = _encodeTimeoutId(); @@ -74,6 +79,11 @@ abstract contract WatcherPrecompileCore is requestParams[params_.payloadHeader.getRequestCount()].middleware ); + _consumeFees( + params_.payloadHeader.getRequestCount(), + watcherPrecompileLimits__.finalizeFees(params_.payloadHeader.getChainSlug()) + ); + uint256 deadline = block.timestamp + expiryTime; payloads[params_.payloadId].deadline = deadline; payloads[params_.payloadId].finalizedTransmitter = transmitter_; @@ -109,6 +119,11 @@ abstract contract WatcherPrecompileCore is /// @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 { + _consumeFees( + params_.payloadHeader.getRequestCount(), + watcherPrecompileLimits__.queryFees(params_.payloadHeader.getChainSlug()) + ); + payloads[params_.payloadId].prevDigestsHash = _getPreviousDigestsHash( params_.payloadHeader.getBatchCount() ); @@ -239,6 +254,16 @@ abstract contract WatcherPrecompileCore is if (signer != owner()) revert InvalidWatcherSignature(); } + function _consumeFees(uint40 requestCount_, uint256 fees_) internal { + RequestMetadata memory requestMetadata_ = requestMetadata[requestCount_]; + IFeesManager(addressResolver__.feesManager()).assignWatcherPrecompileFees( + requestMetadata_.fees.feePoolChain, + requestMetadata_.fees.feePoolToken, + fees_, + requestCount_ + ); + } + /// @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 diff --git a/contracts/protocol/watcherPrecompile/core/WatcherPrecompileStorage.sol b/contracts/protocol/watcherPrecompile/core/WatcherPrecompileStorage.sol index d13ad022..ff40d67a 100644 --- a/contracts/protocol/watcherPrecompile/core/WatcherPrecompileStorage.sol +++ b/contracts/protocol/watcherPrecompile/core/WatcherPrecompileStorage.sol @@ -96,8 +96,12 @@ abstract contract WatcherPrecompileStorage is IWatcherPrecompile { // slot 64 IWatcherPrecompileConfig public watcherPrecompileConfig__; - // slots [65-114]: gap for future storage variables - uint256[50] _gap_after; + // slot 65 + /// @notice Mapping to store the request metadata for each request count + mapping(uint40 => RequestMetadata) public requestMetadata; + + // slots [66-114]: gap for future storage variables + uint256[49] _gap_after; // slots 115-165 (51) reserved for access control // slots 166-216 (51) reserved for addr resolver util From b9ca484232a546472ca98bd7c1be121b40580bd9 Mon Sep 17 00:00:00 2001 From: Ameesha Agrawal Date: Fri, 18 Apr 2025 15:01:02 +0530 Subject: [PATCH 04/21] feat: callback fees in finalize --- .../watcherPrecompile/core/WatcherPrecompile.sol | 10 ++-------- .../watcherPrecompile/core/WatcherPrecompileCore.sol | 5 ++++- .../core/WatcherPrecompileStorage.sol | 7 +++++-- 3 files changed, 11 insertions(+), 11 deletions(-) diff --git a/contracts/protocol/watcherPrecompile/core/WatcherPrecompile.sol b/contracts/protocol/watcherPrecompile/core/WatcherPrecompile.sol index 27dc496b..5f11328b 100644 --- a/contracts/protocol/watcherPrecompile/core/WatcherPrecompile.sol +++ b/contracts/protocol/watcherPrecompile/core/WatcherPrecompile.sol @@ -42,6 +42,7 @@ contract WatcherPrecompile is RequestHandler { expiryTime = expiryTime_; evmxSlug = evmxSlug_; + callBackGasLimit = 5000000; } // ================== Timeout functions ================== @@ -206,21 +207,14 @@ contract WatcherPrecompile is RequestHandler { // todo: non trusted call if (asyncPromise != address(0)) { + // todo: limit the gas used for promise resolution // Resolve each promise with its corresponding return data - - uint256 initialGas = gasleft(); bool success = IPromise(asyncPromise).markResolved( requestCount, resolvedPromises_[i].payloadId, resolvedPromises_[i].returnData ); - uint256 gasUsed = initialGas - gasleft(); - _consumeFees( - requestCount, - gasUsed * tx.gasprice - ); - if (!success) { emit PromiseNotResolved(resolvedPromises_[i].payloadId, asyncPromise); continue; diff --git a/contracts/protocol/watcherPrecompile/core/WatcherPrecompileCore.sol b/contracts/protocol/watcherPrecompile/core/WatcherPrecompileCore.sol index 70b109ad..22f04acf 100644 --- a/contracts/protocol/watcherPrecompile/core/WatcherPrecompileCore.sol +++ b/contracts/protocol/watcherPrecompile/core/WatcherPrecompileCore.sol @@ -256,10 +256,13 @@ abstract contract WatcherPrecompileCore is function _consumeFees(uint40 requestCount_, uint256 fees_) internal { RequestMetadata memory requestMetadata_ = requestMetadata[requestCount_]; + + // for callbacks in all precompiles + uint256 feesToConsume = fees_ + tx.gasprice * callBackGasLimit; IFeesManager(addressResolver__.feesManager()).assignWatcherPrecompileFees( requestMetadata_.fees.feePoolChain, requestMetadata_.fees.feePoolToken, - fees_, + feesToConsume, requestCount_ ); } diff --git a/contracts/protocol/watcherPrecompile/core/WatcherPrecompileStorage.sol b/contracts/protocol/watcherPrecompile/core/WatcherPrecompileStorage.sol index ff40d67a..e28bf412 100644 --- a/contracts/protocol/watcherPrecompile/core/WatcherPrecompileStorage.sol +++ b/contracts/protocol/watcherPrecompile/core/WatcherPrecompileStorage.sol @@ -100,8 +100,11 @@ abstract contract WatcherPrecompileStorage is IWatcherPrecompile { /// @notice Mapping to store the request metadata for each request count mapping(uint40 => RequestMetadata) public requestMetadata; - // slots [66-114]: gap for future storage variables - uint256[49] _gap_after; + // slot 66 + uint256 public callBackGasLimit; + + // 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 From 2a7a8e8d8f90e2f25e0c243231fcfe853b2d2547 Mon Sep 17 00:00:00 2001 From: Ameesha Agrawal Date: Fri, 18 Apr 2025 15:48:02 +0530 Subject: [PATCH 05/21] fix: unblock and assign fees --- .../payload-delivery/AuctionManager.sol | 15 ++++++----- .../protocol/payload-delivery/FeesManager.sol | 26 ++++++++++++------- .../WatcherPrecompileLimits.sol | 17 ++++++++++++ .../core/WatcherPrecompile.sol | 1 - .../core/WatcherPrecompileCore.sol | 2 +- .../core/WatcherPrecompileStorage.sol | 3 --- 6 files changed, 43 insertions(+), 21 deletions(-) diff --git a/contracts/protocol/payload-delivery/AuctionManager.sol b/contracts/protocol/payload-delivery/AuctionManager.sol index d310343c..43b8e0fe 100644 --- a/contracts/protocol/payload-delivery/AuctionManager.sol +++ b/contracts/protocol/payload-delivery/AuctionManager.sol @@ -112,9 +112,12 @@ contract AuctionManager is ); if (!_hasRole(TRANSMITTER_ROLE, transmitter)) revert InvalidTransmitter(); + (uint256 watcherFees, uint256 transmitterFees) = getTransmitterMaxFeesRequired( + requestMetadata.fees.token, + requestCount_ + ); // check if the bid exceeds the max fees quoted by app gateway subtracting the watcher fees - if (fee > getTransmitterMaxFeesRequired(requestMetadata.fees.token, requestCount_)) - revert BidExceedsMaxFees(); + if (fee > transmitterFees) revert BidExceedsMaxFees(); // check if the bid is lower than the existing bid if ( @@ -141,10 +144,10 @@ contract AuctionManager is // block the fees IFeesManager(addressResolver__.feesManager()).blockFees( - requestMetadata.appGateway, + requestMetadata.consumeFrom, requestMetadata.fees, newBid, - watcherPrecompile__().getTotalFeesRequired(requestMetadata.fees.token, requestCount_), + watcherFees, requestCount_ ); @@ -210,7 +213,7 @@ contract AuctionManager is function getTransmitterMaxFeesRequired( address token_, uint40 requestCount_ - ) public view returns (uint256) { + ) public view returns (uint256, uint256) { // check if the bid is for this auction manager if (requestMetadata.auctionManager != address(this)) revert InvalidBid(); @@ -220,7 +223,7 @@ contract AuctionManager is // get the total fees required for the watcher precompile ops uint256 watcherFees = watcherPrecompile__().getTotalFeesRequired(token_, requestCount_); - return requestMetadata.fees.amount - watcherFees; + return (watcherFees, requestMetadata.fees.amount - watcherFees); } function _recoverSigner( diff --git a/contracts/protocol/payload-delivery/FeesManager.sol b/contracts/protocol/payload-delivery/FeesManager.sol index 53ac31bb..3443e54b 100644 --- a/contracts/protocol/payload-delivery/FeesManager.sol +++ b/contracts/protocol/payload-delivery/FeesManager.sol @@ -223,7 +223,7 @@ contract FeesManager is FeesManagerStorage, Initializable, Ownable, AddressResol /// @param requestCount_ The batch identifier /// @dev Only callable by delivery helper function blockFees( - address originAppGateway_, + address consumeFrom_, Fees memory feesGivenByApp_, Bid memory winningBid_, uint256 watcherFees_, @@ -232,11 +232,10 @@ contract FeesManager is FeesManagerStorage, Initializable, Ownable, AddressResol if (msg.sender != deliveryHelper__().getRequestMetadata(requestCount_).auctionManager) revert NotAuctionManager(); - address appGateway = _getCoreAppGateway(originAppGateway_); // Block fees uint256 availableFees = getAvailableFees( feesGivenByApp_.feePoolChain, - appGateway, + consumeFrom_, feesGivenByApp_.feePoolToken ); @@ -246,7 +245,7 @@ contract FeesManager is FeesManagerStorage, Initializable, Ownable, AddressResol uint256 feesNeeded = winningBid_.fee + watcherFees_; if (availableFees < feesNeeded) revert InsufficientFeesAvailable(); - TokenBalance storage tokenBalance = userFeeBalances[appGateway][ + TokenBalance storage tokenBalance = userFeeBalances[consumeFrom_][ feesGivenByApp_.feePoolChain ][feesGivenByApp_.feePoolToken]; @@ -260,6 +259,7 @@ contract FeesManager is FeesManagerStorage, Initializable, Ownable, AddressResol feePoolToken: feesGivenByApp_.feePoolToken, amount: feesNeeded }); + requestCountConsumeFrom[requestCount_] = consumeFrom_; emit FeesBlocked( requestCount_, @@ -274,23 +274,29 @@ contract FeesManager is FeesManagerStorage, Initializable, Ownable, AddressResol /// @param transmitter_ The address of the transmitter who executed the batch function unblockAndAssignFees( uint40 requestCount_, - address transmitter_, - address originAppGateway_ + address transmitter_ ) external override onlyDeliveryHelper { Fees memory fees = requestCountBlockedFees[requestCount_]; if (fees.amount == 0) return; - address appGateway = _getCoreAppGateway(originAppGateway_); - TokenBalance storage tokenBalance = userFeeBalances[appGateway][fees.feePoolChain][ + RequestMetadata memory requestMetadata = deliveryHelper__().getRequestMetadata( + requestCount_ + ); + + TokenBalance storage tokenBalance = userFeeBalances[consumeFrom][fees.feePoolChain][ fees.feePoolToken ]; + uint256 transmitterBid = requestMetadata.winningBid.fee; + uint256 remainingFees = fees.amount - transmitterBid; + // Unblock fees from deposit tokenBalance.blocked -= fees.amount; - tokenBalance.deposited -= fees.amount; + tokenBalance.deposited -= transmitterBid; + tokenBalance.deposited -= remainingFees; // Assign fees to transmitter - transmitterFees[transmitter_][fees.feePoolChain][fees.feePoolToken] += fees.amount; + transmitterFees[transmitter_][fees.feePoolChain][fees.feePoolToken] += transmitterBid; // Clean up storage delete requestCountBlockedFees[requestCount_]; diff --git a/contracts/protocol/watcherPrecompile/WatcherPrecompileLimits.sol b/contracts/protocol/watcherPrecompile/WatcherPrecompileLimits.sol index 1fdf374d..0ab07deb 100644 --- a/contracts/protocol/watcherPrecompile/WatcherPrecompileLimits.sol +++ b/contracts/protocol/watcherPrecompile/WatcherPrecompileLimits.sol @@ -47,6 +47,7 @@ contract WatcherPrecompileLimits is mapping(address => uint256) public queryFees; mapping(address => uint256) public finalizeFees; mapping(address => uint256) public scheduleFees; + mapping(address => uint256) public callBackFees; /// @notice Emitted when the default limit and rate per second are set event DefaultLimitAndRatePerSecondSet(uint256 defaultLimit, uint256 defaultRatePerSecond); @@ -196,6 +197,16 @@ contract WatcherPrecompileLimits is } } + function setCallBackFees( + address[] calldata tokens_, + uint256[] calldata amounts_ + ) external onlyOwner { + require(tokens_.length == amounts_.length, "Length mismatch"); + for (uint256 i = 0; i < tokens_.length; i++) { + callBackFees[tokens_[i]] = amounts_[i]; + } + } + function getTotalFeesRequired( address token_, uint40 requestCount_ @@ -205,9 +216,15 @@ contract WatcherPrecompileLimits is revert WatcherFeesNotSetForToken(token_); } + uint256 totalCallbacks = precompileCount[QUERY][requestCount_] + + precompileCount[FINALIZE][requestCount_] + + precompileCount[SCHEDULE][requestCount_]; + + totalFees += totalCallbacks * callBackFees[token_]; totalFees += precompileCount[QUERY][requestCount_] * queryFees[token_]; totalFees += precompileCount[FINALIZE][requestCount_] * finalizeFees[token_]; totalFees += precompileCount[SCHEDULE][requestCount_] * scheduleFees[token_]; + return totalFees; } } diff --git a/contracts/protocol/watcherPrecompile/core/WatcherPrecompile.sol b/contracts/protocol/watcherPrecompile/core/WatcherPrecompile.sol index 5f11328b..9e1ac974 100644 --- a/contracts/protocol/watcherPrecompile/core/WatcherPrecompile.sol +++ b/contracts/protocol/watcherPrecompile/core/WatcherPrecompile.sol @@ -42,7 +42,6 @@ contract WatcherPrecompile is RequestHandler { expiryTime = expiryTime_; evmxSlug = evmxSlug_; - callBackGasLimit = 5000000; } // ================== Timeout functions ================== diff --git a/contracts/protocol/watcherPrecompile/core/WatcherPrecompileCore.sol b/contracts/protocol/watcherPrecompile/core/WatcherPrecompileCore.sol index 22f04acf..d8cef4f0 100644 --- a/contracts/protocol/watcherPrecompile/core/WatcherPrecompileCore.sol +++ b/contracts/protocol/watcherPrecompile/core/WatcherPrecompileCore.sol @@ -258,7 +258,7 @@ abstract contract WatcherPrecompileCore is RequestMetadata memory requestMetadata_ = requestMetadata[requestCount_]; // for callbacks in all precompiles - uint256 feesToConsume = fees_ + tx.gasprice * callBackGasLimit; + uint256 feesToConsume = fees_ + watcherPrecompileLimits__().callBackFees(); IFeesManager(addressResolver__.feesManager()).assignWatcherPrecompileFees( requestMetadata_.fees.feePoolChain, requestMetadata_.fees.feePoolToken, diff --git a/contracts/protocol/watcherPrecompile/core/WatcherPrecompileStorage.sol b/contracts/protocol/watcherPrecompile/core/WatcherPrecompileStorage.sol index e28bf412..12dd3d53 100644 --- a/contracts/protocol/watcherPrecompile/core/WatcherPrecompileStorage.sol +++ b/contracts/protocol/watcherPrecompile/core/WatcherPrecompileStorage.sol @@ -100,9 +100,6 @@ abstract contract WatcherPrecompileStorage is IWatcherPrecompile { /// @notice Mapping to store the request metadata for each request count mapping(uint40 => RequestMetadata) public requestMetadata; - // slot 66 - uint256 public callBackGasLimit; - // slots [67-114]: gap for future storage variables uint256[48] _gap_after; From b2b49b0b43d65909b9e4d8d1141bec9845d1d1ec Mon Sep 17 00:00:00 2001 From: Ameesha Agrawal Date: Fri, 18 Apr 2025 16:17:06 +0530 Subject: [PATCH 06/21] feat: deduct fees from gateway --- contracts/protocol/payload-delivery/FeesManager.sol | 4 ++-- .../protocol/watcherPrecompile/core/WatcherPrecompile.sol | 8 ++++++++ 2 files changed, 10 insertions(+), 2 deletions(-) diff --git a/contracts/protocol/payload-delivery/FeesManager.sol b/contracts/protocol/payload-delivery/FeesManager.sol index 3443e54b..9b8d96ea 100644 --- a/contracts/protocol/payload-delivery/FeesManager.sol +++ b/contracts/protocol/payload-delivery/FeesManager.sol @@ -27,8 +27,8 @@ abstract contract FeesManagerStorage is IFeesManager { // slot 53 /// @notice Master mapping tracking all fee information - /// @dev userAddress => chainSlug => token => TokenBalance - mapping(address => mapping(uint32 => mapping(address => TokenBalance))) public userFeeBalances; + /// @dev userAddress => chainSlug => TokenBalance + mapping(address => mapping(uint32 => TokenBalance)) public userFeeBalances; // userAddress => appGateway => isWhitelisted mapping(address => mapping(address => bool)) public isAppGatewayWhitelisted; diff --git a/contracts/protocol/watcherPrecompile/core/WatcherPrecompile.sol b/contracts/protocol/watcherPrecompile/core/WatcherPrecompile.sol index 9e1ac974..78f6f102 100644 --- a/contracts/protocol/watcherPrecompile/core/WatcherPrecompile.sol +++ b/contracts/protocol/watcherPrecompile/core/WatcherPrecompile.sol @@ -312,6 +312,14 @@ contract WatcherPrecompile is RequestHandler { ) ) revert InvalidCallerTriggered(); + IFeesManager(addressResolver__.feesManager()).assignWatcherPrecompileFees( + address(0), + uint32(0), + watcherPrecompileLimits__().callBackFees(params_[i].payload), + uint40(0), + appGateway + ); + appGatewayCaller = appGateway; appGatewayCalled[params_[i].triggerId] = true; From 9c39bd07e01ec744f7396dd2914b0e5d3bce47c2 Mon Sep 17 00:00:00 2001 From: Ameesha Agrawal Date: Fri, 18 Apr 2025 17:57:54 +0530 Subject: [PATCH 07/21] fix: remove token and chain context from evmx --- .../payload-delivery/AuctionManager.sol | 16 +- .../protocol/payload-delivery/FeesManager.sol | 223 ++++++++---------- .../protocol/payload-delivery/FeesPlug.sol | 38 +-- 3 files changed, 105 insertions(+), 172 deletions(-) diff --git a/contracts/protocol/payload-delivery/AuctionManager.sol b/contracts/protocol/payload-delivery/AuctionManager.sol index 43b8e0fe..a27cbd3f 100644 --- a/contracts/protocol/payload-delivery/AuctionManager.sol +++ b/contracts/protocol/payload-delivery/AuctionManager.sol @@ -142,15 +142,6 @@ contract AuctionManager is _endAuction(requestCount_); } - // block the fees - IFeesManager(addressResolver__.feesManager()).blockFees( - requestMetadata.consumeFrom, - requestMetadata.fees, - newBid, - watcherFees, - requestCount_ - ); - emit BidPlaced(requestCount_, newBid); } @@ -168,6 +159,13 @@ contract AuctionManager is auctionClosed[requestCount_] = true; + // block the fees + IFeesManager(addressResolver__.feesManager()).blockFees( + 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( diff --git a/contracts/protocol/payload-delivery/FeesManager.sol b/contracts/protocol/payload-delivery/FeesManager.sol index 9b8d96ea..39362e92 100644 --- a/contracts/protocol/payload-delivery/FeesManager.sol +++ b/contracts/protocol/payload-delivery/FeesManager.sol @@ -13,6 +13,16 @@ import {NotAuctionManager, InvalidWatcherSignature, NonceUsed} from "../utils/co import {Bid, Fees, CallType, Parallel, WriteFinality, TokenBalance, QueuePayloadParams, IsPlug, PayloadSubmitParams, RequestParams, RequestMetadata} from "../utils/common/Structs.sol"; abstract contract FeesManagerStorage is IFeesManager { + struct RequestFee { + uint256 blockedCredits; + address consumeFrom; + } + + struct UserCredits { + uint256 totalCredits; + uint256 blockedCredits; + } + // slots [0-49] reserved for gap uint256[50] _gap_before; @@ -25,34 +35,35 @@ abstract contract FeesManagerStorage is IFeesManager { // slot 52 bytes32 public sbType; - // slot 53 - /// @notice Master mapping tracking all fee information - /// @dev userAddress => chainSlug => TokenBalance - mapping(address => mapping(uint32 => TokenBalance)) public userFeeBalances; + // user credits + mapping(address => UserCredits) public userCredits; + + // token pool balances + // token address => chainSlug => amount + mapping(address => mapping(uint32 => uint256)) public tokenPoolBalances; + // user approved app gateways // userAddress => appGateway => isWhitelisted mapping(address => mapping(address => bool)) public isAppGatewayWhitelisted; // slot 54 - /// @notice Mapping to track blocked fees for each async id - /// @dev requestCount => Fees - mapping(uint40 => Fees) public requestCountBlockedFees; + /// @notice Mapping to track request credits details for each request count + /// @dev requestCount => RequestFee + mapping(uint40 => RequestFee) public requestCountCredits; // slot 55 - /// @notice Mapping to track fees to be distributed to transmitters - /// @dev transmitter => chainSlug => token => amount - mapping(address => mapping(uint32 => mapping(address => uint256))) public transmitterFees; + /// @notice Mapping to track credits to be distributed to transmitters + /// @dev transmitter => amount + mapping(address => uint256) public transmitterCredits; - // @dev chainSlug => token => amount - mapping(uint32 => mapping(address => uint256)) public watcherPrecompileFees; + // @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; - mapping(uint40 => address) public requestCountConsumeFrom; - // slots [57-106] reserved for gap uint256[50] _gap_after; @@ -152,61 +163,41 @@ contract FeesManager is FeesManagerStorage, Initializable, Ownable, AddressResol } /// @notice Returns available (unblocked) fees for a gateway - /// @param chainSlug_ The chain identifier - /// @param appGateway_ The app gateway address - /// @param token_ The token address + /// @param consumeFrom_ The app gateway address /// @return The available fee amount - function getAvailableFees( - uint32 chainSlug_, - address consumeFrom_, - address token_ - ) public view returns (uint256) { - TokenBalance memory tokenBalance = userFeeBalances[consumeFrom_][chainSlug_][token_]; - if (tokenBalance.deposited == 0 || tokenBalance.deposited <= tokenBalance.blocked) return 0; - return tokenBalance.deposited - tokenBalance.blocked; + function getAvailableFees(address consumeFrom_) public view returns (uint256) { + UserCredits memory userCredit = userCredits[consumeFrom_]; + if (userCredit.totalCredits == 0 || userCredit.totalCredits <= userCredit.blockedCredits) + return 0; + return userCredit.totalCredits - userCredit.blockedCredits; } /// @notice Adds the fees deposited for an app gateway on a chain - /// @param chainSlug_ The chain identifier - /// @param originAppGateway_ The app gateway address - /// @param token_ The token address + /// @param consumeFrom_ The app gateway address /// @param amount_ The amount deposited function incrementFeesDeposited( - uint32 chainSlug_, - address originAppGateway_, - address token_, + address consumeFrom_, uint256 amount_, uint256 signatureNonce_, bytes memory signature_ ) external { - _isWatcherSignatureValid( - abi.encode(chainSlug_, originAppGateway_, token_, amount_), - signatureNonce_, - signature_ - ); + _isWatcherSignatureValid(abi.encode(consumeFrom_, amount_), signatureNonce_, signature_); - address appGateway = _getCoreAppGateway(originAppGateway_); + UserCredits storage userCredit = userCredits[consumeFrom_]; + userCredit.totalCredits += amount_; + tokenPoolBalances[token_][chainSlug_] += amount_; - TokenBalance storage tokenBalance = userFeeBalances[appGateway][chainSlug_][token_]; - tokenBalance.deposited += amount_; - emit FeesDepositedUpdated(chainSlug_, appGateway, token_, amount_); + emit FeesDepositedUpdated(consumeFrom_, amount_); } function isFeesEnough( - address originAppGateway_, + address appGateway_, address consumeFrom_, - Fees memory fees_ + uint256 amount_ ) external view returns (bool) { - address appGateway = _getCoreAppGateway(originAppGateway_); - address consumeFromCore = _getCoreAppGateway(consumeFrom_); - if (appGateway != consumeFromCore && !isAppGatewayWhitelisted[consumeFromCore][appGateway]) - return false; - uint256 availableFees = getAvailableFees( - fees_.feePoolChain, - consumeFrom_, - fees_.feePoolToken - ); - return availableFees >= fees_.amount; + address appGateway = _getCoreAppGateway(consumeFrom_); + if (!isAppGatewayWhitelisted[consumeFrom_][appGateway]) return false; + return getAvailableFees(consumeFrom_) >= amount_; } /// @notice Whitelists multiple app gateways for the caller @@ -218,55 +209,30 @@ contract FeesManager is FeesManagerStorage, Initializable, Ownable, AddressResol } /// @notice Blocks fees for a request count - /// @param originAppGateway_ The app gateway address - /// @param feesGivenByApp_ The fees data struct given by the app gateway + /// @param consumeFrom_ The fees payer address + /// @param totalFees_ The total fees to block /// @param requestCount_ The batch identifier /// @dev Only callable by delivery helper function blockFees( address consumeFrom_, - Fees memory feesGivenByApp_, - Bid memory winningBid_, - uint256 watcherFees_, + uint256 transmitterFees_, uint40 requestCount_ ) external { if (msg.sender != deliveryHelper__().getRequestMetadata(requestCount_).auctionManager) revert NotAuctionManager(); // Block fees - uint256 availableFees = getAvailableFees( - feesGivenByApp_.feePoolChain, - consumeFrom_, - feesGivenByApp_.feePoolToken - ); + if (getAvailableFees(consumeFrom_) < transmitterFees_) revert InsufficientFeesAvailable(); - if (requestCountBlockedFees[requestCount_].amount > 0) - availableFees += requestCountBlockedFees[requestCount_].amount; + UserCredits storage userCredit = userCredits[consumeFrom_]; + userCredit.blockedCredits += transmitterFees_; - uint256 feesNeeded = winningBid_.fee + watcherFees_; - if (availableFees < feesNeeded) revert InsufficientFeesAvailable(); - - TokenBalance storage tokenBalance = userFeeBalances[consumeFrom_][ - feesGivenByApp_.feePoolChain - ][feesGivenByApp_.feePoolToken]; - - tokenBalance.blocked = - tokenBalance.blocked + - feesNeeded - - requestCountBlockedFees[requestCount_].amount; - - requestCountBlockedFees[requestCount_] = Fees({ - feePoolChain: feesGivenByApp_.feePoolChain, - feePoolToken: feesGivenByApp_.feePoolToken, - amount: feesNeeded + requestCountCredits[requestCount_] = RequestFee({ + blockedCredits: transmitterFees_, + consumeFrom: consumeFrom_ }); - requestCountConsumeFrom[requestCount_] = consumeFrom_; - emit FeesBlocked( - requestCount_, - feesGivenByApp_.feePoolChain, - feesGivenByApp_.feePoolToken, - feesNeeded - ); + emit FeesBlocked(requestCount_, transmitterFees_); } /// @notice Unblocks fees after successful execution and assigns them to the transmitter @@ -276,45 +242,45 @@ contract FeesManager is FeesManagerStorage, Initializable, Ownable, AddressResol uint40 requestCount_, address transmitter_ ) external override onlyDeliveryHelper { - Fees memory fees = requestCountBlockedFees[requestCount_]; - if (fees.amount == 0) return; - - RequestMetadata memory requestMetadata = deliveryHelper__().getRequestMetadata( - requestCount_ - ); - - TokenBalance storage tokenBalance = userFeeBalances[consumeFrom][fees.feePoolChain][ - fees.feePoolToken - ]; + RequestFee memory requestFee = requestCountCredits[requestCount_]; + if (requestFee.blockedCredits == 0) return; - uint256 transmitterBid = requestMetadata.winningBid.fee; - uint256 remainingFees = fees.amount - transmitterBid; + uint256 fees = deliveryHelper__().getRequestMetadata(requestCount_).winningBid.fee; // Unblock fees from deposit - tokenBalance.blocked -= fees.amount; - tokenBalance.deposited -= transmitterBid; - tokenBalance.deposited -= remainingFees; + _useBlockedUserCredits(requestFee.consumeFrom, fees, fees); // Assign fees to transmitter - transmitterFees[transmitter_][fees.feePoolChain][fees.feePoolToken] += transmitterBid; + transmitterFees[transmitter_] += fees; // Clean up storage - delete requestCountBlockedFees[requestCount_]; - emit FeesUnblockedAndAssigned(requestCount_, transmitter_, fees.amount); + delete requestCountCredits[requestCount_]; + emit FeesUnblockedAndAssigned(requestCount_, transmitter_, fees); + } + + function _useBlockedUserCredits( + address consumeFrom_, + uint256 toBlock_, + uint256 toConsume_ + ) internal { + UserCredits storage userCredit = userCredits[consumeFrom_]; + userCredit.blockedCredits -= toBlock_; + userCredit.totalCredits -= toConsume_; } function assignWatcherPrecompileFees( - uint32 chainSlug_, - address token_, uint256 amount_, uint40 requestCount_ ) external onlyWatcherPrecompile { - Fees storage fees = requestCountBlockedFees[requestCount_]; - if (fees.amount == 0) revert NoFeesBlocked(); + RequestFee memory requestFee = requestCountCredits[requestCount_]; + if (requestFee.blockedCredits == 0) revert NoFeesBlocked(); + + // deduct the fees from the user + _useBlockedUserCredits(requestFee.consumeFrom, 0, amount_); - fees.amount -= amount_; - watcherPrecompileFees[chainSlug_][token_] += amount_; - emit WatcherPrecompileFeesAssigned(chainSlug_, token_, amount_, requestCount_); + // add the fees to the watcher precompile + watcherPrecompileCredits += amount_; + emit WatcherPrecompileFeesAssigned(requestCount_, amount_); } function unblockFees(uint40 requestCount_) external { @@ -327,19 +293,15 @@ contract FeesManager is FeesManagerStorage, Initializable, Ownable, AddressResol msg.sender != address(deliveryHelper__()) ) revert InvalidCaller(); - Fees memory fees = requestCountBlockedFees[requestCount_]; - if (fees.amount == 0) return; - - TokenBalance storage tokenBalance = userFeeBalances[requestMetadata.appGateway][ - fees.feePoolChain - ][fees.feePoolToken]; + RequestFee memory requestFee = requestCountCredits[requestCount_]; + if (requestFee.blockedCredits == 0) return; // Unblock fees from deposit - tokenBalance.blocked -= fees.amount; - tokenBalance.deposited += fees.amount; + UserCredits storage userCredit = userCredits[requestFee.consumeFrom]; + userCredit.blockedCredits -= requestFee.blockedCredits; - delete requestCountBlockedFees[requestCount_]; - emit FeesUnblocked(requestCount_, requestMetadata.appGateway); + delete requestCountCredits[requestCount_]; + emit FeesUnblocked(requestCount_, requestFee.consumeFrom); } /// @notice Withdraws fees to a specified receiver @@ -349,21 +311,23 @@ contract FeesManager is FeesManagerStorage, Initializable, Ownable, AddressResol function withdrawTransmitterFees( uint32 chainSlug_, address token_, - address receiver_ + address receiver_, + uint256 amount_ ) external returns (uint40 requestCount) { address transmitter = msg.sender; // Get total fees for the transmitter in given chain and token - uint256 totalFees = transmitterFees[transmitter][chainSlug_][token_]; - if (totalFees == 0) revert NoFeesForTransmitter(); + uint256 totalFees = transmitterFees[transmitter]; + if (totalFees >= amount_) revert InsufficientFeesAvailable(); // Clean up storage - transmitterFees[transmitter][chainSlug_][token_] = 0; + transmitterFees[transmitter] -= amount_; + tokenPoolBalances[token_][chainSlug_] -= amount_; // Create fee distribution payload bytes32 feesId = _encodeFeesId(feesCounter++); bytes memory payload = abi.encodeCall( IFeesPlug.distributeFee, - (token_, totalFees, receiver_, feesId) + (token_, amount_, receiver_, feesId) ); // finalize for plug contract @@ -388,11 +352,10 @@ contract FeesManager is FeesManagerStorage, Initializable, Ownable, AddressResol address source = _getCoreAppGateway(originAppGatewayOrUser_); // Check if amount is available in fees plug - uint256 availableAmount = getAvailableFees(chainSlug_, source, token_); + uint256 availableAmount = getAvailableFees(source); if (availableAmount < amount_) revert InsufficientFeesAvailable(); - TokenBalance storage tokenBalance = userFeeBalances[source][chainSlug_][token_]; - tokenBalance.deposited -= amount_; + tokenPoolBalances[token_][chainSlug_] -= amount_; // Add it to the queue and submit request _queue(chainSlug_, abi.encodeCall(IFeesPlug.withdrawFees, (token_, amount_, receiver_))); diff --git a/contracts/protocol/payload-delivery/FeesPlug.sol b/contracts/protocol/payload-delivery/FeesPlug.sol index 773ceb7d..f21826ab 100644 --- a/contracts/protocol/payload-delivery/FeesPlug.sol +++ b/contracts/protocol/payload-delivery/FeesPlug.sol @@ -50,9 +50,6 @@ contract FeesPlug is IFeesPlug, PlugBase, AccessControl { constructor(address socket_, address owner_) { _setSocket(socket_); _initializeOwner(owner_); - - // ETH is whitelisted by default - whitelistedTokens[ETH_ADDRESS] = true; } /// @notice Distributes fees to the transmitter @@ -88,11 +85,7 @@ contract FeesPlug is IFeesPlug, PlugBase, AccessControl { emit FeesWithdrawn(token_, amount_, receiver_); } - function depositToFee( - address token_, - uint256 amount_, - address receiver_ - ) external payable override { + function depositToFee(address token_, uint256 amount_, address receiver_) external override { _deposit(token_, receiver_, amount_, 0); } @@ -100,17 +93,13 @@ contract FeesPlug is IFeesPlug, PlugBase, AccessControl { address token_, uint256 amount_, address receiver_ - ) external payable override { + ) external override { uint256 nativeAmount_ = amount_ / 10; uint256 feeAmount_ = amount_ - nativeAmount_; _deposit(token_, receiver_, feeAmount_, nativeAmount_); } - function depositToNative( - address token_, - uint256 amount_, - address receiver_ - ) external payable override { + function depositToNative(address token_, uint256 amount_, address receiver_) external override { _deposit(token_, receiver_, 0, amount_); } @@ -127,19 +116,9 @@ contract FeesPlug is IFeesPlug, PlugBase, AccessControl { ) internal override { uint256 totalAmount_ = feeAmount_ + nativeAmount_; if (!whitelistedTokens[token_]) revert TokenNotWhitelisted(token_); - - if (token_ == ETH_ADDRESS) { - if (msg.value != totalAmount_) revert InvalidDepositAmount(); - } else { - if (token_.code.length == 0) revert InvalidTokenAddress(); - } + if (token_.code.length == 0) revert InvalidTokenAddress(); balanceOf[token_] += totalAmount_; - - if (token_ != ETH_ADDRESS) { - SafeTransferLib.safeTransferFrom(token_, msg.sender, address(this), totalAmount_); - } - emit FeesDeposited(receiver_, token_, feeAmount_, nativeAmount_); } @@ -148,11 +127,7 @@ contract FeesPlug is IFeesPlug, PlugBase, AccessControl { /// @param amount_ The amount /// @param receiver_ The receiver address function _transferTokens(address token_, uint256 amount_, address receiver_) internal { - if (token_ == ETH_ADDRESS) { - SafeTransferLib.forceSafeTransferETH(receiver_, amount_); - } else { - SafeTransferLib.safeTransfer(token_, receiver_, amount_); - } + SafeTransferLib.safeTransfer(token_, receiver_, amount_); } function connectSocket( @@ -173,7 +148,6 @@ contract FeesPlug is IFeesPlug, PlugBase, AccessControl { /// @notice Removes a token from the whitelist /// @param token_ The token address to remove function removeTokenFromWhitelist(address token_) external onlyOwner { - if (token_ == ETH_ADDRESS) revert(); // Cannot remove ETH from whitelist whitelistedTokens[token_] = false; emit TokenRemovedFromWhitelist(token_); } @@ -192,6 +166,4 @@ contract FeesPlug is IFeesPlug, PlugBase, AccessControl { ) external onlyRole(RESCUE_ROLE) { RescueFundsLib._rescueFunds(token_, rescueTo_, amount_); } - - receive() external payable {} } From e38a3952cc34e1dd0615e5e4385ae288850d8a6b Mon Sep 17 00:00:00 2001 From: Ameesha Agrawal Date: Fri, 18 Apr 2025 17:59:49 +0530 Subject: [PATCH 08/21] fix: remove old structs --- .../protocol/payload-delivery/FeesManager.sol | 11 +---------- contracts/protocol/utils/common/Structs.sol | 18 ++++++++---------- 2 files changed, 9 insertions(+), 20 deletions(-) diff --git a/contracts/protocol/payload-delivery/FeesManager.sol b/contracts/protocol/payload-delivery/FeesManager.sol index 39362e92..c47232fe 100644 --- a/contracts/protocol/payload-delivery/FeesManager.sol +++ b/contracts/protocol/payload-delivery/FeesManager.sol @@ -10,18 +10,9 @@ import {IFeesManager} from "../../interfaces/IFeesManager.sol"; import {AddressResolverUtil} from "../utils/AddressResolverUtil.sol"; import {NotAuctionManager, InvalidWatcherSignature, NonceUsed} from "../utils/common/Errors.sol"; -import {Bid, Fees, CallType, Parallel, WriteFinality, TokenBalance, QueuePayloadParams, IsPlug, PayloadSubmitParams, RequestParams, RequestMetadata} from "../utils/common/Structs.sol"; +import {Bid, CallType, Parallel, WriteFinality, QueuePayloadParams, IsPlug, PayloadSubmitParams, RequestMetadata, RequestFee, UserCredits} from "../utils/common/Structs.sol"; abstract contract FeesManagerStorage is IFeesManager { - struct RequestFee { - uint256 blockedCredits; - address consumeFrom; - } - - struct UserCredits { - uint256 totalCredits; - uint256 blockedCredits; - } // slots [0-49] reserved for gap uint256[50] _gap_before; diff --git a/contracts/protocol/utils/common/Structs.sol b/contracts/protocol/utils/common/Structs.sol index 32170560..c4f00ca1 100644 --- a/contracts/protocol/utils/common/Structs.sol +++ b/contracts/protocol/utils/common/Structs.sol @@ -119,10 +119,14 @@ struct OverrideParams { } // FM: -struct Fees { - uint32 feePoolChain; - address feePoolToken; - uint256 amount; +struct RequestFee { + uint256 blockedCredits; + address consumeFrom; +} + +struct UserCredits { + uint256 totalCredits; + uint256 blockedCredits; } // digest: @@ -237,9 +241,3 @@ struct ExecuteParams { bytes32 prevDigestsHash; // should be id? hash of hashes address switchboard; } - -/// @notice Struct containing fee amounts and status -struct TokenBalance { - uint256 deposited; // Amount deposited - uint256 blocked; // Amount blocked -} From 6a399f0e4726ccaf693a54dc53d1cb8aa3a3676a Mon Sep 17 00:00:00 2001 From: Ameesha Agrawal Date: Sat, 19 Apr 2025 05:17:50 +0530 Subject: [PATCH 09/21] fix: wip build fix --- contracts/base/AppGatewayBase.sol | 12 +++++------ contracts/interfaces/IAppGateway.sol | 2 +- contracts/interfaces/IAuctionManager.sol | 2 +- contracts/interfaces/IFeesManager.sol | 6 +++--- contracts/interfaces/IMiddleware.sol | 8 +++---- .../protocol/payload-delivery/FeesManager.sol | 8 +++---- .../protocol/payload-delivery/FeesPlug.sol | 2 +- .../app-gateway/DeliveryUtils.sol | 3 ++- .../app-gateway/FeesHelpers.sol | 6 +++--- .../app-gateway/RequestQueue.sol | 19 ++++++++++------- contracts/protocol/utils/FeesPlugin.sol | 21 ------------------- contracts/protocol/utils/common/Structs.sol | 2 +- .../core/WatcherPrecompile.sol | 2 +- script/counter/DeployEVMxCounterApp.s.sol | 7 +------ script/counter/SetFees.s.sol | 7 +------ script/helpers/AppGatewayFeeBalance.s.sol | 1 - script/helpers/PayFeesInArbitrumETH.s.sol | 1 - .../counter/CounterAppGateway.sol | 4 ++-- .../super-token/SuperTokenAppGateway.sol | 2 +- 19 files changed, 43 insertions(+), 72 deletions(-) delete mode 100644 contracts/protocol/utils/FeesPlugin.sol diff --git a/contracts/base/AppGatewayBase.sol b/contracts/base/AppGatewayBase.sol index 82422aae..abd4996d 100644 --- a/contracts/base/AppGatewayBase.sol +++ b/contracts/base/AppGatewayBase.sol @@ -7,19 +7,19 @@ import "../interfaces/IForwarder.sol"; import "../interfaces/IMiddleware.sol"; import "../interfaces/IPromise.sol"; -import {FeesPlugin} from "../protocol/utils/FeesPlugin.sol"; import {InvalidPromise, FeesNotSet, AsyncModifierNotUsed} from "../protocol/utils/common/Errors.sol"; import {FAST} from "../protocol/utils/common/Constants.sol"; /// @title AppGatewayBase /// @notice Abstract contract for the app gateway /// @dev This contract contains helpers for contract deployment, overrides, hooks and request processing -abstract contract AppGatewayBase is AddressResolverUtil, IAppGateway, FeesPlugin { +abstract contract AppGatewayBase is AddressResolverUtil, IAppGateway { OverrideParams public overrideParams; bool public isAsyncModifierSet; address public auctionManager; bytes32 public sbType; bytes public onCompleteData; + uint256 public fees; mapping(address => bool) public isValidPromise; mapping(bytes32 => mapping(uint32 => address)) public override forwarderAddresses; @@ -29,10 +29,10 @@ abstract contract AppGatewayBase is AddressResolverUtil, IAppGateway, FeesPlugin modifier async(bytes memory feesApprovalData_) { _preAsync(); _; - _postAsync(); + _postAsync(feesApprovalData_); } - function _postAsync() internal { + function _postAsync(bytes memory feesApprovalData_) internal { isAsyncModifierSet = false; // todo: cache the feesApprovalData for next async in same request @@ -204,7 +204,7 @@ abstract contract AppGatewayBase is AddressResolverUtil, IAppGateway, FeesPlugin Read isReadCall_, Parallel isParallelCall_, uint256 gasLimit_, - Fees memory fees_ + uint256 fees_ ) internal { overrideParams.isReadCall = isReadCall_; overrideParams.isParallelCall = isParallelCall_; @@ -285,7 +285,7 @@ abstract contract AppGatewayBase is AddressResolverUtil, IAppGateway, FeesPlugin /// @notice Sets fees overrides /// @param fees_ The fees configuration - function _setOverrides(Fees memory fees_) internal { + function _setOverrides(uint256 fees_) internal { fees = fees_; } diff --git a/contracts/interfaces/IAppGateway.sol b/contracts/interfaces/IAppGateway.sol index 8997710b..c8e9fd73 100644 --- a/contracts/interfaces/IAppGateway.sol +++ b/contracts/interfaces/IAppGateway.sol @@ -1,7 +1,7 @@ // SPDX-License-Identifier: GPL-3.0-only pragma solidity ^0.8.21; -import {Fees, Read, Parallel, QueuePayloadParams, OverrideParams, CallType, WriteFinality, PayloadParams} from "../protocol/utils/common/Structs.sol"; +import {Read, Parallel, QueuePayloadParams, OverrideParams, CallType, WriteFinality, PayloadParams} from "../protocol/utils/common/Structs.sol"; /// @title IAppGateway /// @notice Interface for the app gateway diff --git a/contracts/interfaces/IAuctionManager.sol b/contracts/interfaces/IAuctionManager.sol index 45344b7a..a67f0200 100644 --- a/contracts/interfaces/IAuctionManager.sol +++ b/contracts/interfaces/IAuctionManager.sol @@ -1,7 +1,7 @@ // SPDX-License-Identifier: GPL-3.0-only pragma solidity ^0.8.21; -import {Bid, Fees, RequestMetadata, RequestParams} from "../protocol/utils/common/Structs.sol"; +import {Bid, RequestMetadata, RequestParams} from "../protocol/utils/common/Structs.sol"; interface IAuctionManager { /// @notice Bids for an auction diff --git a/contracts/interfaces/IFeesManager.sol b/contracts/interfaces/IFeesManager.sol index 80953ba7..a6c90520 100644 --- a/contracts/interfaces/IFeesManager.sol +++ b/contracts/interfaces/IFeesManager.sol @@ -1,12 +1,12 @@ // SPDX-License-Identifier: GPL-3.0-only pragma solidity ^0.8.21; -import {Fees, Bid, QueuePayloadParams} from "../protocol/utils/common/Structs.sol"; +import {Bid, QueuePayloadParams} from "../protocol/utils/common/Structs.sol"; interface IFeesManager { function blockFees( address appGateway_, - Fees memory fees_, + uint256 fees_, Bid memory winningBid_, uint40 requestCount_ ) external; @@ -16,7 +16,7 @@ interface IFeesManager { function isFeesEnough( address appGateway_, address consumeFrom_, - Fees memory fees_ + uint256 fees_ ) external view returns (bool); function unblockAndAssignFees( diff --git a/contracts/interfaces/IMiddleware.sol b/contracts/interfaces/IMiddleware.sol index ce949529..46a2410d 100644 --- a/contracts/interfaces/IMiddleware.sol +++ b/contracts/interfaces/IMiddleware.sol @@ -1,6 +1,6 @@ // SPDX-License-Identifier: GPL-3.0-only pragma solidity ^0.8.21; -import {PayloadSubmitParams, QueuePayloadParams, Bid, Fees, WriteFinality, CallType, Parallel, IsPlug, RequestMetadata, } from "../protocol/utils/common/Structs.sol"; +import {PayloadSubmitParams, QueuePayloadParams, Bid, WriteFinality, CallType, Parallel, IsPlug, RequestMetadata, RequestFee} from "../protocol/utils/common/Structs.sol"; /// @title IMiddleware /// @notice Interface for the Middleware contract @@ -29,7 +29,7 @@ interface IMiddleware { /// @param onCompleteData_ The data to be passed to the onComplete callback /// @return requestCount The request id function batch( - Fees memory fees_, + uint256 fees_, address auctionManager_, bytes memory feesApprovalData_, bytes memory onCompleteData_ @@ -48,7 +48,7 @@ interface IMiddleware { uint256 amount_, address receiver_, address auctionManager_, - Fees memory fees_ + uint256 fees_ ) external returns (uint40); /// @notice Cancels a request @@ -66,7 +66,7 @@ interface IMiddleware { function startRequestProcessing(uint40 requestCount_, Bid memory winningBid_) external; /// @notice Returns the fees for a request - function getFees(uint40 requestCount_) external view returns (Fees memory); + 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 diff --git a/contracts/protocol/payload-delivery/FeesManager.sol b/contracts/protocol/payload-delivery/FeesManager.sol index c47232fe..0e1bc855 100644 --- a/contracts/protocol/payload-delivery/FeesManager.sol +++ b/contracts/protocol/payload-delivery/FeesManager.sol @@ -13,7 +13,6 @@ import {NotAuctionManager, InvalidWatcherSignature, NonceUsed} from "../utils/co import {Bid, CallType, Parallel, WriteFinality, QueuePayloadParams, IsPlug, PayloadSubmitParams, RequestMetadata, RequestFee, UserCredits} from "../utils/common/Structs.sol"; abstract contract FeesManagerStorage is IFeesManager { - // slots [0-49] reserved for gap uint256[50] _gap_before; @@ -201,7 +200,7 @@ contract FeesManager is FeesManagerStorage, Initializable, Ownable, AddressResol /// @notice Blocks fees for a request count /// @param consumeFrom_ The fees payer address - /// @param totalFees_ The total fees to block + /// @param transmitterFees_ The total fees to block /// @param requestCount_ The batch identifier /// @dev Only callable by delivery helper function blockFees( @@ -327,7 +326,7 @@ contract FeesManager is FeesManagerStorage, Initializable, Ownable, AddressResol /// @notice Withdraws funds to a specified receiver /// @dev This function is used to withdraw fees from the fees plug - /// @param originAppGateway_ The address of the app gateway + /// @param originAppGatewayOrUser_ The address of the app gateway /// @param chainSlug_ The chain identifier /// @param token_ The address of the token /// @param amount_ The amount of tokens to withdraw @@ -378,9 +377,10 @@ contract FeesManager is FeesManagerStorage, Initializable, Ownable, AddressResol appGateway: address(this), auctionManager: address(0), feesApprovalData: bytes(""), - fees: Fees({token: token_, amount: amount_}), + fees: amount_, winningBid: Bid({transmitter: transmitter_, fee: 0, extraData: new bytes(0)}) }); + requestCount = watcherPrecompile__().submitRequest( payloadSubmitParamsArray, requestMetadata diff --git a/contracts/protocol/payload-delivery/FeesPlug.sol b/contracts/protocol/payload-delivery/FeesPlug.sol index f21826ab..2f5bc16f 100644 --- a/contracts/protocol/payload-delivery/FeesPlug.sol +++ b/contracts/protocol/payload-delivery/FeesPlug.sol @@ -107,7 +107,7 @@ contract FeesPlug is IFeesPlug, PlugBase, AccessControl { /// @param token_ The token address /// @param feeAmount_ The amount of fees /// @param nativeAmount_ The amount of native tokens - /// @param appGateway_ The app gateway address + /// @param receiver_ The receiver address function _deposit( address token_, address receiver_, diff --git a/contracts/protocol/payload-delivery/app-gateway/DeliveryUtils.sol b/contracts/protocol/payload-delivery/app-gateway/DeliveryUtils.sol index 8fa29c14..38fcecc9 100644 --- a/contracts/protocol/payload-delivery/app-gateway/DeliveryUtils.sol +++ b/contracts/protocol/payload-delivery/app-gateway/DeliveryUtils.sol @@ -39,10 +39,11 @@ abstract contract DeliveryUtils is uint40 indexed requestCount, address indexed appGateway, PayloadSubmitParams[] payloadSubmitParams, - Fees fees, + uint256 fees, address auctionManager, bool onlyReadRequests ); + /// @notice Emitted when fees are increased event FeesIncreased( address indexed appGateway, diff --git a/contracts/protocol/payload-delivery/app-gateway/FeesHelpers.sol b/contracts/protocol/payload-delivery/app-gateway/FeesHelpers.sol index 1c59ac64..ccda6afe 100644 --- a/contracts/protocol/payload-delivery/app-gateway/FeesHelpers.sol +++ b/contracts/protocol/payload-delivery/app-gateway/FeesHelpers.sol @@ -37,7 +37,7 @@ abstract contract FeesHelpers is RequestQueue { uint256 amount_, address receiver_, address auctionManager_, - Fees memory fees_ + uint256 fees_ ) external returns (uint40) { IFeesManager(addressResolver__.feesManager()).withdrawFees( msg.sender, @@ -53,7 +53,7 @@ abstract contract FeesHelpers is RequestQueue { /// @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 (Fees memory) { - return requests[requestCount_].fees; + function getFees(uint40 requestCount_) external view returns (uint256) { + return requests[requestCount_].fees.amount; } } diff --git a/contracts/protocol/payload-delivery/app-gateway/RequestQueue.sol b/contracts/protocol/payload-delivery/app-gateway/RequestQueue.sol index be9cabf7..12d9d458 100644 --- a/contracts/protocol/payload-delivery/app-gateway/RequestQueue.sol +++ b/contracts/protocol/payload-delivery/app-gateway/RequestQueue.sol @@ -24,19 +24,19 @@ abstract contract RequestQueue is DeliveryUtils { /// @param auctionManager_ The auction manager address /// @return requestCount The ID of the batch function batch( - Fees memory fees_, + uint256 fees_, address auctionManager_, bytes memory feesApprovalData_, bytes memory onCompleteData_ ) external returns (uint40 requestCount) { address appGateway = _getCoreAppGateway(msg.sender); - return _batch(appGateway, auctionManager_, feesApprovalData_, onCompleteData_); + return _batch(appGateway, auctionManager_, fees_, feesApprovalData_, onCompleteData_); } function _checkBatch( address appGateway_, bytes memory feesApprovalData_, - Fees memory fees_ + uint256 fees_ ) internal view { if (queuePayloadParams.length > REQUEST_PAYLOAD_COUNT_LIMIT) revert RequestPayloadCountLimitExceeded(); @@ -64,7 +64,7 @@ abstract contract RequestQueue is DeliveryUtils { function _batch( address appGateway_, address auctionManager_, - Fees memory fees_, + uint256 fees_, bytes memory feesApprovalData_, bytes memory onCompleteData_ ) internal returns (uint40 requestCount) { @@ -89,7 +89,10 @@ abstract contract RequestQueue is DeliveryUtils { }); // process and submit the queue of payloads to watcher precompile - requestCount = watcherPrecompile__().submitRequest(payloadSubmitParamsArray, requestMetadata); + requestCount = watcherPrecompile__().submitRequest( + payloadSubmitParamsArray, + requestMetadata + ); requests[requestCount] = requestMetadata; // send query directly if request contains only reads @@ -98,14 +101,14 @@ abstract contract RequestQueue is DeliveryUtils { watcherPrecompile__().startProcessingRequest(requestCount, address(0)); // to save extra calls from transmitter - uint256 maxTransmitterFees = fees_.amount - - watcherPrecompile__().getTotalFeesRequired(fees_.token, requestCount); + uint256 maxTransmitterFees = fees_ - + watcherPrecompile__().getTotalFeesRequired(requestCount); emit PayloadSubmitted( requestCount, appGateway_, payloadSubmitParamsArray, - Fees({token: fees_.token, amount: fees_.amount - maxTransmitterFees}), + fees_ - maxTransmitterFees, auctionManager_, onlyReadRequests ); diff --git a/contracts/protocol/utils/FeesPlugin.sol b/contracts/protocol/utils/FeesPlugin.sol deleted file mode 100644 index c78bc932..00000000 --- a/contracts/protocol/utils/FeesPlugin.sol +++ /dev/null @@ -1,21 +0,0 @@ -// SPDX-License-Identifier: GPL-3.0-only -pragma solidity ^0.8.21; - -import {Fees} from "../utils/common/Structs.sol"; - -/// @title FeesPlugin -/// @notice Abstract contract for managing fee configurations -/// @dev Provides base functionality for fee management in the system -abstract contract FeesPlugin { - /// @notice Storage for the current fee configuration - /// @dev Contains fee parameters like chain slug, token address, and amount - Fees public fees; - - /// @notice Retrieves the current fee configuration - /// @return Current fee configuration struct - /// @dev Public view function accessible to any caller - /// @dev Used by external contracts to verify fee parameters - function getFees() public view returns (Fees memory) { - return fees; - } -} diff --git a/contracts/protocol/utils/common/Structs.sol b/contracts/protocol/utils/common/Structs.sol index c4f00ca1..36c73bb0 100644 --- a/contracts/protocol/utils/common/Structs.sol +++ b/contracts/protocol/utils/common/Structs.sol @@ -219,7 +219,7 @@ struct RequestParams { struct RequestMetadata { address appGateway; address auctionManager; - Fees fees; + uint256 fees; Bid winningBid; bytes onCompleteData; bool onlyReadRequests; diff --git a/contracts/protocol/watcherPrecompile/core/WatcherPrecompile.sol b/contracts/protocol/watcherPrecompile/core/WatcherPrecompile.sol index 78f6f102..40f1b13d 100644 --- a/contracts/protocol/watcherPrecompile/core/WatcherPrecompile.sol +++ b/contracts/protocol/watcherPrecompile/core/WatcherPrecompile.sol @@ -109,7 +109,7 @@ contract WatcherPrecompile is RequestHandler { /// @return The digest hash of the finalized payload /// @dev This function finalizes a payload request and requests the watcher to release the proofs function finalize( - PayloadParams memory params_,` + PayloadParams memory params_, address transmitter_ ) external returns (bytes32) { return _finalize(params_, transmitter_); diff --git a/script/counter/DeployEVMxCounterApp.s.sol b/script/counter/DeployEVMxCounterApp.s.sol index 9e0cbee8..85755639 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 {Fees} from "../../contracts/protocol/utils/common/Structs.sol"; import {ETH_ADDRESS} from "../../contracts/protocol/utils/common/Constants.sol"; // source .env && forge script script/counter/deployEVMxCounterApp.s.sol --broadcast --skip-simulation --legacy --gas-price 0 @@ -18,11 +17,7 @@ contract CounterDeploy is Script { vm.startBroadcast(deployerPrivateKey); // Setting fee payment on Arbitrum Sepolia - Fees memory fees = Fees({ - feePoolChain: 421614, - feePoolToken: ETH_ADDRESS, - amount: 0.00001 ether - }); + uint256 fees = 0.00001 ether; CounterAppGateway gateway = new CounterAppGateway(addressResolver, fees); diff --git a/script/counter/SetFees.s.sol b/script/counter/SetFees.s.sol index c97a2b0b..e48d9a73 100644 --- a/script/counter/SetFees.s.sol +++ b/script/counter/SetFees.s.sol @@ -5,7 +5,6 @@ import {Script} from "forge-std/Script.sol"; import {console} from "forge-std/console.sol"; import {ETH_ADDRESS} from "../../contracts/protocol/utils/common/Constants.sol"; import {CounterAppGateway} from "../../test/apps/app-gateways/counter/CounterAppGateway.sol"; -import {Fees} from "../../contracts/protocol/utils/common/Structs.sol"; // source .env && forge script script/counter/DeployCounterOnchain.s.sol --broadcast --skip-simulation --legacy --gas-price 0 contract CounterSetFees is Script { @@ -22,11 +21,7 @@ contract CounterSetFees is Script { console.log("Setting fees..."); // Setting fee payment on Arbitrum Sepolia - Fees memory fees = Fees({ - feePoolChain: 421614, - feePoolToken: ETH_ADDRESS, - amount: 0.00001 ether - }); + uint256 fees = 0.00001 ether; appGateway.setFees(fees); } } diff --git a/script/helpers/AppGatewayFeeBalance.s.sol b/script/helpers/AppGatewayFeeBalance.s.sol index 9bf7c2c9..a1faade9 100644 --- a/script/helpers/AppGatewayFeeBalance.s.sol +++ b/script/helpers/AppGatewayFeeBalance.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 {FeesManager} from "../../contracts/protocol/payload-delivery/FeesManager.sol"; -import {Fees} from "../../contracts/protocol/utils/common/Structs.sol"; import {ETH_ADDRESS} from "../../contracts/protocol/utils/common/Constants.sol"; contract CheckDepositedFees is Script { diff --git a/script/helpers/PayFeesInArbitrumETH.s.sol b/script/helpers/PayFeesInArbitrumETH.s.sol index edb8b2fd..6fbd7164 100644 --- a/script/helpers/PayFeesInArbitrumETH.s.sol +++ b/script/helpers/PayFeesInArbitrumETH.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 {FeesPlug} from "../../contracts/protocol/payload-delivery/FeesPlug.sol"; -import {Fees} from "../../contracts/protocol/utils/common/Structs.sol"; import {ETH_ADDRESS} from "../../contracts/protocol/utils/common/Constants.sol"; // source .env && forge script script/helpers/PayFeesInArbitrumETH.s.sol --broadcast --skip-simulation diff --git a/test/apps/app-gateways/counter/CounterAppGateway.sol b/test/apps/app-gateways/counter/CounterAppGateway.sol index a27b2db1..a7b9561b 100644 --- a/test/apps/app-gateways/counter/CounterAppGateway.sol +++ b/test/apps/app-gateways/counter/CounterAppGateway.sol @@ -17,7 +17,7 @@ contract CounterAppGateway is AppGatewayBase, Ownable { uint256 public optCounter; event TimeoutResolved(uint256 creationTimestamp, uint256 executionTimestamp); - constructor(address addressResolver_, Fees memory fees_) AppGatewayBase(addressResolver_) { + constructor(address addressResolver_, uint256 fees_) AppGatewayBase(addressResolver_) { creationCodeWithArgs[counter] = abi.encodePacked(type(Counter).creationCode); creationCodeWithArgs[counter1] = abi.encodePacked(type(Counter).creationCode); _setOverrides(fees_); @@ -120,7 +120,7 @@ contract CounterAppGateway is AppGatewayBase, Ownable { } // UTILS - function setFees(Fees memory fees_) public { + function setFees(uint256 fees_) public { fees = fees_; } diff --git a/test/apps/app-gateways/super-token/SuperTokenAppGateway.sol b/test/apps/app-gateways/super-token/SuperTokenAppGateway.sol index 98488499..58846bc6 100644 --- a/test/apps/app-gateways/super-token/SuperTokenAppGateway.sol +++ b/test/apps/app-gateways/super-token/SuperTokenAppGateway.sol @@ -29,7 +29,7 @@ contract SuperTokenAppGateway is AppGatewayBase, Ownable { constructor( address addressResolver_, address owner_, - Fees memory fees_, + uint256 fees_, ConstructorParams memory params_ ) AppGatewayBase(addressResolver_) { creationCodeWithArgs[superToken] = abi.encodePacked( From fceb0df2fcb223e6af0cbbf4acd1fc2e93476778 Mon Sep 17 00:00:00 2001 From: Akash Date: Mon, 21 Apr 2025 11:42:29 +0530 Subject: [PATCH 10/21] fix: build, wip --- FunctionSignatures.md | 2 +- contracts/base/AppGatewayBase.sol | 26 +- contracts/interfaces/IFeesManager.sol | 29 +- contracts/interfaces/IFeesPlug.sol | 6 +- contracts/interfaces/IMiddleware.sol | 2 +- contracts/interfaces/IWatcherPrecompile.sol | 5 +- .../interfaces/IWatcherPrecompileLimits.sol | 20 +- .../payload-delivery/AuctionManager.sol | 35 +-- .../protocol/payload-delivery/FeesManager.sol | 249 +++++++++++------- .../protocol/payload-delivery/FeesPlug.sol | 2 +- .../app-gateway/DeliveryHelper.sol | 8 +- .../app-gateway/FeesHelpers.sol | 6 +- .../app-gateway/RequestQueue.sol | 36 +-- .../protocol/utils/AddressResolverUtil.sol | 1 + contracts/protocol/utils/common/Constants.sol | 1 + contracts/protocol/utils/common/Structs.sol | 16 +- .../WatcherPrecompileLimits.sol | 84 +++--- .../watcherPrecompile/core/RequestHandler.sol | 23 +- .../core/WatcherPrecompile.sol | 7 +- .../core/WatcherPrecompileCore.sol | 38 +-- test/DeliveryHelper.t.sol | 20 +- test/FeesTest.t.sol | 7 +- test/Inbox.t.sol | 2 +- test/SetupTest.t.sol | 14 +- test/apps/Counter.t.sol | 7 +- test/apps/ParallelCounter.t.sol | 8 +- test/apps/SuperToken.t.sol | 7 +- .../counter/CounterAppGateway.sol | 23 +- .../super-token/SuperTokenAppGateway.sol | 2 +- 29 files changed, 374 insertions(+), 312 deletions(-) diff --git a/FunctionSignatures.md b/FunctionSignatures.md index 3eb60034..557f95f5 100644 --- a/FunctionSignatures.md +++ b/FunctionSignatures.md @@ -169,7 +169,7 @@ | `requestOwnershipHandover` | `0x25692962` | | `sbType` | `0x745de344` | | `transferOwnership` | `0xf2fde38b` | -| `transmitterFees` | `0xefb4cdea` | +| `transmitterCredits` | `0xefb4cdea` | | `unblockAndAssignFees` | `0x3c5366a2` | | `unblockFees` | `0xc1867a4b` | | `watcherPrecompileConfig` | `0x8618a912` | diff --git a/contracts/base/AppGatewayBase.sol b/contracts/base/AppGatewayBase.sol index abd4996d..3f252074 100644 --- a/contracts/base/AppGatewayBase.sol +++ b/contracts/base/AppGatewayBase.sol @@ -19,7 +19,7 @@ abstract contract AppGatewayBase is AddressResolverUtil, IAppGateway { address public auctionManager; bytes32 public sbType; bytes public onCompleteData; - uint256 public fees; + uint256 public maxFees; mapping(address => bool) public isValidPromise; mapping(bytes32 => mapping(uint32 => address)) public override forwarderAddresses; @@ -36,13 +36,12 @@ abstract contract AppGatewayBase is AddressResolverUtil, IAppGateway { isAsyncModifierSet = false; // todo: cache the feesApprovalData for next async in same request - deliveryHelper__().batch(fees, auctionManager, feesApprovalData_, onCompleteData); + deliveryHelper__().batch(maxFees, auctionManager, feesApprovalData_, onCompleteData); _markValidPromises(); onCompleteData = bytes(""); } function _preAsync() internal { - if (fees.feePoolChain == 0) revert FeesNotSet(); isAsyncModifierSet = true; _clearOverrides(); deliveryHelper__().clearQueue(); @@ -188,7 +187,8 @@ abstract contract AppGatewayBase is AddressResolverUtil, IAppGateway { return address(0); } - onChainAddress = IForwarder(forwarderAddresses[contractId_][chainSlug_]).getOnChainAddress(); + onChainAddress = IForwarder(forwarderAddresses[contractId_][chainSlug_]) + .getOnChainAddress(); } //////////////////////////////////////////////////////////////////////////////////////////////// @@ -197,7 +197,7 @@ abstract contract AppGatewayBase is AddressResolverUtil, IAppGateway { /// @notice Sets multiple overrides in one call /// @param isReadCall_ The read call flag - /// @param fees_ The fees configuration + /// @param fees_ The maxFees configuration /// @param gasLimit_ The gas limit /// @param isParallelCall_ The sequential call flag function _setOverrides( @@ -209,7 +209,7 @@ abstract contract AppGatewayBase is AddressResolverUtil, IAppGateway { overrideParams.isReadCall = isReadCall_; overrideParams.isParallelCall = isParallelCall_; overrideParams.gasLimit = gasLimit_; - fees = fees_; + maxFees = fees_; } function _clearOverrides() internal { @@ -221,7 +221,7 @@ abstract contract AppGatewayBase is AddressResolverUtil, IAppGateway { overrideParams.writeFinality = WriteFinality.LOW; } - /// @notice Sets isReadCall, fees and gasLimit overrides + /// @notice Sets isReadCall, maxFees and gasLimit overrides /// @param isReadCall_ The read call flag /// @param isParallelCall_ The sequential call flag /// @param gasLimit_ The gas limit @@ -283,10 +283,10 @@ abstract contract AppGatewayBase is AddressResolverUtil, IAppGateway { overrideParams.value = value_; } - /// @notice Sets fees overrides - /// @param fees_ The fees configuration - function _setOverrides(uint256 fees_) internal { - fees = fees_; + /// @notice Sets maxFees overrides + /// @param fees_ The maxFees configuration + function _setMaxFees(uint256 fees_) internal { + maxFees = fees_; } function getOverrideParams() @@ -315,7 +315,7 @@ abstract contract AppGatewayBase is AddressResolverUtil, IAppGateway { deliveryHelper__().cancelRequest(requestCount_); } - /// @notice increases the transaction fees + /// @notice increases the transaction maxFees /// @param requestCount_ The async ID function _increaseFees(uint40 requestCount_, uint256 newMaxFees_) internal { deliveryHelper__().increaseFees(requestCount_, newMaxFees_); @@ -339,7 +339,7 @@ abstract contract AppGatewayBase is AddressResolverUtil, IAppGateway { amount_, receiver_, auctionManager, - fees + maxFees ); } diff --git a/contracts/interfaces/IFeesManager.sol b/contracts/interfaces/IFeesManager.sol index a6c90520..71b0eb70 100644 --- a/contracts/interfaces/IFeesManager.sol +++ b/contracts/interfaces/IFeesManager.sol @@ -5,25 +5,20 @@ import {Bid, QueuePayloadParams} from "../protocol/utils/common/Structs.sol"; interface IFeesManager { function blockFees( - address appGateway_, - uint256 fees_, - Bid memory winningBid_, + address consumeFrom_, + uint256 transmitterCredits_, uint40 requestCount_ ) external; function unblockFees(uint40 requestCount_) external; function isFeesEnough( - address appGateway_, address consumeFrom_, - uint256 fees_ + address appGateway_, + uint256 amount_ ) external view returns (bool); - function unblockAndAssignFees( - uint40 requestCount_, - address transmitter_, - address appGateway_ - ) external; + function unblockAndAssignFees(uint40 requestCount_, address transmitter_) external; function withdrawFees( address appGateway_, @@ -33,10 +28,14 @@ interface IFeesManager { address receiver_ ) external; - function assignWatcherPrecompileFees( - uint32 chainSlug_, - address token_, - uint256 amount_, - address consumeFrom_ + function assignWatcherPrecompileFeesFromRequestCount( + uint256 fees_, + uint40 requestCount_ ) external; + + function assignWatcherPrecompileFeesFromAddress(uint256 fees_, address consumeFrom_) external; + + function setAppGatewayWhitelist( + bytes memory feeApprovalData_ + ) external returns (address consumeFrom, address appGateway, bool isApproved); } diff --git a/contracts/interfaces/IFeesPlug.sol b/contracts/interfaces/IFeesPlug.sol index 1f4357b8..edcd1aef 100644 --- a/contracts/interfaces/IFeesPlug.sol +++ b/contracts/interfaces/IFeesPlug.sol @@ -6,7 +6,11 @@ interface IFeesPlug { function feesRedeemed(bytes32 feesId_) external view returns (bool); - function deposit(address token_, address appGateway_, uint256 amount_) external payable; + function depositToFee(address token_, uint256 amount_, address receiver_) external; + + function depositToFeeAndNative(address token_, uint256 amount_, address receiver_) external; + + function depositToNative(address token_, uint256 amount_, address receiver_) external; function distributeFee( address feeToken_, diff --git a/contracts/interfaces/IMiddleware.sol b/contracts/interfaces/IMiddleware.sol index 46a2410d..d8714e91 100644 --- a/contracts/interfaces/IMiddleware.sol +++ b/contracts/interfaces/IMiddleware.sol @@ -1,6 +1,6 @@ // SPDX-License-Identifier: GPL-3.0-only pragma solidity ^0.8.21; -import {PayloadSubmitParams, QueuePayloadParams, Bid, WriteFinality, CallType, Parallel, IsPlug, RequestMetadata, RequestFee} from "../protocol/utils/common/Structs.sol"; +import {PayloadSubmitParams, QueuePayloadParams, Bid, WriteFinality, CallType, Parallel, IsPlug, RequestMetadata} from "../protocol/utils/common/Structs.sol"; /// @title IMiddleware /// @notice Interface for the Middleware contract diff --git a/contracts/interfaces/IWatcherPrecompile.sol b/contracts/interfaces/IWatcherPrecompile.sol index 0def0da2..54d7d08e 100644 --- a/contracts/interfaces/IWatcherPrecompile.sol +++ b/contracts/interfaces/IWatcherPrecompile.sol @@ -1,7 +1,7 @@ // SPDX-License-Identifier: GPL-3.0-only pragma solidity ^0.8.21; -import {DigestParams, ResolvedPromises, PayloadParams, TriggerParams, PayloadSubmitParams, RequestParams} from "../protocol/utils/common/Structs.sol"; +import {DigestParams, ResolvedPromises, PayloadParams, TriggerParams, PayloadSubmitParams, Bid, RequestParams, RequestMetadata} from "../protocol/utils/common/Structs.sol"; import {IWatcherPrecompileLimits} from "./IWatcherPrecompileLimits.sol"; import {IWatcherPrecompileConfig} from "./IWatcherPrecompileConfig.sol"; @@ -174,7 +174,8 @@ interface IWatcherPrecompile { function setExpiryTime(uint256 expiryTime_) external; function submitRequest( - PayloadSubmitParams[] calldata payloadSubmitParams + PayloadSubmitParams[] calldata payloadSubmitParams, + RequestMetadata memory requestMetadata ) external returns (uint40 requestCount); function startProcessingRequest(uint40 requestCount, address transmitter) external; diff --git a/contracts/interfaces/IWatcherPrecompileLimits.sol b/contracts/interfaces/IWatcherPrecompileLimits.sol index f7db2adf..394a8cdc 100644 --- a/contracts/interfaces/IWatcherPrecompileLimits.sol +++ b/contracts/interfaces/IWatcherPrecompileLimits.sol @@ -46,14 +46,20 @@ interface IWatcherPrecompileLimits { /// @param appGateway_ The app gateway address /// @param limitType_ The type of limit to consume /// @param consumeLimit_ The amount of limit to consume - function consumeLimit(address appGateway_, bytes32 limitType_, uint256 consumeLimit_) external; + function consumeLimit( + uint40 requestCount_, + address appGateway_, + bytes32 limitType_, + uint256 consumeLimit_ + ) external; + + function getTotalFeesRequired(uint40 requestCount_) external view returns (uint256); + + function queryFees() external view returns (uint256); + function finalizeFees() external view returns (uint256); + function scheduleFees() external view returns (uint256); + function callBackFees() external view returns (uint256); - function getTotalFeesRequired( - address token_, - uint queryCount, - uint finalizeCount, - uint scheduleCount - ) external view returns (uint256); /// @notice Emitted when limit parameters are updated event LimitParamsUpdated(UpdateLimitParams[] updates); diff --git a/contracts/protocol/payload-delivery/AuctionManager.sol b/contracts/protocol/payload-delivery/AuctionManager.sol index a27cbd3f..632a3d15 100644 --- a/contracts/protocol/payload-delivery/AuctionManager.sol +++ b/contracts/protocol/payload-delivery/AuctionManager.sol @@ -95,38 +95,38 @@ contract AuctionManager is /// @notice Places a bid for an auction /// @param requestCount_ The ID of the auction - /// @param fee The bid amount + /// @param bidFees The bid amount /// @param transmitterSignature The signature of the transmitter function bid( uint40 requestCount_, - uint256 fee, + uint256 bidFees, bytes memory transmitterSignature, bytes memory extraData ) external { if (auctionClosed[requestCount_]) revert AuctionClosed(); + RequestMetadata memory requestMetadata = _getRequestMetadata(requestCount_); // check if the transmitter is valid address transmitter = _recoverSigner( - keccak256(abi.encode(address(this), evmxSlug, requestCount_, fee, extraData)), + keccak256(abi.encode(address(this), evmxSlug, requestCount_, bidFees, extraData)), transmitterSignature ); if (!_hasRole(TRANSMITTER_ROLE, transmitter)) revert InvalidTransmitter(); - (uint256 watcherFees, uint256 transmitterFees) = getTransmitterMaxFeesRequired( - requestMetadata.fees.token, + (uint256 watcherFees, uint256 transmitterCredits) = getTransmitterMaxFeesRequired( requestCount_ + // check if the bid exceeds the max fees quoted by app gateway subtracting the watcher fees ); - // check if the bid exceeds the max fees quoted by app gateway subtracting the watcher fees - if (fee > transmitterFees) revert BidExceedsMaxFees(); + if (bidFees > transmitterCredits) revert BidExceedsMaxFees(); // check if the bid is lower than the existing bid if ( winningBids[requestCount_].transmitter != address(0) && - fee >= winningBids[requestCount_].fee + bidFees >= winningBids[requestCount_].fee ) revert LowerBidAlreadyExists(); // create a new bid - Bid memory newBid = Bid({fee: fee, transmitter: transmitter, extraData: extraData}); + Bid memory newBid = Bid({fee: bidFees, transmitter: transmitter, extraData: extraData}); // update the winning bid winningBids[requestCount_] = newBid; @@ -158,7 +158,7 @@ contract AuctionManager is if (winningBid.transmitter == address(0)) revert InvalidTransmitter(); auctionClosed[requestCount_] = true; - + RequestMetadata memory requestMetadata = _getRequestMetadata(requestCount_); // block the fees IFeesManager(addressResolver__.feesManager()).blockFees( requestMetadata.consumeFrom, @@ -209,19 +209,18 @@ contract AuctionManager is } function getTransmitterMaxFeesRequired( - address token_, uint40 requestCount_ ) public view returns (uint256, uint256) { + RequestMetadata memory requestMetadata = _getRequestMetadata(requestCount_); + // check if the bid is for this auction manager if (requestMetadata.auctionManager != address(this)) revert InvalidBid(); // get the request metadata - RequestMetadata memory requestMetadata = IMiddleware(addressResolver__.deliveryHelper()) - .getRequestMetadata(requestCount_); // get the total fees required for the watcher precompile ops - uint256 watcherFees = watcherPrecompile__().getTotalFeesRequired(token_, requestCount_); - return (watcherFees, requestMetadata.fees.amount - watcherFees); + uint256 watcherFees = watcherPrecompileLimits().getTotalFeesRequired(requestCount_); + return (watcherFees, requestMetadata.maxFees - watcherFees); } function _recoverSigner( @@ -232,4 +231,10 @@ contract AuctionManager is // recovered signer is checked for the valid roles later signer = ECDSA.recover(digest, signature_); } + + function _getRequestMetadata( + uint40 requestCount_ + ) internal view returns (RequestMetadata memory) { + return IMiddleware(addressResolver__.deliveryHelper()).getRequestMetadata(requestCount_); + } } diff --git a/contracts/protocol/payload-delivery/FeesManager.sol b/contracts/protocol/payload-delivery/FeesManager.sol index 0e1bc855..260c4b52 100644 --- a/contracts/protocol/payload-delivery/FeesManager.sol +++ b/contracts/protocol/payload-delivery/FeesManager.sol @@ -10,7 +10,7 @@ import {IFeesManager} from "../../interfaces/IFeesManager.sol"; import {AddressResolverUtil} from "../utils/AddressResolverUtil.sol"; import {NotAuctionManager, InvalidWatcherSignature, NonceUsed} from "../utils/common/Errors.sol"; -import {Bid, CallType, Parallel, WriteFinality, QueuePayloadParams, IsPlug, PayloadSubmitParams, RequestMetadata, RequestFee, UserCredits} from "../utils/common/Structs.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 @@ -28,6 +28,9 @@ abstract contract FeesManagerStorage is IFeesManager { // user credits mapping(address => UserCredits) public userCredits; + // user nonce + mapping(address => uint256) public userNonce; + // token pool balances // token address => chainSlug => amount mapping(address => mapping(uint32 => uint256)) public tokenPoolBalances; @@ -39,7 +42,7 @@ abstract contract FeesManagerStorage is IFeesManager { // slot 54 /// @notice Mapping to track request credits details for each request count /// @dev requestCount => RequestFee - mapping(uint40 => RequestFee) public requestCountCredits; + mapping(uint40 => uint256) public requestCountCredits; // slot 55 /// @notice Mapping to track credits to be distributed to transmitters @@ -65,15 +68,9 @@ abstract contract FeesManagerStorage is IFeesManager { contract FeesManager is FeesManagerStorage, Initializable, Ownable, AddressResolverUtil { /// @notice Emitted when fees are blocked for a batch /// @param requestCount The batch identifier - /// @param chainSlug The chain identifier - /// @param token The token address + /// @param consumeFrom The consume from address /// @param amount The blocked amount - event FeesBlocked( - uint40 indexed requestCount, - uint32 indexed chainSlug, - address indexed token, - uint256 amount - ); + event FeesBlocked(uint40 indexed requestCount, address indexed consumeFrom, uint256 amount); /// @notice Emitted when transmitter fees are updated /// @param requestCount The batch identifier @@ -84,12 +81,7 @@ contract FeesManager is FeesManagerStorage, Initializable, Ownable, AddressResol address indexed transmitter, uint256 amount ); - event WatcherPrecompileFeesAssigned( - uint32 chainSlug, - address token, - uint256 amount, - address consumeFrom - ); + event WatcherPrecompileFeesAssigned(uint256 amount, address consumeFrom); /// @notice Emitted when fees deposited are updated /// @param chainSlug The chain identifier /// @param appGateway The app gateway address @@ -163,33 +155,72 @@ contract FeesManager is FeesManagerStorage, Initializable, Ownable, AddressResol } /// @notice Adds the fees deposited for an app gateway on a chain - /// @param consumeFrom_ The app gateway address + /// @param depositTo_ The app gateway address /// @param amount_ The amount deposited + // @dev only callable by watcher precompile + // @dev will need tokenAmount_ and creditAmount_ when introduce tokens except stables function incrementFeesDeposited( - address consumeFrom_, + address depositTo_, + uint32 chainSlug_, + address token_, uint256 amount_, uint256 signatureNonce_, bytes memory signature_ ) external { - _isWatcherSignatureValid(abi.encode(consumeFrom_, amount_), signatureNonce_, signature_); + _isWatcherSignatureValid( + abi.encode(depositTo_, chainSlug_, token_, amount_), + signatureNonce_, + signature_ + ); - UserCredits storage userCredit = userCredits[consumeFrom_]; + UserCredits storage userCredit = userCredits[depositTo_]; userCredit.totalCredits += amount_; tokenPoolBalances[token_][chainSlug_] += amount_; - emit FeesDepositedUpdated(consumeFrom_, amount_); + emit FeesDepositedUpdated(chainSlug_, depositTo_, token_, amount_); } function isFeesEnough( - address appGateway_, address consumeFrom_, + address appGateway_, uint256 amount_ ) external view returns (bool) { - address appGateway = _getCoreAppGateway(consumeFrom_); - if (!isAppGatewayWhitelisted[consumeFrom_][appGateway]) return false; + if (!isAppGatewayWhitelisted[consumeFrom_][appGateway_]) return false; return getAvailableFees(consumeFrom_) >= amount_; } + function _processFeeApprovalData( + bytes memory feeApprovalData_ + ) internal view returns (address consumeFrom, address appGateway, bool isApproved) { + bytes memory signature_; + (consumeFrom, appGateway, isApproved, signature_) = abi.decode( + feeApprovalData_, + (address, address, bool, bytes) + ); + if (signature_.length == 0) return (consumeFrom, appGateway, isApproved); + bytes32 digest = keccak256( + abi.encode( + address(this), + evmxSlug, + consumeFrom, + appGateway, + userNonce[consumeFrom], + isApproved + ) + ); + if (ECDSA.recover(digest, signature_) != consumeFrom) revert InvalidWatcherSignature(); + + isAppGatewayWhitelisted[consumeFrom][appGateway] = isApproved; + userNonce[consumeFrom]++; + return (consumeFrom, appGateway, isApproved); + } + + function setAppGatewayWhitelist( + bytes memory feeApprovalData_ + ) external returns (address consumeFrom, address appGateway, bool isApproved) { + return _processFeeApprovalData(feeApprovalData_); + } + /// @notice Whitelists multiple app gateways for the caller /// @param appGateways_ Array of app gateway addresses to whitelist function whitelistApps(address[] calldata appGateways_) external { @@ -197,32 +228,31 @@ contract FeesManager is FeesManagerStorage, Initializable, Ownable, AddressResol isAppGatewayWhitelisted[msg.sender][appGateways_[i]] = true; } } - + 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 transmitterFees_ The total fees to block + /// @param transmitterCredits_ The total fees to block /// @param requestCount_ The batch identifier /// @dev Only callable by delivery helper function blockFees( address consumeFrom_, - uint256 transmitterFees_, + uint256 transmitterCredits_, uint40 requestCount_ - ) external { - if (msg.sender != deliveryHelper__().getRequestMetadata(requestCount_).auctionManager) - revert NotAuctionManager(); - + ) external onlyAuctionManager(requestCount_) { // Block fees - if (getAvailableFees(consumeFrom_) < transmitterFees_) revert InsufficientFeesAvailable(); + if (getAvailableFees(consumeFrom_) < transmitterCredits_) + revert InsufficientFeesAvailable(); UserCredits storage userCredit = userCredits[consumeFrom_]; - userCredit.blockedCredits += transmitterFees_; + userCredit.blockedCredits += transmitterCredits_; - requestCountCredits[requestCount_] = RequestFee({ - blockedCredits: transmitterFees_, - consumeFrom: consumeFrom_ - }); + requestCountCredits[requestCount_] = transmitterCredits_; - emit FeesBlocked(requestCount_, transmitterFees_); + emit FeesBlocked(requestCount_, consumeFrom_, transmitterCredits_); } /// @notice Unblocks fees after successful execution and assigns them to the transmitter @@ -232,16 +262,18 @@ contract FeesManager is FeesManagerStorage, Initializable, Ownable, AddressResol uint40 requestCount_, address transmitter_ ) external override onlyDeliveryHelper { - RequestFee memory requestFee = requestCountCredits[requestCount_]; - if (requestFee.blockedCredits == 0) return; - - uint256 fees = deliveryHelper__().getRequestMetadata(requestCount_).winningBid.fee; + uint256 blockedCredits = requestCountCredits[requestCount_]; + if (blockedCredits == 0) return; + RequestMetadata memory requestMetadata = deliveryHelper__().getRequestMetadata( + requestCount_ + ); + uint256 fees = requestMetadata.winningBid.fee; // Unblock fees from deposit - _useBlockedUserCredits(requestFee.consumeFrom, fees, fees); + _useBlockedUserCredits(requestMetadata.consumeFrom, blockedCredits, fees); // Assign fees to transmitter - transmitterFees[transmitter_] += fees; + transmitterCredits[transmitter_] += fees; // Clean up storage delete requestCountCredits[requestCount_]; @@ -250,27 +282,43 @@ contract FeesManager is FeesManagerStorage, Initializable, Ownable, AddressResol function _useBlockedUserCredits( address consumeFrom_, - uint256 toBlock_, - uint256 toConsume_ + uint256 toConsumeFromBlocked_, + uint256 toConsumeFromTotal_ ) internal { UserCredits storage userCredit = userCredits[consumeFrom_]; - userCredit.blockedCredits -= toBlock_; + userCredit.blockedCredits -= toConsumeFromBlocked_; + userCredit.totalCredits -= toConsumeFromTotal_; + } + + function _useAvailableUserCredits(address consumeFrom_, uint256 toConsume_) internal { + UserCredits storage userCredit = userCredits[consumeFrom_]; userCredit.totalCredits -= toConsume_; } - function assignWatcherPrecompileFees( - uint256 amount_, + function assignWatcherPrecompileFeesFromRequestCount( + uint256 fees_, uint40 requestCount_ ) external onlyWatcherPrecompile { - RequestFee memory requestFee = requestCountCredits[requestCount_]; - if (requestFee.blockedCredits == 0) revert NoFeesBlocked(); + require(requestCount_ != 0, "Request count cannot be 0"); + RequestMetadata memory requestMetadata = deliveryHelper__().getRequestMetadata( + requestCount_ + ); + _assignWatcherPrecompileFees(fees_, requestMetadata.consumeFrom); + } - // deduct the fees from the user - _useBlockedUserCredits(requestFee.consumeFrom, 0, amount_); + function assignWatcherPrecompileFeesFromAddress( + uint256 fees_, + address consumeFrom_ + ) external onlyWatcherPrecompile { + _assignWatcherPrecompileFees(fees_, consumeFrom_); + } + function _assignWatcherPrecompileFees(uint256 fees_, address consumeFrom_) internal { + // deduct the fees from the user + _useAvailableUserCredits(consumeFrom_, fees_); // add the fees to the watcher precompile - watcherPrecompileCredits += amount_; - emit WatcherPrecompileFeesAssigned(requestCount_, amount_); + watcherPrecompileCredits += fees_; + emit WatcherPrecompileFeesAssigned(fees_, consumeFrom_); } function unblockFees(uint40 requestCount_) external { @@ -283,45 +331,15 @@ contract FeesManager is FeesManagerStorage, Initializable, Ownable, AddressResol msg.sender != address(deliveryHelper__()) ) revert InvalidCaller(); - RequestFee memory requestFee = requestCountCredits[requestCount_]; - if (requestFee.blockedCredits == 0) return; + uint256 blockedCredits = requestCountCredits[requestCount_]; + if (blockedCredits == 0) return; // Unblock fees from deposit - UserCredits storage userCredit = userCredits[requestFee.consumeFrom]; - userCredit.blockedCredits -= requestFee.blockedCredits; + UserCredits storage userCredit = userCredits[requestMetadata.consumeFrom]; + userCredit.blockedCredits -= blockedCredits; delete requestCountCredits[requestCount_]; - emit FeesUnblocked(requestCount_, requestFee.consumeFrom); - } - - /// @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; - // Get total fees for the transmitter in given chain and token - uint256 totalFees = transmitterFees[transmitter]; - if (totalFees >= amount_) revert InsufficientFeesAvailable(); - - // Clean up storage - transmitterFees[transmitter] -= amount_; - tokenPoolBalances[token_][chainSlug_] -= amount_; - - // Create fee distribution payload - bytes32 feesId = _encodeFeesId(feesCounter++); - bytes memory payload = abi.encodeCall( - IFeesPlug.distributeFee, - (token_, amount_, receiver_, feesId) - ); - - // finalize for plug contract - return _submitAndStartProcessing(chainSlug_, payload, transmitter); + emit FeesUnblocked(requestCount_, requestMetadata.consumeFrom); } /// @notice Withdraws funds to a specified receiver @@ -351,11 +369,31 @@ contract FeesManager is FeesManagerStorage, Initializable, Ownable, AddressResol _queue(chainSlug_, abi.encodeCall(IFeesPlug.withdrawFees, (token_, amount_, receiver_))); } - function _submitAndStartProcessing( + /// @notice Withdraws fees to a specified receiver + /// @param chainSlug_ The chain identifier + /// @param token_ The token address + /// @param receiver_ The address of the receiver + function withdrawTransmitterFees( uint32 chainSlug_, - bytes memory payload_, - address transmitter_ - ) internal returns (uint40 requestCount) { + address token_, + address receiver_, + uint256 amount_ + ) external returns (uint40 requestCount) { + address transmitter = msg.sender; + // Get total fees for the transmitter in given chain and token + uint256 totalFees = transmitterCredits[transmitter]; + if (totalFees >= amount_) revert InsufficientFeesAvailable(); + + // Clean up storage + transmitterCredits[transmitter] -= amount_; + tokenPoolBalances[token_][chainSlug_] -= amount_; + + // Create fee distribution payload + bytes32 feesId = _encodeFeesId(feesCounter++); + bytes memory payload = abi.encodeCall( + IFeesPlug.distributeFee, + (token_, amount_, receiver_, feesId) + ); PayloadSubmitParams[] memory payloadSubmitParamsArray = new PayloadSubmitParams[](1); payloadSubmitParamsArray[0] = PayloadSubmitParams({ levelNumber: 0, @@ -370,20 +408,29 @@ contract FeesManager is FeesManagerStorage, Initializable, Ownable, AddressResol gasLimit: 10000000, value: 0, readAt: 0, - payload: payload_ + payload: payload }); RequestMetadata memory requestMetadata = RequestMetadata({ appGateway: address(this), auctionManager: address(0), - feesApprovalData: bytes(""), - fees: amount_, - winningBid: Bid({transmitter: transmitter_, fee: 0, extraData: new bytes(0)}) - }); + maxFees: 0, + winningBid: Bid({transmitter: transmitter, fee: 0, extraData: new bytes(0)}), + onCompleteData: bytes(""), + onlyReadRequests: false, + consumeFrom: address(0) + }); // finalize for plug contract + return _submitAndStartProcessing(payloadSubmitParamsArray, requestMetadata, transmitter); + } + function _submitAndStartProcessing( + PayloadSubmitParams[] memory payloadSubmitParamsArray_, + RequestMetadata memory requestMetadata_, + address transmitter_ + ) internal returns (uint40 requestCount) { requestCount = watcherPrecompile__().submitRequest( - payloadSubmitParamsArray, - requestMetadata + payloadSubmitParamsArray_, + requestMetadata_ ); // same transmitter can execute requests without auction diff --git a/contracts/protocol/payload-delivery/FeesPlug.sol b/contracts/protocol/payload-delivery/FeesPlug.sol index 2f5bc16f..ab6784ce 100644 --- a/contracts/protocol/payload-delivery/FeesPlug.sol +++ b/contracts/protocol/payload-delivery/FeesPlug.sol @@ -113,7 +113,7 @@ contract FeesPlug is IFeesPlug, PlugBase, AccessControl { address receiver_, uint256 feeAmount_, uint256 nativeAmount_ - ) internal override { + ) internal { uint256 totalAmount_ = feeAmount_ + nativeAmount_; if (!whitelistedTokens[token_]) revert TokenNotWhitelisted(token_); if (token_.code.length == 0) revert InvalidTokenAddress(); diff --git a/contracts/protocol/payload-delivery/app-gateway/DeliveryHelper.sol b/contracts/protocol/payload-delivery/app-gateway/DeliveryHelper.sol index 6c7b0c80..6efaedfd 100644 --- a/contracts/protocol/payload-delivery/app-gateway/DeliveryHelper.sol +++ b/contracts/protocol/payload-delivery/app-gateway/DeliveryHelper.sol @@ -41,7 +41,7 @@ contract DeliveryHelper is FeesHelpers { requestMetadata_.winningBid.transmitter = winningBid_.transmitter; if (!isRestarted) { - watcherPrecompile__().startProcessingRequest(requestCount_, winningBid_); + watcherPrecompile__().startProcessingRequest(requestCount_, winningBid_.transmitter); } else { watcherPrecompile__().updateTransmitter(requestCount_, winningBid_.transmitter); } @@ -56,8 +56,7 @@ contract DeliveryHelper is FeesHelpers { if (requestMetadata_.winningBid.transmitter != address(0)) IFeesManager(addressResolver__.feesManager()).unblockAndAssignFees( requestCount_, - requestMetadata_.winningBid.transmitter, - requestMetadata_.appGateway + requestMetadata_.winningBid.transmitter ); IAppGateway(requestMetadata_.appGateway).onRequestComplete( @@ -95,8 +94,7 @@ contract DeliveryHelper is FeesHelpers { if (requests[requestCount_].winningBid.transmitter != address(0)) { IFeesManager(addressResolver__.feesManager()).unblockAndAssignFees( requestCount_, - requests[requestCount_].winningBid.transmitter, - requests[requestCount_].appGateway + requests[requestCount_].winningBid.transmitter ); } else { // If the request has no winning bid, ie. transmitter not assigned, unblock fees diff --git a/contracts/protocol/payload-delivery/app-gateway/FeesHelpers.sol b/contracts/protocol/payload-delivery/app-gateway/FeesHelpers.sol index ccda6afe..2922ba52 100644 --- a/contracts/protocol/payload-delivery/app-gateway/FeesHelpers.sol +++ b/contracts/protocol/payload-delivery/app-gateway/FeesHelpers.sol @@ -21,7 +21,7 @@ abstract contract FeesHelpers is RequestQueue { } if (requests[requestCount_].winningBid.transmitter != address(0)) revert WinningBidExists(); - requests[requestCount_].fees.amount = newMaxFees_; + requests[requestCount_].maxFees = newMaxFees_; emit FeesIncreased(appGateway, requestCount_, newMaxFees_); } @@ -47,13 +47,13 @@ abstract contract FeesHelpers is RequestQueue { receiver_ ); - return _batch(msg.sender, auctionManager_, fees_, bytes("")); + return _batch(msg.sender, auctionManager_, fees_, bytes(""), bytes("")); } /// @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_].fees.amount; + return requests[requestCount_].maxFees; } } diff --git a/contracts/protocol/payload-delivery/app-gateway/RequestQueue.sol b/contracts/protocol/payload-delivery/app-gateway/RequestQueue.sol index 12d9d458..dac1a41d 100644 --- a/contracts/protocol/payload-delivery/app-gateway/RequestQueue.sol +++ b/contracts/protocol/payload-delivery/app-gateway/RequestQueue.sol @@ -20,34 +20,38 @@ abstract contract RequestQueue is DeliveryUtils { } /// @notice Initiates a batch of payloads - /// @param fees_ The fees data + /// @param maxFees_ The fees data /// @param auctionManager_ The auction manager address /// @return requestCount The ID of the batch function batch( - uint256 fees_, + uint256 maxFees_, address auctionManager_, bytes memory feesApprovalData_, bytes memory onCompleteData_ ) external returns (uint40 requestCount) { address appGateway = _getCoreAppGateway(msg.sender); - return _batch(appGateway, auctionManager_, fees_, feesApprovalData_, onCompleteData_); + return _batch(appGateway, auctionManager_, maxFees_, feesApprovalData_, onCompleteData_); } function _checkBatch( address appGateway_, bytes memory feesApprovalData_, - uint256 fees_ - ) internal view { + uint256 maxFees_ + ) internal view returns (address consumeFrom) { if (queuePayloadParams.length > REQUEST_PAYLOAD_COUNT_LIMIT) revert RequestPayloadCountLimitExceeded(); - + (consumeFrom, , ) = IFeesManager(addressResolver__.feesManager()).setAppGatewayWhitelist( + feesApprovalData_ + ); if ( !IFeesManager(addressResolver__.feesManager()).isFeesEnough( + consumeFrom, appGateway_, - feesApprovalData_, - fees_ + maxFees_ ) ) revert InsufficientFees(); + + return consumeFrom; } function _getAuctionManager(address auctionManager_) internal view returns (address) { @@ -64,7 +68,7 @@ abstract contract RequestQueue is DeliveryUtils { function _batch( address appGateway_, address auctionManager_, - uint256 fees_, + uint256 maxFees_, bytes memory feesApprovalData_, bytes memory onCompleteData_ ) internal returns (uint40 requestCount) { @@ -77,15 +81,15 @@ abstract contract RequestQueue is DeliveryUtils { bool onlyReadRequests ) = _createPayloadSubmitParamsArray(); - _checkBatch(appGateway_, feesApprovalData_, fees_); + address consumeFrom = _checkBatch(appGateway_, feesApprovalData_, maxFees_); RequestMetadata memory requestMetadata = RequestMetadata({ appGateway: appGateway_, auctionManager: auctionManager, - feesApprovalData: feesApprovalData_, - fees: fees_, + maxFees: maxFees_, winningBid: Bid({fee: 0, transmitter: address(0), extraData: new bytes(0)}), onCompleteData: onCompleteData_, - onlyReadRequests: onlyReadRequests + onlyReadRequests: onlyReadRequests, + consumeFrom: consumeFrom }); // process and submit the queue of payloads to watcher precompile @@ -101,14 +105,14 @@ abstract contract RequestQueue is DeliveryUtils { watcherPrecompile__().startProcessingRequest(requestCount, address(0)); // to save extra calls from transmitter - uint256 maxTransmitterFees = fees_ - - watcherPrecompile__().getTotalFeesRequired(requestCount); + uint256 maxTransmitterFees = maxFees_ - + watcherPrecompileLimits().getTotalFeesRequired(requestCount); emit PayloadSubmitted( requestCount, appGateway_, payloadSubmitParamsArray, - fees_ - maxTransmitterFees, + maxFees_ - maxTransmitterFees, auctionManager_, onlyReadRequests ); diff --git a/contracts/protocol/utils/AddressResolverUtil.sol b/contracts/protocol/utils/AddressResolverUtil.sol index ec6ba238..21f24611 100644 --- a/contracts/protocol/utils/AddressResolverUtil.sol +++ b/contracts/protocol/utils/AddressResolverUtil.sol @@ -6,6 +6,7 @@ import "../../interfaces/IMiddleware.sol"; import "../../interfaces/IWatcherPrecompile.sol"; import "../../interfaces/IWatcherPrecompileConfig.sol"; import "../../interfaces/IWatcherPrecompileLimits.sol"; +import "../../interfaces/IFeesManager.sol"; /// @title AddressResolverUtil /// @notice Utility contract for resolving system contract addresses diff --git a/contracts/protocol/utils/common/Constants.sol b/contracts/protocol/utils/common/Constants.sol index bf6c8d4a..7b17f483 100644 --- a/contracts/protocol/utils/common/Constants.sol +++ b/contracts/protocol/utils/common/Constants.sol @@ -9,6 +9,7 @@ bytes32 constant DEPLOY = keccak256("DEPLOY"); bytes32 constant QUERY = keccak256("QUERY"); bytes32 constant FINALIZE = keccak256("FINALIZE"); bytes32 constant SCHEDULE = keccak256("SCHEDULE"); +bytes32 constant CALLBACK = keccak256("CALLBACK"); bytes32 constant FAST = keccak256("FAST"); uint256 constant REQUEST_PAYLOAD_COUNT_LIMIT = 10; uint256 constant PAYLOAD_SIZE_LIMIT = 24_500; diff --git a/contracts/protocol/utils/common/Structs.sol b/contracts/protocol/utils/common/Structs.sol index 36c73bb0..4dcffff9 100644 --- a/contracts/protocol/utils/common/Structs.sol +++ b/contracts/protocol/utils/common/Structs.sol @@ -108,6 +108,12 @@ struct Bid { bytes extraData; } +struct OnChainFees { + uint32 chainSlug; + address token; + uint256 amount; +} + // App gateway base: struct OverrideParams { Read isReadCall; @@ -118,12 +124,6 @@ struct OverrideParams { uint256 readAt; } -// FM: -struct RequestFee { - uint256 blockedCredits; - address consumeFrom; -} - struct UserCredits { uint256 totalCredits; uint256 blockedCredits; @@ -219,11 +219,11 @@ struct RequestParams { struct RequestMetadata { address appGateway; address auctionManager; - uint256 fees; + uint256 maxFees; Bid winningBid; bytes onCompleteData; bool onlyReadRequests; - bytes feesApprovalData; + address consumeFrom; } struct ExecuteParams { diff --git a/contracts/protocol/watcherPrecompile/WatcherPrecompileLimits.sol b/contracts/protocol/watcherPrecompile/WatcherPrecompileLimits.sol index 0ab07deb..89e99779 100644 --- a/contracts/protocol/watcherPrecompile/WatcherPrecompileLimits.sol +++ b/contracts/protocol/watcherPrecompile/WatcherPrecompileLimits.sol @@ -6,7 +6,7 @@ import {Ownable} from "solady/auth/Ownable.sol"; import {Gauge} from "../utils/Gauge.sol"; import {AddressResolverUtil} from "../utils/AddressResolverUtil.sol"; import "../../interfaces/IWatcherPrecompileLimits.sol"; -import {SCHEDULE, QUERY, FINALIZE} from "../utils/common/Constants.sol"; +import {SCHEDULE, QUERY, FINALIZE, CALLBACK} from "../utils/common/Constants.sol"; /// @title WatcherPrecompileLimits /// @notice Contract for managing watcher precompile limits @@ -43,16 +43,20 @@ contract WatcherPrecompileLimits is // Mapping to track active app gateways mapping(address => bool) internal _activeAppGateways; - // token => fee amount - mapping(address => uint256) public queryFees; - mapping(address => uint256) public finalizeFees; - mapping(address => uint256) public scheduleFees; - mapping(address => uint256) public callBackFees; + // slot 156: precompileCount + // limitType => requestCount => count + mapping(bytes32 => mapping(uint40 => uint256)) public precompileCount; + + // slot 157: fees + uint256 public queryFees; + uint256 public finalizeFees; + uint256 public scheduleFees; + uint256 public callBackFees; /// @notice Emitted when the default limit and rate per second are set event DefaultLimitAndRatePerSecondSet(uint256 defaultLimit, uint256 defaultRatePerSecond); - event WatcherFeesNotSetForToken(address token_); + error WatcherFeesNotSet(bytes32 limitType); /// @notice Initial initialization (version 1) function initialize( address owner_, @@ -167,63 +171,45 @@ contract WatcherPrecompileLimits is emit DefaultLimitAndRatePerSecondSet(defaultLimit, defaultRatePerSecond); } - function setQueryFees( - address[] calldata tokens_, - uint256[] calldata amounts_ - ) external onlyOwner { - require(tokens_.length == amounts_.length, "Length mismatch"); - for (uint256 i = 0; i < tokens_.length; i++) { - queryFees[tokens_[i]] = amounts_[i]; - } + function setQueryFees(uint256 queryFees_) external onlyOwner { + queryFees = queryFees_; } - function setFinalizeFees( - address[] calldata tokens_, - uint256[] calldata amounts_ - ) external onlyOwner { - require(tokens_.length == amounts_.length, "Length mismatch"); - for (uint256 i = 0; i < tokens_.length; i++) { - finalizeFees[tokens_[i]] = amounts_[i]; - } + function setFinalizeFees(uint256 finalizeFees_) external onlyOwner { + finalizeFees = finalizeFees_; } - function setScheduleFees( - address[] calldata tokens_, - uint256[] calldata amounts_ - ) external onlyOwner { - require(tokens_.length == amounts_.length, "Length mismatch"); - for (uint256 i = 0; i < tokens_.length; i++) { - scheduleFees[tokens_[i]] = amounts_[i]; - } + function setScheduleFees(uint256 scheduleFees_) external onlyOwner { + scheduleFees = scheduleFees_; } - function setCallBackFees( - address[] calldata tokens_, - uint256[] calldata amounts_ - ) external onlyOwner { - require(tokens_.length == amounts_.length, "Length mismatch"); - for (uint256 i = 0; i < tokens_.length; i++) { - callBackFees[tokens_[i]] = amounts_[i]; - } + function setCallBackFees(uint256 callBackFees_) external onlyOwner { + callBackFees = callBackFees_; } - function getTotalFeesRequired( - address token_, - uint40 requestCount_ - ) external view returns (uint256) { + function getTotalFeesRequired(uint40 requestCount_) external view returns (uint256) { uint256 totalFees = 0; - if (queryFees[token_] == 0 || finalizeFees[token_] == 0 || scheduleFees[token_] == 0) { - revert WatcherFeesNotSetForToken(token_); + if (queryFees == 0) { + revert WatcherFeesNotSet(QUERY); + } + if (finalizeFees == 0) { + revert WatcherFeesNotSet(FINALIZE); + } + if (scheduleFees == 0) { + revert WatcherFeesNotSet(SCHEDULE); + } + if (callBackFees == 0) { + revert WatcherFeesNotSet(CALLBACK); } uint256 totalCallbacks = precompileCount[QUERY][requestCount_] + precompileCount[FINALIZE][requestCount_] + precompileCount[SCHEDULE][requestCount_]; - totalFees += totalCallbacks * callBackFees[token_]; - totalFees += precompileCount[QUERY][requestCount_] * queryFees[token_]; - totalFees += precompileCount[FINALIZE][requestCount_] * finalizeFees[token_]; - totalFees += precompileCount[SCHEDULE][requestCount_] * scheduleFees[token_]; + totalFees += totalCallbacks * callBackFees; + totalFees += precompileCount[QUERY][requestCount_] * queryFees; + totalFees += precompileCount[FINALIZE][requestCount_] * finalizeFees; + totalFees += precompileCount[SCHEDULE][requestCount_] * scheduleFees; return totalFees; } diff --git a/contracts/protocol/watcherPrecompile/core/RequestHandler.sol b/contracts/protocol/watcherPrecompile/core/RequestHandler.sol index 3d00e32d..f89e36e5 100644 --- a/contracts/protocol/watcherPrecompile/core/RequestHandler.sol +++ b/contracts/protocol/watcherPrecompile/core/RequestHandler.sol @@ -14,15 +14,16 @@ abstract contract RequestHandler is WatcherPrecompileCore { uint256[50] _request_handler_gap; /// @notice Submits a batch of payload requests from middleware - /// @param payloadSubmitParams Array of payload submit parameters + /// @param payloadSubmitParams_ Array of payload submit parameters + /// @param requestMetadata_ Request metadata /// @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[] calldata payloadSubmitParams, - RequestMetadata calldata requestMetadata + PayloadSubmitParams[] calldata payloadSubmitParams_, + RequestMetadata calldata requestMetadata_ ) public returns (uint40 requestCount) { - address appGateway = _checkAppGateways(payloadSubmitParams); + address appGateway = _checkAppGateways(payloadSubmitParams_); requestCount = nextRequestCount++; uint40 batchCount = nextBatchCount; @@ -32,8 +33,8 @@ abstract contract RequestHandler is WatcherPrecompileCore { uint256 writeCount; PayloadSubmitParams memory lastP; - for (uint256 i = 0; i < payloadSubmitParams.length; i++) { - PayloadSubmitParams memory p = payloadSubmitParams[i]; + 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) { @@ -94,11 +95,9 @@ abstract contract RequestHandler is WatcherPrecompileCore { watcherPrecompileLimits__.consumeLimit(appGateway, FINALIZE, writeCount); requestParams[requestCount].currentBatch = currentBatch; - requestParams[requestCount].payloadsRemaining = payloadSubmitParams.length; + requestParams[requestCount].payloadsRemaining = payloadSubmitParams_.length; requestParams[requestCount].middleware = msg.sender; - requestMetadata[requestCount] = requestMetadata; - emit RequestSubmitted( msg.sender, requestCount, @@ -132,17 +131,17 @@ abstract contract RequestHandler is WatcherPrecompileCore { /// @notice Starts processing a request with a specified transmitter /// @param requestCount The request count to start processing - /// @param winningBid The winning bid, contains fees, transmitter and extra data + /// @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, Bid memory winningBid) public { + 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 = winningBid.transmitter; + r.transmitter = transmitter_; r.currentBatch = batchCount; _processBatch(requestCount, batchCount); diff --git a/contracts/protocol/watcherPrecompile/core/WatcherPrecompile.sol b/contracts/protocol/watcherPrecompile/core/WatcherPrecompile.sol index 40f1b13d..bcea7bd0 100644 --- a/contracts/protocol/watcherPrecompile/core/WatcherPrecompile.sol +++ b/contracts/protocol/watcherPrecompile/core/WatcherPrecompile.sol @@ -312,11 +312,8 @@ contract WatcherPrecompile is RequestHandler { ) ) revert InvalidCallerTriggered(); - IFeesManager(addressResolver__.feesManager()).assignWatcherPrecompileFees( - address(0), - uint32(0), - watcherPrecompileLimits__().callBackFees(params_[i].payload), - uint40(0), + IFeesManager(addressResolver__.feesManager()).assignWatcherPrecompileFeesFromAddress( + watcherPrecompileLimits__.callBackFees(), appGateway ); diff --git a/contracts/protocol/watcherPrecompile/core/WatcherPrecompileCore.sol b/contracts/protocol/watcherPrecompile/core/WatcherPrecompileCore.sol index d8cef4f0..da6c919d 100644 --- a/contracts/protocol/watcherPrecompile/core/WatcherPrecompileCore.sol +++ b/contracts/protocol/watcherPrecompile/core/WatcherPrecompileCore.sol @@ -6,7 +6,7 @@ import {ECDSA} from "solady/utils/ECDSA.sol"; import {AccessControl} from "../../utils/AccessControl.sol"; import "solady/utils/Initializable.sol"; import {AddressResolverUtil} from "../../utils/AddressResolverUtil.sol"; - +import {IFeesManager} from "../../../interfaces/IFeesManager.sol"; /// @title WatcherPrecompileCore /// @notice Core functionality for the WatcherPrecompile system /// @dev This contract implements the core functionality for payload verification, execution, and app configurations @@ -33,10 +33,7 @@ abstract contract WatcherPrecompileCore is ) internal returns (bytes32 timeoutId) { if (delayInSeconds_ > maxTimeoutDelayInSeconds) revert TimeoutDelayTooLarge(); - _consumeFees( - requestCount_, - watcherPrecompileLimits__.scheduleFees(requestMetadata_.fees.feePoolToken) - ); + _consumeCallbackFeesFromAddress(watcherPrecompileLimits__.scheduleFees(), msg.sender); uint256 executeAt = block.timestamp + delayInSeconds_; timeoutId = _encodeTimeoutId(); @@ -79,9 +76,9 @@ abstract contract WatcherPrecompileCore is requestParams[params_.payloadHeader.getRequestCount()].middleware ); - _consumeFees( - params_.payloadHeader.getRequestCount(), - watcherPrecompileLimits__.finalizeFees(params_.payloadHeader.getChainSlug()) + _consumeCallbackFeesFromRequestCount( + watcherPrecompileLimits__.finalizeFees(), + params_.payloadHeader.getRequestCount() ); uint256 deadline = block.timestamp + expiryTime; @@ -119,9 +116,9 @@ abstract contract WatcherPrecompileCore is /// @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 { - _consumeFees( - params_.payloadHeader.getRequestCount(), - watcherPrecompileLimits__.queryFees(params_.payloadHeader.getChainSlug()) + _consumeCallbackFeesFromRequestCount( + watcherPrecompileLimits__.queryFees(), + params_.payloadHeader.getRequestCount() ); payloads[params_.payloadId].prevDigestsHash = _getPreviousDigestsHash( @@ -254,19 +251,24 @@ abstract contract WatcherPrecompileCore is if (signer != owner()) revert InvalidWatcherSignature(); } - function _consumeFees(uint40 requestCount_, uint256 fees_) internal { - RequestMetadata memory requestMetadata_ = requestMetadata[requestCount_]; - + function _consumeCallbackFeesFromRequestCount(uint256 fees_, uint40 requestCount_) internal { // for callbacks in all precompiles - uint256 feesToConsume = fees_ + watcherPrecompileLimits__().callBackFees(); - IFeesManager(addressResolver__.feesManager()).assignWatcherPrecompileFees( - requestMetadata_.fees.feePoolChain, - requestMetadata_.fees.feePoolToken, + uint256 feesToConsume = fees_ + watcherPrecompileLimits__.callBackFees(); + IFeesManager(addressResolver__.feesManager()).assignWatcherPrecompileFeesFromRequestCount( feesToConsume, requestCount_ ); } + function _consumeCallbackFeesFromAddress(uint256 fees_, address consumeFrom_) internal { + // for callbacks in all precompiles + uint256 feesToConsume = fees_ + watcherPrecompileLimits__.callBackFees(); + IFeesManager(addressResolver__.feesManager()).assignWatcherPrecompileFeesFromAddress( + 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 diff --git a/test/DeliveryHelper.t.sol b/test/DeliveryHelper.t.sol index fd7647d7..45877410 100644 --- a/test/DeliveryHelper.t.sol +++ b/test/DeliveryHelper.t.sol @@ -27,7 +27,7 @@ contract DeliveryHelperTest is SetupTest { uint40 indexed requestCount, address indexed appGateway, PayloadSubmitParams[] payloadSubmitParams, - Fees fees, + uint256 maxFees, address auctionManager, bool onlyReadRequests ); @@ -173,18 +173,14 @@ contract DeliveryHelperTest is SetupTest { //////////////////////////////////// Fees //////////////////////////////////// - function depositFees(address appGateway_, Fees memory fees_) internal { - SocketContracts memory socketConfig = getSocketConfig(fees_.feePoolChain); - socketConfig.feesPlug.deposit{value: fees_.amount}( - fees_.feePoolToken, - appGateway_, - fees_.amount - ); + function depositFees(address appGateway_, OnChainFees memory fees_) internal { + SocketContracts memory socketConfig = getSocketConfig(fees_.chainSlug); + socketConfig.feesPlug.deposit{value: fees_.amount}(fees_.token, appGateway_, fees_.amount); bytes memory bytesInput = abi.encode( - fees_.feePoolChain, + fees_.chainSlug, appGateway_, - fees_.feePoolToken, + fees_.token, fees_.amount ); @@ -193,9 +189,9 @@ contract DeliveryHelperTest is SetupTest { ); bytes memory sig = _createSignature(digest, watcherPrivateKey); feesManager.incrementFeesDeposited( - fees_.feePoolChain, + fees_.chainSlug, appGateway_, - fees_.feePoolToken, + fees_.token, fees_.amount, signatureNonce++, sig diff --git a/test/FeesTest.t.sol b/test/FeesTest.t.sol index ec2f9632..7116c8f4 100644 --- a/test/FeesTest.t.sol +++ b/test/FeesTest.t.sol @@ -19,8 +19,11 @@ contract FeesTest is DeliveryHelperTest { setUpDeliveryHelper(); feesConfig = getSocketConfig(feesChainSlug); - counterGateway = new CounterAppGateway(address(addressResolver), createFees(feesAmount)); - depositFees(address(counterGateway), createFees(depositAmount)); + counterGateway = new CounterAppGateway(address(addressResolver), feesAmount); + depositFees( + address(counterGateway), + OnChainFees({chainSlug: feesChainSlug, token: ETH_ADDRESS, amount: depositAmount}) + ); bytes32[] memory contractIds = new bytes32[](1); contractIds[0] = counterGateway.counter(); diff --git a/test/Inbox.t.sol b/test/Inbox.t.sol index 56d7e45f..cdd3baa7 100644 --- a/test/Inbox.t.sol +++ b/test/Inbox.t.sol @@ -26,7 +26,7 @@ contract TriggerTest is DeliveryHelperTest { counter = new Counter(); // Deploy the gateway with fees - gateway = new CounterAppGateway(address(addressResolver), createFees(feesAmount)); + gateway = new CounterAppGateway(address(addressResolver), feesAmount); gateway.setIsValidPlug(arbChainSlug, address(counter)); // Connect the counter to the gateway and socket diff --git a/test/SetupTest.t.sol b/test/SetupTest.t.sol index 152c1729..d5d699c9 100644 --- a/test/SetupTest.t.sol +++ b/test/SetupTest.t.sol @@ -19,7 +19,7 @@ import {ContractFactoryPlug} from "../contracts/protocol/payload-delivery/Contra import {FeesPlug} from "../contracts/protocol/payload-delivery/FeesPlug.sol"; import {ETH_ADDRESS} from "../contracts/protocol/utils/common/Constants.sol"; -import {ResolvedPromises} from "../contracts/protocol/utils/common/Structs.sol"; +import {ResolvedPromises, OnChainFees} from "../contracts/protocol/utils/common/Structs.sol"; import "solady/utils/ERC1967Factory.sol"; @@ -283,10 +283,6 @@ contract SetupTest is Test { return chainSlug_ == arbChainSlug ? arbConfig : optConfig; } - function createFees(uint256 maxFees_) internal view returns (Fees memory) { - return Fees({feePoolChain: arbChainSlug, feePoolToken: ETH_ADDRESS, amount: maxFees_}); - } - function _generateWatcherProof( PayloadParams memory params_ ) internal view returns (bytes memory, bytes32) { @@ -403,4 +399,12 @@ contract SetupTest is Test { mstore(add(sig, 64), sigS) } } + + function _createOnChainFees( + uint32 chainSlug_, + address token_, + uint256 amount_ + ) internal returns (OnChainFees memory) { + return OnChainFees({chainSlug: chainSlug_, token: token_, amount: amount_}); + } } diff --git a/test/apps/Counter.t.sol b/test/apps/Counter.t.sol index 243abcfc..92079168 100644 --- a/test/apps/Counter.t.sol +++ b/test/apps/Counter.t.sol @@ -16,8 +16,11 @@ contract CounterTest is DeliveryHelperTest { function deploySetup() internal { setUpDeliveryHelper(); - counterGateway = new CounterAppGateway(address(addressResolver), createFees(feesAmount)); - depositFees(address(counterGateway), createFees(1 ether)); + counterGateway = new CounterAppGateway(address(addressResolver), feesAmount); + depositFees( + address(counterGateway), + OnChainFees({chainSlug: arbChainSlug, token: ETH_ADDRESS, amount: 1 ether}) + ); counterId = counterGateway.counter(); contractIds[0] = counterId; diff --git a/test/apps/ParallelCounter.t.sol b/test/apps/ParallelCounter.t.sol index fdaa8ac4..6cfc28f3 100644 --- a/test/apps/ParallelCounter.t.sol +++ b/test/apps/ParallelCounter.t.sol @@ -16,11 +16,11 @@ contract ParallelCounterTest is DeliveryHelperTest { function deploySetup() internal { setUpDeliveryHelper(); - parallelCounterGateway = new CounterAppGateway( - address(addressResolver), - createFees(feesAmount) + parallelCounterGateway = new CounterAppGateway(address(addressResolver), feesAmount); + depositFees( + address(parallelCounterGateway), + OnChainFees({chainSlug: arbChainSlug, token: ETH_ADDRESS, amount: 1 ether}) ); - depositFees(address(parallelCounterGateway), createFees(1 ether)); counterId1 = parallelCounterGateway.counter1(); counterId2 = parallelCounterGateway.counter(); contractIds[0] = counterId1; diff --git a/test/apps/SuperToken.t.sol b/test/apps/SuperToken.t.sol index f33ca720..97eadc43 100644 --- a/test/apps/SuperToken.t.sol +++ b/test/apps/SuperToken.t.sol @@ -65,7 +65,7 @@ contract SuperTokenTest is DeliveryHelperTest { SuperTokenAppGateway superTokenApp = new SuperTokenAppGateway( address(addressResolver), owner, - createFees(maxFees), + maxFees, SuperTokenAppGateway.ConstructorParams({ name_: "SUPER TOKEN", symbol_: "SUPER", @@ -76,7 +76,10 @@ contract SuperTokenTest is DeliveryHelperTest { ); // Enable app gateways to do all operations in the Watcher: Read, Write and Schedule on EVMx // Watcher sets the limits for apps in this SOCKET protocol version - depositFees(address(superTokenApp), createFees(1 ether)); + depositFees( + address(superTokenApp), + OnChainFees({chainSlug: arbChainSlug, token: ETH_ADDRESS, amount: 1 ether}) + ); appContracts = AppContracts({ superTokenApp: superTokenApp, diff --git a/test/apps/app-gateways/counter/CounterAppGateway.sol b/test/apps/app-gateways/counter/CounterAppGateway.sol index a7b9561b..dc232047 100644 --- a/test/apps/app-gateways/counter/CounterAppGateway.sol +++ b/test/apps/app-gateways/counter/CounterAppGateway.sol @@ -25,7 +25,7 @@ contract CounterAppGateway is AppGatewayBase, Ownable { } // deploy contracts - function deployContracts(uint32 chainSlug_) external async { + function deployContracts(uint32 chainSlug_) external async(address(this)) { _deploy(counter, chainSlug_, IsPlug.YES); } @@ -33,14 +33,14 @@ contract CounterAppGateway is AppGatewayBase, Ownable { _deploy(counter, chainSlug_, IsPlug.YES); } - function deployParallelContracts(uint32 chainSlug_) external async { + function deployParallelContracts(uint32 chainSlug_) external async(address(this)) { _setOverrides(Parallel.ON); _deploy(counter, chainSlug_, IsPlug.YES); _deploy(counter1, chainSlug_, IsPlug.YES); _setOverrides(Parallel.OFF); } - function deployMultiChainContracts(uint32[] memory chainSlugs_) external async { + function deployMultiChainContracts(uint32[] memory chainSlugs_) external async(address(this)) { _setOverrides(Parallel.ON); for (uint32 i = 0; i < chainSlugs_.length; i++) { _deploy(counter, chainSlugs_[i], IsPlug.YES); @@ -53,7 +53,7 @@ contract CounterAppGateway is AppGatewayBase, Ownable { return; } - function incrementCounters(address[] memory instances_) public async { + function incrementCounters(address[] memory instances_) public async(address(this)) { // the increase function is called on given list of instances // this for (uint256 i = 0; i < instances_.length; i++) { @@ -69,7 +69,7 @@ contract CounterAppGateway is AppGatewayBase, Ownable { } } - function readCounters(address[] memory instances_) public async { + function readCounters(address[] memory instances_) public async(address(this)) { // the increase function is called on given list of instances _setOverrides(Read.ON, Parallel.ON); for (uint256 i = 0; i < instances_.length; i++) { @@ -80,7 +80,10 @@ contract CounterAppGateway is AppGatewayBase, Ownable { _setOverrides(Read.OFF, Parallel.OFF); } - function readCounterAtBlock(address instance_, uint256 blockNumber_) public async { + function readCounterAtBlock( + address instance_, + uint256 blockNumber_ + ) public async(address(this)) { uint32 chainSlug = IForwarder(instance_).getChainSlug(); _setOverrides(Read.ON, Parallel.ON, blockNumber_); ICounter(instance_).getCounter(); @@ -120,8 +123,8 @@ contract CounterAppGateway is AppGatewayBase, Ownable { } // UTILS - function setFees(uint256 fees_) public { - fees = fees_; + function setMaxFees(uint256 fees_) public { + maxFees = fees_; } function withdrawFeeTokens( @@ -133,12 +136,12 @@ contract CounterAppGateway is AppGatewayBase, Ownable { return _withdrawFeeTokens(chainSlug_, token_, amount_, receiver_); } - function testOnChainRevert(uint32 chainSlug) public async { + function testOnChainRevert(uint32 chainSlug) public async(address(this)) { address instance = forwarderAddresses[counter][chainSlug]; ICounter(instance).wrongFunction(); } - function testCallBackRevert(uint32 chainSlug) public async { + function testCallBackRevert(uint32 chainSlug) public async(address(this)) { // the increase function is called on given list of instances _setOverrides(Read.ON, Parallel.ON); address instance = forwarderAddresses[counter][chainSlug]; diff --git a/test/apps/app-gateways/super-token/SuperTokenAppGateway.sol b/test/apps/app-gateways/super-token/SuperTokenAppGateway.sol index 58846bc6..c10045e0 100644 --- a/test/apps/app-gateways/super-token/SuperTokenAppGateway.sol +++ b/test/apps/app-gateways/super-token/SuperTokenAppGateway.sol @@ -60,7 +60,7 @@ contract SuperTokenAppGateway is AppGatewayBase, Ownable { return; } - function transfer(bytes memory order_) external async createFeePool { + 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); From f525c117fa3a624ca5b86ad5be3c390e181d188a Mon Sep 17 00:00:00 2001 From: Ameesha Agrawal Date: Mon, 21 Apr 2025 15:14:48 +0530 Subject: [PATCH 11/21] fix: test build --- contracts/interfaces/IWatcherPrecompileLimits.sol | 1 - .../protocol/payload-delivery/AuctionManager.sol | 14 +++++--------- .../protocol/payload-delivery/FeesManager.sol | 4 +++- .../payload-delivery/app-gateway/RequestQueue.sol | 2 +- .../watcherPrecompile/WatcherPrecompileLimits.sol | 4 ++-- .../watcherPrecompile/core/RequestHandler.sol | 3 +-- .../core/WatcherPrecompileCore.sol | 7 ++++++- 7 files changed, 18 insertions(+), 17 deletions(-) diff --git a/contracts/interfaces/IWatcherPrecompileLimits.sol b/contracts/interfaces/IWatcherPrecompileLimits.sol index 394a8cdc..2bede009 100644 --- a/contracts/interfaces/IWatcherPrecompileLimits.sol +++ b/contracts/interfaces/IWatcherPrecompileLimits.sol @@ -47,7 +47,6 @@ interface IWatcherPrecompileLimits { /// @param limitType_ The type of limit to consume /// @param consumeLimit_ The amount of limit to consume function consumeLimit( - uint40 requestCount_, address appGateway_, bytes32 limitType_, uint256 consumeLimit_ diff --git a/contracts/protocol/payload-delivery/AuctionManager.sol b/contracts/protocol/payload-delivery/AuctionManager.sol index 632a3d15..8e51ba64 100644 --- a/contracts/protocol/payload-delivery/AuctionManager.sol +++ b/contracts/protocol/payload-delivery/AuctionManager.sol @@ -105,7 +105,6 @@ contract AuctionManager is ) external { if (auctionClosed[requestCount_]) revert AuctionClosed(); - RequestMetadata memory requestMetadata = _getRequestMetadata(requestCount_); // check if the transmitter is valid address transmitter = _recoverSigner( keccak256(abi.encode(address(this), evmxSlug, requestCount_, bidFees, extraData)), @@ -113,10 +112,9 @@ contract AuctionManager is ); if (!_hasRole(TRANSMITTER_ROLE, transmitter)) revert InvalidTransmitter(); - (uint256 watcherFees, uint256 transmitterCredits) = getTransmitterMaxFeesRequired( - requestCount_ - // check if the bid exceeds the max fees quoted by app gateway subtracting the watcher fees - ); + uint256 transmitterCredits = getTransmitterMaxFeesRequired(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 @@ -210,17 +208,15 @@ contract AuctionManager is function getTransmitterMaxFeesRequired( uint40 requestCount_ - ) public view returns (uint256, uint256) { + ) 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 request metadata - // get the total fees required for the watcher precompile ops uint256 watcherFees = watcherPrecompileLimits().getTotalFeesRequired(requestCount_); - return (watcherFees, requestMetadata.maxFees - watcherFees); + return requestMetadata.maxFees - watcherFees; } function _recoverSigner( diff --git a/contracts/protocol/payload-delivery/FeesManager.sol b/contracts/protocol/payload-delivery/FeesManager.sol index 260c4b52..99f59885 100644 --- a/contracts/protocol/payload-delivery/FeesManager.sol +++ b/contracts/protocol/payload-delivery/FeesManager.sol @@ -191,7 +191,7 @@ contract FeesManager is FeesManagerStorage, Initializable, Ownable, AddressResol function _processFeeApprovalData( bytes memory feeApprovalData_ - ) internal view returns (address consumeFrom, address appGateway, bool isApproved) { + ) internal returns (address consumeFrom, address appGateway, bool isApproved) { bytes memory signature_; (consumeFrom, appGateway, isApproved, signature_) = abi.decode( feeApprovalData_, @@ -228,11 +228,13 @@ contract FeesManager is FeesManagerStorage, Initializable, Ownable, AddressResol isAppGatewayWhitelisted[msg.sender][appGateways_[i]] = true; } } + 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 diff --git a/contracts/protocol/payload-delivery/app-gateway/RequestQueue.sol b/contracts/protocol/payload-delivery/app-gateway/RequestQueue.sol index dac1a41d..f88ac9cd 100644 --- a/contracts/protocol/payload-delivery/app-gateway/RequestQueue.sol +++ b/contracts/protocol/payload-delivery/app-gateway/RequestQueue.sol @@ -37,7 +37,7 @@ abstract contract RequestQueue is DeliveryUtils { address appGateway_, bytes memory feesApprovalData_, uint256 maxFees_ - ) internal view returns (address consumeFrom) { + ) internal returns (address consumeFrom) { if (queuePayloadParams.length > REQUEST_PAYLOAD_COUNT_LIMIT) revert RequestPayloadCountLimitExceeded(); (consumeFrom, , ) = IFeesManager(addressResolver__.feesManager()).setAppGatewayWhitelist( diff --git a/contracts/protocol/watcherPrecompile/WatcherPrecompileLimits.sol b/contracts/protocol/watcherPrecompile/WatcherPrecompileLimits.sol index 89e99779..f933b29a 100644 --- a/contracts/protocol/watcherPrecompile/WatcherPrecompileLimits.sol +++ b/contracts/protocol/watcherPrecompile/WatcherPrecompileLimits.sol @@ -57,6 +57,7 @@ contract WatcherPrecompileLimits is event DefaultLimitAndRatePerSecondSet(uint256 defaultLimit, uint256 defaultRatePerSecond); error WatcherFeesNotSet(bytes32 limitType); + /// @notice Initial initialization (version 1) function initialize( address owner_, @@ -130,7 +131,6 @@ contract WatcherPrecompileLimits is * @param consumeLimit_ The amount of limit to consume */ function consumeLimit( - uint40 requestCount_, address appGateway_, bytes32 limitType_, uint256 consumeLimit_ @@ -155,7 +155,7 @@ contract WatcherPrecompileLimits is } // Update the limit - precompileCount[limitType_][requestCount_] += consumeLimit_; + // precompileCount[limitType_][requestCount_] += consumeLimit_; _consumeFullLimit(consumeLimit_ * 10 ** limitDecimals, limitParams); } diff --git a/contracts/protocol/watcherPrecompile/core/RequestHandler.sol b/contracts/protocol/watcherPrecompile/core/RequestHandler.sol index f89e36e5..c0508f71 100644 --- a/contracts/protocol/watcherPrecompile/core/RequestHandler.sol +++ b/contracts/protocol/watcherPrecompile/core/RequestHandler.sol @@ -15,13 +15,12 @@ abstract contract RequestHandler is WatcherPrecompileCore { /// @notice Submits a batch of payload requests from middleware /// @param payloadSubmitParams_ Array of payload submit parameters - /// @param requestMetadata_ Request metadata /// @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[] calldata payloadSubmitParams_, - RequestMetadata calldata requestMetadata_ + RequestMetadata calldata ) public returns (uint40 requestCount) { address appGateway = _checkAppGateways(payloadSubmitParams_); diff --git a/contracts/protocol/watcherPrecompile/core/WatcherPrecompileCore.sol b/contracts/protocol/watcherPrecompile/core/WatcherPrecompileCore.sol index da6c919d..2e0a6d5a 100644 --- a/contracts/protocol/watcherPrecompile/core/WatcherPrecompileCore.sol +++ b/contracts/protocol/watcherPrecompile/core/WatcherPrecompileCore.sol @@ -7,6 +7,7 @@ import {AccessControl} from "../../utils/AccessControl.sol"; import "solady/utils/Initializable.sol"; import {AddressResolverUtil} from "../../utils/AddressResolverUtil.sol"; import {IFeesManager} from "../../../interfaces/IFeesManager.sol"; + /// @title WatcherPrecompileCore /// @notice Core functionality for the WatcherPrecompile system /// @dev This contract implements the core functionality for payload verification, execution, and app configurations @@ -50,7 +51,11 @@ abstract contract WatcherPrecompileCore is ); // consumes limit for SCHEDULE precompile - watcherPrecompileLimits__.consumeLimit(_getCoreAppGateway(msg.sender), SCHEDULE, 1); + watcherPrecompileLimits__.consumeLimit( + _getCoreAppGateway(msg.sender), + SCHEDULE, + 1 + ); // emits event for watcher to track timeout and resolve when timeout is reached emit TimeoutRequested(timeoutId, msg.sender, payload_, executeAt); From c38aeeb768926825c295815123d9f6168b615bd1 Mon Sep 17 00:00:00 2001 From: Ameesha Agrawal Date: Mon, 21 Apr 2025 15:22:23 +0530 Subject: [PATCH 12/21] fix: script and test build --- script/counter/SetFees.s.sol | 2 +- .../counter/WithdrawFeesArbitrumFeesPlug.s.sol | 6 +----- script/helpers/AppGatewayFeeBalance.s.sol | 17 +++++------------ script/helpers/PayFeesInArbitrumETH.s.sol | 2 +- test/DeliveryHelper.t.sol | 4 ++-- test/FeesTest.t.sol | 3 ++- test/SetupTest.t.sol | 2 +- .../app-gateways/counter/CounterAppGateway.sol | 16 ++++++++-------- .../super-token/SuperTokenAppGateway.sol | 4 ++-- 9 files changed, 23 insertions(+), 33 deletions(-) diff --git a/script/counter/SetFees.s.sol b/script/counter/SetFees.s.sol index e48d9a73..3f87f4ac 100644 --- a/script/counter/SetFees.s.sol +++ b/script/counter/SetFees.s.sol @@ -22,6 +22,6 @@ contract CounterSetFees is Script { console.log("Setting fees..."); // Setting fee payment on Arbitrum Sepolia uint256 fees = 0.00001 ether; - appGateway.setFees(fees); + // appGateway.setFees(fees); } } diff --git a/script/counter/WithdrawFeesArbitrumFeesPlug.s.sol b/script/counter/WithdrawFeesArbitrumFeesPlug.s.sol index 3a78e9cf..b2b50a25 100644 --- a/script/counter/WithdrawFeesArbitrumFeesPlug.s.sol +++ b/script/counter/WithdrawFeesArbitrumFeesPlug.s.sol @@ -17,11 +17,7 @@ contract WithdrawFees is Script { address appGatewayAddress = vm.envAddress("APP_GATEWAY"); CounterAppGateway appGateway = CounterAppGateway(appGatewayAddress); - uint256 availableFees = feesManager.getAvailableFees( - 421614, - appGatewayAddress, - ETH_ADDRESS - ); + uint256 availableFees = feesManager.getAvailableFees(appGatewayAddress); console.log("Available fees:", availableFees); if (availableFees > 0) { diff --git a/script/helpers/AppGatewayFeeBalance.s.sol b/script/helpers/AppGatewayFeeBalance.s.sol index a1faade9..d2ba38d8 100644 --- a/script/helpers/AppGatewayFeeBalance.s.sol +++ b/script/helpers/AppGatewayFeeBalance.s.sol @@ -11,21 +11,14 @@ contract CheckDepositedFees is Script { vm.createSelectFork(vm.envString("EVMX_RPC")); FeesManager feesManager = FeesManager(payable(vm.envAddress("FEES_MANAGER"))); address appGateway = vm.envAddress("APP_GATEWAY"); - uint32 chain = 421614; - address token = ETH_ADDRESS; - (uint256 deposited, uint256 blocked) = feesManager.userFeeBalances( - appGateway, - chain, - token - ); + + (uint256 totalCredits, uint256 blockedCredits) = feesManager.userCredits(appGateway); console.log("App Gateway:", appGateway); console.log("Fees Manager:", address(feesManager)); - console.logUint(chain); - console.log("Token:", token); - console.log("Deposited fees:", deposited); - console.log("Blocked fees:", blocked); + console.log("totalCredits fees:", totalCredits); + console.log("blockedCredits fees:", blockedCredits); - uint256 availableFees = feesManager.getAvailableFees(chain, appGateway, token); + uint256 availableFees = feesManager.getAvailableFees(appGateway); console.log("Available fees:", availableFees); } } diff --git a/script/helpers/PayFeesInArbitrumETH.s.sol b/script/helpers/PayFeesInArbitrumETH.s.sol index 6fbd7164..9bf56848 100644 --- a/script/helpers/PayFeesInArbitrumETH.s.sol +++ b/script/helpers/PayFeesInArbitrumETH.s.sol @@ -23,6 +23,6 @@ contract DepositFees is Script { console.log("App Gateway:", appGateway); console.log("Fees Plug:", address(feesPlug)); uint feesAmount = 0.001 ether; - feesPlug.deposit{value: feesAmount}(ETH_ADDRESS, appGateway, feesAmount); + feesPlug.depositToFeeAndNative(ETH_ADDRESS, feesAmount, appGateway); } } diff --git a/test/DeliveryHelper.t.sol b/test/DeliveryHelper.t.sol index 45877410..e915a59d 100644 --- a/test/DeliveryHelper.t.sol +++ b/test/DeliveryHelper.t.sol @@ -175,7 +175,7 @@ contract DeliveryHelperTest is SetupTest { function depositFees(address appGateway_, OnChainFees memory fees_) internal { SocketContracts memory socketConfig = getSocketConfig(fees_.chainSlug); - socketConfig.feesPlug.deposit{value: fees_.amount}(fees_.token, appGateway_, fees_.amount); + socketConfig.feesPlug.depositToFeeAndNative(fees_.token, fees_.amount, appGateway_); bytes memory bytesInput = abi.encode( fees_.chainSlug, @@ -189,8 +189,8 @@ contract DeliveryHelperTest is SetupTest { ); bytes memory sig = _createSignature(digest, watcherPrivateKey); feesManager.incrementFeesDeposited( - fees_.chainSlug, appGateway_, + fees_.chainSlug, fees_.token, fees_.amount, signatureNonce++, diff --git a/test/FeesTest.t.sol b/test/FeesTest.t.sol index 7116c8f4..4755cb15 100644 --- a/test/FeesTest.t.sol +++ b/test/FeesTest.t.sol @@ -51,7 +51,8 @@ contract FeesTest is DeliveryHelperTest { uint40 requestCount = feesManager.withdrawTransmitterFees( feesChainSlug, ETH_ADDRESS, - address(receiver) + address(receiver), + feesManager.transmitterCredits(transmitterEOA) ); uint40[] memory batches = watcherPrecompile.getBatches(requestCount); _finalizeBatch(batches[0], new bytes[](0), 0, false); diff --git a/test/SetupTest.t.sol b/test/SetupTest.t.sol index d5d699c9..69237ec1 100644 --- a/test/SetupTest.t.sol +++ b/test/SetupTest.t.sol @@ -404,7 +404,7 @@ contract SetupTest is Test { uint32 chainSlug_, address token_, uint256 amount_ - ) internal returns (OnChainFees memory) { + ) internal pure returns (OnChainFees memory) { return OnChainFees({chainSlug: chainSlug_, token: token_, amount: amount_}); } } diff --git a/test/apps/app-gateways/counter/CounterAppGateway.sol b/test/apps/app-gateways/counter/CounterAppGateway.sol index dc232047..eb5e6e7e 100644 --- a/test/apps/app-gateways/counter/CounterAppGateway.sol +++ b/test/apps/app-gateways/counter/CounterAppGateway.sol @@ -25,7 +25,7 @@ contract CounterAppGateway is AppGatewayBase, Ownable { } // deploy contracts - function deployContracts(uint32 chainSlug_) external async(address(this)) { + function deployContracts(uint32 chainSlug_) external async(bytes("")) { _deploy(counter, chainSlug_, IsPlug.YES); } @@ -33,14 +33,14 @@ contract CounterAppGateway is AppGatewayBase, Ownable { _deploy(counter, chainSlug_, IsPlug.YES); } - function deployParallelContracts(uint32 chainSlug_) external async(address(this)) { + function deployParallelContracts(uint32 chainSlug_) external async(bytes("")) { _setOverrides(Parallel.ON); _deploy(counter, chainSlug_, IsPlug.YES); _deploy(counter1, chainSlug_, IsPlug.YES); _setOverrides(Parallel.OFF); } - function deployMultiChainContracts(uint32[] memory chainSlugs_) external async(address(this)) { + function deployMultiChainContracts(uint32[] memory chainSlugs_) external async(bytes("")) { _setOverrides(Parallel.ON); for (uint32 i = 0; i < chainSlugs_.length; i++) { _deploy(counter, chainSlugs_[i], IsPlug.YES); @@ -53,7 +53,7 @@ contract CounterAppGateway is AppGatewayBase, Ownable { return; } - function incrementCounters(address[] memory instances_) public async(address(this)) { + function incrementCounters(address[] memory instances_) public async(bytes("")) { // the increase function is called on given list of instances // this for (uint256 i = 0; i < instances_.length; i++) { @@ -69,7 +69,7 @@ contract CounterAppGateway is AppGatewayBase, Ownable { } } - function readCounters(address[] memory instances_) public async(address(this)) { + function readCounters(address[] memory instances_) public async(bytes("")) { // the increase function is called on given list of instances _setOverrides(Read.ON, Parallel.ON); for (uint256 i = 0; i < instances_.length; i++) { @@ -83,7 +83,7 @@ contract CounterAppGateway is AppGatewayBase, Ownable { function readCounterAtBlock( address instance_, uint256 blockNumber_ - ) public async(address(this)) { + ) public async(bytes("")) { uint32 chainSlug = IForwarder(instance_).getChainSlug(); _setOverrides(Read.ON, Parallel.ON, blockNumber_); ICounter(instance_).getCounter(); @@ -136,12 +136,12 @@ contract CounterAppGateway is AppGatewayBase, Ownable { return _withdrawFeeTokens(chainSlug_, token_, amount_, receiver_); } - function testOnChainRevert(uint32 chainSlug) public async(address(this)) { + function testOnChainRevert(uint32 chainSlug) public async(bytes("")) { address instance = forwarderAddresses[counter][chainSlug]; ICounter(instance).wrongFunction(); } - function testCallBackRevert(uint32 chainSlug) public async(address(this)) { + function testCallBackRevert(uint32 chainSlug) public async(bytes("")) { // the increase function is called on given list of instances _setOverrides(Read.ON, Parallel.ON); address instance = forwarderAddresses[counter][chainSlug]; diff --git a/test/apps/app-gateways/super-token/SuperTokenAppGateway.sol b/test/apps/app-gateways/super-token/SuperTokenAppGateway.sol index c10045e0..8e38065d 100644 --- a/test/apps/app-gateways/super-token/SuperTokenAppGateway.sol +++ b/test/apps/app-gateways/super-token/SuperTokenAppGateway.sol @@ -49,7 +49,7 @@ contract SuperTokenAppGateway is AppGatewayBase, Ownable { _initializeOwner(owner_); } - function deployContracts(uint32 chainSlug_) external async { + function deployContracts(uint32 chainSlug_) external async(bytes("")) { bytes memory initData = abi.encodeWithSelector(SuperToken.setOwner.selector, owner()); _deploy(superToken, chainSlug_, IsPlug.YES, initData); } @@ -60,7 +60,7 @@ contract SuperTokenAppGateway is AppGatewayBase, Ownable { return; } - function transfer(bytes memory order_) external async { + function transfer(bytes memory order_) external async(bytes("")) { TransferOrder memory order = abi.decode(order_, (TransferOrder)); ISuperToken(order.srcToken).burn(order.user, order.srcAmount); ISuperToken(order.dstToken).mint(order.user, order.srcAmount); From 8c48ba3697e6ba8d503fa8a842da46a19f1713ca Mon Sep 17 00:00:00 2001 From: Ameesha Agrawal Date: Mon, 21 Apr 2025 21:01:24 +0530 Subject: [PATCH 13/21] wip: consume limit --- contracts/protocol/utils/common/Structs.sol | 3 +++ .../WatcherPrecompileLimits.sol | 20 ++++++++----------- .../watcherPrecompile/core/RequestHandler.sol | 5 ++++- .../core/WatcherPrecompileCore.sol | 1 + 4 files changed, 16 insertions(+), 13 deletions(-) diff --git a/contracts/protocol/utils/common/Structs.sol b/contracts/protocol/utils/common/Structs.sol index 4dcffff9..118d772d 100644 --- a/contracts/protocol/utils/common/Structs.sol +++ b/contracts/protocol/utils/common/Structs.sol @@ -210,6 +210,9 @@ struct RequestParams { // updated while processing request uint256 currentBatchPayloadsLeft; uint256 payloadsRemaining; + uint256 queryCount; + uint256 finalizeCount; + uint256 scheduleCount; address middleware; // updated after auction address transmitter; diff --git a/contracts/protocol/watcherPrecompile/WatcherPrecompileLimits.sol b/contracts/protocol/watcherPrecompile/WatcherPrecompileLimits.sol index f933b29a..f0f2921a 100644 --- a/contracts/protocol/watcherPrecompile/WatcherPrecompileLimits.sol +++ b/contracts/protocol/watcherPrecompile/WatcherPrecompileLimits.sol @@ -43,10 +43,6 @@ contract WatcherPrecompileLimits is // Mapping to track active app gateways mapping(address => bool) internal _activeAppGateways; - // slot 156: precompileCount - // limitType => requestCount => count - mapping(bytes32 => mapping(uint40 => uint256)) public precompileCount; - // slot 157: fees uint256 public queryFees; uint256 public finalizeFees; @@ -155,8 +151,6 @@ contract WatcherPrecompileLimits is } // Update the limit - // precompileCount[limitType_][requestCount_] += consumeLimit_; - _consumeFullLimit(consumeLimit_ * 10 ** limitDecimals, limitParams); } @@ -202,14 +196,16 @@ contract WatcherPrecompileLimits is revert WatcherFeesNotSet(CALLBACK); } - uint256 totalCallbacks = precompileCount[QUERY][requestCount_] + - precompileCount[FINALIZE][requestCount_] + - precompileCount[SCHEDULE][requestCount_]; + uint256 queryCount = watcherPrecompile__().requestParams[requestCount_].queryCount; + uint256 finalizeCount = watcherPrecompile__().requestParams[requestCount_].finalizeCount; + uint256 scheduleCount = watcherPrecompile__().requestParams[requestCount_].scheduleCount; + + uint256 totalCallbacks = queryCount + finalizeCount + scheduleCount; totalFees += totalCallbacks * callBackFees; - totalFees += precompileCount[QUERY][requestCount_] * queryFees; - totalFees += precompileCount[FINALIZE][requestCount_] * finalizeFees; - totalFees += precompileCount[SCHEDULE][requestCount_] * scheduleFees; + totalFees += queryCount * queryFees; + totalFees += finalizeCount * finalizeFees; + totalFees += scheduleCount * scheduleFees; return totalFees; } diff --git a/contracts/protocol/watcherPrecompile/core/RequestHandler.sol b/contracts/protocol/watcherPrecompile/core/RequestHandler.sol index c0508f71..e6b32c08 100644 --- a/contracts/protocol/watcherPrecompile/core/RequestHandler.sol +++ b/contracts/protocol/watcherPrecompile/core/RequestHandler.sol @@ -20,7 +20,7 @@ abstract contract RequestHandler is WatcherPrecompileCore { /// @dev It also consumes limits for the app gateway based on the number of reads and writes function submitRequest( PayloadSubmitParams[] calldata payloadSubmitParams_, - RequestMetadata calldata + RequestMetadata calldata requestMetadata_ ) public returns (uint40 requestCount) { address appGateway = _checkAppGateways(payloadSubmitParams_); @@ -93,6 +93,9 @@ abstract contract RequestHandler is WatcherPrecompileCore { watcherPrecompileLimits__.consumeLimit(appGateway, QUERY, readCount); watcherPrecompileLimits__.consumeLimit(appGateway, FINALIZE, writeCount); + requestParams[requestCount].queryCount = readCount; + requestParams[requestCount].finalizeCount = writeCount; + requestParams[requestCount].currentBatch = currentBatch; requestParams[requestCount].payloadsRemaining = payloadSubmitParams_.length; requestParams[requestCount].middleware = msg.sender; diff --git a/contracts/protocol/watcherPrecompile/core/WatcherPrecompileCore.sol b/contracts/protocol/watcherPrecompile/core/WatcherPrecompileCore.sol index 2e0a6d5a..53928cd8 100644 --- a/contracts/protocol/watcherPrecompile/core/WatcherPrecompileCore.sol +++ b/contracts/protocol/watcherPrecompile/core/WatcherPrecompileCore.sol @@ -56,6 +56,7 @@ abstract contract WatcherPrecompileCore is SCHEDULE, 1 ); + // emits event for watcher to track timeout and resolve when timeout is reached emit TimeoutRequested(timeoutId, msg.sender, payload_, executeAt); From 8658b9546a8980ca01392065a25bfe2b748e4942 Mon Sep 17 00:00:00 2001 From: Akash Date: Tue, 22 Apr 2025 16:49:24 +0530 Subject: [PATCH 14/21] fix: fee tests --- contracts/base/AppGatewayBase.sol | 4 +- contracts/interfaces/IFeesManager.sol | 25 ++- contracts/interfaces/IFeesPlug.sol | 19 +-- contracts/interfaces/IMiddleware.sol | 2 +- contracts/interfaces/IWatcherPrecompile.sol | 3 +- .../interfaces/IWatcherPrecompileLimits.sol | 13 +- .../payload-delivery/AuctionManager.sol | 31 ++-- .../protocol/payload-delivery/FeesManager.sol | 117 +++++++------- .../protocol/payload-delivery/FeesPlug.sol | 82 +++------- .../app-gateway/DeliveryHelper.sol | 2 +- .../app-gateway/FeesHelpers.sol | 38 +++++ .../app-gateway/RequestQueue.sol | 147 +++++++++++------- contracts/protocol/utils/common/Structs.sol | 19 +++ .../WatcherPrecompileConfig.sol | 6 +- .../WatcherPrecompileLimits.sol | 34 ++-- .../watcherPrecompile/core/RequestHandler.sol | 5 +- .../core/WatcherPrecompileCore.sol | 15 +- script/helpers/PayFeesInArbitrumETH.s.sol | 2 +- test/DeliveryHelper.t.sol | 48 +++++- test/FeesTest.t.sol | 53 ++++--- test/SetupTest.t.sol | 15 +- test/apps/Counter.t.sol | 8 +- test/apps/ParallelCounter.t.sol | 8 +- test/apps/SuperToken.t.sol | 8 +- test/apps/app-gateways/USDC.sol | 41 +++++ .../counter/CounterAppGateway.sol | 7 +- .../super-token/SuperTokenAppGateway.sol | 2 +- 27 files changed, 455 insertions(+), 299 deletions(-) create mode 100644 test/apps/app-gateways/USDC.sol diff --git a/contracts/base/AppGatewayBase.sol b/contracts/base/AppGatewayBase.sol index 3f252074..572ee516 100644 --- a/contracts/base/AppGatewayBase.sol +++ b/contracts/base/AppGatewayBase.sol @@ -34,7 +34,9 @@ abstract contract AppGatewayBase is AddressResolverUtil, IAppGateway { function _postAsync(bytes memory feesApprovalData_) internal { isAsyncModifierSet = false; - + if (feesApprovalData_.length == 0) { + feesApprovalData_ = abi.encode(address(this), address(this), true, new bytes(0)); + } // todo: cache the feesApprovalData for next async in same request deliveryHelper__().batch(maxFees, auctionManager, feesApprovalData_, onCompleteData); _markValidPromises(); diff --git a/contracts/interfaces/IFeesManager.sol b/contracts/interfaces/IFeesManager.sol index 71b0eb70..7aa27314 100644 --- a/contracts/interfaces/IFeesManager.sol +++ b/contracts/interfaces/IFeesManager.sol @@ -1,7 +1,7 @@ // SPDX-License-Identifier: GPL-3.0-only pragma solidity ^0.8.21; -import {Bid, QueuePayloadParams} from "../protocol/utils/common/Structs.sol"; +import {Bid, QueuePayloadParams, PayloadSubmitParams, AppGatewayWhitelistParams} from "../protocol/utils/common/Structs.sol"; interface IFeesManager { function blockFees( @@ -35,7 +35,28 @@ interface IFeesManager { function assignWatcherPrecompileFeesFromAddress(uint256 fees_, address consumeFrom_) external; - function setAppGatewayWhitelist( + function incrementFeesDeposited( + address depositTo_, + uint32 chainSlug_, + address token_, + uint256 amount_, + uint256 signatureNonce_, + bytes memory signature_ + ) external; + + function whitelistAppGatewayWithSignature( bytes memory feeApprovalData_ ) external returns (address consumeFrom, address appGateway, bool isApproved); + + function whitelistAppGateways(AppGatewayWhitelistParams[] calldata params_) external; + + function getWithdrawTransmitterFeesPayloadParams( + address transmitter_, + uint32 chainSlug_, + address token_, + address receiver_, + uint256 amount_ + ) external returns (PayloadSubmitParams[] memory); + + function getMaxFeesAvailableForWithdraw(address transmitter_) external view returns (uint256); } diff --git a/contracts/interfaces/IFeesPlug.sol b/contracts/interfaces/IFeesPlug.sol index edcd1aef..46797d62 100644 --- a/contracts/interfaces/IFeesPlug.sol +++ b/contracts/interfaces/IFeesPlug.sol @@ -2,22 +2,11 @@ pragma solidity ^0.8.21; interface IFeesPlug { - function balanceOf(address token_) external view returns (uint256); + function depositToFee(address token_, address receiver_, uint256 amount_) external; - function feesRedeemed(bytes32 feesId_) external view returns (bool); + function depositToFeeAndNative(address token_, address receiver_, uint256 amount_) external; - function depositToFee(address token_, uint256 amount_, address receiver_) external; + function depositToNative(address token_, address receiver_, uint256 amount_) external; - function depositToFeeAndNative(address token_, uint256 amount_, address receiver_) external; - - function depositToNative(address token_, uint256 amount_, address receiver_) external; - - function distributeFee( - address feeToken_, - uint256 fee_, - address transmitter_, - bytes32 feesId_ - ) external; - - function withdrawFees(address token_, uint256 amount_, address receiver_) external; + function withdrawFees(address token_, address receiver_, uint256 amount_) external; } diff --git a/contracts/interfaces/IMiddleware.sol b/contracts/interfaces/IMiddleware.sol index d8714e91..00099830 100644 --- a/contracts/interfaces/IMiddleware.sol +++ b/contracts/interfaces/IMiddleware.sol @@ -1,6 +1,6 @@ // SPDX-License-Identifier: GPL-3.0-only pragma solidity ^0.8.21; -import {PayloadSubmitParams, QueuePayloadParams, Bid, WriteFinality, CallType, Parallel, IsPlug, RequestMetadata} from "../protocol/utils/common/Structs.sol"; +import {PayloadSubmitParams, QueuePayloadParams, Bid, WriteFinality, BatchParams, CallType, Parallel, IsPlug, RequestMetadata} from "../protocol/utils/common/Structs.sol"; /// @title IMiddleware /// @notice Interface for the Middleware contract diff --git a/contracts/interfaces/IWatcherPrecompile.sol b/contracts/interfaces/IWatcherPrecompile.sol index 54d7d08e..1265f677 100644 --- a/contracts/interfaces/IWatcherPrecompile.sol +++ b/contracts/interfaces/IWatcherPrecompile.sol @@ -174,8 +174,7 @@ interface IWatcherPrecompile { function setExpiryTime(uint256 expiryTime_) external; function submitRequest( - PayloadSubmitParams[] calldata payloadSubmitParams, - RequestMetadata memory requestMetadata + PayloadSubmitParams[] calldata payloadSubmitParams ) external returns (uint40 requestCount); function startProcessingRequest(uint40 requestCount, address transmitter) external; diff --git a/contracts/interfaces/IWatcherPrecompileLimits.sol b/contracts/interfaces/IWatcherPrecompileLimits.sol index 2bede009..9179706a 100644 --- a/contracts/interfaces/IWatcherPrecompileLimits.sol +++ b/contracts/interfaces/IWatcherPrecompileLimits.sol @@ -46,13 +46,14 @@ interface IWatcherPrecompileLimits { /// @param appGateway_ The app gateway address /// @param limitType_ The type of limit to consume /// @param consumeLimit_ The amount of limit to consume - function consumeLimit( - address appGateway_, - bytes32 limitType_, - uint256 consumeLimit_ - ) external; + function consumeLimit(address appGateway_, bytes32 limitType_, uint256 consumeLimit_) external; - function getTotalFeesRequired(uint40 requestCount_) external view returns (uint256); + 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); diff --git a/contracts/protocol/payload-delivery/AuctionManager.sol b/contracts/protocol/payload-delivery/AuctionManager.sol index 8e51ba64..818e28b3 100644 --- a/contracts/protocol/payload-delivery/AuctionManager.sol +++ b/contracts/protocol/payload-delivery/AuctionManager.sol @@ -112,7 +112,7 @@ contract AuctionManager is ); if (!_hasRole(TRANSMITTER_ROLE, transmitter)) revert InvalidTransmitter(); - uint256 transmitterCredits = getTransmitterMaxFeesRequired(requestCount_); + 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(); @@ -143,6 +143,22 @@ contract AuctionManager is 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 { @@ -206,19 +222,6 @@ contract AuctionManager is emit AuctionStarted(requestCount_); } - function getTransmitterMaxFeesRequired( - 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(requestCount_); - return requestMetadata.maxFees - watcherFees; - } - function _recoverSigner( bytes32 digest_, bytes memory signature_ diff --git a/contracts/protocol/payload-delivery/FeesManager.sol b/contracts/protocol/payload-delivery/FeesManager.sol index 99f59885..4f13a64e 100644 --- a/contracts/protocol/payload-delivery/FeesManager.sol +++ b/contracts/protocol/payload-delivery/FeesManager.sol @@ -6,12 +6,12 @@ import "solady/utils/Initializable.sol"; import "solady/utils/ECDSA.sol"; import {IFeesPlug} from "../../interfaces/IFeesPlug.sol"; -import {IFeesManager} from "../../interfaces/IFeesManager.sol"; +import "../../interfaces/IFeesManager.sol"; import {AddressResolverUtil} from "../utils/AddressResolverUtil.sol"; import {NotAuctionManager, InvalidWatcherSignature, NonceUsed} from "../utils/common/Errors.sol"; import {Bid, CallType, Parallel, WriteFinality, QueuePayloadParams, IsPlug, PayloadSubmitParams, RequestMetadata, UserCredits} from "../utils/common/Structs.sol"; - +import {console} from "forge-std/console.sol"; abstract contract FeesManagerStorage is IFeesManager { // slots [0-49] reserved for gap uint256[50] _gap_before; @@ -32,8 +32,8 @@ abstract contract FeesManagerStorage is IFeesManager { mapping(address => uint256) public userNonce; // token pool balances - // token address => chainSlug => amount - mapping(address => mapping(uint32 => uint256)) public tokenPoolBalances; + // chainSlug => token address => amount + mapping(uint32 => mapping(address => uint256)) public tokenPoolBalances; // user approved app gateways // userAddress => appGateway => isWhitelisted @@ -123,7 +123,8 @@ contract FeesManager is FeesManagerStorage, Initializable, Ownable, AddressResol error NoFeesBlocked(); /// @notice Error thrown when caller is invalid error InvalidCaller(); - + /// @notice Error thrown when user signature is invalid + error InvalidUserSignature(); constructor() { _disableInitializers(); // disable for implementation } @@ -168,14 +169,20 @@ contract FeesManager is FeesManagerStorage, Initializable, Ownable, AddressResol bytes memory signature_ ) external { _isWatcherSignatureValid( - abi.encode(depositTo_, chainSlug_, token_, amount_), + abi.encode( + this.incrementFeesDeposited.selector, + depositTo_, + chainSlug_, + token_, + amount_ + ), signatureNonce_, signature_ ); UserCredits storage userCredit = userCredits[depositTo_]; userCredit.totalCredits += amount_; - tokenPoolBalances[token_][chainSlug_] += amount_; + tokenPoolBalances[chainSlug_][token_] += amount_; emit FeesDepositedUpdated(chainSlug_, depositTo_, token_, amount_); } @@ -185,19 +192,21 @@ contract FeesManager is FeesManagerStorage, Initializable, Ownable, AddressResol address appGateway_, uint256 amount_ ) external view returns (bool) { - if (!isAppGatewayWhitelisted[consumeFrom_][appGateway_]) return false; + // If consumeFrom is not appGateway, check if it is whitelisted + if (consumeFrom_ != appGateway_ && !isAppGatewayWhitelisted[consumeFrom_][appGateway_]) + return false; return getAvailableFees(consumeFrom_) >= amount_; } function _processFeeApprovalData( bytes memory feeApprovalData_ - ) internal returns (address consumeFrom, address appGateway, bool isApproved) { - bytes memory signature_; - (consumeFrom, appGateway, isApproved, signature_) = abi.decode( - feeApprovalData_, - (address, address, bool, bytes) - ); - if (signature_.length == 0) return (consumeFrom, appGateway, isApproved); + ) 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), @@ -208,24 +217,24 @@ contract FeesManager is FeesManagerStorage, Initializable, Ownable, AddressResol isApproved ) ); - if (ECDSA.recover(digest, signature_) != consumeFrom) revert InvalidWatcherSignature(); + if (ECDSA.recover(digest, signature_) != consumeFrom) revert InvalidUserSignature(); isAppGatewayWhitelisted[consumeFrom][appGateway] = isApproved; userNonce[consumeFrom]++; return (consumeFrom, appGateway, isApproved); } - function setAppGatewayWhitelist( + 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 appGateways_ Array of app gateway addresses to whitelist - function whitelistApps(address[] calldata appGateways_) external { - for (uint256 i = 0; i < appGateways_.length; i++) { - isAppGatewayWhitelisted[msg.sender][appGateways_[i]] = true; + /// @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; } } @@ -294,6 +303,7 @@ contract FeesManager is FeesManagerStorage, Initializable, Ownable, AddressResol function _useAvailableUserCredits(address consumeFrom_, uint256 toConsume_) internal { UserCredits storage userCredit = userCredits[consumeFrom_]; + if (userCredit.totalCredits < toConsume_) revert InsufficientFeesAvailable(); userCredit.totalCredits -= toConsume_; } @@ -301,7 +311,6 @@ contract FeesManager is FeesManagerStorage, Initializable, Ownable, AddressResol uint256 fees_, uint40 requestCount_ ) external onlyWatcherPrecompile { - require(requestCount_ != 0, "Request count cannot be 0"); RequestMetadata memory requestMetadata = deliveryHelper__().getRequestMetadata( requestCount_ ); @@ -365,37 +374,31 @@ contract FeesManager is FeesManagerStorage, Initializable, Ownable, AddressResol uint256 availableAmount = getAvailableFees(source); if (availableAmount < amount_) revert InsufficientFeesAvailable(); - tokenPoolBalances[token_][chainSlug_] -= amount_; + tokenPoolBalances[chainSlug_][token_] -= amount_; // Add it to the queue and submit request - _queue(chainSlug_, abi.encodeCall(IFeesPlug.withdrawFees, (token_, amount_, receiver_))); + _queue(chainSlug_, abi.encodeCall(IFeesPlug.withdrawFees, (token_, receiver_, amount_))); } /// @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( + function getWithdrawTransmitterFeesPayloadParams( + address transmitter_, uint32 chainSlug_, address token_, address receiver_, uint256 amount_ - ) external returns (uint40 requestCount) { - address transmitter = msg.sender; - // Get total fees for the transmitter in given chain and token - uint256 totalFees = transmitterCredits[transmitter]; - if (totalFees >= amount_) revert InsufficientFeesAvailable(); + ) external onlyDeliveryHelper returns (PayloadSubmitParams[] memory) { + uint256 maxFeesAvailableForWithdraw = getMaxFeesAvailableForWithdraw(transmitter_); + if (amount_ > maxFeesAvailableForWithdraw) revert InsufficientFeesAvailable(); // Clean up storage - transmitterCredits[transmitter] -= amount_; - tokenPoolBalances[token_][chainSlug_] -= amount_; - - // Create fee distribution payload - bytes32 feesId = _encodeFeesId(feesCounter++); - bytes memory payload = abi.encodeCall( - IFeesPlug.distributeFee, - (token_, amount_, receiver_, feesId) - ); + transmitterCredits[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, @@ -412,31 +415,15 @@ contract FeesManager is FeesManagerStorage, Initializable, Ownable, AddressResol readAt: 0, payload: payload }); - - RequestMetadata memory requestMetadata = RequestMetadata({ - appGateway: address(this), - auctionManager: address(0), - maxFees: 0, - winningBid: Bid({transmitter: transmitter, fee: 0, extraData: new bytes(0)}), - onCompleteData: bytes(""), - onlyReadRequests: false, - consumeFrom: address(0) - }); // finalize for plug contract - return _submitAndStartProcessing(payloadSubmitParamsArray, requestMetadata, transmitter); + return payloadSubmitParamsArray; } - function _submitAndStartProcessing( - PayloadSubmitParams[] memory payloadSubmitParamsArray_, - RequestMetadata memory requestMetadata_, - address transmitter_ - ) internal returns (uint40 requestCount) { - requestCount = watcherPrecompile__().submitRequest( - payloadSubmitParamsArray_, - requestMetadata_ - ); - - // same transmitter can execute requests without auction - watcherPrecompile__().startProcessingRequest(requestCount, transmitter_); + function getMaxFeesAvailableForWithdraw(address transmitter_) public view returns (uint256) { + uint256 watcherFees = watcherPrecompileLimits().getTotalFeesRequired(0, 1, 0, 1); + return + transmitterCredits[transmitter_] > watcherFees + ? transmitterCredits[transmitter_] - watcherFees + : 0; } function _getSwitchboard(uint32 chainSlug_) internal view returns (address) { @@ -487,14 +474,16 @@ contract FeesManager is FeesManagerStorage, Initializable, Ownable, AddressResol } function _isWatcherSignatureValid( - bytes memory digest_, + bytes memory inputData_, uint256 signatureNonce_, bytes memory signature_ ) internal { if (isNonceUsed[signatureNonce_]) revert NonceUsed(); isNonceUsed[signatureNonce_] = true; - bytes32 digest = keccak256(abi.encode(address(this), evmxSlug, signatureNonce_, digest_)); + bytes32 digest = keccak256( + abi.encode(address(this), evmxSlug, signatureNonce_, inputData_) + ); digest = keccak256(abi.encodePacked("\x19Ethereum Signed Message:\n32", digest)); // recovered signer is checked for the valid roles later address signer = ECDSA.recover(digest, signature_); diff --git a/contracts/protocol/payload-delivery/FeesPlug.sol b/contracts/protocol/payload-delivery/FeesPlug.sol index ab6784ce..8a55d9ac 100644 --- a/contracts/protocol/payload-delivery/FeesPlug.sol +++ b/contracts/protocol/payload-delivery/FeesPlug.sol @@ -2,6 +2,7 @@ pragma solidity ^0.8.21; import "solady/utils/SafeTransferLib.sol"; +import "solady/tokens/ERC20.sol"; import "../../base/PlugBase.sol"; import "../utils/AccessControl.sol"; import {RESCUE_ROLE} from "../utils/common/AccessRoles.sol"; @@ -15,32 +16,29 @@ import {InvalidTokenAddress, FeesAlreadyPaid} from "../utils/common/Errors.sol"; /// @dev The amount deposited here is locked and updated in the EVMx for an app gateway /// @dev The fees are redeemed by the transmitters executing request or can be withdrawn by the owner contract FeesPlug is IFeesPlug, PlugBase, AccessControl { - /// @notice Mapping to store the balance of each token - mapping(address => uint256) public override balanceOf; - /// @notice Mapping to store if fees have been redeemed for a given fees ID - mapping(bytes32 => bool) public override feesRedeemed; /// @notice Mapping to store if a token is whitelisted mapping(address => bool) public whitelistedTokens; /// @notice Error thrown when balance is not enough to cover fees - error InsufficientTokenBalance(address token_); + error InsufficientTokenBalance(address token_, uint256 balance_, uint256 fee_); /// @notice Error thrown when deposit amount does not match msg.value error InvalidDepositAmount(); /// @notice Error thrown when token is not whitelisted error TokenNotWhitelisted(address token_); /// @notice Event emitted when fees are deposited - event FeesDeposited(address receiver, address token, uint256 feeAmount, uint256 nativeAmount); + event FeesDeposited(address token, address receiver, uint256 feeAmount, uint256 nativeAmount); /// @notice Event emitted when fees are withdrawn - event FeesWithdrawn(address token, uint256 amount, address receiver); + event FeesWithdrawn(address token, address receiver, uint256 amount); /// @notice Event emitted when a token is whitelisted event TokenWhitelisted(address token); /// @notice Event emitted when a token is removed from whitelist event TokenRemovedFromWhitelist(address token); /// @notice Modifier to check if the balance of a token is enough to withdraw - modifier isFeesEnough(uint256 fee_, address feeToken_) { - if (balanceOf[feeToken_] < fee_) revert InsufficientTokenBalance(feeToken_); + modifier isFeesEnough(address feeToken_, uint256 fee_) { + uint balance_ = ERC20(feeToken_).balanceOf(address(this)); + if (balance_ < fee_) revert InsufficientTokenBalance(feeToken_, balance_, fee_); _; } @@ -52,54 +50,34 @@ contract FeesPlug is IFeesPlug, PlugBase, AccessControl { _initializeOwner(owner_); } - /// @notice Distributes fees to the transmitter - /// @param feeToken_ The token address - /// @param fee_ The amount of fees - /// @param transmitter_ The transmitter address - /// @param feesId_ The fees ID - function distributeFee( - address feeToken_, - uint256 fee_, - address transmitter_, - bytes32 feesId_ - ) external override onlySocket isFeesEnough(fee_, feeToken_) { - if (feesRedeemed[feesId_]) revert FeesAlreadyPaid(); - feesRedeemed[feesId_] = true; - balanceOf[feeToken_] -= fee_; - - _transferTokens(feeToken_, fee_, transmitter_); - } - /// @notice Withdraws fees /// @param token_ The token address /// @param amount_ The amount /// @param receiver_ The receiver address function withdrawFees( address token_, - uint256 amount_, - address receiver_ - ) external override onlySocket isFeesEnough(amount_, token_) { - balanceOf[token_] -= amount_; - - _transferTokens(token_, amount_, receiver_); - emit FeesWithdrawn(token_, amount_, receiver_); + address receiver_, + uint256 amount_ + ) external override onlySocket isFeesEnough(token_, amount_) { + SafeTransferLib.safeTransfer(token_, receiver_, amount_); + emit FeesWithdrawn(token_, receiver_, amount_); } - function depositToFee(address token_, uint256 amount_, address receiver_) external override { + function depositToFee(address token_, address receiver_, uint256 amount_) external override { _deposit(token_, receiver_, amount_, 0); } function depositToFeeAndNative( address token_, - uint256 amount_, - address receiver_ + address receiver_, + uint256 amount_ ) external override { uint256 nativeAmount_ = amount_ / 10; uint256 feeAmount_ = amount_ - nativeAmount_; _deposit(token_, receiver_, feeAmount_, nativeAmount_); } - function depositToNative(address token_, uint256 amount_, address receiver_) external override { + function depositToNative(address token_, address receiver_, uint256 amount_) external override { _deposit(token_, receiver_, 0, amount_); } @@ -116,31 +94,14 @@ contract FeesPlug is IFeesPlug, PlugBase, AccessControl { ) internal { uint256 totalAmount_ = feeAmount_ + nativeAmount_; if (!whitelistedTokens[token_]) revert TokenNotWhitelisted(token_); - if (token_.code.length == 0) revert InvalidTokenAddress(); - - balanceOf[token_] += totalAmount_; + SafeTransferLib.safeTransferFrom(token_, msg.sender, address(this), totalAmount_); emit FeesDeposited(receiver_, token_, feeAmount_, nativeAmount_); } - /// @notice Transfers tokens - /// @param token_ The token address - /// @param amount_ The amount - /// @param receiver_ The receiver address - function _transferTokens(address token_, uint256 amount_, address receiver_) internal { - SafeTransferLib.safeTransfer(token_, receiver_, amount_); - } - - function connectSocket( - address appGateway_, - address socket_, - address switchboard_ - ) external onlyOwner { - _connectSocket(appGateway_, socket_, switchboard_); - } - /// @notice Adds a token to the whitelist /// @param token_ The token address to whitelist function whitelistToken(address token_) external onlyOwner { + if (token_.code.length == 0) revert InvalidTokenAddress(); whitelistedTokens[token_] = true; emit TokenWhitelisted(token_); } @@ -152,6 +113,13 @@ contract FeesPlug is IFeesPlug, PlugBase, AccessControl { emit TokenRemovedFromWhitelist(token_); } + function connectSocket( + address appGateway_, + address socket_, + address switchboard_ + ) external onlyOwner { + _connectSocket(appGateway_, socket_, switchboard_); + } /** * @notice Rescues funds from the contract if they are locked by mistake. This contract does not * theoretically need this function but it is added for safety. diff --git a/contracts/protocol/payload-delivery/app-gateway/DeliveryHelper.sol b/contracts/protocol/payload-delivery/app-gateway/DeliveryHelper.sol index 6efaedfd..81b1464f 100644 --- a/contracts/protocol/payload-delivery/app-gateway/DeliveryHelper.sol +++ b/contracts/protocol/payload-delivery/app-gateway/DeliveryHelper.sol @@ -38,7 +38,7 @@ contract DeliveryHelper is FeesHelpers { RequestMetadata storage requestMetadata_ = requests[requestCount_]; // if a transmitter was already assigned, it means the request was restarted bool isRestarted = requestMetadata_.winningBid.transmitter != address(0); - requestMetadata_.winningBid.transmitter = winningBid_.transmitter; + requestMetadata_.winningBid = winningBid_; if (!isRestarted) { watcherPrecompile__().startProcessingRequest(requestCount_, winningBid_.transmitter); diff --git a/contracts/protocol/payload-delivery/app-gateway/FeesHelpers.sol b/contracts/protocol/payload-delivery/app-gateway/FeesHelpers.sol index 2922ba52..8db2607d 100644 --- a/contracts/protocol/payload-delivery/app-gateway/FeesHelpers.sol +++ b/contracts/protocol/payload-delivery/app-gateway/FeesHelpers.sol @@ -50,6 +50,44 @@ abstract contract FeesHelpers is RequestQueue { return _batch(msg.sender, auctionManager_, fees_, bytes(""), 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() + ).getWithdrawTransmitterFeesPayloadParams( + transmitter, + chainSlug_, + token_, + receiver_, + amount_ + ); + + RequestMetadata memory requestMetadata = RequestMetadata({ + appGateway: address(this), + 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 diff --git a/contracts/protocol/payload-delivery/app-gateway/RequestQueue.sol b/contracts/protocol/payload-delivery/app-gateway/RequestQueue.sol index f88ac9cd..80b09887 100644 --- a/contracts/protocol/payload-delivery/app-gateway/RequestQueue.sol +++ b/contracts/protocol/payload-delivery/app-gateway/RequestQueue.sol @@ -33,34 +33,6 @@ abstract contract RequestQueue is DeliveryUtils { return _batch(appGateway, auctionManager_, maxFees_, feesApprovalData_, onCompleteData_); } - function _checkBatch( - address appGateway_, - bytes memory feesApprovalData_, - uint256 maxFees_ - ) internal returns (address consumeFrom) { - if (queuePayloadParams.length > REQUEST_PAYLOAD_COUNT_LIMIT) - revert RequestPayloadCountLimitExceeded(); - (consumeFrom, , ) = IFeesManager(addressResolver__.feesManager()).setAppGatewayWhitelist( - feesApprovalData_ - ); - if ( - !IFeesManager(addressResolver__.feesManager()).isFeesEnough( - consumeFrom, - appGateway_, - maxFees_ - ) - ) revert InsufficientFees(); - - return consumeFrom; - } - - function _getAuctionManager(address auctionManager_) internal view returns (address) { - return - auctionManager_ == address(0) - ? IAddressResolver(addressResolver__).defaultAuctionManager() - : auctionManager_; - } - /// @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 @@ -73,64 +45,129 @@ abstract contract RequestQueue is DeliveryUtils { bytes memory onCompleteData_ ) internal returns (uint40 requestCount) { if (queuePayloadParams.length == 0) return 0; - address auctionManager = _getAuctionManager(auctionManager_); - // create the payload submit params array in desired format + BatchParams memory params = BatchParams({ + appGateway: appGateway_, + auctionManager: _getAuctionManager(auctionManager_), + maxFees: maxFees_, + feesApprovalData: feesApprovalData_, + onCompleteData: onCompleteData_, + onlyReadRequests: false, + queryCount: 0, + finalizeCount: 0 + }); + + // Split the function into smaller parts ( PayloadSubmitParams[] memory payloadSubmitParamsArray, - bool onlyReadRequests + bool onlyReadRequests, + uint256 queryCount, + uint256 finalizeCount ) = _createPayloadSubmitParamsArray(); - address consumeFrom = _checkBatch(appGateway_, feesApprovalData_, maxFees_); + params.onlyReadRequests = onlyReadRequests; + params.queryCount = queryCount; + params.finalizeCount = finalizeCount; + + address consumeFrom = _checkBatch( + params.appGateway, + params.feesApprovalData, + params.maxFees + ); + + return _submitBatchRequest(payloadSubmitParamsArray, consumeFrom, params); + } + + function _submitBatchRequest( + PayloadSubmitParams[] memory payloadSubmitParamsArray, + address consumeFrom, + BatchParams memory params + ) internal returns (uint40 requestCount) { RequestMetadata memory requestMetadata = RequestMetadata({ - appGateway: appGateway_, - auctionManager: auctionManager, - maxFees: maxFees_, + appGateway: params.appGateway, + auctionManager: params.auctionManager, + maxFees: params.maxFees, winningBid: Bid({fee: 0, transmitter: address(0), extraData: new bytes(0)}), - onCompleteData: onCompleteData_, - onlyReadRequests: onlyReadRequests, - consumeFrom: consumeFrom + onCompleteData: params.onCompleteData, + onlyReadRequests: params.onlyReadRequests, + consumeFrom: consumeFrom, + queryCount: params.queryCount, + finalizeCount: params.finalizeCount }); - // process and submit the queue of payloads to watcher precompile - requestCount = watcherPrecompile__().submitRequest( - payloadSubmitParamsArray, - requestMetadata - ); + requestCount = watcherPrecompile__().submitRequest(payloadSubmitParamsArray); requests[requestCount] = requestMetadata; - // send query directly if request contains only reads - // transmitter should ignore the batch for auction, the transaction will also revert if someone bids - if (onlyReadRequests) + if (params.onlyReadRequests) { watcherPrecompile__().startProcessingRequest(requestCount, address(0)); + } - // to save extra calls from transmitter - uint256 maxTransmitterFees = maxFees_ - - watcherPrecompileLimits().getTotalFeesRequired(requestCount); + uint256 watcherFees = watcherPrecompileLimits().getTotalFeesRequired( + params.queryCount, + params.finalizeCount, + 0, + 0 + ); + if (watcherFees > params.maxFees) revert InsufficientFees(); + uint256 maxTransmitterFees = params.maxFees - watcherFees; emit PayloadSubmitted( requestCount, - appGateway_, + params.appGateway, payloadSubmitParamsArray, - maxFees_ - maxTransmitterFees, - auctionManager_, - onlyReadRequests + maxTransmitterFees, + params.auctionManager, + params.onlyReadRequests ); } + function _getAuctionManager(address auctionManager_) internal view returns (address) { + return + auctionManager_ == address(0) + ? IAddressResolver(addressResolver__).defaultAuctionManager() + : auctionManager_; + } + function _checkBatch( + address appGateway_, + bytes memory feesApprovalData_, + uint256 maxFees_ + ) internal returns (address consumeFrom) { + if (queuePayloadParams.length > REQUEST_PAYLOAD_COUNT_LIMIT) + revert RequestPayloadCountLimitExceeded(); + (consumeFrom, , ) = IFeesManager(addressResolver__.feesManager()) + .whitelistAppGatewayWithSignature(feesApprovalData_); + if ( + !IFeesManager(addressResolver__.feesManager()).isFeesEnough( + consumeFrom, + appGateway_, + maxFees_ + ) + ) revert InsufficientFees(); + + return consumeFrom; + } + /// @notice Creates an array of payload details /// @return payloadDetailsArray An array of payload details function _createPayloadSubmitParamsArray() internal - returns (PayloadSubmitParams[] memory payloadDetailsArray, bool onlyReadRequests) + 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) { + if (queuePayloadParams[i].callType == CallType.READ) { + queryCount++; + } else { onlyReadRequests = false; + finalizeCount++; } // Update level for calls diff --git a/contracts/protocol/utils/common/Structs.sol b/contracts/protocol/utils/common/Structs.sol index 118d772d..e870602d 100644 --- a/contracts/protocol/utils/common/Structs.sol +++ b/contracts/protocol/utils/common/Structs.sol @@ -50,6 +50,23 @@ enum ExecutionStatus { Reverted } +/// @notice Creates a struct to hold batch parameters +struct BatchParams { + address appGateway; + address auctionManager; + uint256 maxFees; + bytes feesApprovalData; + bytes onCompleteData; + bool onlyReadRequests; + uint256 queryCount; + uint256 finalizeCount; +} + +struct AppGatewayWhitelistParams { + address appGateway; + bool isApproved; +} + //// STRUCTS //// // plug: struct LimitParams { @@ -227,6 +244,8 @@ struct RequestMetadata { bytes onCompleteData; bool onlyReadRequests; address consumeFrom; + uint256 queryCount; + uint256 finalizeCount; } struct ExecuteParams { diff --git a/contracts/protocol/watcherPrecompile/WatcherPrecompileConfig.sol b/contracts/protocol/watcherPrecompile/WatcherPrecompileConfig.sol index 5e05a4b5..c996de91 100644 --- a/contracts/protocol/watcherPrecompile/WatcherPrecompileConfig.sol +++ b/contracts/protocol/watcherPrecompile/WatcherPrecompileConfig.sol @@ -203,14 +203,16 @@ contract WatcherPrecompileConfig is } function _isWatcherSignatureValid( - bytes memory digest_, + bytes memory inputData_, uint256 signatureNonce_, bytes memory signature_ ) internal { if (isNonceUsed[signatureNonce_]) revert NonceUsed(); isNonceUsed[signatureNonce_] = true; - bytes32 digest = keccak256(abi.encode(address(this), evmxSlug, signatureNonce_, digest_)); + bytes32 digest = keccak256( + abi.encode(address(this), evmxSlug, signatureNonce_, inputData_) + ); digest = keccak256(abi.encodePacked("\x19Ethereum Signed Message:\n32", digest)); // recovered signer is checked for the valid roles later diff --git a/contracts/protocol/watcherPrecompile/WatcherPrecompileLimits.sol b/contracts/protocol/watcherPrecompile/WatcherPrecompileLimits.sol index f0f2921a..1ea1c2da 100644 --- a/contracts/protocol/watcherPrecompile/WatcherPrecompileLimits.sol +++ b/contracts/protocol/watcherPrecompile/WatcherPrecompileLimits.sol @@ -181,31 +181,17 @@ contract WatcherPrecompileLimits is callBackFees = callBackFees_; } - function getTotalFeesRequired(uint40 requestCount_) external view returns (uint256) { + function getTotalFeesRequired( + uint256 queryCount_, + uint256 finalizeCount_, + uint256 scheduleCount_, + uint256 callbackCount_ + ) external view returns (uint256) { uint256 totalFees = 0; - if (queryFees == 0) { - revert WatcherFeesNotSet(QUERY); - } - if (finalizeFees == 0) { - revert WatcherFeesNotSet(FINALIZE); - } - if (scheduleFees == 0) { - revert WatcherFeesNotSet(SCHEDULE); - } - if (callBackFees == 0) { - revert WatcherFeesNotSet(CALLBACK); - } - - uint256 queryCount = watcherPrecompile__().requestParams[requestCount_].queryCount; - uint256 finalizeCount = watcherPrecompile__().requestParams[requestCount_].finalizeCount; - uint256 scheduleCount = watcherPrecompile__().requestParams[requestCount_].scheduleCount; - - uint256 totalCallbacks = queryCount + finalizeCount + scheduleCount; - - totalFees += totalCallbacks * callBackFees; - totalFees += queryCount * queryFees; - totalFees += finalizeCount * finalizeFees; - totalFees += scheduleCount * scheduleFees; + totalFees += callbackCount_ * callBackFees; + totalFees += queryCount_ * queryFees; + totalFees += finalizeCount_ * finalizeFees; + totalFees += scheduleCount_ * scheduleFees; return totalFees; } diff --git a/contracts/protocol/watcherPrecompile/core/RequestHandler.sol b/contracts/protocol/watcherPrecompile/core/RequestHandler.sol index e6b32c08..1cb77e73 100644 --- a/contracts/protocol/watcherPrecompile/core/RequestHandler.sol +++ b/contracts/protocol/watcherPrecompile/core/RequestHandler.sol @@ -19,8 +19,7 @@ abstract contract RequestHandler is WatcherPrecompileCore { /// @dev This function processes a batch of payload requests and assigns them to batches /// @dev It also consumes limits for the app gateway based on the number of reads and writes function submitRequest( - PayloadSubmitParams[] calldata payloadSubmitParams_, - RequestMetadata calldata requestMetadata_ + PayloadSubmitParams[] calldata payloadSubmitParams_ ) public returns (uint40 requestCount) { address appGateway = _checkAppGateways(payloadSubmitParams_); @@ -95,7 +94,7 @@ abstract contract RequestHandler is WatcherPrecompileCore { requestParams[requestCount].queryCount = readCount; requestParams[requestCount].finalizeCount = writeCount; - + requestParams[requestCount].currentBatch = currentBatch; requestParams[requestCount].payloadsRemaining = payloadSubmitParams_.length; requestParams[requestCount].middleware = msg.sender; diff --git a/contracts/protocol/watcherPrecompile/core/WatcherPrecompileCore.sol b/contracts/protocol/watcherPrecompile/core/WatcherPrecompileCore.sol index 53928cd8..14c6323d 100644 --- a/contracts/protocol/watcherPrecompile/core/WatcherPrecompileCore.sol +++ b/contracts/protocol/watcherPrecompile/core/WatcherPrecompileCore.sol @@ -51,12 +51,7 @@ abstract contract WatcherPrecompileCore is ); // consumes limit for SCHEDULE precompile - watcherPrecompileLimits__.consumeLimit( - _getCoreAppGateway(msg.sender), - SCHEDULE, - 1 - ); - + watcherPrecompileLimits__.consumeLimit(_getCoreAppGateway(msg.sender), SCHEDULE, 1); // emits event for watcher to track timeout and resolve when timeout is reached emit TimeoutRequested(timeoutId, msg.sender, payload_, executeAt); @@ -237,19 +232,21 @@ abstract contract WatcherPrecompileCore is } /// @notice Verifies that a watcher signature is valid - /// @param digest_ The digest to verify + /// @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 digest_, + bytes memory inputData_, uint256 signatureNonce_, bytes memory signature_ ) internal { if (isNonceUsed[signatureNonce_]) revert NonceUsed(); isNonceUsed[signatureNonce_] = true; - bytes32 digest = keccak256(abi.encode(address(this), evmxSlug, signatureNonce_, digest_)); + bytes32 digest = keccak256( + abi.encode(address(this), evmxSlug, signatureNonce_, inputData_) + ); digest = keccak256(abi.encodePacked("\x19Ethereum Signed Message:\n32", digest)); // recovered signer is checked for the valid roles later diff --git a/script/helpers/PayFeesInArbitrumETH.s.sol b/script/helpers/PayFeesInArbitrumETH.s.sol index 9bf56848..0940009a 100644 --- a/script/helpers/PayFeesInArbitrumETH.s.sol +++ b/script/helpers/PayFeesInArbitrumETH.s.sol @@ -23,6 +23,6 @@ contract DepositFees is Script { console.log("App Gateway:", appGateway); console.log("Fees Plug:", address(feesPlug)); uint feesAmount = 0.001 ether; - feesPlug.depositToFeeAndNative(ETH_ADDRESS, feesAmount, appGateway); + feesPlug.depositToFeeAndNative(ETH_ADDRESS, appGateway, feesAmount); } } diff --git a/test/DeliveryHelper.t.sol b/test/DeliveryHelper.t.sol index e915a59d..bf77d5ec 100644 --- a/test/DeliveryHelper.t.sol +++ b/test/DeliveryHelper.t.sol @@ -111,6 +111,15 @@ contract DeliveryHelperTest is SetupTest { arbConfig = deploySocket(arbChainSlug); optConfig = deploySocket(optChainSlug); connectDeliveryHelper(); + + depositUSDCFees( + address(auctionManager), + OnChainFees({ + chainSlug: arbChainSlug, + token: address(arbConfig.feesTokenUSDC), + amount: 1 ether + }) + ); } function connectDeliveryHelper() internal { @@ -173,13 +182,17 @@ contract DeliveryHelperTest is SetupTest { //////////////////////////////////// Fees //////////////////////////////////// - function depositFees(address appGateway_, OnChainFees memory fees_) internal { + function depositUSDCFees(address appGateway_, OnChainFees memory fees_) internal { SocketContracts memory socketConfig = getSocketConfig(fees_.chainSlug); - socketConfig.feesPlug.depositToFeeAndNative(fees_.token, fees_.amount, appGateway_); + vm.startPrank(owner); + ERC20(fees_.token).approve(address(socketConfig.feesPlug), fees_.amount); + socketConfig.feesPlug.depositToFeeAndNative(fees_.token, appGateway_, fees_.amount); + vm.stopPrank(); bytes memory bytesInput = abi.encode( - fees_.chainSlug, + IFeesManager.incrementFeesDeposited.selector, appGateway_, + fees_.chainSlug, fees_.token, fees_.amount ); @@ -198,6 +211,35 @@ contract DeliveryHelperTest is SetupTest { ); } + 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_, diff --git a/test/FeesTest.t.sol b/test/FeesTest.t.sol index 4755cb15..325987b9 100644 --- a/test/FeesTest.t.sol +++ b/test/FeesTest.t.sol @@ -20,9 +20,13 @@ contract FeesTest is DeliveryHelperTest { feesConfig = getSocketConfig(feesChainSlug); counterGateway = new CounterAppGateway(address(addressResolver), feesAmount); - depositFees( + depositUSDCFees( address(counterGateway), - OnChainFees({chainSlug: feesChainSlug, token: ETH_ADDRESS, amount: depositAmount}) + OnChainFees({ + chainSlug: feesChainSlug, + token: address(feesConfig.feesTokenUSDC), + amount: depositAmount + }) ); bytes32[] memory contractIds = new bytes32[](1); @@ -31,40 +35,36 @@ contract FeesTest is DeliveryHelperTest { } function testDistributeFee() public { - uint256 initialFeesPlugBalance = address(feesConfig.feesPlug).balance; - - assertEq( - initialFeesPlugBalance, - address(feesConfig.feesPlug).balance, - "FeesPlug Balance should be correct" + uint256 initialFeesPlugBalance = feesConfig.feesTokenUSDC.balanceOf( + address(feesConfig.feesPlug) ); assertEq( initialFeesPlugBalance, - feesConfig.feesPlug.balanceOf(ETH_ADDRESS), - "FeesPlug balance of counterGateway should be correct" + feesConfig.feesTokenUSDC.balanceOf(address(feesConfig.feesPlug)), + "FeesPlug Balance should be correct" ); - uint256 transmitterReceiverBalanceBefore = address(receiver).balance; - - hoax(transmitterEOA); - uint40 requestCount = feesManager.withdrawTransmitterFees( + uint256 transmitterReceiverBalanceBefore = feesConfig.feesTokenUSDC.balanceOf(receiver); + uint256 withdrawAmount = feesManager.getMaxFeesAvailableForWithdraw(transmitterEOA); + vm.startPrank(transmitterEOA); + uint40 requestCount = deliveryHelper.withdrawTransmitterFees( feesChainSlug, - ETH_ADDRESS, + address(feesConfig.feesTokenUSDC), address(receiver), - feesManager.transmitterCredits(transmitterEOA) + withdrawAmount ); + vm.stopPrank(); uint40[] memory batches = watcherPrecompile.getBatches(requestCount); _finalizeBatch(batches[0], new bytes[](0), 0, false); - assertEq( transmitterReceiverBalanceBefore + bidAmount, - address(receiver).balance, + feesConfig.feesTokenUSDC.balanceOf(receiver), "Transmitter Balance should be correct" ); assertEq( initialFeesPlugBalance - bidAmount, - address(feesConfig.feesPlug).balance, + feesConfig.feesTokenUSDC.balanceOf(address(feesConfig.feesPlug)), "FeesPlug Balance should be correct" ); } @@ -72,25 +72,30 @@ contract FeesTest is DeliveryHelperTest { function testWithdrawFeeTokens() public { assertEq( depositAmount, - feesConfig.feesPlug.balanceOf(ETH_ADDRESS), + feesConfig.feesTokenUSDC.balanceOf(address(feesConfig.feesPlug)), "Balance should be correct" ); - uint256 receiverBalanceBefore = receiver.balance; + uint256 receiverBalanceBefore = feesConfig.feesTokenUSDC.balanceOf(receiver); uint256 withdrawAmount = 0.5 ether; - counterGateway.withdrawFeeTokens(feesChainSlug, ETH_ADDRESS, withdrawAmount, receiver); + counterGateway.withdrawFeeTokens( + feesChainSlug, + address(feesConfig.feesTokenUSDC), + withdrawAmount, + receiver + ); executeRequest(new bytes[](0)); assertEq( depositAmount - withdrawAmount, - address(feesConfig.feesPlug).balance, + feesConfig.feesTokenUSDC.balanceOf(address(feesConfig.feesPlug)), "Fees Balance should be correct" ); assertEq( receiverBalanceBefore + withdrawAmount, - receiver.balance, + feesConfig.feesTokenUSDC.balanceOf(receiver), "Receiver Balance should be correct" ); } diff --git a/test/SetupTest.t.sol b/test/SetupTest.t.sol index 69237ec1..a9f0ebd8 100644 --- a/test/SetupTest.t.sol +++ b/test/SetupTest.t.sol @@ -22,7 +22,7 @@ import {ETH_ADDRESS} from "../contracts/protocol/utils/common/Constants.sol"; import {ResolvedPromises, OnChainFees} from "../contracts/protocol/utils/common/Structs.sol"; import "solady/utils/ERC1967Factory.sol"; - +import "./apps/app-gateways/USDC.sol"; contract SetupTest is Test { using PayloadHeaderDecoder for bytes32; uint public c = 1; @@ -57,6 +57,7 @@ contract SetupTest is Test { SocketBatcher socketBatcher; ContractFactoryPlug contractFactoryPlug; FeesPlug feesPlug; + ERC20 feesTokenUSDC; } AddressResolver public addressResolver; @@ -84,10 +85,12 @@ contract SetupTest is Test { SocketBatcher socketBatcher = new SocketBatcher(owner, socket); FastSwitchboard switchboard = new FastSwitchboard(chainSlug_, socket, owner); + ERC20 feesTokenUSDC = new USDC("USDC", "USDC", 6, owner, 1000000000000000000000000); FeesPlug feesPlug = new FeesPlug(address(socket), owner); ContractFactoryPlug contractFactoryPlug = new ContractFactoryPlug(address(socket), owner); - vm.startPrank(owner); + // feePlug whitelist token + feesPlug.whitelistToken(address(feesTokenUSDC)); // socket socket.grantRole(GOVERNANCE_ROLE, address(owner)); @@ -114,7 +117,8 @@ contract SetupTest is Test { switchboard: switchboard, socketBatcher: socketBatcher, contractFactoryPlug: contractFactoryPlug, - feesPlug: feesPlug + feesPlug: feesPlug, + feesTokenUSDC: feesTokenUSDC }); } @@ -192,6 +196,11 @@ contract SetupTest is Test { vm.startPrank(watcherEOA); addressResolver.setWatcherPrecompile(address(watcherPrecompile)); + watcherPrecompileLimits.setCallBackFees(1); + watcherPrecompileLimits.setFinalizeFees(1); + watcherPrecompileLimits.setQueryFees(1); + watcherPrecompileLimits.setScheduleFees(1); + vm.stopPrank(); } diff --git a/test/apps/Counter.t.sol b/test/apps/Counter.t.sol index 92079168..5e34404f 100644 --- a/test/apps/Counter.t.sol +++ b/test/apps/Counter.t.sol @@ -17,9 +17,13 @@ contract CounterTest is DeliveryHelperTest { setUpDeliveryHelper(); counterGateway = new CounterAppGateway(address(addressResolver), feesAmount); - depositFees( + depositUSDCFees( address(counterGateway), - OnChainFees({chainSlug: arbChainSlug, token: ETH_ADDRESS, amount: 1 ether}) + OnChainFees({ + chainSlug: arbChainSlug, + token: address(arbConfig.feesTokenUSDC), + amount: 1 ether + }) ); counterId = counterGateway.counter(); diff --git a/test/apps/ParallelCounter.t.sol b/test/apps/ParallelCounter.t.sol index 6cfc28f3..5e7e193f 100644 --- a/test/apps/ParallelCounter.t.sol +++ b/test/apps/ParallelCounter.t.sol @@ -17,9 +17,13 @@ contract ParallelCounterTest is DeliveryHelperTest { setUpDeliveryHelper(); parallelCounterGateway = new CounterAppGateway(address(addressResolver), feesAmount); - depositFees( + depositUSDCFees( address(parallelCounterGateway), - OnChainFees({chainSlug: arbChainSlug, token: ETH_ADDRESS, amount: 1 ether}) + OnChainFees({ + chainSlug: arbChainSlug, + token: address(arbConfig.feesTokenUSDC), + amount: 1 ether + }) ); counterId1 = parallelCounterGateway.counter1(); counterId2 = parallelCounterGateway.counter(); diff --git a/test/apps/SuperToken.t.sol b/test/apps/SuperToken.t.sol index 97eadc43..bd5c88ab 100644 --- a/test/apps/SuperToken.t.sol +++ b/test/apps/SuperToken.t.sol @@ -76,9 +76,13 @@ contract SuperTokenTest is DeliveryHelperTest { ); // Enable app gateways to do all operations in the Watcher: Read, Write and Schedule on EVMx // Watcher sets the limits for apps in this SOCKET protocol version - depositFees( + depositUSDCFees( address(superTokenApp), - OnChainFees({chainSlug: arbChainSlug, token: ETH_ADDRESS, amount: 1 ether}) + OnChainFees({ + chainSlug: arbChainSlug, + token: address(arbConfig.feesTokenUSDC), + amount: 1 ether + }) ); appContracts = AppContracts({ diff --git a/test/apps/app-gateways/USDC.sol b/test/apps/app-gateways/USDC.sol new file mode 100644 index 00000000..df08d360 --- /dev/null +++ b/test/apps/app-gateways/USDC.sol @@ -0,0 +1,41 @@ +pragma solidity ^0.8.21; + +import "solady/tokens/ERC20.sol"; + +contract USDC is ERC20 { + string private _name; + string private _symbol; + uint8 private _decimals; + + constructor( + string memory name_, + string memory symbol_, + uint8 decimals_, + address initialSupplyHolder_, + uint256 initialSupply_ + ) { + _name = name_; + _symbol = symbol_; + _decimals = decimals_; + _mint(initialSupplyHolder_, initialSupply_); + } + function name() public view virtual override returns (string memory) { + return _name; + } + + function symbol() public view virtual override returns (string memory) { + return _symbol; + } + + function decimals() public view virtual override returns (uint8) { + return _decimals; + } + + function mint(address to, uint256 amount) external { + _mint(to, amount); + } + + function burn(address from, uint256 amount) external { + _burn(from, amount); + } +} diff --git a/test/apps/app-gateways/counter/CounterAppGateway.sol b/test/apps/app-gateways/counter/CounterAppGateway.sol index eb5e6e7e..f5bca934 100644 --- a/test/apps/app-gateways/counter/CounterAppGateway.sol +++ b/test/apps/app-gateways/counter/CounterAppGateway.sol @@ -20,7 +20,7 @@ contract CounterAppGateway is AppGatewayBase, Ownable { constructor(address addressResolver_, uint256 fees_) AppGatewayBase(addressResolver_) { creationCodeWithArgs[counter] = abi.encodePacked(type(Counter).creationCode); creationCodeWithArgs[counter1] = abi.encodePacked(type(Counter).creationCode); - _setOverrides(fees_); + _setMaxFees(fees_); _initializeOwner(msg.sender); } @@ -80,10 +80,7 @@ contract CounterAppGateway is AppGatewayBase, Ownable { _setOverrides(Read.OFF, Parallel.OFF); } - function readCounterAtBlock( - address instance_, - uint256 blockNumber_ - ) public async(bytes("")) { + function readCounterAtBlock(address instance_, uint256 blockNumber_) public async(bytes("")) { uint32 chainSlug = IForwarder(instance_).getChainSlug(); _setOverrides(Read.ON, Parallel.ON, blockNumber_); ICounter(instance_).getCounter(); diff --git a/test/apps/app-gateways/super-token/SuperTokenAppGateway.sol b/test/apps/app-gateways/super-token/SuperTokenAppGateway.sol index 8e38065d..fbaf6f15 100644 --- a/test/apps/app-gateways/super-token/SuperTokenAppGateway.sol +++ b/test/apps/app-gateways/super-token/SuperTokenAppGateway.sol @@ -45,7 +45,7 @@ contract SuperTokenAppGateway is AppGatewayBase, Ownable { // sets the fees data like max fees, chain and token for all transfers // they can be updated for each transfer as well - _setOverrides(fees_); + _setMaxFees(fees_); _initializeOwner(owner_); } From 665150620e6a7c2ddd3562ecaa7cc576d6168140 Mon Sep 17 00:00:00 2001 From: Akash Date: Tue, 22 Apr 2025 17:39:41 +0530 Subject: [PATCH 15/21] fix: tests --- .../protocol/payload-delivery/FeesManager.sol | 19 ++++--- .../app-gateway/DeliveryHelper.sol | 10 ++-- .../app-gateway/FeesHelpers.sol | 6 +-- test/FeesTest.t.sol | 52 ++++++++++++++----- test/Inbox.t.sol | 9 +++- 5 files changed, 64 insertions(+), 32 deletions(-) diff --git a/contracts/protocol/payload-delivery/FeesManager.sol b/contracts/protocol/payload-delivery/FeesManager.sol index 4f13a64e..073be21e 100644 --- a/contracts/protocol/payload-delivery/FeesManager.sol +++ b/contracts/protocol/payload-delivery/FeesManager.sol @@ -44,10 +44,6 @@ abstract contract FeesManagerStorage is IFeesManager { /// @dev requestCount => RequestFee mapping(uint40 => uint256) public requestCountCredits; - // slot 55 - /// @notice Mapping to track credits to be distributed to transmitters - /// @dev transmitter => amount - mapping(address => uint256) public transmitterCredits; // @dev amount uint256 public watcherPrecompileCredits; @@ -284,7 +280,7 @@ contract FeesManager is FeesManagerStorage, Initializable, Ownable, AddressResol _useBlockedUserCredits(requestMetadata.consumeFrom, blockedCredits, fees); // Assign fees to transmitter - transmitterCredits[transmitter_] += fees; + userCredits[transmitter_].totalCredits += fees; // Clean up storage delete requestCountCredits[requestCount_]; @@ -373,7 +369,8 @@ contract FeesManager is FeesManagerStorage, Initializable, Ownable, AddressResol // Check if amount is available in fees plug uint256 availableAmount = getAvailableFees(source); if (availableAmount < amount_) revert InsufficientFeesAvailable(); - + + _useAvailableUserCredits(source, amount_); tokenPoolBalances[chainSlug_][token_] -= amount_; // Add it to the queue and submit request @@ -395,7 +392,7 @@ contract FeesManager is FeesManagerStorage, Initializable, Ownable, AddressResol if (amount_ > maxFeesAvailableForWithdraw) revert InsufficientFeesAvailable(); // Clean up storage - transmitterCredits[transmitter_] -= amount_; + _useAvailableUserCredits(transmitter_, amount_); tokenPoolBalances[chainSlug_][token_] -= amount_; bytes memory payload = abi.encodeCall(IFeesPlug.withdrawFees, (token_, receiver_, amount_)); @@ -420,9 +417,10 @@ contract FeesManager is FeesManagerStorage, Initializable, Ownable, AddressResol function getMaxFeesAvailableForWithdraw(address transmitter_) public view returns (uint256) { uint256 watcherFees = watcherPrecompileLimits().getTotalFeesRequired(0, 1, 0, 1); + uint256 transmitterCredits = userCredits[transmitter_].totalCredits; return - transmitterCredits[transmitter_] > watcherFees - ? transmitterCredits[transmitter_] - watcherFees + transmitterCredits > watcherFees + ? transmitterCredits - watcherFees : 0; } @@ -454,7 +452,8 @@ contract FeesManager is FeesManagerStorage, Initializable, Ownable, AddressResol } /// @notice hook called by watcher precompile when request is finished - function finishRequest(uint40) external {} + function onRequestComplete(uint40 requestCount_, bytes memory) external { + } function _queue(uint32 chainSlug_, bytes memory payload_) internal { QueuePayloadParams memory queuePayloadParams = _createQueuePayloadParams( diff --git a/contracts/protocol/payload-delivery/app-gateway/DeliveryHelper.sol b/contracts/protocol/payload-delivery/app-gateway/DeliveryHelper.sol index 81b1464f..a950bf92 100644 --- a/contracts/protocol/payload-delivery/app-gateway/DeliveryHelper.sol +++ b/contracts/protocol/payload-delivery/app-gateway/DeliveryHelper.sol @@ -59,10 +59,12 @@ contract DeliveryHelper is FeesHelpers { requestMetadata_.winningBid.transmitter ); - IAppGateway(requestMetadata_.appGateway).onRequestComplete( - requestCount_, - requestMetadata_.onCompleteData - ); + if (requestMetadata_.appGateway.code.length > 0) { + IAppGateway(requestMetadata_.appGateway).onRequestComplete( + requestCount_, + requestMetadata_.onCompleteData + ); + } } /// @notice Cancels a request and settles the fees diff --git a/contracts/protocol/payload-delivery/app-gateway/FeesHelpers.sol b/contracts/protocol/payload-delivery/app-gateway/FeesHelpers.sol index 8db2607d..f737abc3 100644 --- a/contracts/protocol/payload-delivery/app-gateway/FeesHelpers.sol +++ b/contracts/protocol/payload-delivery/app-gateway/FeesHelpers.sol @@ -46,8 +46,8 @@ abstract contract FeesHelpers is RequestQueue { amount_, receiver_ ); - - return _batch(msg.sender, auctionManager_, fees_, bytes(""), bytes("")); + bytes memory feesApprovalData = abi.encode(msg.sender, msg.sender, true, bytes("")); + return _batch(msg.sender, auctionManager_, fees_, feesApprovalData, bytes("")); } /// @notice Withdraws fees to a specified receiver @@ -73,7 +73,7 @@ abstract contract FeesHelpers is RequestQueue { ); RequestMetadata memory requestMetadata = RequestMetadata({ - appGateway: address(this), + appGateway: addressResolver__.feesManager(), auctionManager: address(0), maxFees: 0, winningBid: Bid({transmitter: transmitter, fee: 0, extraData: new bytes(0)}), diff --git a/test/FeesTest.t.sol b/test/FeesTest.t.sol index 325987b9..6c9b7118 100644 --- a/test/FeesTest.t.sol +++ b/test/FeesTest.t.sol @@ -9,7 +9,7 @@ contract FeesTest is DeliveryHelperTest { uint256 constant depositAmount = 1 ether; uint256 constant feesAmount = 0.01 ether; address receiver = address(uint160(c++)); - + address user = address(uint160(c++)); uint32 feesChainSlug = arbChainSlug; SocketContracts feesConfig; @@ -34,7 +34,7 @@ contract FeesTest is DeliveryHelperTest { _deploy(feesChainSlug, IAppGateway(counterGateway), contractIds); } - function testDistributeFee() public { + function testWithdrawTransmitterFees() public { uint256 initialFeesPlugBalance = feesConfig.feesTokenUSDC.balanceOf( address(feesConfig.feesPlug) ); @@ -58,23 +58,18 @@ contract FeesTest is DeliveryHelperTest { uint40[] memory batches = watcherPrecompile.getBatches(requestCount); _finalizeBatch(batches[0], new bytes[](0), 0, false); assertEq( - transmitterReceiverBalanceBefore + bidAmount, + transmitterReceiverBalanceBefore + withdrawAmount, feesConfig.feesTokenUSDC.balanceOf(receiver), "Transmitter Balance should be correct" ); assertEq( - initialFeesPlugBalance - bidAmount, + initialFeesPlugBalance - withdrawAmount, feesConfig.feesTokenUSDC.balanceOf(address(feesConfig.feesPlug)), "FeesPlug Balance should be correct" ); } - function testWithdrawFeeTokens() public { - assertEq( - depositAmount, - feesConfig.feesTokenUSDC.balanceOf(address(feesConfig.feesPlug)), - "Balance should be correct" - ); + function testWithdrawFeeTokensAppGateway() public { uint256 receiverBalanceBefore = feesConfig.feesTokenUSDC.balanceOf(receiver); uint256 withdrawAmount = 0.5 ether; @@ -87,15 +82,44 @@ contract FeesTest is DeliveryHelperTest { ); executeRequest(new bytes[](0)); + assertEq( - depositAmount - withdrawAmount, - feesConfig.feesTokenUSDC.balanceOf(address(feesConfig.feesPlug)), - "Fees Balance should be correct" + receiverBalanceBefore + withdrawAmount, + feesConfig.feesTokenUSDC.balanceOf(receiver), + "Receiver Balance should be correct" + ); + } + + + function testWithdrawFeeTokensUser() public { + + depositUSDCFees( + user, + OnChainFees({ + chainSlug: feesChainSlug, + token: address(feesConfig.feesTokenUSDC), + amount: depositAmount + }) ); + uint256 receiverBalanceBefore = feesConfig.feesTokenUSDC.balanceOf(user); + uint256 withdrawAmount = 0.5 ether; + + vm.prank(user); + deliveryHelper.withdrawTo( + feesChainSlug, + address(feesConfig.feesTokenUSDC), + withdrawAmount, + user, + address(auctionManager), + maxFees + ); + executeRequest(new bytes[](0)); + + assertEq( receiverBalanceBefore + withdrawAmount, - feesConfig.feesTokenUSDC.balanceOf(receiver), + feesConfig.feesTokenUSDC.balanceOf(user), "Receiver Balance should be correct" ); } diff --git a/test/Inbox.t.sol b/test/Inbox.t.sol index cdd3baa7..3bdb8681 100644 --- a/test/Inbox.t.sol +++ b/test/Inbox.t.sol @@ -59,7 +59,14 @@ contract TriggerTest is DeliveryHelperTest { 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(gateway), arbChainSlug); From 15ae407cc11c583d04b505545bce333bee580f2e Mon Sep 17 00:00:00 2001 From: Akash Date: Wed, 23 Apr 2025 13:39:02 +0530 Subject: [PATCH 16/21] fix: renames --- contracts/interfaces/IFeesManager.sol | 18 +-- .../payload-delivery/AuctionManager.sol | 4 +- .../protocol/payload-delivery/FeesManager.sol | 131 +++++++----------- .../protocol/payload-delivery/FeesPlug.sol | 4 +- .../app-gateway/DeliveryHelper.sol | 6 +- .../app-gateway/FeesHelpers.sol | 6 +- .../app-gateway/RequestQueue.sol | 2 +- .../core/WatcherPrecompile.sol | 2 +- .../core/WatcherPrecompileCore.sol | 4 +- .../WithdrawFeesArbitrumFeesPlug.s.sol | 2 +- test/DeliveryHelper.t.sol | 4 +- test/FeesTest.t.sol | 2 +- 12 files changed, 76 insertions(+), 109 deletions(-) diff --git a/contracts/interfaces/IFeesManager.sol b/contracts/interfaces/IFeesManager.sol index 7aa27314..f0fadb1b 100644 --- a/contracts/interfaces/IFeesManager.sol +++ b/contracts/interfaces/IFeesManager.sol @@ -4,21 +4,21 @@ pragma solidity ^0.8.21; import {Bid, QueuePayloadParams, PayloadSubmitParams, AppGatewayWhitelistParams} from "../protocol/utils/common/Structs.sol"; interface IFeesManager { - function blockFees( + function blockCredits( address consumeFrom_, uint256 transmitterCredits_, uint40 requestCount_ ) external; - function unblockFees(uint40 requestCount_) external; + function unblockCredits(uint40 requestCount_) external; - function isFeesEnough( + function isUserCreditsEnough( address consumeFrom_, address appGateway_, uint256 amount_ ) external view returns (bool); - function unblockAndAssignFees(uint40 requestCount_, address transmitter_) external; + function unblockAndAssignCredits(uint40 requestCount_, address transmitter_) external; function withdrawFees( address appGateway_, @@ -28,14 +28,14 @@ interface IFeesManager { address receiver_ ) external; - function assignWatcherPrecompileFeesFromRequestCount( + function assignWatcherPrecompileCreditsFromRequestCount( uint256 fees_, uint40 requestCount_ ) external; - function assignWatcherPrecompileFeesFromAddress(uint256 fees_, address consumeFrom_) external; + function assignWatcherPrecompileCreditsFromAddress(uint256 fees_, address consumeFrom_) external; - function incrementFeesDeposited( + function incrementCreditsDeposited( address depositTo_, uint32 chainSlug_, address token_, @@ -50,7 +50,7 @@ interface IFeesManager { function whitelistAppGateways(AppGatewayWhitelistParams[] calldata params_) external; - function getWithdrawTransmitterFeesPayloadParams( + function getWithdrawTransmitterCreditsPayloadParams( address transmitter_, uint32 chainSlug_, address token_, @@ -58,5 +58,5 @@ interface IFeesManager { uint256 amount_ ) external returns (PayloadSubmitParams[] memory); - function getMaxFeesAvailableForWithdraw(address transmitter_) external view returns (uint256); + function getMaxCreditsAvailableForWithdraw(address transmitter_) external view returns (uint256); } diff --git a/contracts/protocol/payload-delivery/AuctionManager.sol b/contracts/protocol/payload-delivery/AuctionManager.sol index 818e28b3..40f1d38d 100644 --- a/contracts/protocol/payload-delivery/AuctionManager.sol +++ b/contracts/protocol/payload-delivery/AuctionManager.sol @@ -174,7 +174,7 @@ contract AuctionManager is auctionClosed[requestCount_] = true; RequestMetadata memory requestMetadata = _getRequestMetadata(requestCount_); // block the fees - IFeesManager(addressResolver__.feesManager()).blockFees( + IFeesManager(addressResolver__.feesManager()).blockCredits( requestMetadata.consumeFrom, winningBid.fee, requestCount_ @@ -210,7 +210,7 @@ contract AuctionManager is auctionClosed[requestCount_] = false; reAuctionCount[requestCount_]++; - IFeesManager(addressResolver__.feesManager()).unblockFees(requestCount_); + IFeesManager(addressResolver__.feesManager()).unblockCredits(requestCount_); emit AuctionRestarted(requestCount_); } diff --git a/contracts/protocol/payload-delivery/FeesManager.sol b/contracts/protocol/payload-delivery/FeesManager.sol index 073be21e..7210ffea 100644 --- a/contracts/protocol/payload-delivery/FeesManager.sol +++ b/contracts/protocol/payload-delivery/FeesManager.sol @@ -66,24 +66,24 @@ contract FeesManager is FeesManagerStorage, Initializable, Ownable, AddressResol /// @param requestCount The batch identifier /// @param consumeFrom The consume from address /// @param amount The blocked amount - event FeesBlocked(uint40 indexed requestCount, address indexed consumeFrom, uint256 amount); + event CreditsBlocked(uint40 indexed requestCount, address indexed consumeFrom, uint256 amount); /// @notice Emitted when transmitter fees are updated /// @param requestCount The batch identifier /// @param transmitter The transmitter address /// @param amount The new amount deposited - event TransmitterFeesUpdated( + event TransmitterCreditsUpdated( uint40 indexed requestCount, address indexed transmitter, uint256 amount ); - event WatcherPrecompileFeesAssigned(uint256 amount, address consumeFrom); + event WatcherPrecompileCreditsAssigned(uint256 amount, address consumeFrom); /// @notice Emitted when fees deposited are updated /// @param chainSlug The chain identifier /// @param appGateway The app gateway address /// @param token The token address /// @param amount The new amount deposited - event FeesDepositedUpdated( + event CreditsDepositedUpdated( uint32 indexed chainSlug, address indexed appGateway, address indexed token, @@ -94,7 +94,7 @@ contract FeesManager is FeesManagerStorage, Initializable, Ownable, AddressResol /// @param requestCount The batch identifier /// @param transmitter The transmitter address /// @param amount The unblocked amount - event FeesUnblockedAndAssigned( + event CreditsUnblockedAndAssigned( uint40 indexed requestCount, address indexed transmitter, uint256 amount @@ -103,24 +103,28 @@ contract FeesManager is FeesManagerStorage, Initializable, Ownable, AddressResol /// @notice Emitted when fees are unblocked /// @param requestCount The batch identifier /// @param appGateway The app gateway address - event FeesUnblocked(uint40 indexed requestCount, address indexed appGateway); + event CreditsUnblocked(uint40 indexed requestCount, address indexed appGateway); /// @notice Emitted when insufficient watcher precompile fees are available - event InsufficientWatcherPrecompileFeesAvailable( + event InsufficientWatcherPrecompileCreditsAvailable( uint32 chainSlug, address token, address consumeFrom ); /// @notice Error thrown when insufficient fees are available - error InsufficientFeesAvailable(); + error InsufficientCreditsAvailable(); /// @notice Error thrown when no fees are available for a transmitter error NoFeesForTransmitter(); /// @notice Error thrown when no fees was blocked - error NoFeesBlocked(); + 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(); constructor() { _disableInitializers(); // disable for implementation } @@ -144,7 +148,7 @@ contract FeesManager is FeesManagerStorage, Initializable, Ownable, AddressResol /// @notice Returns available (unblocked) fees for a gateway /// @param consumeFrom_ The app gateway address /// @return The available fee amount - function getAvailableFees(address consumeFrom_) public view returns (uint256) { + function getAvailableCredits(address consumeFrom_) public view returns (uint256) { UserCredits memory userCredit = userCredits[consumeFrom_]; if (userCredit.totalCredits == 0 || userCredit.totalCredits <= userCredit.blockedCredits) return 0; @@ -156,42 +160,28 @@ contract FeesManager is FeesManagerStorage, Initializable, Ownable, AddressResol /// @param amount_ The amount deposited // @dev only callable by watcher precompile // @dev will need tokenAmount_ and creditAmount_ when introduce tokens except stables - function incrementFeesDeposited( + function depositCredits( address depositTo_, uint32 chainSlug_, address token_, - uint256 amount_, - uint256 signatureNonce_, - bytes memory signature_ - ) external { - _isWatcherSignatureValid( - abi.encode( - this.incrementFeesDeposited.selector, - depositTo_, - chainSlug_, - token_, - amount_ - ), - signatureNonce_, - signature_ - ); - + uint256 amount_ + ) external payable { + if (msg.value != amount_) revert InvalidAmount(); UserCredits storage userCredit = userCredits[depositTo_]; userCredit.totalCredits += amount_; tokenPoolBalances[chainSlug_][token_] += amount_; - - emit FeesDepositedUpdated(chainSlug_, depositTo_, token_, amount_); + emit CreditsDepositedUpdated(chainSlug_, depositTo_, token_, amount_); } - function isFeesEnough( + 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_]) - return false; - return getAvailableFees(consumeFrom_) >= amount_; + revert AppGatewayNotWhitelisted(); + return getAvailableCredits(consumeFrom_) >= amount_; } function _processFeeApprovalData( @@ -245,27 +235,27 @@ contract FeesManager is FeesManagerStorage, Initializable, Ownable, AddressResol /// @param transmitterCredits_ The total fees to block /// @param requestCount_ The batch identifier /// @dev Only callable by delivery helper - function blockFees( + function blockCredits( address consumeFrom_, uint256 transmitterCredits_, uint40 requestCount_ ) external onlyAuctionManager(requestCount_) { // Block fees - if (getAvailableFees(consumeFrom_) < transmitterCredits_) - revert InsufficientFeesAvailable(); + if (getAvailableCredits(consumeFrom_) < transmitterCredits_) + revert InsufficientCreditsAvailable(); UserCredits storage userCredit = userCredits[consumeFrom_]; userCredit.blockedCredits += transmitterCredits_; requestCountCredits[requestCount_] = transmitterCredits_; - emit FeesBlocked(requestCount_, consumeFrom_, 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 unblockAndAssignFees( + function unblockAndAssignCredits( uint40 requestCount_, address transmitter_ ) external override onlyDeliveryHelper { @@ -284,7 +274,7 @@ contract FeesManager is FeesManagerStorage, Initializable, Ownable, AddressResol // Clean up storage delete requestCountCredits[requestCount_]; - emit FeesUnblockedAndAssigned(requestCount_, transmitter_, fees); + emit CreditsUnblockedAndAssigned(requestCount_, transmitter_, fees); } function _useBlockedUserCredits( @@ -299,36 +289,36 @@ contract FeesManager is FeesManagerStorage, Initializable, Ownable, AddressResol function _useAvailableUserCredits(address consumeFrom_, uint256 toConsume_) internal { UserCredits storage userCredit = userCredits[consumeFrom_]; - if (userCredit.totalCredits < toConsume_) revert InsufficientFeesAvailable(); + if (userCredit.totalCredits < toConsume_) revert InsufficientCreditsAvailable(); userCredit.totalCredits -= toConsume_; } - function assignWatcherPrecompileFeesFromRequestCount( - uint256 fees_, + function assignWatcherPrecompileCreditsFromRequestCount( + uint256 amount_, uint40 requestCount_ ) external onlyWatcherPrecompile { RequestMetadata memory requestMetadata = deliveryHelper__().getRequestMetadata( requestCount_ ); - _assignWatcherPrecompileFees(fees_, requestMetadata.consumeFrom); + _assignWatcherPrecompileCredits(amount_, requestMetadata.consumeFrom); } - function assignWatcherPrecompileFeesFromAddress( - uint256 fees_, + function assignWatcherPrecompileCreditsFromAddress( + uint256 amount_, address consumeFrom_ ) external onlyWatcherPrecompile { - _assignWatcherPrecompileFees(fees_, consumeFrom_); + _assignWatcherPrecompileCredits(amount_, consumeFrom_); } - function _assignWatcherPrecompileFees(uint256 fees_, address consumeFrom_) internal { + function _assignWatcherPrecompileCredits(uint256 amount_, address consumeFrom_) internal { // deduct the fees from the user - _useAvailableUserCredits(consumeFrom_, fees_); + _useAvailableUserCredits(consumeFrom_, amount_); // add the fees to the watcher precompile - watcherPrecompileCredits += fees_; - emit WatcherPrecompileFeesAssigned(fees_, consumeFrom_); + watcherPrecompileCredits += amount_; + emit WatcherPrecompileCreditsAssigned(amount_, consumeFrom_); } - function unblockFees(uint40 requestCount_) external { + function unblockCredits(uint40 requestCount_) external { RequestMetadata memory requestMetadata = deliveryHelper__().getRequestMetadata( requestCount_ ); @@ -346,7 +336,7 @@ contract FeesManager is FeesManagerStorage, Initializable, Ownable, AddressResol userCredit.blockedCredits -= blockedCredits; delete requestCountCredits[requestCount_]; - emit FeesUnblocked(requestCount_, requestMetadata.consumeFrom); + emit CreditsUnblocked(requestCount_, requestMetadata.consumeFrom); } /// @notice Withdraws funds to a specified receiver @@ -356,7 +346,7 @@ contract FeesManager is FeesManagerStorage, Initializable, Ownable, AddressResol /// @param token_ The address of the token /// @param amount_ The amount of tokens to withdraw /// @param receiver_ The address of the receiver - function withdrawFees( + function withdrawCredits( address originAppGatewayOrUser_, uint32 chainSlug_, address token_, @@ -367,9 +357,9 @@ contract FeesManager is FeesManagerStorage, Initializable, Ownable, AddressResol address source = _getCoreAppGateway(originAppGatewayOrUser_); // Check if amount is available in fees plug - uint256 availableAmount = getAvailableFees(source); - if (availableAmount < amount_) revert InsufficientFeesAvailable(); - + uint256 availableAmount = getAvailableCredits(source); + if (availableAmount < amount_) revert InsufficientCreditsAvailable(); + _useAvailableUserCredits(source, amount_); tokenPoolBalances[chainSlug_][token_] -= amount_; @@ -381,15 +371,15 @@ contract FeesManager is FeesManagerStorage, Initializable, Ownable, AddressResol /// @param chainSlug_ The chain identifier /// @param token_ The token address /// @param receiver_ The address of the receiver - function getWithdrawTransmitterFeesPayloadParams( + function getWithdrawTransmitterCreditsPayloadParams( address transmitter_, uint32 chainSlug_, address token_, address receiver_, uint256 amount_ ) external onlyDeliveryHelper returns (PayloadSubmitParams[] memory) { - uint256 maxFeesAvailableForWithdraw = getMaxFeesAvailableForWithdraw(transmitter_); - if (amount_ > maxFeesAvailableForWithdraw) revert InsufficientFeesAvailable(); + uint256 maxCreditsAvailableForWithdraw = getMaxCreditsAvailableForWithdraw(transmitter_); + if (amount_ > maxCreditsAvailableForWithdraw) revert InsufficientCreditsAvailable(); // Clean up storage _useAvailableUserCredits(transmitter_, amount_); @@ -415,7 +405,7 @@ contract FeesManager is FeesManagerStorage, Initializable, Ownable, AddressResol return payloadSubmitParamsArray; } - function getMaxFeesAvailableForWithdraw(address transmitter_) public view returns (uint256) { + function getMaxCreditsAvailableForWithdraw(address transmitter_) public view returns (uint256) { uint256 watcherFees = watcherPrecompileLimits().getTotalFeesRequired(0, 1, 0, 1); uint256 transmitterCredits = userCredits[transmitter_].totalCredits; return @@ -462,30 +452,7 @@ contract FeesManager is FeesManagerStorage, Initializable, Ownable, AddressResol ); deliveryHelper__().queue(queuePayloadParams); } - - function _encodeFeesId(uint256 feesCounter_) internal view returns (bytes32) { - // watcher address (160 bits) | counter (64 bits) - return bytes32((uint256(uint160(address(this))) << 64) | feesCounter_); - } - function _getFeesPlugAddress(uint32 chainSlug_) internal view returns (address) { return watcherPrecompileConfig().feesPlug(chainSlug_); } - - function _isWatcherSignatureValid( - bytes memory 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(); - } } diff --git a/contracts/protocol/payload-delivery/FeesPlug.sol b/contracts/protocol/payload-delivery/FeesPlug.sol index 8a55d9ac..eec87b85 100644 --- a/contracts/protocol/payload-delivery/FeesPlug.sol +++ b/contracts/protocol/payload-delivery/FeesPlug.sol @@ -36,7 +36,7 @@ contract FeesPlug is IFeesPlug, PlugBase, AccessControl { event TokenRemovedFromWhitelist(address token); /// @notice Modifier to check if the balance of a token is enough to withdraw - modifier isFeesEnough(address feeToken_, uint256 fee_) { + modifier isUserCreditsEnough(address feeToken_, uint256 fee_) { uint balance_ = ERC20(feeToken_).balanceOf(address(this)); if (balance_ < fee_) revert InsufficientTokenBalance(feeToken_, balance_, fee_); _; @@ -58,7 +58,7 @@ contract FeesPlug is IFeesPlug, PlugBase, AccessControl { address token_, address receiver_, uint256 amount_ - ) external override onlySocket isFeesEnough(token_, amount_) { + ) external override onlySocket isUserCreditsEnough(token_, amount_) { SafeTransferLib.safeTransfer(token_, receiver_, amount_); emit FeesWithdrawn(token_, receiver_, amount_); } diff --git a/contracts/protocol/payload-delivery/app-gateway/DeliveryHelper.sol b/contracts/protocol/payload-delivery/app-gateway/DeliveryHelper.sol index a950bf92..24a78e3b 100644 --- a/contracts/protocol/payload-delivery/app-gateway/DeliveryHelper.sol +++ b/contracts/protocol/payload-delivery/app-gateway/DeliveryHelper.sol @@ -54,7 +54,7 @@ contract DeliveryHelper is FeesHelpers { // todo: move it to watcher precompile if (requestMetadata_.winningBid.transmitter != address(0)) - IFeesManager(addressResolver__.feesManager()).unblockAndAssignFees( + IFeesManager(addressResolver__.feesManager()).unblockAndAssignCredits( requestCount_, requestMetadata_.winningBid.transmitter ); @@ -94,13 +94,13 @@ contract DeliveryHelper is FeesHelpers { function _settleFees(uint40 requestCount_) internal { // If the request has a winning bid, ie. transmitter already assigned, unblock and assign fees if (requests[requestCount_].winningBid.transmitter != address(0)) { - IFeesManager(addressResolver__.feesManager()).unblockAndAssignFees( + IFeesManager(addressResolver__.feesManager()).unblockAndAssignCredits( requestCount_, requests[requestCount_].winningBid.transmitter ); } else { // If the request has no winning bid, ie. transmitter not assigned, unblock fees - IFeesManager(addressResolver__.feesManager()).unblockFees(requestCount_); + IFeesManager(addressResolver__.feesManager()).unblockCredits(requestCount_); } } diff --git a/contracts/protocol/payload-delivery/app-gateway/FeesHelpers.sol b/contracts/protocol/payload-delivery/app-gateway/FeesHelpers.sol index f737abc3..d7834d8e 100644 --- a/contracts/protocol/payload-delivery/app-gateway/FeesHelpers.sol +++ b/contracts/protocol/payload-delivery/app-gateway/FeesHelpers.sol @@ -9,18 +9,18 @@ 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_); } @@ -64,7 +64,7 @@ abstract contract FeesHelpers is RequestQueue { PayloadSubmitParams[] memory payloadSubmitParamsArray = IFeesManager( addressResolver__.feesManager() - ).getWithdrawTransmitterFeesPayloadParams( + ).getWithdrawTransmitterCreditsPayloadParams( transmitter, chainSlug_, token_, diff --git a/contracts/protocol/payload-delivery/app-gateway/RequestQueue.sol b/contracts/protocol/payload-delivery/app-gateway/RequestQueue.sol index 80b09887..835b0193 100644 --- a/contracts/protocol/payload-delivery/app-gateway/RequestQueue.sol +++ b/contracts/protocol/payload-delivery/app-gateway/RequestQueue.sol @@ -137,7 +137,7 @@ abstract contract RequestQueue is DeliveryUtils { (consumeFrom, , ) = IFeesManager(addressResolver__.feesManager()) .whitelistAppGatewayWithSignature(feesApprovalData_); if ( - !IFeesManager(addressResolver__.feesManager()).isFeesEnough( + !IFeesManager(addressResolver__.feesManager()).isUserCreditsEnough( consumeFrom, appGateway_, maxFees_ diff --git a/contracts/protocol/watcherPrecompile/core/WatcherPrecompile.sol b/contracts/protocol/watcherPrecompile/core/WatcherPrecompile.sol index bcea7bd0..121cf7ce 100644 --- a/contracts/protocol/watcherPrecompile/core/WatcherPrecompile.sol +++ b/contracts/protocol/watcherPrecompile/core/WatcherPrecompile.sol @@ -312,7 +312,7 @@ contract WatcherPrecompile is RequestHandler { ) ) revert InvalidCallerTriggered(); - IFeesManager(addressResolver__.feesManager()).assignWatcherPrecompileFeesFromAddress( + IFeesManager(addressResolver__.feesManager()).assignWatcherPrecompileCreditsFromAddress( watcherPrecompileLimits__.callBackFees(), appGateway ); diff --git a/contracts/protocol/watcherPrecompile/core/WatcherPrecompileCore.sol b/contracts/protocol/watcherPrecompile/core/WatcherPrecompileCore.sol index 14c6323d..1ab6e78f 100644 --- a/contracts/protocol/watcherPrecompile/core/WatcherPrecompileCore.sol +++ b/contracts/protocol/watcherPrecompile/core/WatcherPrecompileCore.sol @@ -257,7 +257,7 @@ abstract contract WatcherPrecompileCore is function _consumeCallbackFeesFromRequestCount(uint256 fees_, uint40 requestCount_) internal { // for callbacks in all precompiles uint256 feesToConsume = fees_ + watcherPrecompileLimits__.callBackFees(); - IFeesManager(addressResolver__.feesManager()).assignWatcherPrecompileFeesFromRequestCount( + IFeesManager(addressResolver__.feesManager()).assignWatcherPrecompileCreditsFromRequestCount( feesToConsume, requestCount_ ); @@ -266,7 +266,7 @@ abstract contract WatcherPrecompileCore is function _consumeCallbackFeesFromAddress(uint256 fees_, address consumeFrom_) internal { // for callbacks in all precompiles uint256 feesToConsume = fees_ + watcherPrecompileLimits__.callBackFees(); - IFeesManager(addressResolver__.feesManager()).assignWatcherPrecompileFeesFromAddress( + IFeesManager(addressResolver__.feesManager()).assignWatcherPrecompileCreditsFromAddress( feesToConsume, consumeFrom_ ); diff --git a/script/counter/WithdrawFeesArbitrumFeesPlug.s.sol b/script/counter/WithdrawFeesArbitrumFeesPlug.s.sol index b2b50a25..ecd8b2b6 100644 --- a/script/counter/WithdrawFeesArbitrumFeesPlug.s.sol +++ b/script/counter/WithdrawFeesArbitrumFeesPlug.s.sol @@ -17,7 +17,7 @@ contract WithdrawFees is Script { address appGatewayAddress = vm.envAddress("APP_GATEWAY"); CounterAppGateway appGateway = CounterAppGateway(appGatewayAddress); - uint256 availableFees = feesManager.getAvailableFees(appGatewayAddress); + uint256 availableFees = feesManager.getMaxCreditsAvailableForWithdraw(appGatewayAddress); console.log("Available fees:", availableFees); if (availableFees > 0) { diff --git a/test/DeliveryHelper.t.sol b/test/DeliveryHelper.t.sol index bf77d5ec..5ea0036d 100644 --- a/test/DeliveryHelper.t.sol +++ b/test/DeliveryHelper.t.sol @@ -190,7 +190,7 @@ contract DeliveryHelperTest is SetupTest { vm.stopPrank(); bytes memory bytesInput = abi.encode( - IFeesManager.incrementFeesDeposited.selector, + IFeesManager.incrementCreditsDeposited.selector, appGateway_, fees_.chainSlug, fees_.token, @@ -201,7 +201,7 @@ contract DeliveryHelperTest is SetupTest { abi.encode(address(feesManager), evmxSlug, signatureNonce, bytesInput) ); bytes memory sig = _createSignature(digest, watcherPrivateKey); - feesManager.incrementFeesDeposited( + feesManager.incrementCreditsDeposited( appGateway_, fees_.chainSlug, fees_.token, diff --git a/test/FeesTest.t.sol b/test/FeesTest.t.sol index 6c9b7118..3383b127 100644 --- a/test/FeesTest.t.sol +++ b/test/FeesTest.t.sol @@ -46,7 +46,7 @@ contract FeesTest is DeliveryHelperTest { ); uint256 transmitterReceiverBalanceBefore = feesConfig.feesTokenUSDC.balanceOf(receiver); - uint256 withdrawAmount = feesManager.getMaxFeesAvailableForWithdraw(transmitterEOA); + uint256 withdrawAmount = feesManager.getMaxCreditsAvailableForWithdraw(transmitterEOA); vm.startPrank(transmitterEOA); uint40 requestCount = deliveryHelper.withdrawTransmitterFees( feesChainSlug, From 0613efa61f1e0559ecd35f6de170514e5b6cf7db Mon Sep 17 00:00:00 2001 From: Ameesha Agrawal Date: Wed, 23 Apr 2025 19:12:15 +0530 Subject: [PATCH 17/21] feat: wrap unwrap fees --- .../protocol/payload-delivery/FeesManager.sol | 30 ++++++++++++++----- .../core/WatcherPrecompile.sol | 7 ++--- 2 files changed, 25 insertions(+), 12 deletions(-) diff --git a/contracts/protocol/payload-delivery/FeesManager.sol b/contracts/protocol/payload-delivery/FeesManager.sol index 7210ffea..119664ff 100644 --- a/contracts/protocol/payload-delivery/FeesManager.sol +++ b/contracts/protocol/payload-delivery/FeesManager.sol @@ -11,7 +11,7 @@ import "../../interfaces/IFeesManager.sol"; import {AddressResolverUtil} from "../utils/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"; -import {console} from "forge-std/console.sol"; + abstract contract FeesManagerStorage is IFeesManager { // slots [0-49] reserved for gap uint256[50] _gap_before; @@ -44,7 +44,6 @@ abstract contract FeesManagerStorage is IFeesManager { /// @dev requestCount => RequestFee mapping(uint40 => uint256) public requestCountCredits; - // @dev amount uint256 public watcherPrecompileCredits; @@ -125,6 +124,7 @@ contract FeesManager is FeesManagerStorage, Initializable, Ownable, AddressResol error AppGatewayNotWhitelisted(); error InvalidAmount(); + constructor() { _disableInitializers(); // disable for implementation } @@ -173,6 +173,23 @@ contract FeesManager is FeesManagerStorage, Initializable, Ownable, AddressResol emit CreditsDepositedUpdated(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_, @@ -408,10 +425,7 @@ contract FeesManager is FeesManagerStorage, Initializable, Ownable, AddressResol 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; + return transmitterCredits > watcherFees ? transmitterCredits - watcherFees : 0; } function _getSwitchboard(uint32 chainSlug_) internal view returns (address) { @@ -442,8 +456,7 @@ contract FeesManager is FeesManagerStorage, Initializable, Ownable, AddressResol } /// @notice hook called by watcher precompile when request is finished - function onRequestComplete(uint40 requestCount_, bytes memory) external { - } + function onRequestComplete(uint40 requestCount_, bytes memory) external {} function _queue(uint32 chainSlug_, bytes memory payload_) internal { QueuePayloadParams memory queuePayloadParams = _createQueuePayloadParams( @@ -452,6 +465,7 @@ contract FeesManager is FeesManagerStorage, Initializable, Ownable, AddressResol ); deliveryHelper__().queue(queuePayloadParams); } + function _getFeesPlugAddress(uint32 chainSlug_) internal view returns (address) { return watcherPrecompileConfig().feesPlug(chainSlug_); } diff --git a/contracts/protocol/watcherPrecompile/core/WatcherPrecompile.sol b/contracts/protocol/watcherPrecompile/core/WatcherPrecompile.sol index 9213a0d0..9a85d88f 100644 --- a/contracts/protocol/watcherPrecompile/core/WatcherPrecompile.sol +++ b/contracts/protocol/watcherPrecompile/core/WatcherPrecompile.sol @@ -175,7 +175,6 @@ contract WatcherPrecompile is RequestHandler { if (r.middleware != msg.sender) revert InvalidCaller(); r.isRequestCancelled = true; - emit RequestCancelledFromGateway(requestCount); } @@ -313,9 +312,9 @@ contract WatcherPrecompile is RequestHandler { ) revert InvalidCallerTriggered(); IFeesManager(addressResolver__.feesManager()).assignWatcherPrecompileCreditsFromAddress( - watcherPrecompileLimits__.callBackFees(), - appGateway - ); + watcherPrecompileLimits__.callBackFees(), + appGateway + ); appGatewayCaller = appGateway; appGatewayCalled[params_[i].triggerId] = true; From 02e9067539598ccdda901d6f5845725f0d54e9a9 Mon Sep 17 00:00:00 2001 From: Ameesha Agrawal Date: Thu, 24 Apr 2025 17:21:42 +0530 Subject: [PATCH 18/21] fix: tests --- contracts/interfaces/IFeesManager.sol | 41 ++++++++------- .../interfaces/IWatcherPrecompileLimits.sol | 2 +- .../protocol/payload-delivery/FeesManager.sol | 50 ++++++++++++++++--- .../app-gateway/FeesHelpers.sol | 5 +- contracts/protocol/socket/Socket.sol | 6 ++- contracts/protocol/socket/SocketConfig.sol | 3 +- .../WatcherPrecompileLimits.sol | 8 +-- .../core/WatcherPrecompile.sol | 6 +-- .../core/WatcherPrecompileCore.sol | 8 ++- script/helpers/AppGatewayFeeBalance.s.sol | 2 +- test/DeliveryHelper.t.sol | 24 ++++----- test/FeesTest.t.sol | 9 +--- test/SetupTest.t.sol | 2 +- test/mock/MockFastSwitchboard.sol | 4 +- 14 files changed, 102 insertions(+), 68 deletions(-) diff --git a/contracts/interfaces/IFeesManager.sol b/contracts/interfaces/IFeesManager.sol index f0fadb1b..4dc24fc5 100644 --- a/contracts/interfaces/IFeesManager.sol +++ b/contracts/interfaces/IFeesManager.sol @@ -20,28 +20,14 @@ interface IFeesManager { function unblockAndAssignCredits(uint40 requestCount_, address transmitter_) external; - function withdrawFees( - address appGateway_, - uint32 chainSlug_, - address token_, - uint256 amount_, - address receiver_ - ) external; - function assignWatcherPrecompileCreditsFromRequestCount( uint256 fees_, uint40 requestCount_ ) external; - function assignWatcherPrecompileCreditsFromAddress(uint256 fees_, address consumeFrom_) external; - - function incrementCreditsDeposited( - address depositTo_, - uint32 chainSlug_, - address token_, - uint256 amount_, - uint256 signatureNonce_, - bytes memory signature_ + function assignWatcherPrecompileCreditsFromAddress( + uint256 fees_, + address consumeFrom_ ) external; function whitelistAppGatewayWithSignature( @@ -58,5 +44,24 @@ interface IFeesManager { uint256 amount_ ) external returns (PayloadSubmitParams[] memory); - function getMaxCreditsAvailableForWithdraw(address transmitter_) external view returns (uint256); + function getMaxCreditsAvailableForWithdraw( + address transmitter_ + ) external view returns (uint256); + + function withdrawCredits( + address originAppGatewayOrUser_, + uint32 chainSlug_, + address token_, + uint256 amount_, + address receiver_ + ) external; + + function depositCredits( + address depositTo_, + uint32 chainSlug_, + address token_, + uint256 amount_, + uint256 signatureNonce_, + bytes memory signature_ + ) external; } diff --git a/contracts/interfaces/IWatcherPrecompileLimits.sol b/contracts/interfaces/IWatcherPrecompileLimits.sol index 9179706a..02899a72 100644 --- a/contracts/interfaces/IWatcherPrecompileLimits.sol +++ b/contracts/interfaces/IWatcherPrecompileLimits.sol @@ -57,7 +57,7 @@ interface IWatcherPrecompileLimits { function queryFees() external view returns (uint256); function finalizeFees() external view returns (uint256); - function scheduleFees() external view returns (uint256); + function timeoutFees() external view returns (uint256); function callBackFees() external view returns (uint256); /// @notice Emitted when limit parameters are updated diff --git a/contracts/protocol/payload-delivery/FeesManager.sol b/contracts/protocol/payload-delivery/FeesManager.sol index 119664ff..34a6ac5e 100644 --- a/contracts/protocol/payload-delivery/FeesManager.sol +++ b/contracts/protocol/payload-delivery/FeesManager.sol @@ -4,10 +4,8 @@ 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 "../utils/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"; @@ -82,7 +80,7 @@ contract FeesManager is FeesManagerStorage, Initializable, Ownable, AddressResol /// @param appGateway The app gateway address /// @param token The token address /// @param amount The new amount deposited - event CreditsDepositedUpdated( + event CreditsDeposited( uint32 indexed chainSlug, address indexed appGateway, address indexed token, @@ -110,6 +108,13 @@ contract FeesManager is FeesManagerStorage, Initializable, Ownable, AddressResol 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 @@ -124,6 +129,7 @@ contract FeesManager is FeesManagerStorage, Initializable, Ownable, AddressResol error AppGatewayNotWhitelisted(); error InvalidAmount(); + error InsufficientBalance(); constructor() { _disableInitializers(); // disable for implementation @@ -164,13 +170,32 @@ contract FeesManager is FeesManagerStorage, Initializable, Ownable, AddressResol address depositTo_, uint32 chainSlug_, address token_, - uint256 amount_ - ) external payable { - if (msg.value != amount_) revert InvalidAmount(); + uint256 amount_, + uint256 signatureNonce_, + bytes memory signature_ + ) external { + if (isNonceUsed[signatureNonce_]) revert NonceUsed(); + isNonceUsed[signatureNonce_] = true; + + // check signature + bytes32 digest = keccak256( + abi.encode( + depositTo_, + chainSlug_, + token_, + amount_, + address(this), + evmxSlug, + signatureNonce_ + ) + ); + + if (_recoverSigner(digest, signature_) != owner()) revert InvalidWatcherSignature(); + UserCredits storage userCredit = userCredits[depositTo_]; userCredit.totalCredits += amount_; tokenPoolBalances[chainSlug_][token_] += amount_; - emit CreditsDepositedUpdated(chainSlug_, depositTo_, token_, amount_); + emit CreditsDeposited(chainSlug_, depositTo_, token_, amount_); } function wrap() external payable { @@ -220,7 +245,7 @@ contract FeesManager is FeesManagerStorage, Initializable, Ownable, AddressResol isApproved ) ); - if (ECDSA.recover(digest, signature_) != consumeFrom) revert InvalidUserSignature(); + if (_recoverSigner(digest, signature_) != consumeFrom) revert InvalidUserSignature(); isAppGatewayWhitelisted[consumeFrom][appGateway] = isApproved; userNonce[consumeFrom]++; @@ -469,4 +494,13 @@ contract FeesManager is FeesManagerStorage, Initializable, Ownable, AddressResol 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/protocol/payload-delivery/app-gateway/FeesHelpers.sol b/contracts/protocol/payload-delivery/app-gateway/FeesHelpers.sol index d7834d8e..8b37a58d 100644 --- a/contracts/protocol/payload-delivery/app-gateway/FeesHelpers.sol +++ b/contracts/protocol/payload-delivery/app-gateway/FeesHelpers.sol @@ -20,7 +20,8 @@ abstract contract FeesHelpers is RequestQueue { revert OnlyAppGateway(); } if (requests[requestCount_].winningBid.transmitter != address(0)) revert WinningBidExists(); - if (requests[requestCount_].maxFees >= newMaxFees_) revert NewMaxFeesLowerThanCurrent(requests[requestCount_].maxFees, newMaxFees_); + if (requests[requestCount_].maxFees >= newMaxFees_) + revert NewMaxFeesLowerThanCurrent(requests[requestCount_].maxFees, newMaxFees_); requests[requestCount_].maxFees = newMaxFees_; emit FeesIncreased(appGateway, requestCount_, newMaxFees_); } @@ -39,7 +40,7 @@ abstract contract FeesHelpers is RequestQueue { address auctionManager_, uint256 fees_ ) external returns (uint40) { - IFeesManager(addressResolver__.feesManager()).withdrawFees( + IFeesManager(addressResolver__.feesManager()).withdrawCredits( msg.sender, chainSlug_, token_, diff --git a/contracts/protocol/socket/Socket.sol b/contracts/protocol/socket/Socket.sol index ced0edf4..021150fd 100644 --- a/contracts/protocol/socket/Socket.sol +++ b/contracts/protocol/socket/Socket.sol @@ -137,9 +137,11 @@ contract Socket is SocketUtils { } } else { payloadExecuted[payloadId_] = ExecutionStatus.Reverted; - address receiver = transmissionParams_.refundAddress == address(0) ? msg.sender : transmissionParams_.refundAddress; + address receiver = transmissionParams_.refundAddress == address(0) + ? msg.sender + : transmissionParams_.refundAddress; SafeTransferLib.forceSafeTransferETH(receiver, msg.value); - + emit ExecutionFailed(payloadId_, returnData); } diff --git a/contracts/protocol/socket/SocketConfig.sol b/contracts/protocol/socket/SocketConfig.sol index b47a2754..5f8c0a8d 100644 --- a/contracts/protocol/socket/SocketConfig.sol +++ b/contracts/protocol/socket/SocketConfig.sol @@ -21,7 +21,7 @@ import {MAX_COPY_BYTES} from "../utils/common/Constants.sol"; abstract contract SocketConfig is ISocket, AccessControl { // socket fee manager ISocketFeeManager public socketFeeManager; - + // @notice mapping of switchboard address to its status, helps socket to block invalid switchboards mapping(address => SwitchboardStatus) public isValidSwitchboard; @@ -63,7 +63,6 @@ abstract contract SocketConfig is ISocket, AccessControl { emit SwitchboardDisabled(msg.sender); } - function setSocketFeeManager(address socketFeeManager_) external onlyRole(GOVERNANCE_ROLE) { emit SocketFeeManagerUpdated(address(socketFeeManager), socketFeeManager_); socketFeeManager = ISocketFeeManager(socketFeeManager_); diff --git a/contracts/protocol/watcherPrecompile/WatcherPrecompileLimits.sol b/contracts/protocol/watcherPrecompile/WatcherPrecompileLimits.sol index 1ea1c2da..01a084da 100644 --- a/contracts/protocol/watcherPrecompile/WatcherPrecompileLimits.sol +++ b/contracts/protocol/watcherPrecompile/WatcherPrecompileLimits.sol @@ -46,7 +46,7 @@ contract WatcherPrecompileLimits is // slot 157: fees uint256 public queryFees; uint256 public finalizeFees; - uint256 public scheduleFees; + uint256 public timeoutFees; uint256 public callBackFees; /// @notice Emitted when the default limit and rate per second are set @@ -173,8 +173,8 @@ contract WatcherPrecompileLimits is finalizeFees = finalizeFees_; } - function setScheduleFees(uint256 scheduleFees_) external onlyOwner { - scheduleFees = scheduleFees_; + function setTimeoutFees(uint256 timeoutFees_) external onlyOwner { + timeoutFees = timeoutFees_; } function setCallBackFees(uint256 callBackFees_) external onlyOwner { @@ -191,7 +191,7 @@ contract WatcherPrecompileLimits is totalFees += callbackCount_ * callBackFees; totalFees += queryCount_ * queryFees; totalFees += finalizeCount_ * finalizeFees; - totalFees += scheduleCount_ * scheduleFees; + totalFees += scheduleCount_ * timeoutFees; return totalFees; } diff --git a/contracts/protocol/watcherPrecompile/core/WatcherPrecompile.sol b/contracts/protocol/watcherPrecompile/core/WatcherPrecompile.sol index 9a85d88f..48cae8fe 100644 --- a/contracts/protocol/watcherPrecompile/core/WatcherPrecompile.sol +++ b/contracts/protocol/watcherPrecompile/core/WatcherPrecompile.sol @@ -312,9 +312,9 @@ contract WatcherPrecompile is RequestHandler { ) revert InvalidCallerTriggered(); IFeesManager(addressResolver__.feesManager()).assignWatcherPrecompileCreditsFromAddress( - watcherPrecompileLimits__.callBackFees(), - appGateway - ); + watcherPrecompileLimits__.callBackFees(), + appGateway + ); appGatewayCaller = appGateway; appGatewayCalled[params_[i].triggerId] = true; diff --git a/contracts/protocol/watcherPrecompile/core/WatcherPrecompileCore.sol b/contracts/protocol/watcherPrecompile/core/WatcherPrecompileCore.sol index 0d787502..ce146eaf 100644 --- a/contracts/protocol/watcherPrecompile/core/WatcherPrecompileCore.sol +++ b/contracts/protocol/watcherPrecompile/core/WatcherPrecompileCore.sol @@ -36,7 +36,7 @@ abstract contract WatcherPrecompileCore is ) internal returns (bytes32 timeoutId) { if (delayInSeconds_ > maxTimeoutDelayInSeconds) revert TimeoutDelayTooLarge(); - _consumeCallbackFeesFromAddress(watcherPrecompileLimits__.scheduleFees(), msg.sender); + _consumeCallbackFeesFromAddress(watcherPrecompileLimits__.timeoutFees(), msg.sender); uint256 executeAt = block.timestamp + delayInSeconds_; timeoutId = _encodeTimeoutId(); @@ -259,10 +259,8 @@ abstract contract WatcherPrecompileCore is function _consumeCallbackFeesFromRequestCount(uint256 fees_, uint40 requestCount_) internal { // for callbacks in all precompiles uint256 feesToConsume = fees_ + watcherPrecompileLimits__.callBackFees(); - IFeesManager(addressResolver__.feesManager()).assignWatcherPrecompileCreditsFromRequestCount( - feesToConsume, - requestCount_ - ); + IFeesManager(addressResolver__.feesManager()) + .assignWatcherPrecompileCreditsFromRequestCount(feesToConsume, requestCount_); } function _consumeCallbackFeesFromAddress(uint256 fees_, address consumeFrom_) internal { diff --git a/script/helpers/AppGatewayFeeBalance.s.sol b/script/helpers/AppGatewayFeeBalance.s.sol index d2ba38d8..8e80861f 100644 --- a/script/helpers/AppGatewayFeeBalance.s.sol +++ b/script/helpers/AppGatewayFeeBalance.s.sol @@ -18,7 +18,7 @@ contract CheckDepositedFees is Script { console.log("totalCredits fees:", totalCredits); console.log("blockedCredits fees:", blockedCredits); - uint256 availableFees = feesManager.getAvailableFees(appGateway); + uint256 availableFees = feesManager.getAvailableCredits(appGateway); console.log("Available fees:", availableFees); } } diff --git a/test/DeliveryHelper.t.sol b/test/DeliveryHelper.t.sol index d4c91351..03e005a0 100644 --- a/test/DeliveryHelper.t.sol +++ b/test/DeliveryHelper.t.sol @@ -189,25 +189,25 @@ contract DeliveryHelperTest is SetupTest { socketConfig.feesPlug.depositToFeeAndNative(fees_.token, appGateway_, fees_.amount); vm.stopPrank(); - bytes memory bytesInput = abi.encode( - IFeesManager.incrementCreditsDeposited.selector, - appGateway_, - fees_.chainSlug, - fees_.token, - fees_.amount - ); - bytes32 digest = keccak256( - abi.encode(address(feesManager), evmxSlug, signatureNonce, bytesInput) + abi.encode( + appGateway_, + fees_.chainSlug, + fees_.token, + fees_.amount, + address(feesManager), + evmxSlug, + signatureNonce + ) ); - bytes memory sig = _createSignature(digest, watcherPrivateKey); - feesManager.incrementCreditsDeposited( + + feesManager.depositCredits( appGateway_, fees_.chainSlug, fees_.token, fees_.amount, signatureNonce++, - sig + _createSignature(digest, watcherPrivateKey) ); } diff --git a/test/FeesTest.t.sol b/test/FeesTest.t.sol index 3383b127..1f6a0ba0 100644 --- a/test/FeesTest.t.sol +++ b/test/FeesTest.t.sol @@ -70,7 +70,6 @@ contract FeesTest is DeliveryHelperTest { } function testWithdrawFeeTokensAppGateway() public { - uint256 receiverBalanceBefore = feesConfig.feesTokenUSDC.balanceOf(receiver); uint256 withdrawAmount = 0.5 ether; @@ -82,7 +81,6 @@ contract FeesTest is DeliveryHelperTest { ); executeRequest(new bytes[](0)); - assertEq( receiverBalanceBefore + withdrawAmount, feesConfig.feesTokenUSDC.balanceOf(receiver), @@ -90,9 +88,7 @@ contract FeesTest is DeliveryHelperTest { ); } - - function testWithdrawFeeTokensUser() public { - + function testWithdrawFeeTokensUser() public { depositUSDCFees( user, OnChainFees({ @@ -111,12 +107,11 @@ contract FeesTest is DeliveryHelperTest { address(feesConfig.feesTokenUSDC), withdrawAmount, user, - address(auctionManager), + address(auctionManager), maxFees ); executeRequest(new bytes[](0)); - assertEq( receiverBalanceBefore + withdrawAmount, feesConfig.feesTokenUSDC.balanceOf(user), diff --git a/test/SetupTest.t.sol b/test/SetupTest.t.sol index 322ac45d..cf4a6ca2 100644 --- a/test/SetupTest.t.sol +++ b/test/SetupTest.t.sol @@ -202,7 +202,7 @@ contract SetupTest is Test { watcherPrecompileLimits.setCallBackFees(1); watcherPrecompileLimits.setFinalizeFees(1); watcherPrecompileLimits.setQueryFees(1); - watcherPrecompileLimits.setScheduleFees(1); + watcherPrecompileLimits.setTimeoutFees(1); vm.stopPrank(); } diff --git a/test/mock/MockFastSwitchboard.sol b/test/mock/MockFastSwitchboard.sol index e831d02c..3341fab8 100644 --- a/test/mock/MockFastSwitchboard.sol +++ b/test/mock/MockFastSwitchboard.sol @@ -21,11 +21,11 @@ contract MockFastSwitchboard is ISwitchboard { owner = owner_; } - function attest(bytes32 , bytes calldata ) external { + function attest(bytes32, bytes calldata) external { // TODO: implement } - function allowPayload(bytes32 , bytes32) external pure returns (bool) { + function allowPayload(bytes32, bytes32) external pure returns (bool) { // digest has enough attestations return true; } From f1a6ce3b05942f8f5e401a8947bfe02ff4102780 Mon Sep 17 00:00:00 2001 From: Ameesha Agrawal Date: Thu, 24 Apr 2025 22:59:36 +0530 Subject: [PATCH 19/21] fix: payable deposit credit --- contracts/interfaces/IFeesManager.sol | 3 +-- .../protocol/payload-delivery/FeesManager.sol | 14 +++++++------- test/DeliveryHelper.t.sol | 3 +-- 3 files changed, 9 insertions(+), 11 deletions(-) diff --git a/contracts/interfaces/IFeesManager.sol b/contracts/interfaces/IFeesManager.sol index 4dc24fc5..02701602 100644 --- a/contracts/interfaces/IFeesManager.sol +++ b/contracts/interfaces/IFeesManager.sol @@ -60,8 +60,7 @@ interface IFeesManager { address depositTo_, uint32 chainSlug_, address token_, - uint256 amount_, uint256 signatureNonce_, bytes memory signature_ - ) external; + ) external payable; } diff --git a/contracts/protocol/payload-delivery/FeesManager.sol b/contracts/protocol/payload-delivery/FeesManager.sol index 34a6ac5e..acc816c1 100644 --- a/contracts/protocol/payload-delivery/FeesManager.sol +++ b/contracts/protocol/payload-delivery/FeesManager.sol @@ -163,27 +163,27 @@ contract FeesManager is FeesManagerStorage, Initializable, Ownable, AddressResol /// @notice Adds the fees deposited for an app gateway on a chain /// @param depositTo_ The app gateway address - /// @param amount_ The amount deposited // @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 amount_, uint256 signatureNonce_, bytes memory signature_ - ) external { + ) 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_, + amount, address(this), evmxSlug, signatureNonce_ @@ -193,9 +193,9 @@ contract FeesManager is FeesManagerStorage, Initializable, Ownable, AddressResol 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_); + userCredit.totalCredits += amount; + tokenPoolBalances[chainSlug_][token_] += amount; + emit CreditsDeposited(chainSlug_, depositTo_, token_, amount); } function wrap() external payable { diff --git a/test/DeliveryHelper.t.sol b/test/DeliveryHelper.t.sol index 03e005a0..e8a689ad 100644 --- a/test/DeliveryHelper.t.sol +++ b/test/DeliveryHelper.t.sol @@ -201,11 +201,10 @@ contract DeliveryHelperTest is SetupTest { ) ); - feesManager.depositCredits( + feesManager.depositCredits{value: fees_.amount}( appGateway_, fees_.chainSlug, fees_.token, - fees_.amount, signatureNonce++, _createSignature(digest, watcherPrivateKey) ); From 91dbe9d444fbb498a52f474fcc0ea63f4893beda Mon Sep 17 00:00:00 2001 From: Ameesha Agrawal Date: Thu, 24 Apr 2025 23:34:46 +0530 Subject: [PATCH 20/21] fix: remove approval data from middleware and watcher --- contracts/base/AppGatewayBase.sol | 25 ++++++++------- contracts/interfaces/IMiddleware.sol | 3 +- .../protocol/payload-delivery/FeesManager.sol | 2 +- .../app-gateway/FeesHelpers.sol | 5 +-- .../app-gateway/RequestQueue.sol | 31 +++++++------------ contracts/protocol/utils/common/Structs.sol | 1 - test/apps/Counter.t.sol | 2 +- 7 files changed, 32 insertions(+), 37 deletions(-) diff --git a/contracts/base/AppGatewayBase.sol b/contracts/base/AppGatewayBase.sol index 572ee516..64fc82ad 100644 --- a/contracts/base/AppGatewayBase.sol +++ b/contracts/base/AppGatewayBase.sol @@ -25,29 +25,33 @@ abstract contract AppGatewayBase is AddressResolverUtil, IAppGateway { mapping(bytes32 => mapping(uint32 => address)) public override forwarderAddresses; mapping(bytes32 => bytes) public creationCodeWithArgs; + address public consumeFrom; + /// @notice Modifier to treat functions async modifier async(bytes memory feesApprovalData_) { - _preAsync(); + _preAsync(feesApprovalData_); _; - _postAsync(feesApprovalData_); + _postAsync(); } - function _postAsync(bytes memory feesApprovalData_) internal { + function _postAsync() internal { isAsyncModifierSet = false; - if (feesApprovalData_.length == 0) { - feesApprovalData_ = abi.encode(address(this), address(this), true, new bytes(0)); - } - // todo: cache the feesApprovalData for next async in same request - deliveryHelper__().batch(maxFees, auctionManager, feesApprovalData_, onCompleteData); + + deliveryHelper__().batch(maxFees, auctionManager, consumeFrom, onCompleteData); _markValidPromises(); onCompleteData = bytes(""); } - function _preAsync() internal { + function _preAsync(bytes memory feesApprovalData_) internal { isAsyncModifierSet = true; _clearOverrides(); deliveryHelper__().clearQueue(); addressResolver__.clearPromises(); + + 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 @@ -189,8 +193,7 @@ abstract contract AppGatewayBase is AddressResolverUtil, IAppGateway { return address(0); } - onChainAddress = IForwarder(forwarderAddresses[contractId_][chainSlug_]) - .getOnChainAddress(); + onChainAddress = IForwarder(forwarderAddresses[contractId_][chainSlug_]).getOnChainAddress(); } //////////////////////////////////////////////////////////////////////////////////////////////// diff --git a/contracts/interfaces/IMiddleware.sol b/contracts/interfaces/IMiddleware.sol index 00099830..6bbd5a3d 100644 --- a/contracts/interfaces/IMiddleware.sol +++ b/contracts/interfaces/IMiddleware.sol @@ -25,13 +25,12 @@ interface IMiddleware { /// @notice Batches a request /// @param fees_ The fees for the request /// @param auctionManager_ The address of the auction manager - /// @param feesApprovalData_ the data to be passed to the fees manager /// @param onCompleteData_ The data to be passed to the onComplete callback /// @return requestCount The request id function batch( uint256 fees_, address auctionManager_, - bytes memory feesApprovalData_, + address consumeFrom_, bytes memory onCompleteData_ ) external returns (uint40 requestCount); diff --git a/contracts/protocol/payload-delivery/FeesManager.sol b/contracts/protocol/payload-delivery/FeesManager.sol index acc816c1..b2e5b75e 100644 --- a/contracts/protocol/payload-delivery/FeesManager.sol +++ b/contracts/protocol/payload-delivery/FeesManager.sol @@ -246,9 +246,9 @@ contract FeesManager is FeesManagerStorage, Initializable, Ownable, AddressResol ) ); if (_recoverSigner(digest, signature_) != consumeFrom) revert InvalidUserSignature(); - isAppGatewayWhitelisted[consumeFrom][appGateway] = isApproved; userNonce[consumeFrom]++; + return (consumeFrom, appGateway, isApproved); } diff --git a/contracts/protocol/payload-delivery/app-gateway/FeesHelpers.sol b/contracts/protocol/payload-delivery/app-gateway/FeesHelpers.sol index 8b37a58d..f1845865 100644 --- a/contracts/protocol/payload-delivery/app-gateway/FeesHelpers.sol +++ b/contracts/protocol/payload-delivery/app-gateway/FeesHelpers.sol @@ -10,6 +10,7 @@ abstract contract FeesHelpers is RequestQueue { 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 @@ -47,8 +48,7 @@ abstract contract FeesHelpers is RequestQueue { amount_, receiver_ ); - bytes memory feesApprovalData = abi.encode(msg.sender, msg.sender, true, bytes("")); - return _batch(msg.sender, auctionManager_, fees_, feesApprovalData, bytes("")); + return _batch(msg.sender, auctionManager_, msg.sender, fees_, bytes("")); } /// @notice Withdraws fees to a specified receiver @@ -89,6 +89,7 @@ abstract contract FeesHelpers is RequestQueue { // 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 diff --git a/contracts/protocol/payload-delivery/app-gateway/RequestQueue.sol b/contracts/protocol/payload-delivery/app-gateway/RequestQueue.sol index 835b0193..2fe73e94 100644 --- a/contracts/protocol/payload-delivery/app-gateway/RequestQueue.sol +++ b/contracts/protocol/payload-delivery/app-gateway/RequestQueue.sol @@ -26,11 +26,11 @@ abstract contract RequestQueue is DeliveryUtils { function batch( uint256 maxFees_, address auctionManager_, - bytes memory feesApprovalData_, + address consumeFrom_, bytes memory onCompleteData_ ) external returns (uint40 requestCount) { address appGateway = _getCoreAppGateway(msg.sender); - return _batch(appGateway, auctionManager_, maxFees_, feesApprovalData_, onCompleteData_); + return _batch(appGateway, auctionManager_, consumeFrom_, maxFees_, onCompleteData_); } /// @notice Initiates a batch of payloads @@ -40,8 +40,8 @@ abstract contract RequestQueue is DeliveryUtils { function _batch( address appGateway_, address auctionManager_, + address consumeFrom_, uint256 maxFees_, - bytes memory feesApprovalData_, bytes memory onCompleteData_ ) internal returns (uint40 requestCount) { if (queuePayloadParams.length == 0) return 0; @@ -50,7 +50,6 @@ abstract contract RequestQueue is DeliveryUtils { appGateway: appGateway_, auctionManager: _getAuctionManager(auctionManager_), maxFees: maxFees_, - feesApprovalData: feesApprovalData_, onCompleteData: onCompleteData_, onlyReadRequests: false, queryCount: 0, @@ -69,18 +68,14 @@ abstract contract RequestQueue is DeliveryUtils { params.queryCount = queryCount; params.finalizeCount = finalizeCount; - address consumeFrom = _checkBatch( - params.appGateway, - params.feesApprovalData, - params.maxFees - ); + _checkBatch(consumeFrom_, params.appGateway, params.maxFees); - return _submitBatchRequest(payloadSubmitParamsArray, consumeFrom, params); + return _submitBatchRequest(payloadSubmitParamsArray, consumeFrom_, params); } function _submitBatchRequest( PayloadSubmitParams[] memory payloadSubmitParamsArray, - address consumeFrom, + address consumeFrom_, BatchParams memory params ) internal returns (uint40 requestCount) { RequestMetadata memory requestMetadata = RequestMetadata({ @@ -90,7 +85,7 @@ abstract contract RequestQueue is DeliveryUtils { winningBid: Bid({fee: 0, transmitter: address(0), extraData: new bytes(0)}), onCompleteData: params.onCompleteData, onlyReadRequests: params.onlyReadRequests, - consumeFrom: consumeFrom, + consumeFrom: consumeFrom_, queryCount: params.queryCount, finalizeCount: params.finalizeCount }); @@ -127,24 +122,22 @@ abstract contract RequestQueue is DeliveryUtils { ? IAddressResolver(addressResolver__).defaultAuctionManager() : auctionManager_; } + function _checkBatch( + address consumeFrom_, address appGateway_, - bytes memory feesApprovalData_, uint256 maxFees_ - ) internal returns (address consumeFrom) { + ) internal view { if (queuePayloadParams.length > REQUEST_PAYLOAD_COUNT_LIMIT) revert RequestPayloadCountLimitExceeded(); - (consumeFrom, , ) = IFeesManager(addressResolver__.feesManager()) - .whitelistAppGatewayWithSignature(feesApprovalData_); + if ( !IFeesManager(addressResolver__.feesManager()).isUserCreditsEnough( - consumeFrom, + consumeFrom_, appGateway_, maxFees_ ) ) revert InsufficientFees(); - - return consumeFrom; } /// @notice Creates an array of payload details diff --git a/contracts/protocol/utils/common/Structs.sol b/contracts/protocol/utils/common/Structs.sol index 8e63a0ea..5926ea9d 100644 --- a/contracts/protocol/utils/common/Structs.sol +++ b/contracts/protocol/utils/common/Structs.sol @@ -55,7 +55,6 @@ struct BatchParams { address appGateway; address auctionManager; uint256 maxFees; - bytes feesApprovalData; bytes onCompleteData; bool onlyReadRequests; uint256 queryCount; diff --git a/test/apps/Counter.t.sol b/test/apps/Counter.t.sol index 5e34404f..8980a314 100644 --- a/test/apps/Counter.t.sol +++ b/test/apps/Counter.t.sol @@ -34,7 +34,7 @@ contract CounterTest is DeliveryHelperTest { requestCount = _deploy(chainSlug, IAppGateway(counterGateway), contractIds); } - function testCounterDeployment() external { + function testCounterDeployment1() external { deploySetup(); deployCounterApp(arbChainSlug); From eb419c325e3609adc14a5cc86d94305f321be704 Mon Sep 17 00:00:00 2001 From: Ameesha Agrawal Date: Thu, 24 Apr 2025 23:40:11 +0530 Subject: [PATCH 21/21] feat: modifier with consume from in case gateways resolve it --- contracts/base/AppGatewayBase.sol | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/contracts/base/AppGatewayBase.sol b/contracts/base/AppGatewayBase.sol index 64fc82ad..4b7e9950 100644 --- a/contracts/base/AppGatewayBase.sol +++ b/contracts/base/AppGatewayBase.sol @@ -34,6 +34,15 @@ abstract contract AppGatewayBase is AddressResolverUtil, IAppGateway { _postAsync(); } + // todo: can't overload modifier with same name, can rename later + /// @notice Modifier to treat functions async with consume from address + modifier asyncWithConsume(address consumeFrom_) { + _preAsync(new bytes(0)); + consumeFrom = consumeFrom_; + _; + _postAsync(); + } + function _postAsync() internal { isAsyncModifierSet = false; @@ -48,6 +57,10 @@ abstract contract AppGatewayBase is AddressResolverUtil, IAppGateway { deliveryHelper__().clearQueue(); addressResolver__.clearPromises(); + _handleFeesApproval(feesApprovalData_); + } + + function _handleFeesApproval(bytes memory feesApprovalData_) internal { if (feesApprovalData_.length > 0) { (consumeFrom, , ) = IFeesManager(addressResolver__.feesManager()) .whitelistAppGatewayWithSignature(feesApprovalData_);