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 f3eaf0f3..3443e54b 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; @@ -221,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_, @@ -230,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 ); @@ -244,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]; @@ -258,6 +259,7 @@ contract FeesManager is FeesManagerStorage, Initializable, Ownable, AddressResol feePoolToken: feesGivenByApp_.feePoolToken, amount: feesNeeded }); + requestCountConsumeFrom[requestCount_] = consumeFrom_; emit FeesBlocked( requestCount_, @@ -272,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_]; @@ -299,15 +307,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 +419,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/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/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..9e1ac974 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,11 +202,14 @@ 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)) { + // todo: limit the gas used for promise resolution // Resolve each promise with its corresponding return data bool success = IPromise(asyncPromise).markResolved( - payloadParams.payloadHeader.getRequestCount(), + requestCount, resolvedPromises_[i].payloadId, resolvedPromises_[i].returnData ); @@ -224,9 +221,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 +229,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..d8cef4f0 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,19 @@ abstract contract WatcherPrecompileCore is if (signer != owner()) revert InvalidWatcherSignature(); } + function _consumeFees(uint40 requestCount_, uint256 fees_) internal { + RequestMetadata memory requestMetadata_ = requestMetadata[requestCount_]; + + // for callbacks in all precompiles + uint256 feesToConsume = fees_ + watcherPrecompileLimits__().callBackFees(); + IFeesManager(addressResolver__.feesManager()).assignWatcherPrecompileFees( + requestMetadata_.fees.feePoolChain, + requestMetadata_.fees.feePoolToken, + feesToConsume, + 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..12dd3d53 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 [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