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..c47232fe 100644 --- a/contracts/protocol/payload-delivery/FeesManager.sol +++ b/contracts/protocol/payload-delivery/FeesManager.sol @@ -10,9 +10,10 @@ 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 { + // slots [0-49] reserved for gap uint256[50] _gap_before; @@ -25,34 +26,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 +154,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 +200,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 (requestCountBlockedFees[requestCount_].amount > 0) - availableFees += requestCountBlockedFees[requestCount_].amount; - - uint256 feesNeeded = winningBid_.fee + watcherFees_; - if (availableFees < feesNeeded) revert InsufficientFeesAvailable(); + if (getAvailableFees(consumeFrom_) < transmitterFees_) revert InsufficientFeesAvailable(); - TokenBalance storage tokenBalance = userFeeBalances[consumeFrom_][ - feesGivenByApp_.feePoolChain - ][feesGivenByApp_.feePoolToken]; + UserCredits storage userCredit = userCredits[consumeFrom_]; + userCredit.blockedCredits += transmitterFees_; - 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 +233,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_ - ); + RequestFee memory requestFee = requestCountCredits[requestCount_]; + if (requestFee.blockedCredits == 0) return; - TokenBalance storage tokenBalance = userFeeBalances[consumeFrom][fees.feePoolChain][ - fees.feePoolToken - ]; - - 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 +284,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 +302,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 +343,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 {} } 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 -}