From 6c6e9f5aabe4f392fc0981d1c0b9dd62b3076461 Mon Sep 17 00:00:00 2001 From: ChiTimesChi <88190723+ChiTimesChi@users.noreply.github.com> Date: Fri, 20 May 2022 18:45:24 +0300 Subject: [PATCH 01/47] fix GasFeePricing interface --- contracts/messaging/interfaces/IGasFeePricing.sol | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/contracts/messaging/interfaces/IGasFeePricing.sol b/contracts/messaging/interfaces/IGasFeePricing.sol index 27131a31b..9a071c1f1 100644 --- a/contracts/messaging/interfaces/IGasFeePricing.sol +++ b/contracts/messaging/interfaces/IGasFeePricing.sol @@ -24,5 +24,5 @@ interface IGasFeePricing { * @notice Returns srcGasToken fee to charge in wei for the cross-chain message based on the gas limit * @param _options Versioned struct used to instruct relayer on how to proceed with gas limits. Contains data on gas limit to submit tx with. */ - function estimateGasFee(uint256 _dstChainId, bytes calldata _options) external returns (uint256); + function estimateGasFee(uint256 _dstChainId, bytes calldata _options) external view returns (uint256); } From 0b21dbb95b5a342f7bbc15729dde3a078258d4b7 Mon Sep 17 00:00:00 2001 From: ChiTimesChi <88190723+ChiTimesChi@users.noreply.github.com> Date: Sat, 21 May 2022 02:09:07 +0300 Subject: [PATCH 02/47] GasFeePricing: first draft --- .../messaging/GasFeePricingUpgradeable.sol | 141 ++++++++++++++++++ contracts/messaging/libraries/Options.sol | 51 +++++++ 2 files changed, 192 insertions(+) create mode 100644 contracts/messaging/GasFeePricingUpgradeable.sol create mode 100644 contracts/messaging/libraries/Options.sol diff --git a/contracts/messaging/GasFeePricingUpgradeable.sol b/contracts/messaging/GasFeePricingUpgradeable.sol new file mode 100644 index 000000000..8ae426c75 --- /dev/null +++ b/contracts/messaging/GasFeePricingUpgradeable.sol @@ -0,0 +1,141 @@ +// SPDX-License-Identifier: MIT + +pragma solidity 0.8.13; + +import "./framework/SynMessagingReceiverUpgradeable.sol"; +import "./interfaces/IGasFeePricing.sol"; +import "./libraries/Options.sol"; + +contract GasFeePricingUpgradeable is SynMessagingReceiverUpgradeable, IGasFeePricing { + struct ChainConfig { + // Amount of gas units needed to receive "update chainInfo" message + uint128 gasAmountNeeded; + // Maximum gas airdrop available on chain + uint128 maxGasDrop; + } + + struct ChainInfo { + // Price of chain's gas token in USD, scaled to wei + uint128 gasTokenPrice; + // Price of chain's 1 gas unit in wei + uint128 gasUnitPrice; + } + + struct ChainRatios { + // USD price ratio of dstGasToken / srcGasToken, scaled to wei + uint96 gasTokenPriceRatio; + // How much 1 gas unit on dst chain is worth, + // expressed in src chain wei, multiplied by 10**18 (aka in attoWei = 10^-18 wei) + uint160 gasUnitPriceRatio; + // To calculate gas cost of tx on dst chain, which consumes gasAmount gas units: + // (gasAmount * gasUnitPriceRatio) / 10**18 + // This number is expressed in src chain wei + } + + event ChainInfoUpdated(uint256 indexed chainId, uint256 gasTokenPrice, uint256 gasUnitPrice); + + event MarkupsUpdated(uint256 markupGasDrop, uint256 markupGasUsage); + + // dstChainId => Info + mapping(uint256 => ChainInfo) public dstInfo; + // dstChainId => Ratios + mapping(uint256 => ChainRatios) public dstRatios; + // dstChainId => Config + mapping(uint256 => ChainConfig) public dstConfig; + + ChainInfo public srcInfo; + + // how much message sender is paying, multiple of "estimated price" + // markup of 100% means user is paying exactly the projected price + // set this more than 100% to make sure messaging fees cover the expenses to deliver the msg + uint128 public markupGasDrop; + uint128 public markupGasUsage; + + uint256 public constant DEFAULT_GAS_LIMIT = 200000; + uint256 public constant MARKUP_DENOMINATOR = 100; + + function initialize( + address _messageBus, + uint256 _srcGasTokenPrice, + uint128 _markupGasDrop, + uint128 _markupGasUsage + ) external initializer { + __Ownable_init_unchained(); + messageBus = _messageBus; + srcInfo.gasTokenPrice = uint96(_srcGasTokenPrice); + _updateMarkups(_markupGasDrop, _markupGasUsage); + } + + function estimateGasFee(uint256 _dstChainId, bytes calldata _options) external view returns (uint256 fee) { + uint256 gasLimit; + uint256 dstAirdrop; + if (_options.length != 0) { + (gasLimit, dstAirdrop, ) = Options.decode(_options); + if (dstAirdrop != 0) { + require(dstAirdrop <= dstConfig[_dstChainId].maxGasDrop, "GasDrop higher than max"); + } + } else { + gasLimit = DEFAULT_GAS_LIMIT; + } + + ChainRatios memory dstRatio = dstRatios[_dstChainId]; + (uint128 _markupGasDrop, uint128 _markupGasUsage) = (markupGasDrop, markupGasUsage); + + // Calculate how much gas airdrop is worth in src chain wei + uint256 feeGasDrop = (dstAirdrop * dstRatio.gasTokenPriceRatio) / 10**18; + // Calculate how much gas usage is worth in src chain wei + uint256 feeGasUsage = (gasLimit * dstRatio.gasUnitPriceRatio) / 10**18; + + // Sum up the fees multiplied by their respective markups + fee = (feeGasDrop * _markupGasDrop + feeGasUsage * _markupGasUsage) / MARKUP_DENOMINATOR; + } + + function setCostPerChain( + uint256 _dstChainId, + uint256 _gasUnitPrice, + uint256 _gasTokenPrice + ) external onlyOwner { + _setCostPerChain(_dstChainId, _gasUnitPrice, _gasTokenPrice); + } + + function updateMarkups(uint128 _markupGasDrop, uint128 _markupGasUsage) external onlyOwner { + _updateMarkups(_markupGasDrop, _markupGasUsage); + } + + function _setCostPerChain( + uint256 _dstChainId, + uint256 _gasUnitPrice, + uint256 _gasTokenPrice + ) internal { + require(_gasUnitPrice != 0 && _gasTokenPrice != 0, "Can't set to zero"); + uint256 _srcGasTokenPrice = srcInfo.gasTokenPrice; + require(_srcGasTokenPrice != 0, "Src gas token price is not set"); + + dstInfo[_dstChainId] = ChainInfo({ + gasTokenPrice: uint128(_gasTokenPrice), + gasUnitPrice: uint128(_gasUnitPrice) + }); + dstRatios[_dstChainId] = ChainRatios({ + gasTokenPriceRatio: uint96((_gasTokenPrice * 10**18) / _srcGasTokenPrice), + gasUnitPriceRatio: uint160((_gasUnitPrice * _gasTokenPrice * 10**18) / _srcGasTokenPrice) + }); + + emit ChainInfoUpdated(_dstChainId, _gasTokenPrice, _gasUnitPrice); + } + + function _updateMarkups(uint128 _markupGasDrop, uint128 _markupGasUsage) internal { + require( + _markupGasDrop >= MARKUP_DENOMINATOR && _markupGasUsage >= MARKUP_DENOMINATOR, + "Markup can not be lower than 1" + ); + (markupGasDrop, markupGasUsage) = (_markupGasDrop, _markupGasUsage); + emit MarkupsUpdated(_markupGasDrop, _markupGasUsage); + } + + function _handleMessage( + bytes32 _srcAddress, + uint256 _srcChainId, + bytes memory _message, + address _executor + ) internal override {} +} diff --git a/contracts/messaging/libraries/Options.sol b/contracts/messaging/libraries/Options.sol new file mode 100644 index 000000000..913abfd2c --- /dev/null +++ b/contracts/messaging/libraries/Options.sol @@ -0,0 +1,51 @@ +// SPDX-License-Identifier: MIT + +pragma solidity ^0.8.0; + +library Options { + enum TxType { + UNKNOWN, + DEFAULT, + GASDROP + } + + function encode(uint256 _gasLimit) internal pure returns (bytes memory) { + return abi.encodePacked(uint16(TxType.DEFAULT), _gasLimit); + } + + function encode( + uint256 _gasLimit, + uint256 _gasDropAmount, + bytes32 _dstReceiver + ) internal pure returns (bytes memory) { + return abi.encodePacked(uint16(TxType.GASDROP), _gasLimit, _gasDropAmount, _dstReceiver); + } + + function decode(bytes memory _options) + internal + pure + returns ( + uint256 gasLimit, + uint256 gasDropAmount, + bytes32 dstReceiver + ) + { + require(_options.length == 2 + 32 || _options.length == 2 + 32 * 3, "Wrong _options size"); + uint16 txType; + // solhint-disable-next-line + assembly { + txType := mload(add(_options, 2)) + gasLimit := mload(add(_options, 34)) + } + + if (txType == uint16(TxType.GASDROP)) { + // solhint-disable-next-line + assembly { + gasDropAmount := mload(add(_options, 66)) + dstReceiver := mload(add(_options, 98)) + } + require(gasDropAmount != 0, "gasDropAmount empty"); + require(dstReceiver != bytes32(0), "dstReceiver empty"); + } + } +} From 656210509d203c1e4c22d235872e6cab318f4a5a Mon Sep 17 00:00:00 2001 From: ChiTimesChi <88190723+ChiTimesChi@users.noreply.github.com> Date: Sat, 21 May 2022 13:45:32 +0300 Subject: [PATCH 03/47] chore: formatting + docs --- .../messaging/GasFeePricingUpgradeable.sol | 56 +++++++++++++++++++ 1 file changed, 56 insertions(+) diff --git a/contracts/messaging/GasFeePricingUpgradeable.sol b/contracts/messaging/GasFeePricingUpgradeable.sol index 8ae426c75..704612f5f 100644 --- a/contracts/messaging/GasFeePricingUpgradeable.sol +++ b/contracts/messaging/GasFeePricingUpgradeable.sol @@ -7,6 +7,11 @@ import "./interfaces/IGasFeePricing.sol"; import "./libraries/Options.sol"; contract GasFeePricingUpgradeable is SynMessagingReceiverUpgradeable, IGasFeePricing { + /*‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾ + STRUCTS + __________________________________________________________________________*/ + + /// @dev Dst chain's basic variables, that are unlikely to change over time. struct ChainConfig { // Amount of gas units needed to receive "update chainInfo" message uint128 gasAmountNeeded; @@ -14,6 +19,8 @@ contract GasFeePricingUpgradeable is SynMessagingReceiverUpgradeable, IGasFeePri uint128 maxGasDrop; } + /// @dev Information about dst chain's gas price, which can change over time + /// due to gas token price movement, or gas spikes. struct ChainInfo { // Price of chain's gas token in USD, scaled to wei uint128 gasTokenPrice; @@ -21,6 +28,9 @@ contract GasFeePricingUpgradeable is SynMessagingReceiverUpgradeable, IGasFeePri uint128 gasUnitPrice; } + /// @dev Ratio between src and dst gas price ratio. + /// Used for calculating a fee for sending a msg from src to dst chain. + /// Updated whenever "gas information" is changed for either source or destination chain. struct ChainRatios { // USD price ratio of dstGasToken / srcGasToken, scaled to wei uint96 gasTokenPriceRatio; @@ -32,10 +42,18 @@ contract GasFeePricingUpgradeable is SynMessagingReceiverUpgradeable, IGasFeePri // This number is expressed in src chain wei } + /*‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾ + EVENTS + __________________________________________________________________________*/ + event ChainInfoUpdated(uint256 indexed chainId, uint256 gasTokenPrice, uint256 gasUnitPrice); event MarkupsUpdated(uint256 markupGasDrop, uint256 markupGasUsage); + /*‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾ + DESTINATION CHAINS STORAGE + __________________________________________________________________________*/ + // dstChainId => Info mapping(uint256 => ChainInfo) public dstInfo; // dstChainId => Ratios @@ -43,6 +61,10 @@ contract GasFeePricingUpgradeable is SynMessagingReceiverUpgradeable, IGasFeePri // dstChainId => Config mapping(uint256 => ChainConfig) public dstConfig; + /*‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾ + SOURCE CHAIN STORAGE + __________________________________________________________________________*/ + ChainInfo public srcInfo; // how much message sender is paying, multiple of "estimated price" @@ -51,9 +73,17 @@ contract GasFeePricingUpgradeable is SynMessagingReceiverUpgradeable, IGasFeePri uint128 public markupGasDrop; uint128 public markupGasUsage; + /*‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾ + CONSTANTS + __________________________________________________________________________*/ + uint256 public constant DEFAULT_GAS_LIMIT = 200000; uint256 public constant MARKUP_DENOMINATOR = 100; + /*‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾ + INITIALIZER + __________________________________________________________________________*/ + function initialize( address _messageBus, uint256 _srcGasTokenPrice, @@ -66,6 +96,11 @@ contract GasFeePricingUpgradeable is SynMessagingReceiverUpgradeable, IGasFeePri _updateMarkups(_markupGasDrop, _markupGasUsage); } + /*‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾ + VIEWS + __________________________________________________________________________*/ + + /// @notice Get the fee for sending a message to dst chain with given options function estimateGasFee(uint256 _dstChainId, bytes calldata _options) external view returns (uint256 fee) { uint256 gasLimit; uint256 dstAirdrop; @@ -90,6 +125,11 @@ contract GasFeePricingUpgradeable is SynMessagingReceiverUpgradeable, IGasFeePri fee = (feeGasDrop * _markupGasDrop + feeGasUsage * _markupGasUsage) / MARKUP_DENOMINATOR; } + /*‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾ + ONLY OWNER + __________________________________________________________________________*/ + + /// @dev Update information about gas unit/token price for a dst chain. function setCostPerChain( uint256 _dstChainId, uint256 _gasUnitPrice, @@ -98,10 +138,18 @@ contract GasFeePricingUpgradeable is SynMessagingReceiverUpgradeable, IGasFeePri _setCostPerChain(_dstChainId, _gasUnitPrice, _gasTokenPrice); } + /// @notice Updates markups, that are used for determining how much fee + // to charge on top of "projected gas cost" of delivering the message function updateMarkups(uint128 _markupGasDrop, uint128 _markupGasUsage) external onlyOwner { _updateMarkups(_markupGasDrop, _markupGasUsage); } + /*‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾ + UPDATE STATE LOGIC + __________________________________________________________________________*/ + + /// @dev Updates information about dst chain gas token/unit price. + /// Dst chain ratios are updated as well. function _setCostPerChain( uint256 _dstChainId, uint256 _gasUnitPrice, @@ -123,6 +171,9 @@ contract GasFeePricingUpgradeable is SynMessagingReceiverUpgradeable, IGasFeePri emit ChainInfoUpdated(_dstChainId, _gasTokenPrice, _gasUnitPrice); } + /// @dev Updates the markups. + /// Markup = 100% means exactly the "projected gas cost" will be charged. + /// Thus, markup can't be lower than 100%. function _updateMarkups(uint128 _markupGasDrop, uint128 _markupGasUsage) internal { require( _markupGasDrop >= MARKUP_DENOMINATOR && _markupGasUsage >= MARKUP_DENOMINATOR, @@ -132,6 +183,11 @@ contract GasFeePricingUpgradeable is SynMessagingReceiverUpgradeable, IGasFeePri emit MarkupsUpdated(_markupGasDrop, _markupGasUsage); } + /*‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾ + MESSAGING LOGIC + __________________________________________________________________________*/ + + /// @dev Handles the received message. function _handleMessage( bytes32 _srcAddress, uint256 _srcChainId, From 36c037228e5b0bfdaad4474813f36a69d6e94791 Mon Sep 17 00:00:00 2001 From: ChiTimesChi <88190723+ChiTimesChi@users.noreply.github.com> Date: Sat, 21 May 2022 13:50:59 +0300 Subject: [PATCH 04/47] estimateGasFees for a bunch of messages --- .../messaging/GasFeePricingUpgradeable.sol | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/contracts/messaging/GasFeePricingUpgradeable.sol b/contracts/messaging/GasFeePricingUpgradeable.sol index 704612f5f..f6fa195e6 100644 --- a/contracts/messaging/GasFeePricingUpgradeable.sol +++ b/contracts/messaging/GasFeePricingUpgradeable.sol @@ -102,6 +102,23 @@ contract GasFeePricingUpgradeable is SynMessagingReceiverUpgradeable, IGasFeePri /// @notice Get the fee for sending a message to dst chain with given options function estimateGasFee(uint256 _dstChainId, bytes calldata _options) external view returns (uint256 fee) { + fee = _estimateGasFee(_dstChainId, _options); + } + + /// @notice Get the fee for sending a message to a bunch of chains with given options + function estimateGasFees(uint256[] calldata _dstChainIds, bytes[] calldata _options) + external + view + returns (uint256 fee) + { + require(_dstChainIds.length == _options.length, "!arrays"); + for (uint256 i = 0; i < _dstChainIds.length; ++i) { + fee = fee + _estimateGasFee(_dstChainIds[i], _options[i]); + } + } + + /// @dev Extracts the gas information from options and calculates the messaging fee + function _estimateGasFee(uint256 _dstChainId, bytes calldata _options) internal view returns (uint256 fee) { uint256 gasLimit; uint256 dstAirdrop; if (_options.length != 0) { From ba0cb185608ed688e5d4e3234935131bf766bac6 Mon Sep 17 00:00:00 2001 From: ChiTimesChi <88190723+ChiTimesChi@users.noreply.github.com> Date: Sat, 21 May 2022 14:50:29 +0300 Subject: [PATCH 05/47] _send(): multiple messages at a time --- .../SynMessagingReceiverUpgradeable.sol | 62 ++++++++++++++++--- 1 file changed, 54 insertions(+), 8 deletions(-) diff --git a/contracts/messaging/framework/SynMessagingReceiverUpgradeable.sol b/contracts/messaging/framework/SynMessagingReceiverUpgradeable.sol index 0800c5cb9..4f77fd65d 100644 --- a/contracts/messaging/framework/SynMessagingReceiverUpgradeable.sol +++ b/contracts/messaging/framework/SynMessagingReceiverUpgradeable.sol @@ -44,15 +44,17 @@ abstract contract SynMessagingReceiverUpgradeable is ISynMessagingReceiver, Owna address _executor ) internal virtual; + // Send a message using full msg.value as fees, refund extra to msg.sender function _send( bytes32 _receiver, uint256 _dstChainId, bytes memory _message, bytes memory _options ) internal virtual { - _send(_receiver, _dstChainId, _message, _options, payable(msg.sender)); + _send(_receiver, _dstChainId, _message, _options, msg.value, payable(msg.sender)); } + // Send a message using full msg.value as fees, refund extra to specified address function _send( bytes32 _receiver, uint256 _dstChainId, @@ -60,14 +62,58 @@ abstract contract SynMessagingReceiverUpgradeable is ISynMessagingReceiver, Owna bytes memory _options, address payable _refundAddress ) internal virtual { - require(trustedRemoteLookup[_dstChainId] != bytes32(0), "Receiver not trusted remote"); - IMessageBus(messageBus).sendMessage{value: msg.value}( - _receiver, - _dstChainId, - _message, - _options, - _refundAddress + _send(_receiver, _dstChainId, _message, _options, msg.value, _refundAddress); + } + + // Send a message to a bunch of chains, refund extra fees to specified address + function _send( + bytes32[] memory _receivers, + uint256[] memory _dstChainIds, + bytes memory _message, + bytes[] memory _options, + uint256[] memory _fees, + address payable _refundAddress + ) internal { + require( + _receivers.length == _fees.length && _dstChainIds.length == _fees.length && _options.length == _fees.length, + "!arrays" + ); + for (uint256 i = 0; i < _fees.length; ++i) { + _send(_receivers[i], _dstChainIds[i], _message, _options[i], _fees[i], _refundAddress); + } + } + + // Send a bunch of messages to a bunch of chains, refund extra fees to specified address + function _send( + bytes32[] memory _receivers, + uint256[] memory _dstChainIds, + bytes[] memory _messages, + bytes[] memory _options, + uint256[] memory _fees, + address payable _refundAddress + ) internal { + require( + _receivers.length == _fees.length && + _dstChainIds.length == _fees.length && + _messages.length == _fees.length && + _options.length == _fees.length, + "!arrays" ); + for (uint256 i = 0; i < _fees.length; ++i) { + _send(_receivers[i], _dstChainIds[i], _messages[i], _options[i], _fees[i], _refundAddress); + } + } + + function _send( + bytes32 _receiver, + uint256 _dstChainId, + bytes memory _message, + bytes memory _options, + uint256 _fee, + address payable _refundAddress + ) internal { + require(trustedRemoteLookup[_dstChainId] != bytes32(0), "Receiver not trusted remote"); + IMessageBus(messageBus).sendMessage{value: _fee}(_receiver, _dstChainId, _message, _options, _refundAddress); } //** Config Functions */ From 03e53e25ae1dfdc382210f29dc1d39aacc60e81d Mon Sep 17 00:00:00 2001 From: ChiTimesChi <88190723+ChiTimesChi@users.noreply.github.com> Date: Sat, 21 May 2022 15:30:53 +0300 Subject: [PATCH 06/47] Cross-chain gas price setup: first draft --- .../messaging/GasFeePricingUpgradeable.sol | 149 ++++++++++++++++-- 1 file changed, 139 insertions(+), 10 deletions(-) diff --git a/contracts/messaging/GasFeePricingUpgradeable.sol b/contracts/messaging/GasFeePricingUpgradeable.sol index f6fa195e6..a3feca4c7 100644 --- a/contracts/messaging/GasFeePricingUpgradeable.sol +++ b/contracts/messaging/GasFeePricingUpgradeable.sol @@ -60,6 +60,10 @@ contract GasFeePricingUpgradeable is SynMessagingReceiverUpgradeable, IGasFeePri mapping(uint256 => ChainRatios) public dstRatios; // dstChainId => Config mapping(uint256 => ChainConfig) public dstConfig; + // dstChainId => GasFeePricing contract address + mapping(uint256 => bytes32) public dstGasFeePricing; + + uint256[] internal dstChainIds; /*‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾ SOURCE CHAIN STORAGE @@ -119,29 +123,65 @@ contract GasFeePricingUpgradeable is SynMessagingReceiverUpgradeable, IGasFeePri /// @dev Extracts the gas information from options and calculates the messaging fee function _estimateGasFee(uint256 _dstChainId, bytes calldata _options) internal view returns (uint256 fee) { + uint256 gasAirdrop; uint256 gasLimit; - uint256 dstAirdrop; if (_options.length != 0) { - (gasLimit, dstAirdrop, ) = Options.decode(_options); - if (dstAirdrop != 0) { - require(dstAirdrop <= dstConfig[_dstChainId].maxGasDrop, "GasDrop higher than max"); + (gasLimit, gasAirdrop, ) = Options.decode(_options); + if (gasAirdrop != 0) { + require(gasAirdrop <= dstConfig[_dstChainId].maxGasDrop, "GasDrop higher than max"); } } else { gasLimit = DEFAULT_GAS_LIMIT; } + fee = _estimateGasFee(_dstChainId, gasAirdrop, gasLimit); + } + + /// @dev Returns a gas fee for sending a message to dst chain, given the amount of gas to airdrop, + /// and amount of gas units for message execution on dst chain. + function _estimateGasFee( + uint256 _dstChainId, + uint256 _gasAirdrop, + uint256 _gasLimit + ) internal view returns (uint256 fee) { ChainRatios memory dstRatio = dstRatios[_dstChainId]; (uint128 _markupGasDrop, uint128 _markupGasUsage) = (markupGasDrop, markupGasUsage); // Calculate how much gas airdrop is worth in src chain wei - uint256 feeGasDrop = (dstAirdrop * dstRatio.gasTokenPriceRatio) / 10**18; + uint256 feeGasDrop = (_gasAirdrop * dstRatio.gasTokenPriceRatio) / 10**18; // Calculate how much gas usage is worth in src chain wei - uint256 feeGasUsage = (gasLimit * dstRatio.gasUnitPriceRatio) / 10**18; + uint256 feeGasUsage = (_gasLimit * dstRatio.gasUnitPriceRatio) / 10**18; // Sum up the fees multiplied by their respective markups fee = (feeGasDrop * _markupGasDrop + feeGasUsage * _markupGasUsage) / MARKUP_DENOMINATOR; } + /// @notice Get total gas fee for calling updateChainInfo() + function estimateUpdateFees() external view returns (uint256 totalFee) { + (totalFee, ) = _estimateUpdateFees(); + } + + /// @dev Returns total gas fee for calling updateChainInfo(), as well as + /// fee for each dst chain. + function _estimateUpdateFees() internal view returns (uint256 totalFee, uint256[] memory fees) { + uint256[] memory _chainIds = dstChainIds; + fees = new uint256[](_chainIds.length); + for (uint256 i = 0; i < _chainIds.length; ++i) { + uint256 chainId = _chainIds[i]; + uint256 gasLimit = dstConfig[chainId].gasAmountNeeded; + if (gasLimit == 0) gasLimit = DEFAULT_GAS_LIMIT; + + uint256 fee = _estimateGasFee(chainId, 0, gasLimit); + totalFee += fee; + fees[i] = fee; + } + } + + /// @dev Converts address to bytes32 + function _addressToBytes32(address _addr) internal pure returns (bytes32) { + return bytes32(uint256(uint160(_addr))); + } + /*‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾ ONLY OWNER __________________________________________________________________________*/ @@ -155,6 +195,61 @@ contract GasFeePricingUpgradeable is SynMessagingReceiverUpgradeable, IGasFeePri _setCostPerChain(_dstChainId, _gasUnitPrice, _gasTokenPrice); } + /// @notice Update information about gas unit/token price for a bunch of chains. + /// Handy for initial setup. + function setCostPerChain( + uint256[] memory _dstChainIds, + uint256[] memory _gasUnitPrices, + uint256[] memory _gasTokenPrices + ) external onlyOwner { + require( + _dstChainIds.length == _gasUnitPrices.length && _dstChainIds.length == _gasTokenPrices.length, + "!arrays" + ); + for (uint256 i = 0; i < _dstChainIds.length; ++i) { + _setCostPerChain(_dstChainIds[i], _gasUnitPrices[i], _gasTokenPrices[i]); + } + } + + /// @notice Update GasFeePricing addresses on a bunch of dst chains. Needed for cross-chain setups. + function setGasFeePricingAddresses(uint256[] memory _dstChainIds, address[] memory _dstGasFeePricing) + external + onlyOwner + { + require(_dstChainIds.length == _dstGasFeePricing.length, "!arrays"); + for (uint256 i = 0; i < _dstChainIds.length; ++i) { + dstGasFeePricing[_dstChainIds[i]] = _addressToBytes32(_dstGasFeePricing[i]); + } + } + + /// @notice Update information about source chain gas token/unit price on all configured dst chains, + /// as well as on the source chain itself. + function updateChainInfo(uint256 _gasTokenPrice, uint256 _gasUnitPrice) external payable onlyOwner { + (uint256 totalFee, uint256[] memory fees) = _estimateUpdateFees(); + require(msg.value >= totalFee, "msg.value doesn't cover all the fees"); + + // TODO: replace placeholder with actual message + bytes memory message = bytes(""); + uint256[] memory chainIds = dstChainIds; + bytes32[] memory receivers = new bytes32[](chainIds.length); + bytes[] memory options = new bytes[](chainIds.length); + + for (uint256 i = 0; i < chainIds.length; ++i) { + uint256 chainId = chainIds[i]; + uint256 gasLimit = dstConfig[chainId].gasAmountNeeded; + if (gasLimit == 0) gasLimit = DEFAULT_GAS_LIMIT; + + receivers[i] = dstGasFeePricing[chainId]; + options[i] = Options.encode(gasLimit); + } + + // send messages before updating the values, so that it's possible to use + // estimateUpdateFees() to calculate the needed fee for the update + _send(receivers, chainIds, message, options, fees, payable(msg.sender)); + _updateChainInfo(_gasTokenPrice, _gasUnitPrice); + if (msg.value > totalFee) payable(msg.sender).transfer(msg.value - totalFee); + } + /// @notice Updates markups, that are used for determining how much fee // to charge on top of "projected gas cost" of delivering the message function updateMarkups(uint128 _markupGasDrop, uint128 _markupGasUsage) external onlyOwner { @@ -176,18 +271,52 @@ contract GasFeePricingUpgradeable is SynMessagingReceiverUpgradeable, IGasFeePri uint256 _srcGasTokenPrice = srcInfo.gasTokenPrice; require(_srcGasTokenPrice != 0, "Src gas token price is not set"); + if (dstInfo[_dstChainId].gasTokenPrice == 0) { + // store dst chainId only if it wasn't added already + dstChainIds.push(_dstChainId); + } + dstInfo[_dstChainId] = ChainInfo({ gasTokenPrice: uint128(_gasTokenPrice), gasUnitPrice: uint128(_gasUnitPrice) }); - dstRatios[_dstChainId] = ChainRatios({ - gasTokenPriceRatio: uint96((_gasTokenPrice * 10**18) / _srcGasTokenPrice), - gasUnitPriceRatio: uint160((_gasUnitPrice * _gasTokenPrice * 10**18) / _srcGasTokenPrice) - }); + _updateChainRatios(_srcGasTokenPrice, _dstChainId, _gasTokenPrice, _gasUnitPrice); emit ChainInfoUpdated(_dstChainId, _gasTokenPrice, _gasUnitPrice); } + /// @dev Updates information about src chain gas token/unit price. + /// All the dst chain ratios are updated as well, if gas token price changed + function _updateChainInfo(uint256 _gasTokenPrice, uint256 _gasUnitPrice) internal { + if (srcInfo.gasTokenPrice != _gasTokenPrice) { + // update ratios only if gas token price has changed + uint256[] memory chainIds = dstChainIds; + for (uint256 i = 0; i < chainIds.length; ++i) { + uint256 chainId = chainIds[i]; + ChainInfo memory info = dstInfo[chainId]; + _updateChainRatios(_gasTokenPrice, chainId, info.gasTokenPrice, info.gasUnitPrice); + } + } + + srcInfo = ChainInfo({gasTokenPrice: uint128(_gasTokenPrice), gasUnitPrice: uint128(_gasUnitPrice)}); + + // TODO: use context chainid here + emit ChainInfoUpdated(block.chainid, _gasTokenPrice, _gasUnitPrice); + } + + /// @dev Updates gas token/unit ratios for a given dst chain + function _updateChainRatios( + uint256 _srcGasTokenPrice, + uint256 _dstChainId, + uint256 _dstGasTokenPrice, + uint256 _dstGasUnitPrice + ) internal { + dstRatios[_dstChainId] = ChainRatios({ + gasTokenPriceRatio: uint96((_dstGasTokenPrice * 10**18) / _srcGasTokenPrice), + gasUnitPriceRatio: uint160((_dstGasUnitPrice * _dstGasTokenPrice * 10**18) / _srcGasTokenPrice) + }); + } + /// @dev Updates the markups. /// Markup = 100% means exactly the "projected gas cost" will be charged. /// Thus, markup can't be lower than 100%. From 69dc3245ed8f69fee35fa0cfdfea4d1755b7095b Mon Sep 17 00:00:00 2001 From: ChiTimesChi <88190723+ChiTimesChi@users.noreply.github.com> Date: Sat, 21 May 2022 20:02:29 +0300 Subject: [PATCH 07/47] GasFeePricing: messages to update chain info on all other chains --- .../messaging/GasFeePricingUpgradeable.sol | 162 ++++++++++++------ 1 file changed, 114 insertions(+), 48 deletions(-) diff --git a/contracts/messaging/GasFeePricingUpgradeable.sol b/contracts/messaging/GasFeePricingUpgradeable.sol index a3feca4c7..4e54bd39f 100644 --- a/contracts/messaging/GasFeePricingUpgradeable.sol +++ b/contracts/messaging/GasFeePricingUpgradeable.sol @@ -69,6 +69,7 @@ contract GasFeePricingUpgradeable is SynMessagingReceiverUpgradeable, IGasFeePri SOURCE CHAIN STORAGE __________________________________________________________________________*/ + ChainConfig public srcConfig; ChainInfo public srcInfo; // how much message sender is paying, multiple of "estimated price" @@ -84,6 +85,9 @@ contract GasFeePricingUpgradeable is SynMessagingReceiverUpgradeable, IGasFeePri uint256 public constant DEFAULT_GAS_LIMIT = 200000; uint256 public constant MARKUP_DENOMINATOR = 100; + uint8 public constant CHAIN_CONFIG_UPDATED = 1; + uint8 public constant CHAIN_INFO_UPDATED = 2; + /*‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾ INITIALIZER __________________________________________________________________________*/ @@ -192,7 +196,7 @@ contract GasFeePricingUpgradeable is SynMessagingReceiverUpgradeable, IGasFeePri uint256 _gasUnitPrice, uint256 _gasTokenPrice ) external onlyOwner { - _setCostPerChain(_dstChainId, _gasUnitPrice, _gasTokenPrice); + _updateDstChainInfo(_dstChainId, _gasUnitPrice, _gasTokenPrice); } /// @notice Update information about gas unit/token price for a bunch of chains. @@ -207,7 +211,7 @@ contract GasFeePricingUpgradeable is SynMessagingReceiverUpgradeable, IGasFeePri "!arrays" ); for (uint256 i = 0; i < _dstChainIds.length; ++i) { - _setCostPerChain(_dstChainIds[i], _gasUnitPrices[i], _gasTokenPrices[i]); + _updateDstChainInfo(_dstChainIds[i], _gasUnitPrices[i], _gasTokenPrices[i]); } } @@ -222,32 +226,21 @@ contract GasFeePricingUpgradeable is SynMessagingReceiverUpgradeable, IGasFeePri } } + /// @notice Update information about source chain config: + /// amount of gas needed to do _updateDstChainInfo() + /// and maximum airdrop available on this chain + function updateChainConfig(uint256 _gasAmountNeeded, uint256 _maxGasDrop) external payable onlyOwner { + _sendUpdateMessages(CHAIN_CONFIG_UPDATED, _gasAmountNeeded, _maxGasDrop); + srcConfig = ChainConfig({gasAmountNeeded: uint128(_gasAmountNeeded), maxGasDrop: uint128(_maxGasDrop)}); + } + /// @notice Update information about source chain gas token/unit price on all configured dst chains, /// as well as on the source chain itself. function updateChainInfo(uint256 _gasTokenPrice, uint256 _gasUnitPrice) external payable onlyOwner { - (uint256 totalFee, uint256[] memory fees) = _estimateUpdateFees(); - require(msg.value >= totalFee, "msg.value doesn't cover all the fees"); - - // TODO: replace placeholder with actual message - bytes memory message = bytes(""); - uint256[] memory chainIds = dstChainIds; - bytes32[] memory receivers = new bytes32[](chainIds.length); - bytes[] memory options = new bytes[](chainIds.length); - - for (uint256 i = 0; i < chainIds.length; ++i) { - uint256 chainId = chainIds[i]; - uint256 gasLimit = dstConfig[chainId].gasAmountNeeded; - if (gasLimit == 0) gasLimit = DEFAULT_GAS_LIMIT; - - receivers[i] = dstGasFeePricing[chainId]; - options[i] = Options.encode(gasLimit); - } - // send messages before updating the values, so that it's possible to use // estimateUpdateFees() to calculate the needed fee for the update - _send(receivers, chainIds, message, options, fees, payable(msg.sender)); - _updateChainInfo(_gasTokenPrice, _gasUnitPrice); - if (msg.value > totalFee) payable(msg.sender).transfer(msg.value - totalFee); + _sendUpdateMessages(CHAIN_INFO_UPDATED, _gasTokenPrice, _gasUnitPrice); + _updateSrcChainInfo(_gasTokenPrice, _gasUnitPrice); } /// @notice Updates markups, that are used for determining how much fee @@ -260,9 +253,42 @@ contract GasFeePricingUpgradeable is SynMessagingReceiverUpgradeable, IGasFeePri UPDATE STATE LOGIC __________________________________________________________________________*/ + /// @dev Updates information about src chain gas token/unit price. + /// All the dst chain ratios are updated as well, if gas token price changed + function _updateSrcChainInfo(uint256 _gasTokenPrice, uint256 _gasUnitPrice) internal { + if (srcInfo.gasTokenPrice != _gasTokenPrice) { + // update ratios only if gas token price has changed + uint256[] memory chainIds = dstChainIds; + for (uint256 i = 0; i < chainIds.length; ++i) { + uint256 chainId = chainIds[i]; + ChainInfo memory info = dstInfo[chainId]; + _updateDstChainRatios(_gasTokenPrice, chainId, info.gasTokenPrice, info.gasUnitPrice); + } + } + + srcInfo = ChainInfo({gasTokenPrice: uint128(_gasTokenPrice), gasUnitPrice: uint128(_gasUnitPrice)}); + + // TODO: use context chainid here + emit ChainInfoUpdated(block.chainid, _gasTokenPrice, _gasUnitPrice); + } + + /// @dev Updates dst chain config: + /// Amount of gas needed to do _updateDstChainInfo() + /// Maximum airdrop available on this chain + function _updateDstChainConfig( + uint256 _dstChainId, + uint256 _gasAmountNeeded, + uint256 _maxGasDrop + ) internal { + dstConfig[_dstChainId] = ChainConfig({ + gasAmountNeeded: uint128(_gasAmountNeeded), + maxGasDrop: uint128(_maxGasDrop) + }); + } + /// @dev Updates information about dst chain gas token/unit price. /// Dst chain ratios are updated as well. - function _setCostPerChain( + function _updateDstChainInfo( uint256 _dstChainId, uint256 _gasUnitPrice, uint256 _gasTokenPrice @@ -280,32 +306,13 @@ contract GasFeePricingUpgradeable is SynMessagingReceiverUpgradeable, IGasFeePri gasTokenPrice: uint128(_gasTokenPrice), gasUnitPrice: uint128(_gasUnitPrice) }); - _updateChainRatios(_srcGasTokenPrice, _dstChainId, _gasTokenPrice, _gasUnitPrice); + _updateDstChainRatios(_srcGasTokenPrice, _dstChainId, _gasTokenPrice, _gasUnitPrice); emit ChainInfoUpdated(_dstChainId, _gasTokenPrice, _gasUnitPrice); } - /// @dev Updates information about src chain gas token/unit price. - /// All the dst chain ratios are updated as well, if gas token price changed - function _updateChainInfo(uint256 _gasTokenPrice, uint256 _gasUnitPrice) internal { - if (srcInfo.gasTokenPrice != _gasTokenPrice) { - // update ratios only if gas token price has changed - uint256[] memory chainIds = dstChainIds; - for (uint256 i = 0; i < chainIds.length; ++i) { - uint256 chainId = chainIds[i]; - ChainInfo memory info = dstInfo[chainId]; - _updateChainRatios(_gasTokenPrice, chainId, info.gasTokenPrice, info.gasUnitPrice); - } - } - - srcInfo = ChainInfo({gasTokenPrice: uint128(_gasTokenPrice), gasUnitPrice: uint128(_gasUnitPrice)}); - - // TODO: use context chainid here - emit ChainInfoUpdated(block.chainid, _gasTokenPrice, _gasUnitPrice); - } - /// @dev Updates gas token/unit ratios for a given dst chain - function _updateChainRatios( + function _updateDstChainRatios( uint256 _srcGasTokenPrice, uint256 _dstChainId, uint256 _dstGasTokenPrice, @@ -333,11 +340,70 @@ contract GasFeePricingUpgradeable is SynMessagingReceiverUpgradeable, IGasFeePri MESSAGING LOGIC __________________________________________________________________________*/ + /// @dev Sends "something updated" messages to all registered dst chains + function _sendUpdateMessages( + uint8 _msgType, + uint256 _newValueA, + uint256 _newValueB + ) internal { + (uint256 totalFee, uint256[] memory fees) = _estimateUpdateFees(); + require(msg.value >= totalFee, "msg.value doesn't cover all the fees"); + + bytes memory message = _encodeMessage(_msgType, uint128(_newValueA), uint128(_newValueB)); + uint256[] memory chainIds = dstChainIds; + bytes32[] memory receivers = new bytes32[](chainIds.length); + bytes[] memory options = new bytes[](chainIds.length); + + for (uint256 i = 0; i < chainIds.length; ++i) { + uint256 chainId = chainIds[i]; + uint256 gasLimit = dstConfig[chainId].gasAmountNeeded; + if (gasLimit == 0) gasLimit = DEFAULT_GAS_LIMIT; + + receivers[i] = dstGasFeePricing[chainId]; + options[i] = Options.encode(gasLimit); + } + + _send(receivers, chainIds, message, options, fees, payable(msg.sender)); + if (msg.value > totalFee) payable(msg.sender).transfer(msg.value - totalFee); + } + /// @dev Handles the received message. function _handleMessage( - bytes32 _srcAddress, + bytes32, uint256 _srcChainId, bytes memory _message, - address _executor - ) internal override {} + address + ) internal override { + (uint8 msgType, uint128 newValueA, uint128 newValueB) = _decodeMessage(_message); + if (msgType == CHAIN_CONFIG_UPDATED) { + _updateDstChainConfig(_srcChainId, newValueA, newValueB); + } else if (msgType == CHAIN_INFO_UPDATED) { + _updateDstChainInfo(_srcChainId, newValueA, newValueB); + } else { + revert("Unknown message type"); + } + } + + /// @dev Encodes "something updated" message. We're taking advantage that + /// whether it's chain config or info, it's always two uint128 values that were updated. + function _encodeMessage( + uint8 _msgType, + uint128 _newValueA, + uint128 _newValueB + ) internal pure returns (bytes memory) { + return abi.encode(_msgType, _newValueA, _newValueB); + } + + /// @dev Decodes "something updated" message. + function _decodeMessage(bytes memory _message) + internal + pure + returns ( + uint8 msgType, + uint128 newValueA, + uint128 newValueB + ) + { + (msgType, newValueA, newValueB) = abi.decode(_message, (uint8, uint128, uint128)); + } } From dbfbd437499c92fd9de85d291bda0e7b1d3d2e99 Mon Sep 17 00:00:00 2001 From: ChiTimesChi <88190723+ChiTimesChi@users.noreply.github.com> Date: Sat, 21 May 2022 20:07:47 +0300 Subject: [PATCH 08/47] GasFeePricing: manual setDstChainConfig --- .../messaging/GasFeePricingUpgradeable.sol | 26 ++++++++++++++++++- 1 file changed, 25 insertions(+), 1 deletion(-) diff --git a/contracts/messaging/GasFeePricingUpgradeable.sol b/contracts/messaging/GasFeePricingUpgradeable.sol index 4e54bd39f..1048d2b9c 100644 --- a/contracts/messaging/GasFeePricingUpgradeable.sol +++ b/contracts/messaging/GasFeePricingUpgradeable.sol @@ -201,7 +201,7 @@ contract GasFeePricingUpgradeable is SynMessagingReceiverUpgradeable, IGasFeePri /// @notice Update information about gas unit/token price for a bunch of chains. /// Handy for initial setup. - function setCostPerChain( + function setCostPerChains( uint256[] memory _dstChainIds, uint256[] memory _gasUnitPrices, uint256[] memory _gasTokenPrices @@ -215,6 +215,30 @@ contract GasFeePricingUpgradeable is SynMessagingReceiverUpgradeable, IGasFeePri } } + /// @dev Update config (gasLimit for sending messages to chain, max gas airdrop) for a dst chain. + function setDstChainConfig( + uint256 _dstChainId, + uint256 _gasAmountNeeded, + uint256 _maxGasDrop + ) external onlyOwner { + _updateDstChainConfig(_dstChainId, _gasAmountNeeded, _maxGasDrop); + } + + /// @dev Update config (gasLimit for sending messages to chain, max gas airdrop) for a bunch of chains. + function setDstChainConfigs( + uint256[] memory _dstChainIds, + uint256[] memory _gasAmountsNeeded, + uint256[] memory _maxGasDrops + ) external onlyOwner { + require( + _dstChainIds.length == _gasAmountsNeeded.length && _dstChainIds.length == _maxGasDrops.length, + "!arrays" + ); + for (uint256 i = 0; i < _dstChainIds.length; ++i) { + _updateDstChainConfig(_dstChainIds[i], _gasAmountsNeeded[i], _maxGasDrops[i]); + } + } + /// @notice Update GasFeePricing addresses on a bunch of dst chains. Needed for cross-chain setups. function setGasFeePricingAddresses(uint256[] memory _dstChainIds, address[] memory _dstGasFeePricing) external From 775783a990fc744921abac81d52d5518b71a204a Mon Sep 17 00:00:00 2001 From: ChiTimesChi <88190723+ChiTimesChi@users.noreply.github.com> Date: Sat, 21 May 2022 20:10:54 +0300 Subject: [PATCH 09/47] estaimateFee should be view --- contracts/messaging/MessageBusSenderUpgradeable.sol | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/contracts/messaging/MessageBusSenderUpgradeable.sol b/contracts/messaging/MessageBusSenderUpgradeable.sol index 9418e45bb..8cde074ef 100644 --- a/contracts/messaging/MessageBusSenderUpgradeable.sol +++ b/contracts/messaging/MessageBusSenderUpgradeable.sol @@ -45,7 +45,7 @@ contract MessageBusSenderUpgradeable is OwnableUpgradeable, PausableUpgradeable return keccak256(abi.encode(_srcAddress, _srcChainId, _dstAddress, _dstChainId, _srcNonce, _message)); } - function estimateFee(uint256 _dstChainId, bytes calldata _options) public returns (uint256) { + function estimateFee(uint256 _dstChainId, bytes calldata _options) public view returns (uint256) { uint256 fee = IGasFeePricing(gasFeePricing).estimateGasFee(_dstChainId, _options); require(fee != 0, "Fee not set"); return fee; From 42c5c474a52a66d0ff6c109a63ea27f394e08551 Mon Sep 17 00:00:00 2001 From: ChiTimesChi <88190723+ChiTimesChi@users.noreply.github.com> Date: Sat, 21 May 2022 21:29:35 +0300 Subject: [PATCH 10/47] add src-messaging to remappings --- remappings.txt | 4 ++- test/dfk/HeroBridgeTest.sol | 26 +++++++++---------- test/dfk/HeroEncoding.t.sol | 2 +- test/dfk/TearBridge.t.sol | 10 +++---- test/messaging/AuthVerifier.t.sol | 2 +- test/messaging/BatchMessageSender.t.sol | 10 +++---- test/messaging/GasFeePricing.t.sol | 2 +- test/messaging/GasFeePricingUpgradeable.t.sol | 0 .../MessageBusReceiverUpgradeable.t.sol | 4 +-- .../MessageBusSenderUpgradeable.t.sol | 4 +-- test/messaging/MessageBusUpgradeable.t.sol | 4 +-- test/messaging/PingPong.t.sol | 10 +++---- 12 files changed, 40 insertions(+), 38 deletions(-) create mode 100644 test/messaging/GasFeePricingUpgradeable.t.sol diff --git a/remappings.txt b/remappings.txt index b7ab34b6d..bf33463ad 100644 --- a/remappings.txt +++ b/remappings.txt @@ -1,2 +1,4 @@ ds-test=lib/ds-test/src -forge-std=lib/forge-std/src \ No newline at end of file +forge-std=lib/forge-std/src + +src-messaging=contracts/messaging \ No newline at end of file diff --git a/test/dfk/HeroBridgeTest.sol b/test/dfk/HeroBridgeTest.sol index e6753bec4..42538765d 100644 --- a/test/dfk/HeroBridgeTest.sol +++ b/test/dfk/HeroBridgeTest.sol @@ -3,19 +3,19 @@ pragma solidity 0.8.13; import "forge-std/Test.sol"; import {Utilities} from "../utils/Utilities.sol"; -import "../../contracts/messaging/dfk/types/HeroTypes.sol"; - -import "../../contracts/messaging/dfk/bridge/HeroBridgeUpgradeable.sol"; -import "../../contracts/messaging/dfk/random/RandomGenerator.sol"; -import "../../contracts/messaging/dfk/auctions/AssistingAuctionUpgradeable.sol"; -import "../../contracts/messaging/dfk/StatScienceUpgradeable.sol"; -import "../../contracts/messaging/dfk/HeroCoreUpgradeable.sol"; - -import "../../contracts/messaging/MessageBusUpgradeable.sol"; -import "../../contracts/messaging/GasFeePricing.sol"; -import "../../contracts/messaging/AuthVerifier.sol"; -import "../../contracts/messaging/apps/PingPong.sol"; -import "../../contracts/messaging/AuthVerifier.sol"; +import "src-messaging/dfk/types/HeroTypes.sol"; + +import "src-messaging/dfk/bridge/HeroBridgeUpgradeable.sol"; +import "src-messaging/dfk/random/RandomGenerator.sol"; +import "src-messaging/dfk/auctions/AssistingAuctionUpgradeable.sol"; +import "src-messaging/dfk/StatScienceUpgradeable.sol"; +import "src-messaging/dfk/HeroCoreUpgradeable.sol"; + +import "src-messaging/MessageBusUpgradeable.sol"; +import "src-messaging/GasFeePricing.sol"; +import "src-messaging/AuthVerifier.sol"; +import "src-messaging/apps/PingPong.sol"; +import "src-messaging/AuthVerifier.sol"; contract HeroBridgeUpgradeableTest is Test { Utilities internal utils; diff --git a/test/dfk/HeroEncoding.t.sol b/test/dfk/HeroEncoding.t.sol index bf2da81d2..087e0e5bb 100644 --- a/test/dfk/HeroEncoding.t.sol +++ b/test/dfk/HeroEncoding.t.sol @@ -1,7 +1,7 @@ pragma solidity 0.8.13; import "forge-std/Test.sol"; -import "../../contracts/messaging/dfk/types/HeroTypes.sol"; +import "src-messaging/dfk/types/HeroTypes.sol"; contract HeroEncodingTest is Test { function testHeroStruct() public { diff --git a/test/dfk/TearBridge.t.sol b/test/dfk/TearBridge.t.sol index a9e008d1f..1e0de8e57 100644 --- a/test/dfk/TearBridge.t.sol +++ b/test/dfk/TearBridge.t.sol @@ -3,12 +3,12 @@ pragma solidity 0.8.13; import "forge-std/Test.sol"; import {Utilities} from "../utils/Utilities.sol"; -import "../../contracts/messaging/dfk/bridge/TearBridge.sol"; -import "../../contracts/messaging/dfk/inventory/GaiaTears.sol"; +import "src-messaging/dfk/bridge/TearBridge.sol"; +import "src-messaging/dfk/inventory/GaiaTears.sol"; -import "../../contracts/messaging/MessageBusUpgradeable.sol"; -import "../../contracts/messaging/GasFeePricing.sol"; -import "../../contracts/messaging/AuthVerifier.sol"; +import "src-messaging/MessageBusUpgradeable.sol"; +import "src-messaging/GasFeePricing.sol"; +import "src-messaging/AuthVerifier.sol"; contract TearBridgeTest is Test { Utilities internal utils; diff --git a/test/messaging/AuthVerifier.t.sol b/test/messaging/AuthVerifier.t.sol index 8fb86af04..4e21ea4cc 100644 --- a/test/messaging/AuthVerifier.t.sol +++ b/test/messaging/AuthVerifier.t.sol @@ -1,7 +1,7 @@ pragma solidity 0.8.13; import "forge-std/Test.sol"; -import "../../contracts/messaging/AuthVerifier.sol"; +import "src-messaging/AuthVerifier.sol"; contract AuthVerifierTest is Test { AuthVerifier public authVerifier; diff --git a/test/messaging/BatchMessageSender.t.sol b/test/messaging/BatchMessageSender.t.sol index e8ab1bc11..a73d7f75b 100644 --- a/test/messaging/BatchMessageSender.t.sol +++ b/test/messaging/BatchMessageSender.t.sol @@ -2,11 +2,11 @@ pragma solidity 0.8.13; import "forge-std/Test.sol"; import {Utilities} from "../utils/Utilities.sol"; -import "../../contracts/messaging/MessageBusUpgradeable.sol"; -import "../../contracts/messaging/GasFeePricing.sol"; -import "../../contracts/messaging/AuthVerifier.sol"; -import "../../contracts/messaging/apps/BatchMessageSender.sol"; -import "../../contracts/messaging/AuthVerifier.sol"; +import "src-messaging/MessageBusUpgradeable.sol"; +import "src-messaging/GasFeePricing.sol"; +import "src-messaging/AuthVerifier.sol"; +import "src-messaging/apps/BatchMessageSender.sol"; +import "src-messaging/AuthVerifier.sol"; contract BatchMessageSenderTest is Test { Utilities internal utils; diff --git a/test/messaging/GasFeePricing.t.sol b/test/messaging/GasFeePricing.t.sol index 22216215b..fb7ac9279 100644 --- a/test/messaging/GasFeePricing.t.sol +++ b/test/messaging/GasFeePricing.t.sol @@ -2,7 +2,7 @@ pragma solidity 0.8.13; import "forge-std/Test.sol"; -import "../../contracts/messaging/GasFeePricing.sol"; +import "src-messaging/GasFeePricing.sol"; contract GasFeePricingTest is Test { GasFeePricing public gasFeePricing; diff --git a/test/messaging/GasFeePricingUpgradeable.t.sol b/test/messaging/GasFeePricingUpgradeable.t.sol new file mode 100644 index 000000000..e69de29bb diff --git a/test/messaging/MessageBusReceiverUpgradeable.t.sol b/test/messaging/MessageBusReceiverUpgradeable.t.sol index 21b4a3ccb..f86e14d71 100644 --- a/test/messaging/MessageBusReceiverUpgradeable.t.sol +++ b/test/messaging/MessageBusReceiverUpgradeable.t.sol @@ -3,8 +3,8 @@ pragma solidity 0.8.13; import "forge-std/Test.sol"; -import "../../contracts/messaging/MessageBusUpgradeable.sol"; -import "../../contracts/messaging/AuthVerifier.sol"; +import "src-messaging/MessageBusUpgradeable.sol"; +import "src-messaging/AuthVerifier.sol"; import "@openzeppelin/contracts-4.5.0/proxy/transparent/TransparentUpgradeableProxy.sol"; diff --git a/test/messaging/MessageBusSenderUpgradeable.t.sol b/test/messaging/MessageBusSenderUpgradeable.t.sol index 8f4181e0c..2c99cb55d 100644 --- a/test/messaging/MessageBusSenderUpgradeable.t.sol +++ b/test/messaging/MessageBusSenderUpgradeable.t.sol @@ -3,8 +3,8 @@ pragma solidity 0.8.13; import "forge-std/Test.sol"; -import "../../contracts/messaging/MessageBusUpgradeable.sol"; -import "../../contracts/messaging/GasFeePricing.sol"; +import "src-messaging/MessageBusUpgradeable.sol"; +import "src-messaging/GasFeePricing.sol"; import "./GasFeePricing.t.sol"; diff --git a/test/messaging/MessageBusUpgradeable.t.sol b/test/messaging/MessageBusUpgradeable.t.sol index 5a87b4825..129bba6ce 100644 --- a/test/messaging/MessageBusUpgradeable.t.sol +++ b/test/messaging/MessageBusUpgradeable.t.sol @@ -3,8 +3,8 @@ pragma solidity 0.8.13; import "forge-std/Test.sol"; -import "../../contracts/messaging/MessageBusUpgradeable.sol"; -import "../../contracts/messaging/AuthVerifier.sol"; +import "src-messaging/MessageBusUpgradeable.sol"; +import "src-messaging/AuthVerifier.sol"; import "@openzeppelin/contracts-4.5.0/proxy/transparent/TransparentUpgradeableProxy.sol"; diff --git a/test/messaging/PingPong.t.sol b/test/messaging/PingPong.t.sol index 2957e4d18..51f9db42c 100644 --- a/test/messaging/PingPong.t.sol +++ b/test/messaging/PingPong.t.sol @@ -2,11 +2,11 @@ pragma solidity 0.8.13; import "forge-std/Test.sol"; import {Utilities} from "../utils/Utilities.sol"; -import "../../contracts/messaging/MessageBusUpgradeable.sol"; -import "../../contracts/messaging/GasFeePricing.sol"; -import "../../contracts/messaging/AuthVerifier.sol"; -import "../../contracts/messaging/apps/PingPong.sol"; -import "../../contracts/messaging/AuthVerifier.sol"; +import "src-messaging/MessageBusUpgradeable.sol"; +import "src-messaging/GasFeePricing.sol"; +import "src-messaging/AuthVerifier.sol"; +import "src-messaging/apps/PingPong.sol"; +import "src-messaging/AuthVerifier.sol"; contract PingPongTest is Test { Utilities internal utils; From 548766be2a5ab0ba8d4b0f25e84caba6574e67e9 Mon Sep 17 00:00:00 2001 From: ChiTimesChi <88190723+ChiTimesChi@users.noreply.github.com> Date: Sat, 21 May 2022 22:07:05 +0300 Subject: [PATCH 11/47] update test Utilities --- test/utils/Utilities.sol | 166 +++++++++++++++++++++++++++++++++++---- 1 file changed, 152 insertions(+), 14 deletions(-) diff --git a/test/utils/Utilities.sol b/test/utils/Utilities.sol index 1345fde84..62dce470b 100644 --- a/test/utils/Utilities.sol +++ b/test/utils/Utilities.sol @@ -1,28 +1,49 @@ -// SPDX-License-Identifier: Unlicense +// SPDX-License-Identifier: MIT pragma solidity >=0.8.0; import "forge-std/Test.sol"; +import "@openzeppelin/contracts-4.5.0/utils/Strings.sol"; +import "@openzeppelin/contracts-4.5.0/proxy/transparent/TransparentUpgradeableProxy.sol"; -//common utilities for forge tests +interface IAccessControl { + function getRoleMember(bytes32 role, uint256 index) external view returns (address); + + function grantRole(bytes32 role, address account) external; + + function revokeRole(bytes32 role, address account) external; +} + +interface IProxy { + function upgradeTo(address) external; +} + +// Common utilities for forge tests contract Utilities is Test { - bytes32 internal nextUser = keccak256(abi.encodePacked("user address")); + bytes32 internal nextUser = keccak256("user address"); + + bytes32 internal nextKappa = keccak256("kappa"); - function addressToBytes32(address _addr) public pure returns (bytes32) { - return bytes32(uint256(uint160(_addr))); + bytes32 internal constant ADMIN_SLOT = 0xb53127684a568b3173ae13b9f8a6016e243e63b6e8ee1178d6a717850b5d6103; + + address internal immutable attacker; + + constructor() { + attacker = bytes32ToAddress(keccak256("user address")); } - function bytes32ToAddress(bytes32 bys) public pure returns (address) { - return address(uint160(uint256(bys))); + // -- CAST FUNCTIONS -- + + function addressToBytes32(address addr) public pure returns (bytes32) { + return bytes32(uint256(uint160(addr))); } - function getNextUserAddress() external returns (address payable) { - //bytes32 to address conversion - address payable user = payable(address(uint160(uint256(nextUser)))); - nextUser = keccak256(abi.encodePacked(nextUser)); - return user; + function bytes32ToAddress(bytes32 value) public pure returns (address) { + return address(uint160(uint256(value))); } - //create users with 100 ether balance + // -- SETUP FUNCTIONS -- + + // create users with 100 ether balance function createUsers(uint256 userNum) external returns (address payable[] memory) { address payable[] memory users = new address payable[](userNum); for (uint256 i = 0; i < userNum; i++) { @@ -33,9 +54,126 @@ contract Utilities is Test { return users; } - //move block.number forward by a given number of blocks + function createEmptyUsers(uint256 userNum) external returns (address[] memory users) { + users = new address[](userNum); + for (uint256 i = 0; i < userNum; ++i) { + users[i] = this.getNextUserAddress(); + } + } + + // generate fresh address + function getNextUserAddress() external returns (address payable) { + //bytes32 to address conversion + address payable user = payable(address(uint160(uint256(nextUser)))); + nextUser = keccak256(abi.encodePacked(nextUser)); + return user; + } + + function getNextKappa() external returns (bytes32 kappa) { + kappa = nextKappa; + nextKappa = keccak256(abi.encodePacked(kappa)); + } + + function deployTransparentProxy(address impl) external returns (address proxy) { + // Setup proxy with needed logic and custom admin, + // we don't need to upgrade anything, so no need to setup ProxyAdmin + proxy = address(new TransparentUpgradeableProxy(impl, address(420), bytes(""))); + } + + // Upgrades Transparent Proxy implementation + function upgradeTo(address proxy, address impl) external { + address admin = bytes32ToAddress(vm.load(proxy, ADMIN_SLOT)); + vm.startPrank(admin); + IProxy(proxy).upgradeTo(impl); + vm.stopPrank(); + } + + // -- VIEW FUNCTIONS -- + + function getRoleMember(address accessControlled, bytes32 role) external view returns (address) { + return IAccessControl(accessControlled).getRoleMember(role, 0); + } + + // -- EVM FUNCTIONS -- + + /// @notice Get state modifying function return value without modifying the state + function peekReturnValue( + address caller, + address _contract, + bytes memory payload, + uint256 value + ) external { + vm.prank(caller); + (bool success, bytes memory data) = _contract.call{value: value}(payload); + assertTrue(success, "Call failed"); + revert(string(data)); + } + + // move block.number forward by a given number of blocks function mineBlocks(uint256 numBlocks) external { uint256 targetBlock = block.number + numBlocks; vm.roll(targetBlock); } + + function checkAccess( + address _contract, + bytes memory payload, + string memory revertMsg + ) external { + this.checkRevert(attacker, _contract, payload, "Attacker gained access", revertMsg); + } + + function checkAccessControl( + address _contract, + bytes memory payload, + bytes32 neededRole + ) external { + this.checkAccess(_contract, payload, _getAccessControlRevertMsg(neededRole, attacker)); + } + + function checkRevert( + address executor, + address _contract, + bytes memory payload, + string memory revertMsg + ) external { + this.checkRevert(executor, _contract, payload, revertMsg, revertMsg); + } + + function checkRevert( + address executor, + address _contract, + bytes memory payload, + string memory failReason, + string memory revertMsg + ) external { + hoax(executor); + (bool success, bytes memory returnData) = _contract.call(payload); + assertTrue(!success, failReason); + assertEq(this.getRevertMsg(returnData), revertMsg, "Unexpected revert message"); + } + + // -- INTERNAL STUFF -- + + function _getAccessControlRevertMsg(bytes32 role, address account) internal pure returns (string memory revertMsg) { + revertMsg = string( + abi.encodePacked( + "AccessControl: account ", + Strings.toHexString(uint160(account), 20), + " is missing role ", + Strings.toHexString(uint256(role), 32) + ) + ); + } + + function getRevertMsg(bytes memory _returnData) external pure returns (string memory) { + // If the _res length is less than 68, then the transaction failed silently (without a revert message) + if (_returnData.length < 68) return "Transaction reverted silently"; + + assembly { + // Slice the sighash. + _returnData := add(_returnData, 0x04) + } + return abi.decode(_returnData, (string)); // All that remains is the revert string + } } From b1249fa2f501395a6d2c4971ab2cf48c43efea36 Mon Sep 17 00:00:00 2001 From: ChiTimesChi <88190723+ChiTimesChi@users.noreply.github.com> Date: Sat, 21 May 2022 22:19:48 +0300 Subject: [PATCH 12/47] tests: access, initialized for GasFeePricing --- test/messaging/GasFeePricingUpgradeable.t.sol | 115 ++++++++++++++++++ 1 file changed, 115 insertions(+) diff --git a/test/messaging/GasFeePricingUpgradeable.t.sol b/test/messaging/GasFeePricingUpgradeable.t.sol index e69de29bb..5be09c385 100644 --- a/test/messaging/GasFeePricingUpgradeable.t.sol +++ b/test/messaging/GasFeePricingUpgradeable.t.sol @@ -0,0 +1,115 @@ +// SPDX-License-Identifier: MIT + +pragma solidity 0.8.13; + +import "forge-std/Test.sol"; +import {Utilities} from "../utils/Utilities.sol"; + +import "src-messaging/AuthVerifier.sol"; +import "src-messaging/GasFeePricingUpgradeable.sol"; +import "src-messaging/MessageBusUpgradeable.sol"; + +contract GasFeePricingUpgradeableTest is Test { + Utilities internal utils; + + AuthVerifier internal authVerifier; + GasFeePricingUpgradeable internal gasFeePricing; + MessageBusUpgradeable internal messageBus; + + uint256 internal srcGasTokenPrice; + uint128 internal markupGasDrop; + uint128 internal markupGasUsage; + + address public constant NODE = address(1337); + + function setUp() public { + utils = new Utilities(); + authVerifier = new AuthVerifier(NODE); + + srcGasTokenPrice = 10**18; + markupGasDrop = 150; + markupGasUsage = 200; + + MessageBusUpgradeable busImpl = new MessageBusUpgradeable(); + GasFeePricingUpgradeable pricingImpl = new GasFeePricingUpgradeable(); + + messageBus = MessageBusUpgradeable(utils.deployTransparentProxy(address(busImpl))); + gasFeePricing = GasFeePricingUpgradeable(utils.deployTransparentProxy(address(pricingImpl))); + + // I don't have extra 10M laying around, so let's initialize those proxies + messageBus.initialize(address(gasFeePricing), address(authVerifier)); + gasFeePricing.initialize(address(messageBus), srcGasTokenPrice, markupGasDrop, markupGasUsage); + } + + function testInitialized() public { + utils.checkAccess( + address(messageBus), + abi.encodeWithSelector(MessageBusUpgradeable.initialize.selector, address(0), address(0)), + "Initializable: contract is already initialized" + ); + + utils.checkAccess( + address(gasFeePricing), + abi.encodeWithSelector(GasFeePricingUpgradeable.initialize.selector, address(0), 0, 0, 0), + "Initializable: contract is already initialized" + ); + } + + function testCheckAccessControl() public { + address _gfp = address(gasFeePricing); + utils.checkAccess( + _gfp, + abi.encodeWithSelector(GasFeePricingUpgradeable.setCostPerChain.selector, 0, 0, 0), + "Ownable: caller is not the owner" + ); + utils.checkAccess( + _gfp, + abi.encodeWithSelector( + GasFeePricingUpgradeable.setCostPerChains.selector, + new uint256[](1), + new uint256[](1), + new uint256[](1) + ), + "Ownable: caller is not the owner" + ); + utils.checkAccess( + _gfp, + abi.encodeWithSelector(GasFeePricingUpgradeable.setDstChainConfig.selector, 0, 0, 0), + "Ownable: caller is not the owner" + ); + utils.checkAccess( + _gfp, + abi.encodeWithSelector( + GasFeePricingUpgradeable.setDstChainConfigs.selector, + new uint256[](1), + new uint256[](1), + new uint256[](1) + ), + "Ownable: caller is not the owner" + ); + utils.checkAccess( + _gfp, + abi.encodeWithSelector( + GasFeePricingUpgradeable.setGasFeePricingAddresses.selector, + new uint256[](1), + new address[](1) + ), + "Ownable: caller is not the owner" + ); + utils.checkAccess( + _gfp, + abi.encodeWithSelector(GasFeePricingUpgradeable.updateChainConfig.selector, 0, 0), + "Ownable: caller is not the owner" + ); + utils.checkAccess( + _gfp, + abi.encodeWithSelector(GasFeePricingUpgradeable.updateChainInfo.selector, 0, 0), + "Ownable: caller is not the owner" + ); + utils.checkAccess( + _gfp, + abi.encodeWithSelector(GasFeePricingUpgradeable.updateMarkups.selector, 0, 0), + "Ownable: caller is not the owner" + ); + } +} From 42c5cf0dd31544614e14083e1fc983ac08f3d6dc Mon Sep 17 00:00:00 2001 From: ChiTimesChi <88190723+ChiTimesChi@users.noreply.github.com> Date: Sat, 21 May 2022 23:27:49 +0300 Subject: [PATCH 13/47] tests: prepare for setters/getters testing --- test/messaging/GasFeePricingUpgradeable.t.sol | 154 +++++++++++++++++- 1 file changed, 151 insertions(+), 3 deletions(-) diff --git a/test/messaging/GasFeePricingUpgradeable.t.sol b/test/messaging/GasFeePricingUpgradeable.t.sol index 5be09c385..8c28e74a1 100644 --- a/test/messaging/GasFeePricingUpgradeable.t.sol +++ b/test/messaging/GasFeePricingUpgradeable.t.sol @@ -10,23 +10,43 @@ import "src-messaging/GasFeePricingUpgradeable.sol"; import "src-messaging/MessageBusUpgradeable.sol"; contract GasFeePricingUpgradeableTest is Test { + struct ChainVars { + uint256 gasTokenPrice; + uint256 gasUnitPrice; + uint256 gasAmountNeeded; + uint256 maxGasDrop; + address gasFeePricing; + } + Utilities internal utils; AuthVerifier internal authVerifier; GasFeePricingUpgradeable internal gasFeePricing; MessageBusUpgradeable internal messageBus; - uint256 internal srcGasTokenPrice; + ChainVars internal srcVars; + uint128 internal markupGasDrop; uint128 internal markupGasUsage; + mapping(uint256 => ChainVars) internal dstVars; + address public constant NODE = address(1337); + // enable receiving overpaid fees + receive() external payable { + this; + } + + /*‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾ + SETUP + __________________________________________________________________________*/ + function setUp() public { utils = new Utilities(); authVerifier = new AuthVerifier(NODE); - srcGasTokenPrice = 10**18; + srcVars.gasTokenPrice = 10**18; markupGasDrop = 150; markupGasUsage = 200; @@ -38,9 +58,13 @@ contract GasFeePricingUpgradeableTest is Test { // I don't have extra 10M laying around, so let's initialize those proxies messageBus.initialize(address(gasFeePricing), address(authVerifier)); - gasFeePricing.initialize(address(messageBus), srcGasTokenPrice, markupGasDrop, markupGasUsage); + gasFeePricing.initialize(address(messageBus), srcVars.gasTokenPrice, markupGasDrop, markupGasUsage); } + /*‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾ + SECURITY TESTS + __________________________________________________________________________*/ + function testInitialized() public { utils.checkAccess( address(messageBus), @@ -112,4 +136,128 @@ contract GasFeePricingUpgradeableTest is Test { "Ownable: caller is not the owner" ); } + + /*‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾ + GETTERS/SETTERS TESTS + __________________________________________________________________________*/ + + function testInitializedCorrectly() public { + (uint128 _gasTokenPrice, ) = gasFeePricing.srcInfo(); + assertEq(_gasTokenPrice, srcVars.gasTokenPrice, "Failed to init: gasTokenPrice"); + assertEq(gasFeePricing.markupGasDrop(), markupGasDrop, "Failed to init: markupGasDrop"); + assertEq(gasFeePricing.markupGasUsage(), markupGasUsage, "Failed to init: markupGasUsage"); + assertEq(gasFeePricing.messageBus(), address(messageBus), "Failed to init: messageBus"); + } + + /*‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾ + INTERNAL CHECKERS + __________________________________________________________________________*/ + + function _checkDstChainConfig(uint256 _dstChainId) internal { + (uint128 gasAmountNeeded, uint128 maxGasDrop) = gasFeePricing.dstConfig(_dstChainId); + assertEq(gasAmountNeeded, dstVars[_dstChainId].gasAmountNeeded, "dstGasAmountNeeded is incorrect"); + assertEq(maxGasDrop, dstVars[_dstChainId].maxGasDrop, "dstMaxGasDrop is incorrect"); + } + + function _checkDstChainInfo(uint256 _dstChainId) internal { + (uint128 gasTokenPrice, uint128 gasUnitPrice) = gasFeePricing.dstInfo(_dstChainId); + assertEq(gasTokenPrice, dstVars[_dstChainId].gasTokenPrice, "dstGasTokenPrice is incorrect"); + assertEq(gasUnitPrice, dstVars[_dstChainId].gasUnitPrice, "dstGasUnitPrice is incorrect"); + } + + function _checkDstChainRatios(uint256 _dstChainId) internal { + (uint96 gasTokenPriceRatio, uint160 gasUnitPriceRatio) = gasFeePricing.dstRatios(_dstChainId); + uint256 _gasTokenPriceRatio = (dstVars[_dstChainId].gasTokenPrice * 10**18) / srcVars.gasTokenPrice; + uint256 _gasUnitPriceRatio = (dstVars[_dstChainId].gasUnitPrice * dstVars[_dstChainId].gasTokenPrice * 10**18) / + srcVars.gasTokenPrice; + assertEq(gasTokenPriceRatio, _gasTokenPriceRatio, "gasTokenPriceRatio is incorrect"); + assertEq(gasUnitPriceRatio, _gasUnitPriceRatio, "gasUnitPriceRatio is incorrect"); + } + + function _checkSrcChainConfig() internal { + (uint128 gasAmountNeeded, uint128 maxGasDrop) = gasFeePricing.srcConfig(); + assertEq(gasAmountNeeded, srcVars.gasAmountNeeded, "srcGasAmountNeeded is incorrect"); + assertEq(maxGasDrop, srcVars.maxGasDrop, "srcMaxGasDrop is incorrect"); + } + + function _checkSrcChainInfo() internal { + (uint128 gasTokenPrice, uint128 gasUnitPrice) = gasFeePricing.srcInfo(); + assertEq(gasTokenPrice, srcVars.gasTokenPrice, "srcGasTokenPrice is incorrect"); + assertEq(gasUnitPrice, srcVars.gasUnitPrice, "gasUnitPrice is incorrect"); + } + + /*‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾ + INTERNAL SETTERS + __________________________________________________________________________*/ + + function _setDstChainConfig( + uint256 _dstChainId, + uint256 _gasAmountNeeded, + uint256 _maxGasDrop + ) internal { + dstVars[_dstChainId].gasAmountNeeded = _gasAmountNeeded; + dstVars[_dstChainId].maxGasDrop = _maxGasDrop; + gasFeePricing.setDstChainConfig(_dstChainId, _gasAmountNeeded, _maxGasDrop); + } + + function _setDstChainConfigs( + uint256[] memory _dstChainIds, + uint256[] memory _gasAmountsNeeded, + uint256[] memory _maxGasDrops + ) internal { + for (uint256 i = 0; i < _dstChainIds.length; ++i) { + dstVars[_dstChainIds[i]].gasAmountNeeded = _gasAmountsNeeded[i]; + dstVars[_dstChainIds[i]].maxGasDrop = _maxGasDrops[i]; + } + gasFeePricing.setDstChainConfigs(_dstChainIds, _gasAmountsNeeded, _maxGasDrops); + } + + function _setDstChainInfo( + uint256 _dstChainId, + uint256 _gasTokenPrice, + uint256 _gasUnitPrice + ) internal { + dstVars[_dstChainId].gasTokenPrice = _gasTokenPrice; + dstVars[_dstChainId].gasUnitPrice = _gasUnitPrice; + gasFeePricing.setCostPerChain(_dstChainId, _gasUnitPrice, _gasTokenPrice); + } + + function _setDstChainsInfo( + uint256[] memory _dstChainIds, + uint256[] memory _gasTokenPrices, + uint256[] memory _gasUnitPrices + ) internal { + for (uint256 i = 0; i < _dstChainIds.length; ++i) { + dstVars[_dstChainIds[i]].gasTokenPrice = _gasTokenPrices[i]; + dstVars[_dstChainIds[i]].gasUnitPrice = _gasUnitPrices[i]; + } + gasFeePricing.setCostPerChains(_dstChainIds, _gasUnitPrices, _gasTokenPrices); + } + + function _setDstGasFeePricingAddresses(uint256[] memory _dstChainIds, address[] memory _dstGasFeePricing) internal { + for (uint256 i = 0; i < _dstChainIds.length; ++i) { + dstVars[_dstChainIds[i]].gasFeePricing = _dstGasFeePricing[i]; + } + gasFeePricing.setGasFeePricingAddresses(_dstChainIds, _dstGasFeePricing); + } + + function _setSrcChainConfig(uint256 _gasAmountNeeded, uint256 _maxGasDrop) internal { + srcVars.gasAmountNeeded = _gasAmountNeeded; + srcVars.maxGasDrop = _maxGasDrop; + uint256 fee = gasFeePricing.estimateUpdateFees(); + gasFeePricing.updateChainConfig{value: fee}(_gasAmountNeeded, _maxGasDrop); + } + + function _setSrcChainInfo(uint256 _gasTokenPrice, uint256 _gasUnitPrice) internal { + srcVars.gasTokenPrice = _gasTokenPrice; + srcVars.gasUnitPrice = _gasUnitPrice; + uint256 fee = gasFeePricing.estimateUpdateFees(); + gasFeePricing.updateChainInfo{value: fee}(_gasTokenPrice, _gasUnitPrice); + } + + function _setSrcMarkups(uint128 _markupGasDrop, uint128 _markupGasUsage) internal { + markupGasDrop = _markupGasDrop; + markupGasUsage = _markupGasUsage; + gasFeePricing.updateMarkups(_markupGasDrop, _markupGasUsage); + } } From b6c6b311b12a0e4a0ce6b4c4a418966247d58579 Mon Sep 17 00:00:00 2001 From: ChiTimesChi <88190723+ChiTimesChi@users.noreply.github.com> Date: Sat, 21 May 2022 23:59:49 +0300 Subject: [PATCH 14/47] I like pretty boxes --- .../messaging/GasFeePricingUpgradeable.sol | 60 +++++++++---------- test/messaging/GasFeePricingUpgradeable.t.sol | 30 +++++----- 2 files changed, 45 insertions(+), 45 deletions(-) diff --git a/contracts/messaging/GasFeePricingUpgradeable.sol b/contracts/messaging/GasFeePricingUpgradeable.sol index 1048d2b9c..23beb9967 100644 --- a/contracts/messaging/GasFeePricingUpgradeable.sol +++ b/contracts/messaging/GasFeePricingUpgradeable.sol @@ -7,9 +7,9 @@ import "./interfaces/IGasFeePricing.sol"; import "./libraries/Options.sol"; contract GasFeePricingUpgradeable is SynMessagingReceiverUpgradeable, IGasFeePricing { - /*‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾ - STRUCTS - __________________________________________________________________________*/ + /*┌──────────────────────────────────────────────────────────────────────┐ + │ STRUCTS │ + └──────────────────────────────────────────────────────────────────────┘*/ /// @dev Dst chain's basic variables, that are unlikely to change over time. struct ChainConfig { @@ -42,17 +42,17 @@ contract GasFeePricingUpgradeable is SynMessagingReceiverUpgradeable, IGasFeePri // This number is expressed in src chain wei } - /*‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾ - EVENTS - __________________________________________________________________________*/ + /*┌──────────────────────────────────────────────────────────────────────┐ + │ EVENTS │ + └──────────────────────────────────────────────────────────────────────┘*/ event ChainInfoUpdated(uint256 indexed chainId, uint256 gasTokenPrice, uint256 gasUnitPrice); event MarkupsUpdated(uint256 markupGasDrop, uint256 markupGasUsage); - /*‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾ - DESTINATION CHAINS STORAGE - __________________________________________________________________________*/ + /*┌──────────────────────────────────────────────────────────────────────┐ + │ DESTINATION CHAINS STORAGE │ + └──────────────────────────────────────────────────────────────────────┘*/ // dstChainId => Info mapping(uint256 => ChainInfo) public dstInfo; @@ -65,9 +65,9 @@ contract GasFeePricingUpgradeable is SynMessagingReceiverUpgradeable, IGasFeePri uint256[] internal dstChainIds; - /*‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾ - SOURCE CHAIN STORAGE - __________________________________________________________________________*/ + /*┌──────────────────────────────────────────────────────────────────────┐ + │ SOURCE CHAIN STORAGE │ + └──────────────────────────────────────────────────────────────────────┘*/ ChainConfig public srcConfig; ChainInfo public srcInfo; @@ -78,9 +78,9 @@ contract GasFeePricingUpgradeable is SynMessagingReceiverUpgradeable, IGasFeePri uint128 public markupGasDrop; uint128 public markupGasUsage; - /*‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾ - CONSTANTS - __________________________________________________________________________*/ + /*┌──────────────────────────────────────────────────────────────────────┐ + │ CONSTANTS │ + └──────────────────────────────────────────────────────────────────────┘*/ uint256 public constant DEFAULT_GAS_LIMIT = 200000; uint256 public constant MARKUP_DENOMINATOR = 100; @@ -88,9 +88,9 @@ contract GasFeePricingUpgradeable is SynMessagingReceiverUpgradeable, IGasFeePri uint8 public constant CHAIN_CONFIG_UPDATED = 1; uint8 public constant CHAIN_INFO_UPDATED = 2; - /*‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾ - INITIALIZER - __________________________________________________________________________*/ + /*┌──────────────────────────────────────────────────────────────────────┐ + │ INITIALIZER │ + └──────────────────────────────────────────────────────────────────────┘*/ function initialize( address _messageBus, @@ -104,9 +104,9 @@ contract GasFeePricingUpgradeable is SynMessagingReceiverUpgradeable, IGasFeePri _updateMarkups(_markupGasDrop, _markupGasUsage); } - /*‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾ - VIEWS - __________________________________________________________________________*/ + /*┌──────────────────────────────────────────────────────────────────────┐ + │ VIEWS │ + └──────────────────────────────────────────────────────────────────────┘*/ /// @notice Get the fee for sending a message to dst chain with given options function estimateGasFee(uint256 _dstChainId, bytes calldata _options) external view returns (uint256 fee) { @@ -186,9 +186,9 @@ contract GasFeePricingUpgradeable is SynMessagingReceiverUpgradeable, IGasFeePri return bytes32(uint256(uint160(_addr))); } - /*‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾ - ONLY OWNER - __________________________________________________________________________*/ + /*┌──────────────────────────────────────────────────────────────────────┐ + │ ONLY OWNER │ + └──────────────────────────────────────────────────────────────────────┘*/ /// @dev Update information about gas unit/token price for a dst chain. function setCostPerChain( @@ -273,9 +273,9 @@ contract GasFeePricingUpgradeable is SynMessagingReceiverUpgradeable, IGasFeePri _updateMarkups(_markupGasDrop, _markupGasUsage); } - /*‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾ - UPDATE STATE LOGIC - __________________________________________________________________________*/ + /*┌──────────────────────────────────────────────────────────────────────┐ + │ UPDATE STATE LOGIC │ + └──────────────────────────────────────────────────────────────────────┘*/ /// @dev Updates information about src chain gas token/unit price. /// All the dst chain ratios are updated as well, if gas token price changed @@ -360,9 +360,9 @@ contract GasFeePricingUpgradeable is SynMessagingReceiverUpgradeable, IGasFeePri emit MarkupsUpdated(_markupGasDrop, _markupGasUsage); } - /*‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾ - MESSAGING LOGIC - __________________________________________________________________________*/ + /*┌──────────────────────────────────────────────────────────────────────┐ + │ MESSAGING LOGIC │ + └──────────────────────────────────────────────────────────────────────┘*/ /// @dev Sends "something updated" messages to all registered dst chains function _sendUpdateMessages( diff --git a/test/messaging/GasFeePricingUpgradeable.t.sol b/test/messaging/GasFeePricingUpgradeable.t.sol index 8c28e74a1..fedfd1698 100644 --- a/test/messaging/GasFeePricingUpgradeable.t.sol +++ b/test/messaging/GasFeePricingUpgradeable.t.sol @@ -38,9 +38,9 @@ contract GasFeePricingUpgradeableTest is Test { this; } - /*‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾ - SETUP - __________________________________________________________________________*/ + /*┌──────────────────────────────────────────────────────────────────────┐ + │ SETUP │ + └──────────────────────────────────────────────────────────────────────┘*/ function setUp() public { utils = new Utilities(); @@ -61,9 +61,9 @@ contract GasFeePricingUpgradeableTest is Test { gasFeePricing.initialize(address(messageBus), srcVars.gasTokenPrice, markupGasDrop, markupGasUsage); } - /*‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾ - SECURITY TESTS - __________________________________________________________________________*/ + /*┌──────────────────────────────────────────────────────────────────────┐ + │ SECURITY TESTS │ + └──────────────────────────────────────────────────────────────────────┘*/ function testInitialized() public { utils.checkAccess( @@ -137,9 +137,9 @@ contract GasFeePricingUpgradeableTest is Test { ); } - /*‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾ - GETTERS/SETTERS TESTS - __________________________________________________________________________*/ + /*┌──────────────────────────────────────────────────────────────────────┐ + │ GETTERS/SETTERS TESTS │ + └──────────────────────────────────────────────────────────────────────┘*/ function testInitializedCorrectly() public { (uint128 _gasTokenPrice, ) = gasFeePricing.srcInfo(); @@ -149,9 +149,9 @@ contract GasFeePricingUpgradeableTest is Test { assertEq(gasFeePricing.messageBus(), address(messageBus), "Failed to init: messageBus"); } - /*‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾ - INTERNAL CHECKERS - __________________________________________________________________________*/ + /*┌──────────────────────────────────────────────────────────────────────┐ + │ INTERNAL CHECKERS │ + └──────────────────────────────────────────────────────────────────────┘*/ function _checkDstChainConfig(uint256 _dstChainId) internal { (uint128 gasAmountNeeded, uint128 maxGasDrop) = gasFeePricing.dstConfig(_dstChainId); @@ -186,9 +186,9 @@ contract GasFeePricingUpgradeableTest is Test { assertEq(gasUnitPrice, srcVars.gasUnitPrice, "gasUnitPrice is incorrect"); } - /*‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾ - INTERNAL SETTERS - __________________________________________________________________________*/ + /*┌──────────────────────────────────────────────────────────────────────┐ + │ INTERNAL SETTERS │ + └──────────────────────────────────────────────────────────────────────┘*/ function _setDstChainConfig( uint256 _dstChainId, From 25829f505c4bb02b16007bdfb12dbc529a7b2da2 Mon Sep 17 00:00:00 2001 From: ChiTimesChi <88190723+ChiTimesChi@users.noreply.github.com> Date: Sun, 22 May 2022 01:27:56 +0300 Subject: [PATCH 15/47] GasFeePricing: min fee for gas usage on dst chain --- .../messaging/GasFeePricingUpgradeable.sol | 30 ++++++++++++++++++- test/messaging/GasFeePricingUpgradeable.t.sol | 18 +++++++++++ 2 files changed, 47 insertions(+), 1 deletion(-) diff --git a/contracts/messaging/GasFeePricingUpgradeable.sol b/contracts/messaging/GasFeePricingUpgradeable.sol index 23beb9967..3525e4a1a 100644 --- a/contracts/messaging/GasFeePricingUpgradeable.sol +++ b/contracts/messaging/GasFeePricingUpgradeable.sol @@ -72,6 +72,8 @@ contract GasFeePricingUpgradeable is SynMessagingReceiverUpgradeable, IGasFeePri ChainConfig public srcConfig; ChainInfo public srcInfo; + uint256 public minGasUsageFee; + // how much message sender is paying, multiple of "estimated price" // markup of 100% means user is paying exactly the projected price // set this more than 100% to make sure messaging fees cover the expenses to deliver the msg @@ -82,6 +84,8 @@ contract GasFeePricingUpgradeable is SynMessagingReceiverUpgradeable, IGasFeePri │ CONSTANTS │ └──────────────────────────────────────────────────────────────────────┘*/ + uint256 public constant DEFAULT_MIN_FEE_USD = 10**18; + uint256 public constant DEFAULT_GAS_LIMIT = 200000; uint256 public constant MARKUP_DENOMINATOR = 100; @@ -101,6 +105,7 @@ contract GasFeePricingUpgradeable is SynMessagingReceiverUpgradeable, IGasFeePri __Ownable_init_unchained(); messageBus = _messageBus; srcInfo.gasTokenPrice = uint96(_srcGasTokenPrice); + minGasUsageFee = _calculateMinGasUsageFee(DEFAULT_MIN_FEE_USD, _srcGasTokenPrice); _updateMarkups(_markupGasDrop, _markupGasUsage); } @@ -157,7 +162,12 @@ contract GasFeePricingUpgradeable is SynMessagingReceiverUpgradeable, IGasFeePri uint256 feeGasUsage = (_gasLimit * dstRatio.gasUnitPriceRatio) / 10**18; // Sum up the fees multiplied by their respective markups - fee = (feeGasDrop * _markupGasDrop + feeGasUsage * _markupGasUsage) / MARKUP_DENOMINATOR; + feeGasDrop = (feeGasDrop * _markupGasDrop) / MARKUP_DENOMINATOR; + feeGasUsage = (feeGasUsage * _markupGasUsage) / MARKUP_DENOMINATOR; + // Check if gas usage fee is lower than minimum + uint256 _minGasUsageFee = minGasUsageFee; + if (feeGasUsage < _minGasUsageFee) feeGasUsage = _minGasUsageFee; + fee = feeGasDrop + feeGasUsage; } /// @notice Get total gas fee for calling updateChainInfo() @@ -181,6 +191,14 @@ contract GasFeePricingUpgradeable is SynMessagingReceiverUpgradeable, IGasFeePri } } + function _calculateMinGasUsageFee(uint256 _minFeeUsd, uint256 _gasTokenPrice) + internal + pure + returns (uint256 minFee) + { + minFee = (_minFeeUsd * 10**18) / _gasTokenPrice; + } + /// @dev Converts address to bytes32 function _addressToBytes32(address _addr) internal pure returns (bytes32) { return bytes32(uint256(uint160(_addr))); @@ -250,6 +268,16 @@ contract GasFeePricingUpgradeable is SynMessagingReceiverUpgradeable, IGasFeePri } } + /// @notice Update the minimum fee for gas usage on message delivery. Quoted in src chain wei. + function setMinGasUsageFee(uint256 _minGasUsageFee) external onlyOwner { + minGasUsageFee = _minGasUsageFee; + } + + /// @notice Update the minimum fee for gas usage on message delivery. Quoted in USD. + function setMinGasUsageFeeUsd(uint256 _minGasUsageFeeUsd) external onlyOwner { + minGasUsageFee = _calculateMinGasUsageFee(_minGasUsageFeeUsd, srcInfo.gasTokenPrice); + } + /// @notice Update information about source chain config: /// amount of gas needed to do _updateDstChainInfo() /// and maximum airdrop available on this chain diff --git a/test/messaging/GasFeePricingUpgradeable.t.sol b/test/messaging/GasFeePricingUpgradeable.t.sol index fedfd1698..3c5c822d3 100644 --- a/test/messaging/GasFeePricingUpgradeable.t.sol +++ b/test/messaging/GasFeePricingUpgradeable.t.sol @@ -96,6 +96,7 @@ contract GasFeePricingUpgradeableTest is Test { ), "Ownable: caller is not the owner" ); + utils.checkAccess( _gfp, abi.encodeWithSelector(GasFeePricingUpgradeable.setDstChainConfig.selector, 0, 0, 0), @@ -111,6 +112,7 @@ contract GasFeePricingUpgradeableTest is Test { ), "Ownable: caller is not the owner" ); + utils.checkAccess( _gfp, abi.encodeWithSelector( @@ -120,16 +122,30 @@ contract GasFeePricingUpgradeableTest is Test { ), "Ownable: caller is not the owner" ); + + utils.checkAccess( + _gfp, + abi.encodeWithSelector(GasFeePricingUpgradeable.setMinGasUsageFee.selector, 0), + "Ownable: caller is not the owner" + ); + utils.checkAccess( + _gfp, + abi.encodeWithSelector(GasFeePricingUpgradeable.setMinGasUsageFeeUsd.selector, 0), + "Ownable: caller is not the owner" + ); + utils.checkAccess( _gfp, abi.encodeWithSelector(GasFeePricingUpgradeable.updateChainConfig.selector, 0, 0), "Ownable: caller is not the owner" ); + utils.checkAccess( _gfp, abi.encodeWithSelector(GasFeePricingUpgradeable.updateChainInfo.selector, 0, 0), "Ownable: caller is not the owner" ); + utils.checkAccess( _gfp, abi.encodeWithSelector(GasFeePricingUpgradeable.updateMarkups.selector, 0, 0), @@ -149,6 +165,8 @@ contract GasFeePricingUpgradeableTest is Test { assertEq(gasFeePricing.messageBus(), address(messageBus), "Failed to init: messageBus"); } + function testSetCostPerChain() public {} + /*┌──────────────────────────────────────────────────────────────────────┐ │ INTERNAL CHECKERS │ └──────────────────────────────────────────────────────────────────────┘*/ From 73d82735afa7000ff925594ebcd3803569536e58 Mon Sep 17 00:00:00 2001 From: ChiTimesChi <88190723+ChiTimesChi@users.noreply.github.com> Date: Sun, 22 May 2022 01:47:41 +0300 Subject: [PATCH 16/47] GasFeePricing: better docs --- .../messaging/GasFeePricingUpgradeable.sol | 45 +++++++++++++------ 1 file changed, 32 insertions(+), 13 deletions(-) diff --git a/contracts/messaging/GasFeePricingUpgradeable.sol b/contracts/messaging/GasFeePricingUpgradeable.sol index 3525e4a1a..0c9a363a7 100644 --- a/contracts/messaging/GasFeePricingUpgradeable.sol +++ b/contracts/messaging/GasFeePricingUpgradeable.sol @@ -45,38 +45,57 @@ contract GasFeePricingUpgradeable is SynMessagingReceiverUpgradeable, IGasFeePri /*┌──────────────────────────────────────────────────────────────────────┐ │ EVENTS │ └──────────────────────────────────────────────────────────────────────┘*/ - + /// @dev see "Structs" docs event ChainInfoUpdated(uint256 indexed chainId, uint256 gasTokenPrice, uint256 gasUnitPrice); - + /// @dev see "Source chain storage" docs event MarkupsUpdated(uint256 markupGasDrop, uint256 markupGasUsage); /*┌──────────────────────────────────────────────────────────────────────┐ │ DESTINATION CHAINS STORAGE │ └──────────────────────────────────────────────────────────────────────┘*/ - // dstChainId => Info + /// @dev dstChainId => Info mapping(uint256 => ChainInfo) public dstInfo; - // dstChainId => Ratios + /// @dev dstChainId => Ratios mapping(uint256 => ChainRatios) public dstRatios; - // dstChainId => Config + /// @dev dstChainId => Config mapping(uint256 => ChainConfig) public dstConfig; - // dstChainId => GasFeePricing contract address + /// @dev dstChainId => GasFeePricing contract address mapping(uint256 => bytes32) public dstGasFeePricing; - + /// @dev list of all dst chain ids uint256[] internal dstChainIds; /*┌──────────────────────────────────────────────────────────────────────┐ │ SOURCE CHAIN STORAGE │ └──────────────────────────────────────────────────────────────────────┘*/ - + /// @dev See "Structs" docs ChainConfig public srcConfig; ChainInfo public srcInfo; - + /// @dev Minimum fee related to gas usage on dst chain uint256 public minGasUsageFee; - // how much message sender is paying, multiple of "estimated price" - // markup of 100% means user is paying exactly the projected price - // set this more than 100% to make sure messaging fees cover the expenses to deliver the msg + /** + * @notice Whenever the messaging fee is calculated, it takes into account things as: + * gas token prices on src and dst chain, gas limit for executing message on dst chain + * and gas unit price on dst chain. In other words, message sender is paying dst chain + * gas fees (to cover gas usage and gasdrop), but in src chain gas token. + * The price values are static, though are supposed to be updated in the event of high + * volatility. It is implied that gas token/unit prices reflect respective latest + * average prices. + * + * Because of this, the markups are used, both for "gas drop fee", and "gas usage fee". + * Markup is a value of 100% or higher. This is the coefficient applied to + * "projected gas fee", that is calculated using static gas token/unit prices. + * Markup of 100% means that exactly "projected gas fee" will be charged, markup of 150% + * will result in fee that is 50% higher than "projected", etc. + * + * There are separate markups for gasDrop and gasUsage. gasDropFee is calculated only using + * src and dst gas token prices, while gasUsageFee also takes into account dst chain gas + * unit price, which is an extra source of volatility. + * + * Generally, markupGasUsage >= markupGasDrop >= 100%. While markups can be set to 100%, + * this is not recommended. + */ uint128 public markupGasDrop; uint128 public markupGasUsage; @@ -296,7 +315,7 @@ contract GasFeePricingUpgradeable is SynMessagingReceiverUpgradeable, IGasFeePri } /// @notice Updates markups, that are used for determining how much fee - // to charge on top of "projected gas cost" of delivering the message + /// to charge on top of "projected gas cost" of delivering the message function updateMarkups(uint128 _markupGasDrop, uint128 _markupGasUsage) external onlyOwner { _updateMarkups(_markupGasDrop, _markupGasUsage); } From a6f17838904b9351694445d48a69642d7a5831fb Mon Sep 17 00:00:00 2001 From: ChiTimesChi <88190723+ChiTimesChi@users.noreply.github.com> Date: Sun, 22 May 2022 12:16:43 +0300 Subject: [PATCH 17/47] markups now start from 0%, not 100% --- .../messaging/GasFeePricingUpgradeable.sol | 23 ++++++++----------- test/messaging/GasFeePricingUpgradeable.t.sol | 7 ++++-- 2 files changed, 14 insertions(+), 16 deletions(-) diff --git a/contracts/messaging/GasFeePricingUpgradeable.sol b/contracts/messaging/GasFeePricingUpgradeable.sol index 0c9a363a7..01c0e663b 100644 --- a/contracts/messaging/GasFeePricingUpgradeable.sol +++ b/contracts/messaging/GasFeePricingUpgradeable.sol @@ -84,16 +84,16 @@ contract GasFeePricingUpgradeable is SynMessagingReceiverUpgradeable, IGasFeePri * average prices. * * Because of this, the markups are used, both for "gas drop fee", and "gas usage fee". - * Markup is a value of 100% or higher. This is the coefficient applied to + * Markup is a value of 0% or higher. This is the coefficient applied to * "projected gas fee", that is calculated using static gas token/unit prices. - * Markup of 100% means that exactly "projected gas fee" will be charged, markup of 150% + * Markup of 0% means that exactly "projected gas fee" will be charged, markup of 50% * will result in fee that is 50% higher than "projected", etc. * * There are separate markups for gasDrop and gasUsage. gasDropFee is calculated only using * src and dst gas token prices, while gasUsageFee also takes into account dst chain gas * unit price, which is an extra source of volatility. * - * Generally, markupGasUsage >= markupGasDrop >= 100%. While markups can be set to 100%, + * Generally, markupGasUsage >= markupGasDrop >= 0%. While markups can be set to 0%, * this is not recommended. */ uint128 public markupGasDrop; @@ -181,8 +181,8 @@ contract GasFeePricingUpgradeable is SynMessagingReceiverUpgradeable, IGasFeePri uint256 feeGasUsage = (_gasLimit * dstRatio.gasUnitPriceRatio) / 10**18; // Sum up the fees multiplied by their respective markups - feeGasDrop = (feeGasDrop * _markupGasDrop) / MARKUP_DENOMINATOR; - feeGasUsage = (feeGasUsage * _markupGasUsage) / MARKUP_DENOMINATOR; + feeGasDrop = (feeGasDrop * (_markupGasDrop + MARKUP_DENOMINATOR)) / MARKUP_DENOMINATOR; + feeGasUsage = (feeGasUsage * (_markupGasUsage + MARKUP_DENOMINATOR)) / MARKUP_DENOMINATOR; // Check if gas usage fee is lower than minimum uint256 _minGasUsageFee = minGasUsageFee; if (feeGasUsage < _minGasUsageFee) feeGasUsage = _minGasUsageFee; @@ -314,8 +314,8 @@ contract GasFeePricingUpgradeable is SynMessagingReceiverUpgradeable, IGasFeePri _updateSrcChainInfo(_gasTokenPrice, _gasUnitPrice); } - /// @notice Updates markups, that are used for determining how much fee - /// to charge on top of "projected gas cost" of delivering the message + /// @notice Updates markups (see "Source chain storage" docs), that are used for determining + /// how much fee to charge on top of "projected gas cost" of delivering the message. function updateMarkups(uint128 _markupGasDrop, uint128 _markupGasUsage) external onlyOwner { _updateMarkups(_markupGasDrop, _markupGasUsage); } @@ -395,14 +395,9 @@ contract GasFeePricingUpgradeable is SynMessagingReceiverUpgradeable, IGasFeePri }); } - /// @dev Updates the markups. - /// Markup = 100% means exactly the "projected gas cost" will be charged. - /// Thus, markup can't be lower than 100%. + /// @dev Updates the markups (see "Source chain storage" docs). + /// Markup = 0% means exactly the "projected gas cost" will be charged. function _updateMarkups(uint128 _markupGasDrop, uint128 _markupGasUsage) internal { - require( - _markupGasDrop >= MARKUP_DENOMINATOR && _markupGasUsage >= MARKUP_DENOMINATOR, - "Markup can not be lower than 1" - ); (markupGasDrop, markupGasUsage) = (_markupGasDrop, _markupGasUsage); emit MarkupsUpdated(_markupGasDrop, _markupGasUsage); } diff --git a/test/messaging/GasFeePricingUpgradeable.t.sol b/test/messaging/GasFeePricingUpgradeable.t.sol index 3c5c822d3..85e1a6bd9 100644 --- a/test/messaging/GasFeePricingUpgradeable.t.sol +++ b/test/messaging/GasFeePricingUpgradeable.t.sol @@ -46,9 +46,12 @@ contract GasFeePricingUpgradeableTest is Test { utils = new Utilities(); authVerifier = new AuthVerifier(NODE); + // src gas token is worth exactly 1 USD srcVars.gasTokenPrice = 10**18; - markupGasDrop = 150; - markupGasUsage = 200; + // take 50% on top of airdrop value + markupGasDrop = 50; + // take 100% on top of gasUsage value + markupGasUsage = 100; MessageBusUpgradeable busImpl = new MessageBusUpgradeable(); GasFeePricingUpgradeable pricingImpl = new GasFeePricingUpgradeable(); From 4e3ff05f2d4e5d20e15b637c2f2a8def7b3906c1 Mon Sep 17 00:00:00 2001 From: ChiTimesChi <88190723+ChiTimesChi@users.noreply.github.com> Date: Sun, 22 May 2022 17:17:44 +0300 Subject: [PATCH 18/47] I strive for perfection --- .../messaging/GasFeePricingUpgradeable.sol | 62 ++++++++++--------- test/messaging/GasFeePricingUpgradeable.t.sol | 30 ++++----- 2 files changed, 47 insertions(+), 45 deletions(-) diff --git a/contracts/messaging/GasFeePricingUpgradeable.sol b/contracts/messaging/GasFeePricingUpgradeable.sol index 01c0e663b..b7a276afb 100644 --- a/contracts/messaging/GasFeePricingUpgradeable.sol +++ b/contracts/messaging/GasFeePricingUpgradeable.sol @@ -7,9 +7,9 @@ import "./interfaces/IGasFeePricing.sol"; import "./libraries/Options.sol"; contract GasFeePricingUpgradeable is SynMessagingReceiverUpgradeable, IGasFeePricing { - /*┌──────────────────────────────────────────────────────────────────────┐ - │ STRUCTS │ - └──────────────────────────────────────────────────────────────────────┘*/ + /*╔══════════════════════════════════════════════════════════════════════╗*\ + ▏*║ STRUCTS ║*▕ + \*╚══════════════════════════════════════════════════════════════════════╝*/ /// @dev Dst chain's basic variables, that are unlikely to change over time. struct ChainConfig { @@ -42,17 +42,18 @@ contract GasFeePricingUpgradeable is SynMessagingReceiverUpgradeable, IGasFeePri // This number is expressed in src chain wei } - /*┌──────────────────────────────────────────────────────────────────────┐ - │ EVENTS │ - └──────────────────────────────────────────────────────────────────────┘*/ + /*╔══════════════════════════════════════════════════════════════════════╗*\ + ▏*║ EVENTS ║*▕ + \*╚══════════════════════════════════════════════════════════════════════╝*/ + /// @dev see "Structs" docs event ChainInfoUpdated(uint256 indexed chainId, uint256 gasTokenPrice, uint256 gasUnitPrice); /// @dev see "Source chain storage" docs event MarkupsUpdated(uint256 markupGasDrop, uint256 markupGasUsage); - /*┌──────────────────────────────────────────────────────────────────────┐ - │ DESTINATION CHAINS STORAGE │ - └──────────────────────────────────────────────────────────────────────┘*/ + /*╔══════════════════════════════════════════════════════════════════════╗*\ + ▏*║ DESTINATION CHAINS STORAGE ║*▕ + \*╚══════════════════════════════════════════════════════════════════════╝*/ /// @dev dstChainId => Info mapping(uint256 => ChainInfo) public dstInfo; @@ -65,9 +66,10 @@ contract GasFeePricingUpgradeable is SynMessagingReceiverUpgradeable, IGasFeePri /// @dev list of all dst chain ids uint256[] internal dstChainIds; - /*┌──────────────────────────────────────────────────────────────────────┐ - │ SOURCE CHAIN STORAGE │ - └──────────────────────────────────────────────────────────────────────┘*/ + /*╔══════════════════════════════════════════════════════════════════════╗*\ + ▏*║ SOURCE CHAIN STORAGE ║*▕ + \*╚══════════════════════════════════════════════════════════════════════╝*/ + /// @dev See "Structs" docs ChainConfig public srcConfig; ChainInfo public srcInfo; @@ -99,9 +101,9 @@ contract GasFeePricingUpgradeable is SynMessagingReceiverUpgradeable, IGasFeePri uint128 public markupGasDrop; uint128 public markupGasUsage; - /*┌──────────────────────────────────────────────────────────────────────┐ - │ CONSTANTS │ - └──────────────────────────────────────────────────────────────────────┘*/ + /*╔══════════════════════════════════════════════════════════════════════╗*\ + ▏*║ CONSTANTS ║*▕ + \*╚══════════════════════════════════════════════════════════════════════╝*/ uint256 public constant DEFAULT_MIN_FEE_USD = 10**18; @@ -111,9 +113,9 @@ contract GasFeePricingUpgradeable is SynMessagingReceiverUpgradeable, IGasFeePri uint8 public constant CHAIN_CONFIG_UPDATED = 1; uint8 public constant CHAIN_INFO_UPDATED = 2; - /*┌──────────────────────────────────────────────────────────────────────┐ - │ INITIALIZER │ - └──────────────────────────────────────────────────────────────────────┘*/ + /*╔══════════════════════════════════════════════════════════════════════╗*\ + ▏*║ INITIALIZER ║*▕ + \*╚══════════════════════════════════════════════════════════════════════╝*/ function initialize( address _messageBus, @@ -128,9 +130,9 @@ contract GasFeePricingUpgradeable is SynMessagingReceiverUpgradeable, IGasFeePri _updateMarkups(_markupGasDrop, _markupGasUsage); } - /*┌──────────────────────────────────────────────────────────────────────┐ - │ VIEWS │ - └──────────────────────────────────────────────────────────────────────┘*/ + /*╔══════════════════════════════════════════════════════════════════════╗*\ + ▏*║ VIEWS ║*▕ + \*╚══════════════════════════════════════════════════════════════════════╝*/ /// @notice Get the fee for sending a message to dst chain with given options function estimateGasFee(uint256 _dstChainId, bytes calldata _options) external view returns (uint256 fee) { @@ -223,9 +225,9 @@ contract GasFeePricingUpgradeable is SynMessagingReceiverUpgradeable, IGasFeePri return bytes32(uint256(uint160(_addr))); } - /*┌──────────────────────────────────────────────────────────────────────┐ - │ ONLY OWNER │ - └──────────────────────────────────────────────────────────────────────┘*/ + /*╔══════════════════════════════════════════════════════════════════════╗*\ + ▏*║ ONLY OWNER ║*▕ + \*╚══════════════════════════════════════════════════════════════════════╝*/ /// @dev Update information about gas unit/token price for a dst chain. function setCostPerChain( @@ -320,9 +322,9 @@ contract GasFeePricingUpgradeable is SynMessagingReceiverUpgradeable, IGasFeePri _updateMarkups(_markupGasDrop, _markupGasUsage); } - /*┌──────────────────────────────────────────────────────────────────────┐ - │ UPDATE STATE LOGIC │ - └──────────────────────────────────────────────────────────────────────┘*/ + /*╔══════════════════════════════════════════════════════════════════════╗*\ + ▏*║ UPDATE STATE LOGIC ║*▕ + \*╚══════════════════════════════════════════════════════════════════════╝*/ /// @dev Updates information about src chain gas token/unit price. /// All the dst chain ratios are updated as well, if gas token price changed @@ -402,9 +404,9 @@ contract GasFeePricingUpgradeable is SynMessagingReceiverUpgradeable, IGasFeePri emit MarkupsUpdated(_markupGasDrop, _markupGasUsage); } - /*┌──────────────────────────────────────────────────────────────────────┐ - │ MESSAGING LOGIC │ - └──────────────────────────────────────────────────────────────────────┘*/ + /*╔══════════════════════════════════════════════════════════════════════╗*\ + ▏*║ MESSAGING LOGIC ║*▕ + \*╚══════════════════════════════════════════════════════════════════════╝*/ /// @dev Sends "something updated" messages to all registered dst chains function _sendUpdateMessages( diff --git a/test/messaging/GasFeePricingUpgradeable.t.sol b/test/messaging/GasFeePricingUpgradeable.t.sol index 85e1a6bd9..eaf922e0c 100644 --- a/test/messaging/GasFeePricingUpgradeable.t.sol +++ b/test/messaging/GasFeePricingUpgradeable.t.sol @@ -38,9 +38,9 @@ contract GasFeePricingUpgradeableTest is Test { this; } - /*┌──────────────────────────────────────────────────────────────────────┐ - │ SETUP │ - └──────────────────────────────────────────────────────────────────────┘*/ + /*╔══════════════════════════════════════════════════════════════════════╗*\ + ▏*║ SETUP ║*▕ + \*╚══════════════════════════════════════════════════════════════════════╝*/ function setUp() public { utils = new Utilities(); @@ -64,9 +64,9 @@ contract GasFeePricingUpgradeableTest is Test { gasFeePricing.initialize(address(messageBus), srcVars.gasTokenPrice, markupGasDrop, markupGasUsage); } - /*┌──────────────────────────────────────────────────────────────────────┐ - │ SECURITY TESTS │ - └──────────────────────────────────────────────────────────────────────┘*/ + /*╔══════════════════════════════════════════════════════════════════════╗*\ + ▏*║ SECURITY TESTS ║*▕ + \*╚══════════════════════════════════════════════════════════════════════╝*/ function testInitialized() public { utils.checkAccess( @@ -156,9 +156,9 @@ contract GasFeePricingUpgradeableTest is Test { ); } - /*┌──────────────────────────────────────────────────────────────────────┐ - │ GETTERS/SETTERS TESTS │ - └──────────────────────────────────────────────────────────────────────┘*/ + /*╔══════════════════════════════════════════════════════════════════════╗*\ + ▏*║ GETTERS/SETTERS TESTS ║*▕ + \*╚══════════════════════════════════════════════════════════════════════╝*/ function testInitializedCorrectly() public { (uint128 _gasTokenPrice, ) = gasFeePricing.srcInfo(); @@ -170,9 +170,9 @@ contract GasFeePricingUpgradeableTest is Test { function testSetCostPerChain() public {} - /*┌──────────────────────────────────────────────────────────────────────┐ - │ INTERNAL CHECKERS │ - └──────────────────────────────────────────────────────────────────────┘*/ + /*╔══════════════════════════════════════════════════════════════════════╗*\ + ▏*║ INTERNAL CHECKERS ║*▕ + \*╚══════════════════════════════════════════════════════════════════════╝*/ function _checkDstChainConfig(uint256 _dstChainId) internal { (uint128 gasAmountNeeded, uint128 maxGasDrop) = gasFeePricing.dstConfig(_dstChainId); @@ -207,9 +207,9 @@ contract GasFeePricingUpgradeableTest is Test { assertEq(gasUnitPrice, srcVars.gasUnitPrice, "gasUnitPrice is incorrect"); } - /*┌──────────────────────────────────────────────────────────────────────┐ - │ INTERNAL SETTERS │ - └──────────────────────────────────────────────────────────────────────┘*/ + /*╔══════════════════════════════════════════════════════════════════════╗*\ + ▏*║ INTERNAL SETTERS ║*▕ + \*╚══════════════════════════════════════════════════════════════════════╝*/ function _setDstChainConfig( uint256 _dstChainId, From 869d3abb1331fcb1fd1c9a4b467ddf909027de79 Mon Sep 17 00:00:00 2001 From: ChiTimesChi <88190723+ChiTimesChi@users.noreply.github.com> Date: Sun, 22 May 2022 18:46:01 +0300 Subject: [PATCH 19/47] GFP: markups are now chain-specific --- .../messaging/GasFeePricingUpgradeable.sol | 128 ++++++++++-------- test/messaging/GasFeePricingUpgradeable.t.sol | 48 ++++--- 2 files changed, 103 insertions(+), 73 deletions(-) diff --git a/contracts/messaging/GasFeePricingUpgradeable.sol b/contracts/messaging/GasFeePricingUpgradeable.sol index b7a276afb..a7f30a196 100644 --- a/contracts/messaging/GasFeePricingUpgradeable.sol +++ b/contracts/messaging/GasFeePricingUpgradeable.sol @@ -11,12 +11,39 @@ contract GasFeePricingUpgradeable is SynMessagingReceiverUpgradeable, IGasFeePri ▏*║ STRUCTS ║*▕ \*╚══════════════════════════════════════════════════════════════════════╝*/ + /** + * @notice Whenever the messaging fee is calculated, it takes into account things as: + * gas token prices on src and dst chain, gas limit for executing message on dst chain + * and gas unit price on dst chain. In other words, message sender is paying dst chain + * gas fees (to cover gas usage and gasdrop), but in src chain gas token. + * The price values are static, though are supposed to be updated in the event of high + * volatility. It is implied that gas token/unit prices reflect respective latest + * average prices. + * + * Because of this, the markups are used, both for "gas drop fee", and "gas usage fee". + * Markup is a value of 0% or higher. This is the coefficient applied to + * "projected gas fee", that is calculated using static gas token/unit prices. + * Markup of 0% means that exactly "projected gas fee" will be charged, markup of 50% + * will result in fee that is 50% higher than "projected", etc. + * + * There are separate markups for gasDrop and gasUsage. gasDropFee is calculated only using + * src and dst gas token prices, while gasUsageFee also takes into account dst chain gas + * unit price, which is an extra source of volatility. + * + * Generally, markupGasUsage >= markupGasDrop >= 0%. While markups can be set to 0%, + * this is not recommended. + */ + /// @dev Dst chain's basic variables, that are unlikely to change over time. struct ChainConfig { // Amount of gas units needed to receive "update chainInfo" message - uint128 gasAmountNeeded; + uint112 gasAmountNeeded; // Maximum gas airdrop available on chain - uint128 maxGasDrop; + uint112 maxGasDrop; + // Markup for gas airdrop + uint16 markupGasDrop; + // Markup for gas usage + uint16 markupGasUsage; } /// @dev Information about dst chain's gas price, which can change over time @@ -48,8 +75,8 @@ contract GasFeePricingUpgradeable is SynMessagingReceiverUpgradeable, IGasFeePri /// @dev see "Structs" docs event ChainInfoUpdated(uint256 indexed chainId, uint256 gasTokenPrice, uint256 gasUnitPrice); - /// @dev see "Source chain storage" docs - event MarkupsUpdated(uint256 markupGasDrop, uint256 markupGasUsage); + /// @dev see "Structs" docs + event MarkupsUpdated(uint256 indexed chainId, uint256 markupGasDrop, uint256 markupGasUsage); /*╔══════════════════════════════════════════════════════════════════════╗*\ ▏*║ DESTINATION CHAINS STORAGE ║*▕ @@ -71,36 +98,12 @@ contract GasFeePricingUpgradeable is SynMessagingReceiverUpgradeable, IGasFeePri \*╚══════════════════════════════════════════════════════════════════════╝*/ /// @dev See "Structs" docs + /// srcConfig.markupGasDrop and srcConfig.markupGasUsage values are not used ChainConfig public srcConfig; ChainInfo public srcInfo; /// @dev Minimum fee related to gas usage on dst chain uint256 public minGasUsageFee; - /** - * @notice Whenever the messaging fee is calculated, it takes into account things as: - * gas token prices on src and dst chain, gas limit for executing message on dst chain - * and gas unit price on dst chain. In other words, message sender is paying dst chain - * gas fees (to cover gas usage and gasdrop), but in src chain gas token. - * The price values are static, though are supposed to be updated in the event of high - * volatility. It is implied that gas token/unit prices reflect respective latest - * average prices. - * - * Because of this, the markups are used, both for "gas drop fee", and "gas usage fee". - * Markup is a value of 0% or higher. This is the coefficient applied to - * "projected gas fee", that is calculated using static gas token/unit prices. - * Markup of 0% means that exactly "projected gas fee" will be charged, markup of 50% - * will result in fee that is 50% higher than "projected", etc. - * - * There are separate markups for gasDrop and gasUsage. gasDropFee is calculated only using - * src and dst gas token prices, while gasUsageFee also takes into account dst chain gas - * unit price, which is an extra source of volatility. - * - * Generally, markupGasUsage >= markupGasDrop >= 0%. While markups can be set to 0%, - * this is not recommended. - */ - uint128 public markupGasDrop; - uint128 public markupGasUsage; - /*╔══════════════════════════════════════════════════════════════════════╗*\ ▏*║ CONSTANTS ║*▕ \*╚══════════════════════════════════════════════════════════════════════╝*/ @@ -117,17 +120,11 @@ contract GasFeePricingUpgradeable is SynMessagingReceiverUpgradeable, IGasFeePri ▏*║ INITIALIZER ║*▕ \*╚══════════════════════════════════════════════════════════════════════╝*/ - function initialize( - address _messageBus, - uint256 _srcGasTokenPrice, - uint128 _markupGasDrop, - uint128 _markupGasUsage - ) external initializer { + function initialize(address _messageBus, uint256 _srcGasTokenPrice) external initializer { __Ownable_init_unchained(); messageBus = _messageBus; srcInfo.gasTokenPrice = uint96(_srcGasTokenPrice); minGasUsageFee = _calculateMinGasUsageFee(DEFAULT_MIN_FEE_USD, _srcGasTokenPrice); - _updateMarkups(_markupGasDrop, _markupGasUsage); } /*╔══════════════════════════════════════════════════════════════════════╗*\ @@ -153,18 +150,19 @@ contract GasFeePricingUpgradeable is SynMessagingReceiverUpgradeable, IGasFeePri /// @dev Extracts the gas information from options and calculates the messaging fee function _estimateGasFee(uint256 _dstChainId, bytes calldata _options) internal view returns (uint256 fee) { + ChainConfig memory config = dstConfig[_dstChainId]; uint256 gasAirdrop; uint256 gasLimit; if (_options.length != 0) { (gasLimit, gasAirdrop, ) = Options.decode(_options); if (gasAirdrop != 0) { - require(gasAirdrop <= dstConfig[_dstChainId].maxGasDrop, "GasDrop higher than max"); + require(gasAirdrop <= config.maxGasDrop, "GasDrop higher than max"); } } else { gasLimit = DEFAULT_GAS_LIMIT; } - fee = _estimateGasFee(_dstChainId, gasAirdrop, gasLimit); + fee = _estimateGasFee(_dstChainId, gasAirdrop, gasLimit, config.markupGasDrop, config.markupGasUsage); } /// @dev Returns a gas fee for sending a message to dst chain, given the amount of gas to airdrop, @@ -172,10 +170,11 @@ contract GasFeePricingUpgradeable is SynMessagingReceiverUpgradeable, IGasFeePri function _estimateGasFee( uint256 _dstChainId, uint256 _gasAirdrop, - uint256 _gasLimit + uint256 _gasLimit, + uint256 _markupGasDrop, + uint256 _markupGasUsage ) internal view returns (uint256 fee) { ChainRatios memory dstRatio = dstRatios[_dstChainId]; - (uint128 _markupGasDrop, uint128 _markupGasUsage) = (markupGasDrop, markupGasUsage); // Calculate how much gas airdrop is worth in src chain wei uint256 feeGasDrop = (_gasAirdrop * dstRatio.gasTokenPriceRatio) / 10**18; @@ -203,10 +202,11 @@ contract GasFeePricingUpgradeable is SynMessagingReceiverUpgradeable, IGasFeePri fees = new uint256[](_chainIds.length); for (uint256 i = 0; i < _chainIds.length; ++i) { uint256 chainId = _chainIds[i]; - uint256 gasLimit = dstConfig[chainId].gasAmountNeeded; + ChainConfig memory config = dstConfig[chainId]; + uint256 gasLimit = config.gasAmountNeeded; if (gasLimit == 0) gasLimit = DEFAULT_GAS_LIMIT; - uint256 fee = _estimateGasFee(chainId, 0, gasLimit); + uint256 fee = _estimateGasFee(chainId, 0, gasLimit, config.markupGasDrop, config.markupGasUsage); totalFee += fee; fees[i] = fee; } @@ -304,7 +304,10 @@ contract GasFeePricingUpgradeable is SynMessagingReceiverUpgradeable, IGasFeePri /// and maximum airdrop available on this chain function updateChainConfig(uint256 _gasAmountNeeded, uint256 _maxGasDrop) external payable onlyOwner { _sendUpdateMessages(CHAIN_CONFIG_UPDATED, _gasAmountNeeded, _maxGasDrop); - srcConfig = ChainConfig({gasAmountNeeded: uint128(_gasAmountNeeded), maxGasDrop: uint128(_maxGasDrop)}); + ChainConfig memory config = srcConfig; + config.gasAmountNeeded = uint112(_gasAmountNeeded); + config.maxGasDrop = uint112(_maxGasDrop); + srcConfig = config; } /// @notice Update information about source chain gas token/unit price on all configured dst chains, @@ -316,10 +319,20 @@ contract GasFeePricingUpgradeable is SynMessagingReceiverUpgradeable, IGasFeePri _updateSrcChainInfo(_gasTokenPrice, _gasUnitPrice); } - /// @notice Updates markups (see "Source chain storage" docs), that are used for determining + /// @notice Updates markups (see "Structs" docs) for a bunch of chains. Markups are used for determining /// how much fee to charge on top of "projected gas cost" of delivering the message. - function updateMarkups(uint128 _markupGasDrop, uint128 _markupGasUsage) external onlyOwner { - _updateMarkups(_markupGasDrop, _markupGasUsage); + function updateMarkups( + uint256[] memory _dstChainIds, + uint16[] memory _markupsGasDrop, + uint16[] memory _markupsGasUsage + ) external onlyOwner { + require( + _dstChainIds.length == _markupsGasDrop.length && _dstChainIds.length == _markupsGasUsage.length, + "!arrays" + ); + for (uint256 i = 0; i < _dstChainIds.length; ++i) { + _updateMarkups(_dstChainIds[i], _markupsGasDrop[i], _markupsGasUsage[i]); + } } /*╔══════════════════════════════════════════════════════════════════════╗*\ @@ -353,10 +366,10 @@ contract GasFeePricingUpgradeable is SynMessagingReceiverUpgradeable, IGasFeePri uint256 _gasAmountNeeded, uint256 _maxGasDrop ) internal { - dstConfig[_dstChainId] = ChainConfig({ - gasAmountNeeded: uint128(_gasAmountNeeded), - maxGasDrop: uint128(_maxGasDrop) - }); + ChainConfig memory config = dstConfig[_dstChainId]; + config.gasAmountNeeded = uint112(_gasAmountNeeded); + config.maxGasDrop = uint112(_maxGasDrop); + dstConfig[_dstChainId] = config; } /// @dev Updates information about dst chain gas token/unit price. @@ -397,11 +410,18 @@ contract GasFeePricingUpgradeable is SynMessagingReceiverUpgradeable, IGasFeePri }); } - /// @dev Updates the markups (see "Source chain storage" docs). + /// @dev Updates the markups (see "Structs" docs). /// Markup = 0% means exactly the "projected gas cost" will be charged. - function _updateMarkups(uint128 _markupGasDrop, uint128 _markupGasUsage) internal { - (markupGasDrop, markupGasUsage) = (_markupGasDrop, _markupGasUsage); - emit MarkupsUpdated(_markupGasDrop, _markupGasUsage); + function _updateMarkups( + uint256 _dstChainId, + uint16 _markupGasDrop, + uint16 _markupGasUsage + ) internal { + ChainConfig memory config = dstConfig[_dstChainId]; + config.markupGasDrop = _markupGasDrop; + config.markupGasUsage = _markupGasUsage; + dstConfig[_dstChainId] = config; + emit MarkupsUpdated(_dstChainId, _markupGasDrop, _markupGasUsage); } /*╔══════════════════════════════════════════════════════════════════════╗*\ diff --git a/test/messaging/GasFeePricingUpgradeable.t.sol b/test/messaging/GasFeePricingUpgradeable.t.sol index eaf922e0c..194e5187f 100644 --- a/test/messaging/GasFeePricingUpgradeable.t.sol +++ b/test/messaging/GasFeePricingUpgradeable.t.sol @@ -15,6 +15,8 @@ contract GasFeePricingUpgradeableTest is Test { uint256 gasUnitPrice; uint256 gasAmountNeeded; uint256 maxGasDrop; + uint256 markupGasDrop; + uint256 markupGasUsage; address gasFeePricing; } @@ -26,9 +28,6 @@ contract GasFeePricingUpgradeableTest is Test { ChainVars internal srcVars; - uint128 internal markupGasDrop; - uint128 internal markupGasUsage; - mapping(uint256 => ChainVars) internal dstVars; address public constant NODE = address(1337); @@ -48,10 +47,6 @@ contract GasFeePricingUpgradeableTest is Test { // src gas token is worth exactly 1 USD srcVars.gasTokenPrice = 10**18; - // take 50% on top of airdrop value - markupGasDrop = 50; - // take 100% on top of gasUsage value - markupGasUsage = 100; MessageBusUpgradeable busImpl = new MessageBusUpgradeable(); GasFeePricingUpgradeable pricingImpl = new GasFeePricingUpgradeable(); @@ -61,7 +56,7 @@ contract GasFeePricingUpgradeableTest is Test { // I don't have extra 10M laying around, so let's initialize those proxies messageBus.initialize(address(gasFeePricing), address(authVerifier)); - gasFeePricing.initialize(address(messageBus), srcVars.gasTokenPrice, markupGasDrop, markupGasUsage); + gasFeePricing.initialize(address(messageBus), srcVars.gasTokenPrice); } /*╔══════════════════════════════════════════════════════════════════════╗*\ @@ -151,7 +146,12 @@ contract GasFeePricingUpgradeableTest is Test { utils.checkAccess( _gfp, - abi.encodeWithSelector(GasFeePricingUpgradeable.updateMarkups.selector, 0, 0), + abi.encodeWithSelector( + GasFeePricingUpgradeable.updateMarkups.selector, + new uint256[](1), + new uint16[](1), + new uint16[](1) + ), "Ownable: caller is not the owner" ); } @@ -163,8 +163,6 @@ contract GasFeePricingUpgradeableTest is Test { function testInitializedCorrectly() public { (uint128 _gasTokenPrice, ) = gasFeePricing.srcInfo(); assertEq(_gasTokenPrice, srcVars.gasTokenPrice, "Failed to init: gasTokenPrice"); - assertEq(gasFeePricing.markupGasDrop(), markupGasDrop, "Failed to init: markupGasDrop"); - assertEq(gasFeePricing.markupGasUsage(), markupGasUsage, "Failed to init: markupGasUsage"); assertEq(gasFeePricing.messageBus(), address(messageBus), "Failed to init: messageBus"); } @@ -175,7 +173,7 @@ contract GasFeePricingUpgradeableTest is Test { \*╚══════════════════════════════════════════════════════════════════════╝*/ function _checkDstChainConfig(uint256 _dstChainId) internal { - (uint128 gasAmountNeeded, uint128 maxGasDrop) = gasFeePricing.dstConfig(_dstChainId); + (uint112 gasAmountNeeded, uint112 maxGasDrop, , ) = gasFeePricing.dstConfig(_dstChainId); assertEq(gasAmountNeeded, dstVars[_dstChainId].gasAmountNeeded, "dstGasAmountNeeded is incorrect"); assertEq(maxGasDrop, dstVars[_dstChainId].maxGasDrop, "dstMaxGasDrop is incorrect"); } @@ -186,6 +184,12 @@ contract GasFeePricingUpgradeableTest is Test { assertEq(gasUnitPrice, dstVars[_dstChainId].gasUnitPrice, "dstGasUnitPrice is incorrect"); } + function _checkDstChainMarkups(uint256 _dstChainId) internal { + (, , uint16 markupGasDrop, uint16 markupGasUsage) = gasFeePricing.dstConfig(_dstChainId); + assertEq(markupGasDrop, dstVars[_dstChainId].markupGasDrop, "dstMarkupGasDrop is incorrect"); + assertEq(markupGasUsage, dstVars[_dstChainId].markupGasUsage, "dstMarkupGasUsage is incorrect"); + } + function _checkDstChainRatios(uint256 _dstChainId) internal { (uint96 gasTokenPriceRatio, uint160 gasUnitPriceRatio) = gasFeePricing.dstRatios(_dstChainId); uint256 _gasTokenPriceRatio = (dstVars[_dstChainId].gasTokenPrice * 10**18) / srcVars.gasTokenPrice; @@ -196,7 +200,7 @@ contract GasFeePricingUpgradeableTest is Test { } function _checkSrcChainConfig() internal { - (uint128 gasAmountNeeded, uint128 maxGasDrop) = gasFeePricing.srcConfig(); + (uint112 gasAmountNeeded, uint112 maxGasDrop, , ) = gasFeePricing.srcConfig(); assertEq(gasAmountNeeded, srcVars.gasAmountNeeded, "srcGasAmountNeeded is incorrect"); assertEq(maxGasDrop, srcVars.maxGasDrop, "srcMaxGasDrop is incorrect"); } @@ -262,6 +266,18 @@ contract GasFeePricingUpgradeableTest is Test { gasFeePricing.setGasFeePricingAddresses(_dstChainIds, _dstGasFeePricing); } + function _setDstMarkups( + uint256[] memory _dstChainIds, + uint16[] memory _markupsGasDrop, + uint16[] memory _markupsGasUsage + ) internal { + for (uint256 i = 0; i < _dstChainIds.length; ++i) { + dstVars[_dstChainIds[i]].markupGasDrop = _markupsGasDrop[i]; + dstVars[_dstChainIds[i]].markupGasUsage = _markupsGasUsage[i]; + } + gasFeePricing.updateMarkups(_dstChainIds, _markupsGasDrop, _markupsGasUsage); + } + function _setSrcChainConfig(uint256 _gasAmountNeeded, uint256 _maxGasDrop) internal { srcVars.gasAmountNeeded = _gasAmountNeeded; srcVars.maxGasDrop = _maxGasDrop; @@ -275,10 +291,4 @@ contract GasFeePricingUpgradeableTest is Test { uint256 fee = gasFeePricing.estimateUpdateFees(); gasFeePricing.updateChainInfo{value: fee}(_gasTokenPrice, _gasUnitPrice); } - - function _setSrcMarkups(uint128 _markupGasDrop, uint128 _markupGasUsage) internal { - markupGasDrop = _markupGasDrop; - markupGasUsage = _markupGasUsage; - gasFeePricing.updateMarkups(_markupGasDrop, _markupGasUsage); - } } From e17a6edf254bf3a2af1e7b9359fab38debfa7bcf Mon Sep 17 00:00:00 2001 From: ChiTimesChi <88190723+ChiTimesChi@users.noreply.github.com> Date: Sun, 22 May 2022 19:22:25 +0300 Subject: [PATCH 20/47] GFP: fix encoding --- .../messaging/GasFeePricingUpgradeable.sol | 39 ++++--------------- .../libraries/GasFeePricingUpdates.sol | 37 ++++++++++++++++++ test/messaging/GasFeePricingUpgradeable.t.sol | 17 ++++++++ 3 files changed, 61 insertions(+), 32 deletions(-) create mode 100644 contracts/messaging/libraries/GasFeePricingUpdates.sol diff --git a/contracts/messaging/GasFeePricingUpgradeable.sol b/contracts/messaging/GasFeePricingUpgradeable.sol index a7f30a196..1e22abe85 100644 --- a/contracts/messaging/GasFeePricingUpgradeable.sol +++ b/contracts/messaging/GasFeePricingUpgradeable.sol @@ -5,6 +5,7 @@ pragma solidity 0.8.13; import "./framework/SynMessagingReceiverUpgradeable.sol"; import "./interfaces/IGasFeePricing.sol"; import "./libraries/Options.sol"; +import "./libraries/GasFeePricingUpdates.sol"; contract GasFeePricingUpgradeable is SynMessagingReceiverUpgradeable, IGasFeePricing { /*╔══════════════════════════════════════════════════════════════════════╗*\ @@ -113,9 +114,6 @@ contract GasFeePricingUpgradeable is SynMessagingReceiverUpgradeable, IGasFeePri uint256 public constant DEFAULT_GAS_LIMIT = 200000; uint256 public constant MARKUP_DENOMINATOR = 100; - uint8 public constant CHAIN_CONFIG_UPDATED = 1; - uint8 public constant CHAIN_INFO_UPDATED = 2; - /*╔══════════════════════════════════════════════════════════════════════╗*\ ▏*║ INITIALIZER ║*▕ \*╚══════════════════════════════════════════════════════════════════════╝*/ @@ -303,7 +301,7 @@ contract GasFeePricingUpgradeable is SynMessagingReceiverUpgradeable, IGasFeePri /// amount of gas needed to do _updateDstChainInfo() /// and maximum airdrop available on this chain function updateChainConfig(uint256 _gasAmountNeeded, uint256 _maxGasDrop) external payable onlyOwner { - _sendUpdateMessages(CHAIN_CONFIG_UPDATED, _gasAmountNeeded, _maxGasDrop); + _sendUpdateMessages(uint8(GasFeePricingUpdates.MsgType.UPDATE_CONFIG), _gasAmountNeeded, _maxGasDrop); ChainConfig memory config = srcConfig; config.gasAmountNeeded = uint112(_gasAmountNeeded); config.maxGasDrop = uint112(_maxGasDrop); @@ -315,7 +313,7 @@ contract GasFeePricingUpgradeable is SynMessagingReceiverUpgradeable, IGasFeePri function updateChainInfo(uint256 _gasTokenPrice, uint256 _gasUnitPrice) external payable onlyOwner { // send messages before updating the values, so that it's possible to use // estimateUpdateFees() to calculate the needed fee for the update - _sendUpdateMessages(CHAIN_INFO_UPDATED, _gasTokenPrice, _gasUnitPrice); + _sendUpdateMessages(uint8(GasFeePricingUpdates.MsgType.UPDATE_INFO), _gasTokenPrice, _gasUnitPrice); _updateSrcChainInfo(_gasTokenPrice, _gasUnitPrice); } @@ -437,7 +435,7 @@ contract GasFeePricingUpgradeable is SynMessagingReceiverUpgradeable, IGasFeePri (uint256 totalFee, uint256[] memory fees) = _estimateUpdateFees(); require(msg.value >= totalFee, "msg.value doesn't cover all the fees"); - bytes memory message = _encodeMessage(_msgType, uint128(_newValueA), uint128(_newValueB)); + bytes memory message = GasFeePricingUpdates.encode(_msgType, uint128(_newValueA), uint128(_newValueB)); uint256[] memory chainIds = dstChainIds; bytes32[] memory receivers = new bytes32[](chainIds.length); bytes[] memory options = new bytes[](chainIds.length); @@ -462,36 +460,13 @@ contract GasFeePricingUpgradeable is SynMessagingReceiverUpgradeable, IGasFeePri bytes memory _message, address ) internal override { - (uint8 msgType, uint128 newValueA, uint128 newValueB) = _decodeMessage(_message); - if (msgType == CHAIN_CONFIG_UPDATED) { + (uint8 msgType, uint128 newValueA, uint128 newValueB) = GasFeePricingUpdates.decode(_message); + if (msgType == uint8(GasFeePricingUpdates.MsgType.UPDATE_CONFIG)) { _updateDstChainConfig(_srcChainId, newValueA, newValueB); - } else if (msgType == CHAIN_INFO_UPDATED) { + } else if (msgType == uint8(GasFeePricingUpdates.MsgType.UPDATE_INFO)) { _updateDstChainInfo(_srcChainId, newValueA, newValueB); } else { revert("Unknown message type"); } } - - /// @dev Encodes "something updated" message. We're taking advantage that - /// whether it's chain config or info, it's always two uint128 values that were updated. - function _encodeMessage( - uint8 _msgType, - uint128 _newValueA, - uint128 _newValueB - ) internal pure returns (bytes memory) { - return abi.encode(_msgType, _newValueA, _newValueB); - } - - /// @dev Decodes "something updated" message. - function _decodeMessage(bytes memory _message) - internal - pure - returns ( - uint8 msgType, - uint128 newValueA, - uint128 newValueB - ) - { - (msgType, newValueA, newValueB) = abi.decode(_message, (uint8, uint128, uint128)); - } } diff --git a/contracts/messaging/libraries/GasFeePricingUpdates.sol b/contracts/messaging/libraries/GasFeePricingUpdates.sol new file mode 100644 index 000000000..e148f2239 --- /dev/null +++ b/contracts/messaging/libraries/GasFeePricingUpdates.sol @@ -0,0 +1,37 @@ +// SPDX-License-Identifier: MIT + +pragma solidity ^0.8.0; + +library GasFeePricingUpdates { + enum MsgType { + UNKNOWN, + UPDATE_CONFIG, + UPDATE_INFO + } + + function encode( + uint8 _txType, + uint128 _newValueA, + uint128 _newValueB + ) internal pure returns (bytes memory) { + return abi.encodePacked(_txType, _newValueA, _newValueB); + } + + function decode(bytes memory _message) + internal + pure + returns ( + uint8 txType, + uint128 newValueA, + uint128 newValueB + ) + { + require(_message.length == 33, "Unknown message format"); + // solhint-disable-next-line + assembly { + txType := mload(add(_message, 1)) + newValueA := mload(add(_message, 17)) + newValueB := mload(add(_message, 33)) + } + } +} diff --git a/test/messaging/GasFeePricingUpgradeable.t.sol b/test/messaging/GasFeePricingUpgradeable.t.sol index 194e5187f..570640f9a 100644 --- a/test/messaging/GasFeePricingUpgradeable.t.sol +++ b/test/messaging/GasFeePricingUpgradeable.t.sol @@ -8,6 +8,7 @@ import {Utilities} from "../utils/Utilities.sol"; import "src-messaging/AuthVerifier.sol"; import "src-messaging/GasFeePricingUpgradeable.sol"; import "src-messaging/MessageBusUpgradeable.sol"; +import "src-messaging/libraries/GasFeePricingUpdates.sol"; contract GasFeePricingUpgradeableTest is Test { struct ChainVars { @@ -156,6 +157,22 @@ contract GasFeePricingUpgradeableTest is Test { ); } + /*╔══════════════════════════════════════════════════════════════════════╗*\ + ▏*║ ENCODING TESTS ║*▕ + \*╚══════════════════════════════════════════════════════════════════════╝*/ + + function testEncodeDecode( + uint8 msgType, + uint128 newValueA, + uint128 newValueB + ) public { + bytes memory message = GasFeePricingUpdates.encode(msgType, newValueA, newValueB); + (uint8 _msgType, uint128 _newValueA, uint128 _newValueB) = GasFeePricingUpdates.decode(message); + assertEq(_msgType, msgType, "Failed to encode msgType"); + assertEq(_newValueA, newValueA, "Failed to encode newValueA"); + assertEq(_newValueB, newValueB, "Failed to encode newValueB"); + } + /*╔══════════════════════════════════════════════════════════════════════╗*\ ▏*║ GETTERS/SETTERS TESTS ║*▕ \*╚══════════════════════════════════════════════════════════════════════╝*/ From 44ec353dbde55718869c2625ee1c03ec134639c2 Mon Sep 17 00:00:00 2001 From: ChiTimesChi <88190723+ChiTimesChi@users.noreply.github.com> Date: Sun, 22 May 2022 19:36:54 +0300 Subject: [PATCH 21/47] GFP: remove duplicated functions, refactor names --- .../messaging/GasFeePricingUpgradeable.sol | 87 ++++++--------- test/messaging/GasFeePricingUpgradeable.t.sol | 101 ++++++------------ 2 files changed, 65 insertions(+), 123 deletions(-) diff --git a/contracts/messaging/GasFeePricingUpgradeable.sol b/contracts/messaging/GasFeePricingUpgradeable.sol index 1e22abe85..0a0998209 100644 --- a/contracts/messaging/GasFeePricingUpgradeable.sol +++ b/contracts/messaging/GasFeePricingUpgradeable.sol @@ -7,7 +7,7 @@ import "./interfaces/IGasFeePricing.sol"; import "./libraries/Options.sol"; import "./libraries/GasFeePricingUpdates.sol"; -contract GasFeePricingUpgradeable is SynMessagingReceiverUpgradeable, IGasFeePricing { +contract GasFeePricingUpgradeable is SynMessagingReceiverUpgradeable { /*╔══════════════════════════════════════════════════════════════════════╗*\ ▏*║ STRUCTS ║*▕ \*╚══════════════════════════════════════════════════════════════════════╝*/ @@ -227,18 +227,32 @@ contract GasFeePricingUpgradeable is SynMessagingReceiverUpgradeable, IGasFeePri ▏*║ ONLY OWNER ║*▕ \*╚══════════════════════════════════════════════════════════════════════╝*/ - /// @dev Update information about gas unit/token price for a dst chain. - function setCostPerChain( - uint256 _dstChainId, - uint256 _gasUnitPrice, - uint256 _gasTokenPrice + /// @notice Update GasFeePricing addresses on a bunch of dst chains. Needed for cross-chain setups. + function setDstAddress(uint256[] memory _dstChainIds, address[] memory _dstGasFeePricing) external onlyOwner { + require(_dstChainIds.length == _dstGasFeePricing.length, "!arrays"); + for (uint256 i = 0; i < _dstChainIds.length; ++i) { + dstGasFeePricing[_dstChainIds[i]] = _addressToBytes32(_dstGasFeePricing[i]); + } + } + + /// @dev Update config (gasLimit for sending messages to chain, max gas airdrop) for a bunch of chains. + function setDstConfig( + uint256[] memory _dstChainIds, + uint256[] memory _gasAmountsNeeded, + uint256[] memory _maxGasDrops ) external onlyOwner { - _updateDstChainInfo(_dstChainId, _gasUnitPrice, _gasTokenPrice); + require( + _dstChainIds.length == _gasAmountsNeeded.length && _dstChainIds.length == _maxGasDrops.length, + "!arrays" + ); + for (uint256 i = 0; i < _dstChainIds.length; ++i) { + _updateDstChainConfig(_dstChainIds[i], _gasAmountsNeeded[i], _maxGasDrops[i]); + } } /// @notice Update information about gas unit/token price for a bunch of chains. /// Handy for initial setup. - function setCostPerChains( + function setDstInfo( uint256[] memory _dstChainIds, uint256[] memory _gasUnitPrices, uint256[] memory _gasTokenPrices @@ -252,55 +266,36 @@ contract GasFeePricingUpgradeable is SynMessagingReceiverUpgradeable, IGasFeePri } } - /// @dev Update config (gasLimit for sending messages to chain, max gas airdrop) for a dst chain. - function setDstChainConfig( - uint256 _dstChainId, - uint256 _gasAmountNeeded, - uint256 _maxGasDrop - ) external onlyOwner { - _updateDstChainConfig(_dstChainId, _gasAmountNeeded, _maxGasDrop); - } - - /// @dev Update config (gasLimit for sending messages to chain, max gas airdrop) for a bunch of chains. - function setDstChainConfigs( + /// @notice Sets markups (see "Structs" docs) for a bunch of chains. Markups are used for determining + /// how much fee to charge on top of "projected gas cost" of delivering the message. + function setDstMarkups( uint256[] memory _dstChainIds, - uint256[] memory _gasAmountsNeeded, - uint256[] memory _maxGasDrops + uint16[] memory _markupsGasDrop, + uint16[] memory _markupsGasUsage ) external onlyOwner { require( - _dstChainIds.length == _gasAmountsNeeded.length && _dstChainIds.length == _maxGasDrops.length, + _dstChainIds.length == _markupsGasDrop.length && _dstChainIds.length == _markupsGasUsage.length, "!arrays" ); for (uint256 i = 0; i < _dstChainIds.length; ++i) { - _updateDstChainConfig(_dstChainIds[i], _gasAmountsNeeded[i], _maxGasDrops[i]); - } - } - - /// @notice Update GasFeePricing addresses on a bunch of dst chains. Needed for cross-chain setups. - function setGasFeePricingAddresses(uint256[] memory _dstChainIds, address[] memory _dstGasFeePricing) - external - onlyOwner - { - require(_dstChainIds.length == _dstGasFeePricing.length, "!arrays"); - for (uint256 i = 0; i < _dstChainIds.length; ++i) { - dstGasFeePricing[_dstChainIds[i]] = _addressToBytes32(_dstGasFeePricing[i]); + _updateMarkups(_dstChainIds[i], _markupsGasDrop[i], _markupsGasUsage[i]); } } /// @notice Update the minimum fee for gas usage on message delivery. Quoted in src chain wei. - function setMinGasUsageFee(uint256 _minGasUsageFee) external onlyOwner { + function setMinFee(uint256 _minGasUsageFee) external onlyOwner { minGasUsageFee = _minGasUsageFee; } /// @notice Update the minimum fee for gas usage on message delivery. Quoted in USD. - function setMinGasUsageFeeUsd(uint256 _minGasUsageFeeUsd) external onlyOwner { + function setMinFeeUsd(uint256 _minGasUsageFeeUsd) external onlyOwner { minGasUsageFee = _calculateMinGasUsageFee(_minGasUsageFeeUsd, srcInfo.gasTokenPrice); } /// @notice Update information about source chain config: /// amount of gas needed to do _updateDstChainInfo() /// and maximum airdrop available on this chain - function updateChainConfig(uint256 _gasAmountNeeded, uint256 _maxGasDrop) external payable onlyOwner { + function updateSrcConfig(uint256 _gasAmountNeeded, uint256 _maxGasDrop) external payable onlyOwner { _sendUpdateMessages(uint8(GasFeePricingUpdates.MsgType.UPDATE_CONFIG), _gasAmountNeeded, _maxGasDrop); ChainConfig memory config = srcConfig; config.gasAmountNeeded = uint112(_gasAmountNeeded); @@ -310,29 +305,13 @@ contract GasFeePricingUpgradeable is SynMessagingReceiverUpgradeable, IGasFeePri /// @notice Update information about source chain gas token/unit price on all configured dst chains, /// as well as on the source chain itself. - function updateChainInfo(uint256 _gasTokenPrice, uint256 _gasUnitPrice) external payable onlyOwner { + function updateSrcInfo(uint256 _gasTokenPrice, uint256 _gasUnitPrice) external payable onlyOwner { // send messages before updating the values, so that it's possible to use // estimateUpdateFees() to calculate the needed fee for the update _sendUpdateMessages(uint8(GasFeePricingUpdates.MsgType.UPDATE_INFO), _gasTokenPrice, _gasUnitPrice); _updateSrcChainInfo(_gasTokenPrice, _gasUnitPrice); } - /// @notice Updates markups (see "Structs" docs) for a bunch of chains. Markups are used for determining - /// how much fee to charge on top of "projected gas cost" of delivering the message. - function updateMarkups( - uint256[] memory _dstChainIds, - uint16[] memory _markupsGasDrop, - uint16[] memory _markupsGasUsage - ) external onlyOwner { - require( - _dstChainIds.length == _markupsGasDrop.length && _dstChainIds.length == _markupsGasUsage.length, - "!arrays" - ); - for (uint256 i = 0; i < _dstChainIds.length; ++i) { - _updateMarkups(_dstChainIds[i], _markupsGasDrop[i], _markupsGasUsage[i]); - } - } - /*╔══════════════════════════════════════════════════════════════════════╗*\ ▏*║ UPDATE STATE LOGIC ║*▕ \*╚══════════════════════════════════════════════════════════════════════╝*/ diff --git a/test/messaging/GasFeePricingUpgradeable.t.sol b/test/messaging/GasFeePricingUpgradeable.t.sol index 570640f9a..90d8606c6 100644 --- a/test/messaging/GasFeePricingUpgradeable.t.sol +++ b/test/messaging/GasFeePricingUpgradeable.t.sol @@ -82,77 +82,60 @@ contract GasFeePricingUpgradeableTest is Test { address _gfp = address(gasFeePricing); utils.checkAccess( _gfp, - abi.encodeWithSelector(GasFeePricingUpgradeable.setCostPerChain.selector, 0, 0, 0), + abi.encodeWithSelector(GasFeePricingUpgradeable.setDstAddress.selector, new uint256[](1), new address[](1)), "Ownable: caller is not the owner" ); utils.checkAccess( _gfp, abi.encodeWithSelector( - GasFeePricingUpgradeable.setCostPerChains.selector, + GasFeePricingUpgradeable.setDstConfig.selector, new uint256[](1), new uint256[](1), new uint256[](1) ), "Ownable: caller is not the owner" ); - - utils.checkAccess( - _gfp, - abi.encodeWithSelector(GasFeePricingUpgradeable.setDstChainConfig.selector, 0, 0, 0), - "Ownable: caller is not the owner" - ); utils.checkAccess( _gfp, abi.encodeWithSelector( - GasFeePricingUpgradeable.setDstChainConfigs.selector, + GasFeePricingUpgradeable.setDstInfo.selector, new uint256[](1), new uint256[](1), new uint256[](1) ), "Ownable: caller is not the owner" ); - utils.checkAccess( _gfp, abi.encodeWithSelector( - GasFeePricingUpgradeable.setGasFeePricingAddresses.selector, + GasFeePricingUpgradeable.setDstMarkups.selector, new uint256[](1), - new address[](1) + new uint16[](1), + new uint16[](1) ), "Ownable: caller is not the owner" ); utils.checkAccess( _gfp, - abi.encodeWithSelector(GasFeePricingUpgradeable.setMinGasUsageFee.selector, 0), + abi.encodeWithSelector(GasFeePricingUpgradeable.setMinFee.selector, 0), "Ownable: caller is not the owner" ); utils.checkAccess( _gfp, - abi.encodeWithSelector(GasFeePricingUpgradeable.setMinGasUsageFeeUsd.selector, 0), + abi.encodeWithSelector(GasFeePricingUpgradeable.setMinFeeUsd.selector, 0), "Ownable: caller is not the owner" ); utils.checkAccess( _gfp, - abi.encodeWithSelector(GasFeePricingUpgradeable.updateChainConfig.selector, 0, 0), + abi.encodeWithSelector(GasFeePricingUpgradeable.updateSrcConfig.selector, 0, 0), "Ownable: caller is not the owner" ); utils.checkAccess( _gfp, - abi.encodeWithSelector(GasFeePricingUpgradeable.updateChainInfo.selector, 0, 0), - "Ownable: caller is not the owner" - ); - - utils.checkAccess( - _gfp, - abi.encodeWithSelector( - GasFeePricingUpgradeable.updateMarkups.selector, - new uint256[](1), - new uint16[](1), - new uint16[](1) - ), + abi.encodeWithSelector(GasFeePricingUpgradeable.updateSrcInfo.selector, 0, 0), "Ownable: caller is not the owner" ); } @@ -189,25 +172,25 @@ contract GasFeePricingUpgradeableTest is Test { ▏*║ INTERNAL CHECKERS ║*▕ \*╚══════════════════════════════════════════════════════════════════════╝*/ - function _checkDstChainConfig(uint256 _dstChainId) internal { + function _checkDstConfig(uint256 _dstChainId) internal { (uint112 gasAmountNeeded, uint112 maxGasDrop, , ) = gasFeePricing.dstConfig(_dstChainId); assertEq(gasAmountNeeded, dstVars[_dstChainId].gasAmountNeeded, "dstGasAmountNeeded is incorrect"); assertEq(maxGasDrop, dstVars[_dstChainId].maxGasDrop, "dstMaxGasDrop is incorrect"); } - function _checkDstChainInfo(uint256 _dstChainId) internal { + function _checkDstInfo(uint256 _dstChainId) internal { (uint128 gasTokenPrice, uint128 gasUnitPrice) = gasFeePricing.dstInfo(_dstChainId); assertEq(gasTokenPrice, dstVars[_dstChainId].gasTokenPrice, "dstGasTokenPrice is incorrect"); assertEq(gasUnitPrice, dstVars[_dstChainId].gasUnitPrice, "dstGasUnitPrice is incorrect"); } - function _checkDstChainMarkups(uint256 _dstChainId) internal { + function _checkDstMarkups(uint256 _dstChainId) internal { (, , uint16 markupGasDrop, uint16 markupGasUsage) = gasFeePricing.dstConfig(_dstChainId); assertEq(markupGasDrop, dstVars[_dstChainId].markupGasDrop, "dstMarkupGasDrop is incorrect"); assertEq(markupGasUsage, dstVars[_dstChainId].markupGasUsage, "dstMarkupGasUsage is incorrect"); } - function _checkDstChainRatios(uint256 _dstChainId) internal { + function _checkDstRatios(uint256 _dstChainId) internal { (uint96 gasTokenPriceRatio, uint160 gasUnitPriceRatio) = gasFeePricing.dstRatios(_dstChainId); uint256 _gasTokenPriceRatio = (dstVars[_dstChainId].gasTokenPrice * 10**18) / srcVars.gasTokenPrice; uint256 _gasUnitPriceRatio = (dstVars[_dstChainId].gasUnitPrice * dstVars[_dstChainId].gasTokenPrice * 10**18) / @@ -216,13 +199,13 @@ contract GasFeePricingUpgradeableTest is Test { assertEq(gasUnitPriceRatio, _gasUnitPriceRatio, "gasUnitPriceRatio is incorrect"); } - function _checkSrcChainConfig() internal { + function _checkSrcConfig() internal { (uint112 gasAmountNeeded, uint112 maxGasDrop, , ) = gasFeePricing.srcConfig(); assertEq(gasAmountNeeded, srcVars.gasAmountNeeded, "srcGasAmountNeeded is incorrect"); assertEq(maxGasDrop, srcVars.maxGasDrop, "srcMaxGasDrop is incorrect"); } - function _checkSrcChainInfo() internal { + function _checkSrcInfo() internal { (uint128 gasTokenPrice, uint128 gasUnitPrice) = gasFeePricing.srcInfo(); assertEq(gasTokenPrice, srcVars.gasTokenPrice, "srcGasTokenPrice is incorrect"); assertEq(gasUnitPrice, srcVars.gasUnitPrice, "gasUnitPrice is incorrect"); @@ -232,17 +215,7 @@ contract GasFeePricingUpgradeableTest is Test { ▏*║ INTERNAL SETTERS ║*▕ \*╚══════════════════════════════════════════════════════════════════════╝*/ - function _setDstChainConfig( - uint256 _dstChainId, - uint256 _gasAmountNeeded, - uint256 _maxGasDrop - ) internal { - dstVars[_dstChainId].gasAmountNeeded = _gasAmountNeeded; - dstVars[_dstChainId].maxGasDrop = _maxGasDrop; - gasFeePricing.setDstChainConfig(_dstChainId, _gasAmountNeeded, _maxGasDrop); - } - - function _setDstChainConfigs( + function _setDstConfig( uint256[] memory _dstChainIds, uint256[] memory _gasAmountsNeeded, uint256[] memory _maxGasDrops @@ -251,20 +224,10 @@ contract GasFeePricingUpgradeableTest is Test { dstVars[_dstChainIds[i]].gasAmountNeeded = _gasAmountsNeeded[i]; dstVars[_dstChainIds[i]].maxGasDrop = _maxGasDrops[i]; } - gasFeePricing.setDstChainConfigs(_dstChainIds, _gasAmountsNeeded, _maxGasDrops); + gasFeePricing.setDstConfig(_dstChainIds, _gasAmountsNeeded, _maxGasDrops); } - function _setDstChainInfo( - uint256 _dstChainId, - uint256 _gasTokenPrice, - uint256 _gasUnitPrice - ) internal { - dstVars[_dstChainId].gasTokenPrice = _gasTokenPrice; - dstVars[_dstChainId].gasUnitPrice = _gasUnitPrice; - gasFeePricing.setCostPerChain(_dstChainId, _gasUnitPrice, _gasTokenPrice); - } - - function _setDstChainsInfo( + function _setDstInfo( uint256[] memory _dstChainIds, uint256[] memory _gasTokenPrices, uint256[] memory _gasUnitPrices @@ -273,14 +236,7 @@ contract GasFeePricingUpgradeableTest is Test { dstVars[_dstChainIds[i]].gasTokenPrice = _gasTokenPrices[i]; dstVars[_dstChainIds[i]].gasUnitPrice = _gasUnitPrices[i]; } - gasFeePricing.setCostPerChains(_dstChainIds, _gasUnitPrices, _gasTokenPrices); - } - - function _setDstGasFeePricingAddresses(uint256[] memory _dstChainIds, address[] memory _dstGasFeePricing) internal { - for (uint256 i = 0; i < _dstChainIds.length; ++i) { - dstVars[_dstChainIds[i]].gasFeePricing = _dstGasFeePricing[i]; - } - gasFeePricing.setGasFeePricingAddresses(_dstChainIds, _dstGasFeePricing); + gasFeePricing.setDstInfo(_dstChainIds, _gasUnitPrices, _gasTokenPrices); } function _setDstMarkups( @@ -292,20 +248,27 @@ contract GasFeePricingUpgradeableTest is Test { dstVars[_dstChainIds[i]].markupGasDrop = _markupsGasDrop[i]; dstVars[_dstChainIds[i]].markupGasUsage = _markupsGasUsage[i]; } - gasFeePricing.updateMarkups(_dstChainIds, _markupsGasDrop, _markupsGasUsage); + gasFeePricing.setDstMarkups(_dstChainIds, _markupsGasDrop, _markupsGasUsage); + } + + function _setDstAddress(uint256[] memory _dstChainIds, address[] memory _dstGasFeePricing) internal { + for (uint256 i = 0; i < _dstChainIds.length; ++i) { + dstVars[_dstChainIds[i]].gasFeePricing = _dstGasFeePricing[i]; + } + gasFeePricing.setDstAddress(_dstChainIds, _dstGasFeePricing); } - function _setSrcChainConfig(uint256 _gasAmountNeeded, uint256 _maxGasDrop) internal { + function _updateSrcConfig(uint256 _gasAmountNeeded, uint256 _maxGasDrop) internal { srcVars.gasAmountNeeded = _gasAmountNeeded; srcVars.maxGasDrop = _maxGasDrop; uint256 fee = gasFeePricing.estimateUpdateFees(); - gasFeePricing.updateChainConfig{value: fee}(_gasAmountNeeded, _maxGasDrop); + gasFeePricing.updateSrcConfig{value: fee}(_gasAmountNeeded, _maxGasDrop); } - function _setSrcChainInfo(uint256 _gasTokenPrice, uint256 _gasUnitPrice) internal { + function _updateSrcInfo(uint256 _gasTokenPrice, uint256 _gasUnitPrice) internal { srcVars.gasTokenPrice = _gasTokenPrice; srcVars.gasUnitPrice = _gasUnitPrice; uint256 fee = gasFeePricing.estimateUpdateFees(); - gasFeePricing.updateChainInfo{value: fee}(_gasTokenPrice, _gasUnitPrice); + gasFeePricing.updateSrcInfo{value: fee}(_gasTokenPrice, _gasUnitPrice); } } From 8e444159951529c6ef29c4caf450ada5942270d3 Mon Sep 17 00:00:00 2001 From: ChiTimesChi <88190723+ChiTimesChi@users.noreply.github.com> Date: Sun, 22 May 2022 19:51:49 +0300 Subject: [PATCH 22/47] tests: check minGasUsageFee post-initialization --- contracts/messaging/GasFeePricingUpgradeable.sol | 2 +- test/messaging/GasFeePricingUpgradeable.t.sol | 10 ++++++++++ 2 files changed, 11 insertions(+), 1 deletion(-) diff --git a/contracts/messaging/GasFeePricingUpgradeable.sol b/contracts/messaging/GasFeePricingUpgradeable.sol index 0a0998209..7c9251ff2 100644 --- a/contracts/messaging/GasFeePricingUpgradeable.sol +++ b/contracts/messaging/GasFeePricingUpgradeable.sol @@ -287,7 +287,7 @@ contract GasFeePricingUpgradeable is SynMessagingReceiverUpgradeable { minGasUsageFee = _minGasUsageFee; } - /// @notice Update the minimum fee for gas usage on message delivery. Quoted in USD. + /// @notice Update the minimum fee for gas usage on message delivery. Quoted in USD, scaled to wei. function setMinFeeUsd(uint256 _minGasUsageFeeUsd) external onlyOwner { minGasUsageFee = _calculateMinGasUsageFee(_minGasUsageFeeUsd, srcInfo.gasTokenPrice); } diff --git a/test/messaging/GasFeePricingUpgradeable.t.sol b/test/messaging/GasFeePricingUpgradeable.t.sol index 90d8606c6..1728c1841 100644 --- a/test/messaging/GasFeePricingUpgradeable.t.sol +++ b/test/messaging/GasFeePricingUpgradeable.t.sol @@ -164,6 +164,7 @@ contract GasFeePricingUpgradeableTest is Test { (uint128 _gasTokenPrice, ) = gasFeePricing.srcInfo(); assertEq(_gasTokenPrice, srcVars.gasTokenPrice, "Failed to init: gasTokenPrice"); assertEq(gasFeePricing.messageBus(), address(messageBus), "Failed to init: messageBus"); + _checkMinFeeUsd(10**18); } function testSetCostPerChain() public {} @@ -199,6 +200,15 @@ contract GasFeePricingUpgradeableTest is Test { assertEq(gasUnitPriceRatio, _gasUnitPriceRatio, "gasUnitPriceRatio is incorrect"); } + function _checkMinFee(uint256 _expectedMinFee) internal { + assertEq(gasFeePricing.minGasUsageFee(), _expectedMinFee, "minGasUsageFee is incorrect"); + } + + function _checkMinFeeUsd(uint256 _expectedMinFeeUsd) internal { + uint256 _expectedMinFee = (_expectedMinFeeUsd * 10**18) / srcVars.gasTokenPrice; + _checkMinFee(_expectedMinFee); + } + function _checkSrcConfig() internal { (uint112 gasAmountNeeded, uint112 maxGasDrop, , ) = gasFeePricing.srcConfig(); assertEq(gasAmountNeeded, srcVars.gasAmountNeeded, "srcGasAmountNeeded is incorrect"); From 5580db9c405d7be32a3e8692393787dc07b47cdd Mon Sep 17 00:00:00 2001 From: ChiTimesChi <88190723+ChiTimesChi@users.noreply.github.com> Date: Sun, 22 May 2022 20:10:46 +0300 Subject: [PATCH 23/47] setTrustedRemotes for a bunch of chains --- .../framework/SynMessagingReceiverUpgradeable.sol | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/contracts/messaging/framework/SynMessagingReceiverUpgradeable.sol b/contracts/messaging/framework/SynMessagingReceiverUpgradeable.sol index 4f77fd65d..90f59df34 100644 --- a/contracts/messaging/framework/SynMessagingReceiverUpgradeable.sol +++ b/contracts/messaging/framework/SynMessagingReceiverUpgradeable.sol @@ -12,7 +12,7 @@ abstract contract SynMessagingReceiverUpgradeable is ISynMessagingReceiver, Owna // Maps chain ID to the bytes32 trusted addresses allowed to be source senders mapping(uint256 => bytes32) internal trustedRemoteLookup; - event SetTrustedRemote(uint256 _srcChainId, bytes32 _srcAddress); + event SetTrustedRemote(uint256 indexed _srcChainId, bytes32 _srcAddress); /** * @notice Executes a message called by MessageBus (MessageBusReceiver) @@ -123,6 +123,17 @@ abstract contract SynMessagingReceiverUpgradeable is ISynMessagingReceiver, Owna // allow owner to set trusted addresses allowed to be source senders function setTrustedRemote(uint256 _srcChainId, bytes32 _srcAddress) external onlyOwner { + _setTrustedRemote(_srcChainId, _srcAddress); + } + + function setTrustedRemotes(uint256[] memory _srcChainIds, bytes32[] memory _srcAddresses) external onlyOwner { + require(_srcChainIds.length == _srcAddresses.length, "!arrays"); + for (uint256 i = 0; i < _srcChainIds.length; ++i) { + _setTrustedRemote(_srcChainIds[i], _srcAddresses[i]); + } + } + + function _setTrustedRemote(uint256 _srcChainId, bytes32 _srcAddress) internal { trustedRemoteLookup[_srcChainId] = _srcAddress; emit SetTrustedRemote(_srcChainId, _srcAddress); } From 74fe4409875b37428511fde2fb2877169c6d4fc5 Mon Sep 17 00:00:00 2001 From: ChiTimesChi <88190723+ChiTimesChi@users.noreply.github.com> Date: Sun, 22 May 2022 20:12:46 +0300 Subject: [PATCH 24/47] GFP: remove dstGasFeePricing(was shadowing trustedRemoteLookup) --- .../messaging/GasFeePricingUpgradeable.sol | 17 +---------------- test/messaging/GasFeePricingUpgradeable.t.sol | 12 ------------ 2 files changed, 1 insertion(+), 28 deletions(-) diff --git a/contracts/messaging/GasFeePricingUpgradeable.sol b/contracts/messaging/GasFeePricingUpgradeable.sol index 7c9251ff2..857b5c95b 100644 --- a/contracts/messaging/GasFeePricingUpgradeable.sol +++ b/contracts/messaging/GasFeePricingUpgradeable.sol @@ -89,8 +89,6 @@ contract GasFeePricingUpgradeable is SynMessagingReceiverUpgradeable { mapping(uint256 => ChainRatios) public dstRatios; /// @dev dstChainId => Config mapping(uint256 => ChainConfig) public dstConfig; - /// @dev dstChainId => GasFeePricing contract address - mapping(uint256 => bytes32) public dstGasFeePricing; /// @dev list of all dst chain ids uint256[] internal dstChainIds; @@ -218,23 +216,10 @@ contract GasFeePricingUpgradeable is SynMessagingReceiverUpgradeable { minFee = (_minFeeUsd * 10**18) / _gasTokenPrice; } - /// @dev Converts address to bytes32 - function _addressToBytes32(address _addr) internal pure returns (bytes32) { - return bytes32(uint256(uint160(_addr))); - } - /*╔══════════════════════════════════════════════════════════════════════╗*\ ▏*║ ONLY OWNER ║*▕ \*╚══════════════════════════════════════════════════════════════════════╝*/ - /// @notice Update GasFeePricing addresses on a bunch of dst chains. Needed for cross-chain setups. - function setDstAddress(uint256[] memory _dstChainIds, address[] memory _dstGasFeePricing) external onlyOwner { - require(_dstChainIds.length == _dstGasFeePricing.length, "!arrays"); - for (uint256 i = 0; i < _dstChainIds.length; ++i) { - dstGasFeePricing[_dstChainIds[i]] = _addressToBytes32(_dstGasFeePricing[i]); - } - } - /// @dev Update config (gasLimit for sending messages to chain, max gas airdrop) for a bunch of chains. function setDstConfig( uint256[] memory _dstChainIds, @@ -424,7 +409,7 @@ contract GasFeePricingUpgradeable is SynMessagingReceiverUpgradeable { uint256 gasLimit = dstConfig[chainId].gasAmountNeeded; if (gasLimit == 0) gasLimit = DEFAULT_GAS_LIMIT; - receivers[i] = dstGasFeePricing[chainId]; + receivers[i] = trustedRemoteLookup[chainId]; options[i] = Options.encode(gasLimit); } diff --git a/test/messaging/GasFeePricingUpgradeable.t.sol b/test/messaging/GasFeePricingUpgradeable.t.sol index 1728c1841..df4e3938b 100644 --- a/test/messaging/GasFeePricingUpgradeable.t.sol +++ b/test/messaging/GasFeePricingUpgradeable.t.sol @@ -80,11 +80,6 @@ contract GasFeePricingUpgradeableTest is Test { function testCheckAccessControl() public { address _gfp = address(gasFeePricing); - utils.checkAccess( - _gfp, - abi.encodeWithSelector(GasFeePricingUpgradeable.setDstAddress.selector, new uint256[](1), new address[](1)), - "Ownable: caller is not the owner" - ); utils.checkAccess( _gfp, abi.encodeWithSelector( @@ -261,13 +256,6 @@ contract GasFeePricingUpgradeableTest is Test { gasFeePricing.setDstMarkups(_dstChainIds, _markupsGasDrop, _markupsGasUsage); } - function _setDstAddress(uint256[] memory _dstChainIds, address[] memory _dstGasFeePricing) internal { - for (uint256 i = 0; i < _dstChainIds.length; ++i) { - dstVars[_dstChainIds[i]].gasFeePricing = _dstGasFeePricing[i]; - } - gasFeePricing.setDstAddress(_dstChainIds, _dstGasFeePricing); - } - function _updateSrcConfig(uint256 _gasAmountNeeded, uint256 _maxGasDrop) internal { srcVars.gasAmountNeeded = _gasAmountNeeded; srcVars.maxGasDrop = _maxGasDrop; From a58544c867c7451b3918bea6e9f6fcf58e838738 Mon Sep 17 00:00:00 2001 From: ChiTimesChi <88190723+ChiTimesChi@users.noreply.github.com> Date: Sun, 22 May 2022 20:41:09 +0300 Subject: [PATCH 25/47] GFP: allow to set zero gas unit price (hi Aurora) --- contracts/messaging/GasFeePricingUpgradeable.sol | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/contracts/messaging/GasFeePricingUpgradeable.sol b/contracts/messaging/GasFeePricingUpgradeable.sol index 857b5c95b..ef7763b2b 100644 --- a/contracts/messaging/GasFeePricingUpgradeable.sol +++ b/contracts/messaging/GasFeePricingUpgradeable.sol @@ -341,7 +341,13 @@ contract GasFeePricingUpgradeable is SynMessagingReceiverUpgradeable { uint256 _gasUnitPrice, uint256 _gasTokenPrice ) internal { - require(_gasUnitPrice != 0 && _gasTokenPrice != 0, "Can't set to zero"); + /** + * @dev Some chains (i.e. Aurora) allow free transactions, + * so we're not checking gasUnitPrice for being zero. + * gasUnitPrice is never used as denominator, and there's + * a minimum fee for gas usage, so this can't be taken advantage of. + */ + require(_gasTokenPrice != 0, "Dst gas token price is not set"); uint256 _srcGasTokenPrice = srcInfo.gasTokenPrice; require(_srcGasTokenPrice != 0, "Src gas token price is not set"); From 5d14361152641590db09073b6713ec22bd9d3cdd Mon Sep 17 00:00:00 2001 From: ChiTimesChi <88190723+ChiTimesChi@users.noreply.github.com> Date: Sun, 22 May 2022 23:11:22 +0300 Subject: [PATCH 26/47] GFP: add missing checks, unify ordering --- contracts/messaging/GasFeePricingUpgradeable.sol | 13 +++++++++++-- test/messaging/GasFeePricingUpgradeable.t.sol | 2 +- 2 files changed, 12 insertions(+), 3 deletions(-) diff --git a/contracts/messaging/GasFeePricingUpgradeable.sol b/contracts/messaging/GasFeePricingUpgradeable.sol index ef7763b2b..bb9255870 100644 --- a/contracts/messaging/GasFeePricingUpgradeable.sol +++ b/contracts/messaging/GasFeePricingUpgradeable.sol @@ -239,8 +239,8 @@ contract GasFeePricingUpgradeable is SynMessagingReceiverUpgradeable { /// Handy for initial setup. function setDstInfo( uint256[] memory _dstChainIds, - uint256[] memory _gasUnitPrices, - uint256[] memory _gasTokenPrices + uint256[] memory _gasTokenPrices, + uint256[] memory _gasUnitPrices ) external onlyOwner { require( _dstChainIds.length == _gasUnitPrices.length && _dstChainIds.length == _gasTokenPrices.length, @@ -281,6 +281,7 @@ contract GasFeePricingUpgradeable is SynMessagingReceiverUpgradeable { /// amount of gas needed to do _updateDstChainInfo() /// and maximum airdrop available on this chain function updateSrcConfig(uint256 _gasAmountNeeded, uint256 _maxGasDrop) external payable onlyOwner { + require(_gasAmountNeeded != 0, "Gas amount is not set"); _sendUpdateMessages(uint8(GasFeePricingUpdates.MsgType.UPDATE_CONFIG), _gasAmountNeeded, _maxGasDrop); ChainConfig memory config = srcConfig; config.gasAmountNeeded = uint112(_gasAmountNeeded); @@ -291,6 +292,13 @@ contract GasFeePricingUpgradeable is SynMessagingReceiverUpgradeable { /// @notice Update information about source chain gas token/unit price on all configured dst chains, /// as well as on the source chain itself. function updateSrcInfo(uint256 _gasTokenPrice, uint256 _gasUnitPrice) external payable onlyOwner { + /** + * @dev Some chains (i.e. Aurora) allow free transactions, + * so we're not checking gasUnitPrice for being zero. + * gasUnitPrice is never used as denominator, and there's + * a minimum fee for gas usage, so this can't be taken advantage of. + */ + require(_gasTokenPrice != 0, "Gas token price is not set"); // send messages before updating the values, so that it's possible to use // estimateUpdateFees() to calculate the needed fee for the update _sendUpdateMessages(uint8(GasFeePricingUpdates.MsgType.UPDATE_INFO), _gasTokenPrice, _gasUnitPrice); @@ -328,6 +336,7 @@ contract GasFeePricingUpgradeable is SynMessagingReceiverUpgradeable { uint256 _gasAmountNeeded, uint256 _maxGasDrop ) internal { + require(_gasAmountNeeded != 0, "Gas amount is not set"); ChainConfig memory config = dstConfig[_dstChainId]; config.gasAmountNeeded = uint112(_gasAmountNeeded); config.maxGasDrop = uint112(_maxGasDrop); diff --git a/test/messaging/GasFeePricingUpgradeable.t.sol b/test/messaging/GasFeePricingUpgradeable.t.sol index df4e3938b..baa1124c0 100644 --- a/test/messaging/GasFeePricingUpgradeable.t.sol +++ b/test/messaging/GasFeePricingUpgradeable.t.sol @@ -241,7 +241,7 @@ contract GasFeePricingUpgradeableTest is Test { dstVars[_dstChainIds[i]].gasTokenPrice = _gasTokenPrices[i]; dstVars[_dstChainIds[i]].gasUnitPrice = _gasUnitPrices[i]; } - gasFeePricing.setDstInfo(_dstChainIds, _gasUnitPrices, _gasTokenPrices); + gasFeePricing.setDstInfo(_dstChainIds, _gasTokenPrices, _gasUnitPrices); } function _setDstMarkups( From cd8858321ce814e1f9c9cdfd322b2caca7ff3657 Mon Sep 17 00:00:00 2001 From: ChiTimesChi <88190723+ChiTimesChi@users.noreply.github.com> Date: Sun, 22 May 2022 23:24:10 +0300 Subject: [PATCH 27/47] tests: GFP setters --- test/messaging/GasFeePricingUpgradeable.t.sol | 213 +++++++++++++++++- 1 file changed, 211 insertions(+), 2 deletions(-) diff --git a/test/messaging/GasFeePricingUpgradeable.t.sol b/test/messaging/GasFeePricingUpgradeable.t.sol index baa1124c0..577020695 100644 --- a/test/messaging/GasFeePricingUpgradeable.t.sol +++ b/test/messaging/GasFeePricingUpgradeable.t.sol @@ -31,6 +31,9 @@ contract GasFeePricingUpgradeableTest is Test { mapping(uint256 => ChainVars) internal dstVars; + uint256[] internal dstChainIds; + uint256 internal constant TEST_CHAINS = 5; + address public constant NODE = address(1337); // enable receiving overpaid fees @@ -58,13 +61,22 @@ contract GasFeePricingUpgradeableTest is Test { // I don't have extra 10M laying around, so let's initialize those proxies messageBus.initialize(address(gasFeePricing), address(authVerifier)); gasFeePricing.initialize(address(messageBus), srcVars.gasTokenPrice); + + dstChainIds = new uint256[](TEST_CHAINS); + for (uint256 i = 0; i < TEST_CHAINS; ++i) { + uint256 dstChainId = i + 1; + dstChainIds[i] = dstChainId; + address dstGasFeePricing = utils.getNextUserAddress(); + dstVars[dstChainId].gasFeePricing = dstGasFeePricing; + gasFeePricing.setTrustedRemote(dstChainId, utils.addressToBytes32(dstGasFeePricing)); + } } /*╔══════════════════════════════════════════════════════════════════════╗*\ ▏*║ SECURITY TESTS ║*▕ \*╚══════════════════════════════════════════════════════════════════════╝*/ - function testInitialized() public { + function testIsInitialized() public { utils.checkAccess( address(messageBus), abi.encodeWithSelector(MessageBusUpgradeable.initialize.selector, address(0), address(0)), @@ -162,7 +174,204 @@ contract GasFeePricingUpgradeableTest is Test { _checkMinFeeUsd(10**18); } - function testSetCostPerChain() public {} + function testSetDstConfig() public { + uint256[] memory gasAmountsNeeded = new uint256[](TEST_CHAINS); + uint256[] memory maxGasDrops = new uint256[](TEST_CHAINS); + for (uint256 i = 0; i < TEST_CHAINS; ++i) { + gasAmountsNeeded[i] = (i + 1) * 420420; + maxGasDrops[i] = (i + 1) * 10**18; + } + _setDstConfig(dstChainIds, gasAmountsNeeded, maxGasDrops); + for (uint256 i = 0; i < TEST_CHAINS; ++i) { + _checkDstConfig(dstChainIds[i]); + } + } + + function testSetDstConfigZeroDropSucceeds() public { + uint256[] memory gasAmountsNeeded = new uint256[](TEST_CHAINS); + uint256[] memory maxGasDrops = new uint256[](TEST_CHAINS); + for (uint256 i = 0; i < TEST_CHAINS; ++i) { + gasAmountsNeeded[i] = (i + 1) * 133769; + maxGasDrops[i] = i * 10**18; + } + _setDstConfig(dstChainIds, gasAmountsNeeded, maxGasDrops); + for (uint256 i = 0; i < TEST_CHAINS; ++i) { + _checkDstConfig(dstChainIds[i]); + } + } + + function testSetDstConfigZeroGasReverts() public { + uint256[] memory gasAmountsNeeded = new uint256[](TEST_CHAINS); + uint256[] memory maxGasDrops = new uint256[](TEST_CHAINS); + for (uint256 i = 0; i < TEST_CHAINS; ++i) { + gasAmountsNeeded[i] = i * 133769; + maxGasDrops[i] = (i + 1) * 10**18; + } + utils.checkRevert( + address(this), + address(gasFeePricing), + abi.encodeWithSelector( + GasFeePricingUpgradeable.setDstConfig.selector, + dstChainIds, + gasAmountsNeeded, + maxGasDrops + ), + "Gas amount is not set" + ); + } + + function testSetDstInfo() public { + (uint256[] memory gasUnitPrices, uint256[] memory gasTokenPrices) = _generateTestInfoValues(); + _setDstInfo(dstChainIds, gasTokenPrices, gasUnitPrices); + for (uint256 i = 0; i < TEST_CHAINS; ++i) { + uint256 chainId = dstChainIds[i]; + _checkDstInfo(chainId); + _checkDstRatios(chainId); + } + } + + function testSetDstInfoZeroTokenPriceReverts() public { + (uint256[] memory gasTokenPrices, uint256[] memory gasUnitPrices) = _generateTestInfoValues(); + gasTokenPrices[2] = 0; + utils.checkRevert( + address(this), + address(gasFeePricing), + abi.encodeWithSelector( + GasFeePricingUpgradeable.setDstInfo.selector, + dstChainIds, + gasTokenPrices, + gasUnitPrices + ), + "Dst gas token price is not set" + ); + } + + function testSetDstInfoZeroUnitPriceSucceeds() public { + (uint256[] memory gasTokenPrices, uint256[] memory gasUnitPrices) = _generateTestInfoValues(); + gasTokenPrices[2] = 100 * 10**18; + gasUnitPrices[3] = 0; + _setDstInfo(dstChainIds, gasTokenPrices, gasUnitPrices); + for (uint256 i = 0; i < TEST_CHAINS; ++i) { + uint256 chainId = dstChainIds[i]; + _checkDstInfo(chainId); + _checkDstRatios(chainId); + } + } + + function _generateTestInfoValues() + internal + pure + returns (uint256[] memory gasTokenPrices, uint256[] memory gasUnitPrices) + { + gasTokenPrices = new uint256[](TEST_CHAINS); + gasUnitPrices = new uint256[](TEST_CHAINS); + // 100 gwei, gasToken = $2000 + gasTokenPrices[0] = 2000 * 10**18; + gasUnitPrices[0] = 100 * 10**9; + // 5 gwei, gasToken = $1000 + gasTokenPrices[1] = 1000 * 10**18; + gasUnitPrices[1] = 5 * 10**9; + // 2000 gwei, gasToken = $0.5 + gasTokenPrices[2] = (5 * 10**18) / 10; + gasUnitPrices[2] = 2000 * 10**9; + // 1 gwei, gasToken = $2000 + gasTokenPrices[3] = 2000 * 10**18; + gasUnitPrices[3] = 10**9; + // 0.04 gwei, gasToken = $0.01 + gasTokenPrices[4] = (1 * 10**18) / 100; + gasUnitPrices[4] = (4 * 10**9) / 100; + } + + function testSetDstMarkups() public { + uint16[] memory markupsGasDrop = new uint16[](TEST_CHAINS); + uint16[] memory markupsGasUsage = new uint16[](TEST_CHAINS); + for (uint16 i = 0; i < TEST_CHAINS; ++i) { + // this will set the first chain markups to [0, 0] + markupsGasDrop[i] = i * 13; + markupsGasUsage[i] = i * 42; + } + _setDstMarkups(dstChainIds, markupsGasDrop, markupsGasUsage); + for (uint256 i = 0; i < TEST_CHAINS; ++i) { + _checkDstMarkups(dstChainIds[i]); + } + } + + function testSetMinFee() public { + uint256 minGasUsageFee = 1234567890; + gasFeePricing.setMinFee(minGasUsageFee); + _checkMinFee(minGasUsageFee); + } + + function testSetMinFeeUsd(uint16 alphaUsd) public { + uint256 minGasFeeUsageUsd = uint256(alphaUsd) * 10**16; + gasFeePricing.setMinFeeUsd(minGasFeeUsageUsd); + _checkMinFeeUsd(minGasFeeUsageUsd); + } + + function testUpdateSrcConfig() public { + uint256 gasAmountNeeded = 10**6; + uint256 maxGasDrop = 10 * 10**18; + _updateSrcConfig(gasAmountNeeded, maxGasDrop); + _checkSrcConfig(); + } + + function testUpdateSrcConfigZeroDropSucceeds() public { + uint256 gasAmountNeeded = 2 * 10**6; + // should be able to set to zero + uint256 maxGasDrop = 0; + _updateSrcConfig(gasAmountNeeded, maxGasDrop); + _checkSrcConfig(); + } + + function testUpdateSrcConfigZeroGasReverts() public { + // should NOT be able to set to zero + uint256 gasAmountNeeded = 0; + uint256 maxGasDrop = 10**18; + utils.checkRevert( + address(this), + address(gasFeePricing), + abi.encodeWithSelector(GasFeePricingUpgradeable.updateSrcConfig.selector, gasAmountNeeded, maxGasDrop), + "Gas amount is not set" + ); + } + + function testUpdateSrcInfo() public { + testSetDstInfo(); + + uint256 gasTokenPrice = 2 * 10**18; + uint256 gasUnitPrice = 10 * 10**9; + _updateSrcInfo(gasTokenPrice, gasUnitPrice); + _checkSrcInfo(); + for (uint256 i = 0; i < TEST_CHAINS; ++i) { + _checkDstRatios(i + 1); + } + } + + function testUpdateSrcInfoZeroTokenPriceReverts() public { + testSetDstInfo(); + + uint256 gasTokenPrice = 0; + uint256 gasUnitPrice = 10 * 10**9; + + utils.checkRevert( + address(this), + address(gasFeePricing), + abi.encodeWithSelector(GasFeePricingUpgradeable.updateSrcInfo.selector, gasTokenPrice, gasUnitPrice), + "Gas token price is not set" + ); + } + + function testUpdateSrcInfoZeroUnitPriceSucceeds() public { + testSetDstInfo(); + + uint256 gasTokenPrice = 4 * 10**17; + uint256 gasUnitPrice = 0; + _updateSrcInfo(gasTokenPrice, gasUnitPrice); + _checkSrcInfo(); + for (uint256 i = 0; i < TEST_CHAINS; ++i) { + _checkDstRatios(i + 1); + } + } /*╔══════════════════════════════════════════════════════════════════════╗*\ ▏*║ INTERNAL CHECKERS ║*▕ From 221e204e83b36ab17c82eb1963775c25038b428a Mon Sep 17 00:00:00 2001 From: ChiTimesChi <88190723+ChiTimesChi@users.noreply.github.com> Date: Mon, 23 May 2022 15:00:01 +0300 Subject: [PATCH 28/47] GFP: refactoring + better docs - variable names should be more clear now - enforced alfabetical order of variables to avoid confusion - docs on the srtucts are more verbose - tests are updated to reflect the these changes --- .../messaging/GasFeePricingUpgradeable.sol | 169 +++++++++++------- test/messaging/GasFeePricingUpgradeable.t.sol | 155 ++++++++-------- 2 files changed, 185 insertions(+), 139 deletions(-) diff --git a/contracts/messaging/GasFeePricingUpgradeable.sol b/contracts/messaging/GasFeePricingUpgradeable.sol index bb9255870..8fb67e33a 100644 --- a/contracts/messaging/GasFeePricingUpgradeable.sol +++ b/contracts/messaging/GasFeePricingUpgradeable.sol @@ -35,39 +35,93 @@ contract GasFeePricingUpgradeable is SynMessagingReceiverUpgradeable { * this is not recommended. */ - /// @dev Dst chain's basic variables, that are unlikely to change over time. + /** + * @dev Chain's Config is supposed to be PARTLY synchronized cross-chain, i.e. + * GasFeePricing contracts on different chain will have the SAME values for + * the same dst chain: + * - gasDropMax: maximum gas airdrop available on chain + * uint112 => max value ~= 5 * 10**33 + * - gasUnitsRcvMsg: Amount of gas units needed for GasFeePricing contract + * to receive "update chain Config/Info" message + * uint112 => max value ~= 5 * 10**33 + * These are universal values, and they should be the same on all GasFeePricing + * contracts. + * + * Some of the values, however, are set unique for every "src-dst" chain combination: + * - markupGasDrop: Markup for gas airdrop + * uint16 => max value = 65535 + * - markupGasUsage: Markup for gas usage + * uint16 => max value = 65535 + * These values depend on correlation between src and dst chains. For instance, + * if both chains have the same gas token (like ETH), markup for the gas drop + * can be set to 0, as gasDrop is limited, and the slight price difference between ETH + * on src and dst chain can not be taken advantage of. + * + * On the contrary, if src and dst gas tokens have proven to be not that correlated + * in terms of their price, higher markup is needed to compensate potential price spikes. + * + * ChainConfig is optimized to fit into one word of storage. + * ChainConfig is not supposed to be updated regularly (the values are more or less persistent). + */ + struct ChainConfig { - // Amount of gas units needed to receive "update chainInfo" message - uint112 gasAmountNeeded; - // Maximum gas airdrop available on chain - uint112 maxGasDrop; - // Markup for gas airdrop + /// @dev Values below are synchronized cross-chain + uint112 gasDropMax; + uint112 gasUnitsRcvMsg; + /// @dev Values below are src-chain specific uint16 markupGasDrop; - // Markup for gas usage uint16 markupGasUsage; } - /// @dev Information about dst chain's gas price, which can change over time - /// due to gas token price movement, or gas spikes. + /** + * @dev Chain's Info is supposed to be FULLY synchronized cross-chain, i.e. + * GasFeePricing contracts on different chain will have the SAME values for + * the same dst chain: + * - gasTokenPrice: Price of chain's gas token in USD, scaled to wei + * uint128 => max value ~= 3 * 10**38 + * - gasUnitPrice: Price of chain's 1 gas unit in wei + * uint128 => max value ~= 3 * 10**38 + * + * These are universal values, and they should be the same on all GasFeePricing + * contracts. + * + * ChainInfo is optimized to fit into one word of storage. + * ChainInfo is supposed to be updated regularly, as the chain's gas token or unit + * price changes drastically. + */ struct ChainInfo { - // Price of chain's gas token in USD, scaled to wei + /// @dev Values below are synchronized cross-chain uint128 gasTokenPrice; - // Price of chain's 1 gas unit in wei uint128 gasUnitPrice; } - /// @dev Ratio between src and dst gas price ratio. - /// Used for calculating a fee for sending a msg from src to dst chain. - /// Updated whenever "gas information" is changed for either source or destination chain. + /** + * @dev Chain's Ratios are supposed to be FULLY chain-specific, i.e. + * GasFeePricing contracts on different chain will have different values for + * the same dst chain: + * - gasTokenPriceRatio: USD price ratio of dstGasToken / srcGasToken, scaled to wei + * uint96 => max value ~= 8 * 10**28 + * - gasUnitPriceRatio: How much 1 gas unit on dst chain is worth, expressed in src chain wei, + * multiplied by 10**18 (aka in attoWei = 10^-18 wei) + * uint160 => max value ~= 10**48 + * These values are updated whenever "gas information" is updated for either source or destination chain. + * + * ChainRatios is optimized to fit into one word of storage. + */ + + /** + * @dev Chain's Ratios are used for calculating a fee for sending a msg from src to dst chain. + * To calculate cost of tx gas airdrop (assuming gasDrop airdrop value): + * (gasDrop * gasTokenPriceRatio) / 10**18 + * To calculate cost of tx gas usage on dst chain (assuming gasAmount gas units): + * (gasAmount * gasUnitPriceRatio) / 10**18 + * + * Both numbers are expressed in src chain wei. + */ struct ChainRatios { - // USD price ratio of dstGasToken / srcGasToken, scaled to wei + /// @dev Values below are src-chain specific uint96 gasTokenPriceRatio; - // How much 1 gas unit on dst chain is worth, - // expressed in src chain wei, multiplied by 10**18 (aka in attoWei = 10^-18 wei) uint160 gasUnitPriceRatio; - // To calculate gas cost of tx on dst chain, which consumes gasAmount gas units: - // (gasAmount * gasUnitPriceRatio) / 10**18 - // This number is expressed in src chain wei } /*╔══════════════════════════════════════════════════════════════════════╗*\ @@ -152,7 +206,7 @@ contract GasFeePricingUpgradeable is SynMessagingReceiverUpgradeable { if (_options.length != 0) { (gasLimit, gasAirdrop, ) = Options.decode(_options); if (gasAirdrop != 0) { - require(gasAirdrop <= config.maxGasDrop, "GasDrop higher than max"); + require(gasAirdrop <= config.gasDropMax, "GasDrop higher than max"); } } else { gasLimit = DEFAULT_GAS_LIMIT; @@ -199,7 +253,7 @@ contract GasFeePricingUpgradeable is SynMessagingReceiverUpgradeable { for (uint256 i = 0; i < _chainIds.length; ++i) { uint256 chainId = _chainIds[i]; ChainConfig memory config = dstConfig[chainId]; - uint256 gasLimit = config.gasAmountNeeded; + uint256 gasLimit = config.gasUnitsRcvMsg; if (gasLimit == 0) gasLimit = DEFAULT_GAS_LIMIT; uint256 fee = _estimateGasFee(chainId, 0, gasLimit, config.markupGasDrop, config.markupGasUsage); @@ -222,48 +276,39 @@ contract GasFeePricingUpgradeable is SynMessagingReceiverUpgradeable { /// @dev Update config (gasLimit for sending messages to chain, max gas airdrop) for a bunch of chains. function setDstConfig( - uint256[] memory _dstChainIds, - uint256[] memory _gasAmountsNeeded, - uint256[] memory _maxGasDrops + uint256[] memory _dstChainId, + uint256[] memory _gasDropMax, + uint256[] memory _gasUnitsRcvMsg ) external onlyOwner { - require( - _dstChainIds.length == _gasAmountsNeeded.length && _dstChainIds.length == _maxGasDrops.length, - "!arrays" - ); - for (uint256 i = 0; i < _dstChainIds.length; ++i) { - _updateDstChainConfig(_dstChainIds[i], _gasAmountsNeeded[i], _maxGasDrops[i]); + require(_dstChainId.length == _gasDropMax.length && _dstChainId.length == _gasUnitsRcvMsg.length, "!arrays"); + for (uint256 i = 0; i < _dstChainId.length; ++i) { + _updateDstChainConfig(_dstChainId[i], _gasDropMax[i], _gasUnitsRcvMsg[i]); } } /// @notice Update information about gas unit/token price for a bunch of chains. /// Handy for initial setup. function setDstInfo( - uint256[] memory _dstChainIds, - uint256[] memory _gasTokenPrices, - uint256[] memory _gasUnitPrices + uint256[] memory _dstChainId, + uint256[] memory _gasTokenPrice, + uint256[] memory _gasUnitPrice ) external onlyOwner { - require( - _dstChainIds.length == _gasUnitPrices.length && _dstChainIds.length == _gasTokenPrices.length, - "!arrays" - ); - for (uint256 i = 0; i < _dstChainIds.length; ++i) { - _updateDstChainInfo(_dstChainIds[i], _gasUnitPrices[i], _gasTokenPrices[i]); + require(_dstChainId.length == _gasTokenPrice.length && _dstChainId.length == _gasUnitPrice.length, "!arrays"); + for (uint256 i = 0; i < _dstChainId.length; ++i) { + _updateDstChainInfo(_dstChainId[i], _gasTokenPrice[i], _gasUnitPrice[i]); } } /// @notice Sets markups (see "Structs" docs) for a bunch of chains. Markups are used for determining /// how much fee to charge on top of "projected gas cost" of delivering the message. function setDstMarkups( - uint256[] memory _dstChainIds, - uint16[] memory _markupsGasDrop, - uint16[] memory _markupsGasUsage + uint256[] memory _dstChainId, + uint16[] memory _markupGasDrop, + uint16[] memory _markupGasUsage ) external onlyOwner { - require( - _dstChainIds.length == _markupsGasDrop.length && _dstChainIds.length == _markupsGasUsage.length, - "!arrays" - ); - for (uint256 i = 0; i < _dstChainIds.length; ++i) { - _updateMarkups(_dstChainIds[i], _markupsGasDrop[i], _markupsGasUsage[i]); + require(_dstChainId.length == _markupGasDrop.length && _dstChainId.length == _markupGasUsage.length, "!arrays"); + for (uint256 i = 0; i < _dstChainId.length; ++i) { + _updateMarkups(_dstChainId[i], _markupGasDrop[i], _markupGasUsage[i]); } } @@ -280,12 +325,12 @@ contract GasFeePricingUpgradeable is SynMessagingReceiverUpgradeable { /// @notice Update information about source chain config: /// amount of gas needed to do _updateDstChainInfo() /// and maximum airdrop available on this chain - function updateSrcConfig(uint256 _gasAmountNeeded, uint256 _maxGasDrop) external payable onlyOwner { - require(_gasAmountNeeded != 0, "Gas amount is not set"); - _sendUpdateMessages(uint8(GasFeePricingUpdates.MsgType.UPDATE_CONFIG), _gasAmountNeeded, _maxGasDrop); + function updateSrcConfig(uint256 _gasDropMax, uint256 _gasUnitsRcvMsg) external payable onlyOwner { + require(_gasUnitsRcvMsg != 0, "Gas amount is not set"); + _sendUpdateMessages(uint8(GasFeePricingUpdates.MsgType.UPDATE_CONFIG), _gasDropMax, _gasUnitsRcvMsg); ChainConfig memory config = srcConfig; - config.gasAmountNeeded = uint112(_gasAmountNeeded); - config.maxGasDrop = uint112(_maxGasDrop); + config.gasDropMax = uint112(_gasDropMax); + config.gasUnitsRcvMsg = uint112(_gasUnitsRcvMsg); srcConfig = config; } @@ -333,13 +378,13 @@ contract GasFeePricingUpgradeable is SynMessagingReceiverUpgradeable { /// Maximum airdrop available on this chain function _updateDstChainConfig( uint256 _dstChainId, - uint256 _gasAmountNeeded, - uint256 _maxGasDrop + uint256 _gasDropMax, + uint256 _gasUnitsRcvMsg ) internal { - require(_gasAmountNeeded != 0, "Gas amount is not set"); + require(_gasUnitsRcvMsg != 0, "Gas amount is not set"); ChainConfig memory config = dstConfig[_dstChainId]; - config.gasAmountNeeded = uint112(_gasAmountNeeded); - config.maxGasDrop = uint112(_maxGasDrop); + config.gasDropMax = uint112(_gasDropMax); + config.gasUnitsRcvMsg = uint112(_gasUnitsRcvMsg); dstConfig[_dstChainId] = config; } @@ -347,8 +392,8 @@ contract GasFeePricingUpgradeable is SynMessagingReceiverUpgradeable { /// Dst chain ratios are updated as well. function _updateDstChainInfo( uint256 _dstChainId, - uint256 _gasUnitPrice, - uint256 _gasTokenPrice + uint256 _gasTokenPrice, + uint256 _gasUnitPrice ) internal { /** * @dev Some chains (i.e. Aurora) allow free transactions, @@ -421,7 +466,7 @@ contract GasFeePricingUpgradeable is SynMessagingReceiverUpgradeable { for (uint256 i = 0; i < chainIds.length; ++i) { uint256 chainId = chainIds[i]; - uint256 gasLimit = dstConfig[chainId].gasAmountNeeded; + uint256 gasLimit = dstConfig[chainId].gasUnitsRcvMsg; if (gasLimit == 0) gasLimit = DEFAULT_GAS_LIMIT; receivers[i] = trustedRemoteLookup[chainId]; diff --git a/test/messaging/GasFeePricingUpgradeable.t.sol b/test/messaging/GasFeePricingUpgradeable.t.sol index 577020695..efc5377f7 100644 --- a/test/messaging/GasFeePricingUpgradeable.t.sol +++ b/test/messaging/GasFeePricingUpgradeable.t.sol @@ -14,8 +14,8 @@ contract GasFeePricingUpgradeableTest is Test { struct ChainVars { uint256 gasTokenPrice; uint256 gasUnitPrice; - uint256 gasAmountNeeded; - uint256 maxGasDrop; + uint256 gasDropMax; + uint256 gasUnitsRcvMsg; uint256 markupGasDrop; uint256 markupGasUsage; address gasFeePricing; @@ -175,37 +175,37 @@ contract GasFeePricingUpgradeableTest is Test { } function testSetDstConfig() public { - uint256[] memory gasAmountsNeeded = new uint256[](TEST_CHAINS); - uint256[] memory maxGasDrops = new uint256[](TEST_CHAINS); + uint256[] memory gasUnitsRcvMsg = new uint256[](TEST_CHAINS); + uint256[] memory gasDropMax = new uint256[](TEST_CHAINS); for (uint256 i = 0; i < TEST_CHAINS; ++i) { - gasAmountsNeeded[i] = (i + 1) * 420420; - maxGasDrops[i] = (i + 1) * 10**18; + gasUnitsRcvMsg[i] = (i + 1) * 420420; + gasDropMax[i] = (i + 1) * 10**18; } - _setDstConfig(dstChainIds, gasAmountsNeeded, maxGasDrops); + _setDstConfig(dstChainIds, gasDropMax, gasUnitsRcvMsg); for (uint256 i = 0; i < TEST_CHAINS; ++i) { _checkDstConfig(dstChainIds[i]); } } function testSetDstConfigZeroDropSucceeds() public { - uint256[] memory gasAmountsNeeded = new uint256[](TEST_CHAINS); - uint256[] memory maxGasDrops = new uint256[](TEST_CHAINS); + uint256[] memory gasDropMax = new uint256[](TEST_CHAINS); + uint256[] memory gasUnitsRcvMsg = new uint256[](TEST_CHAINS); for (uint256 i = 0; i < TEST_CHAINS; ++i) { - gasAmountsNeeded[i] = (i + 1) * 133769; - maxGasDrops[i] = i * 10**18; + gasDropMax[i] = i * 10**18; + gasUnitsRcvMsg[i] = (i + 1) * 133769; } - _setDstConfig(dstChainIds, gasAmountsNeeded, maxGasDrops); + _setDstConfig(dstChainIds, gasDropMax, gasUnitsRcvMsg); for (uint256 i = 0; i < TEST_CHAINS; ++i) { _checkDstConfig(dstChainIds[i]); } } function testSetDstConfigZeroGasReverts() public { - uint256[] memory gasAmountsNeeded = new uint256[](TEST_CHAINS); - uint256[] memory maxGasDrops = new uint256[](TEST_CHAINS); + uint256[] memory gasDropMax = new uint256[](TEST_CHAINS); + uint256[] memory gasUnitsRcvMsg = new uint256[](TEST_CHAINS); for (uint256 i = 0; i < TEST_CHAINS; ++i) { - gasAmountsNeeded[i] = i * 133769; - maxGasDrops[i] = (i + 1) * 10**18; + gasDropMax[i] = (i + 1) * 10**18; + gasUnitsRcvMsg[i] = i * 133769; } utils.checkRevert( address(this), @@ -213,15 +213,15 @@ contract GasFeePricingUpgradeableTest is Test { abi.encodeWithSelector( GasFeePricingUpgradeable.setDstConfig.selector, dstChainIds, - gasAmountsNeeded, - maxGasDrops + gasDropMax, + gasUnitsRcvMsg ), "Gas amount is not set" ); } function testSetDstInfo() public { - (uint256[] memory gasUnitPrices, uint256[] memory gasTokenPrices) = _generateTestInfoValues(); + (uint256[] memory gasTokenPrices, uint256[] memory gasUnitPrices) = _generateTestInfoValues(); _setDstInfo(dstChainIds, gasTokenPrices, gasUnitPrices); for (uint256 i = 0; i < TEST_CHAINS; ++i) { uint256 chainId = dstChainIds[i]; @@ -283,14 +283,14 @@ contract GasFeePricingUpgradeableTest is Test { } function testSetDstMarkups() public { - uint16[] memory markupsGasDrop = new uint16[](TEST_CHAINS); - uint16[] memory markupsGasUsage = new uint16[](TEST_CHAINS); + uint16[] memory markupGasDrop = new uint16[](TEST_CHAINS); + uint16[] memory markupGasUsage = new uint16[](TEST_CHAINS); for (uint16 i = 0; i < TEST_CHAINS; ++i) { // this will set the first chain markups to [0, 0] - markupsGasDrop[i] = i * 13; - markupsGasUsage[i] = i * 42; + markupGasDrop[i] = i * 13; + markupGasUsage[i] = i * 42; } - _setDstMarkups(dstChainIds, markupsGasDrop, markupsGasUsage); + _setDstMarkups(dstChainIds, markupGasDrop, markupGasUsage); for (uint256 i = 0; i < TEST_CHAINS; ++i) { _checkDstMarkups(dstChainIds[i]); } @@ -309,28 +309,29 @@ contract GasFeePricingUpgradeableTest is Test { } function testUpdateSrcConfig() public { - uint256 gasAmountNeeded = 10**6; - uint256 maxGasDrop = 10 * 10**18; - _updateSrcConfig(gasAmountNeeded, maxGasDrop); + uint256 gasDropMax = 10 * 10**18; + uint256 gasUnitsRcvMsg = 10**6; + _updateSrcConfig(gasDropMax, gasUnitsRcvMsg); _checkSrcConfig(); } function testUpdateSrcConfigZeroDropSucceeds() public { - uint256 gasAmountNeeded = 2 * 10**6; // should be able to set to zero - uint256 maxGasDrop = 0; - _updateSrcConfig(gasAmountNeeded, maxGasDrop); + uint256 gasDropMax = 0; + uint256 gasUnitsRcvMsg = 2 * 10**6; + _updateSrcConfig(gasDropMax, gasUnitsRcvMsg); _checkSrcConfig(); } function testUpdateSrcConfigZeroGasReverts() public { + uint256 gasDropMax = 10**18; // should NOT be able to set to zero - uint256 gasAmountNeeded = 0; - uint256 maxGasDrop = 10**18; + uint256 gasUnitsRcvMsg = 0; + utils.checkRevert( address(this), address(gasFeePricing), - abi.encodeWithSelector(GasFeePricingUpgradeable.updateSrcConfig.selector, gasAmountNeeded, maxGasDrop), + abi.encodeWithSelector(GasFeePricingUpgradeable.updateSrcConfig.selector, gasDropMax, gasUnitsRcvMsg), "Gas amount is not set" ); } @@ -377,28 +378,28 @@ contract GasFeePricingUpgradeableTest is Test { ▏*║ INTERNAL CHECKERS ║*▕ \*╚══════════════════════════════════════════════════════════════════════╝*/ - function _checkDstConfig(uint256 _dstChainId) internal { - (uint112 gasAmountNeeded, uint112 maxGasDrop, , ) = gasFeePricing.dstConfig(_dstChainId); - assertEq(gasAmountNeeded, dstVars[_dstChainId].gasAmountNeeded, "dstGasAmountNeeded is incorrect"); - assertEq(maxGasDrop, dstVars[_dstChainId].maxGasDrop, "dstMaxGasDrop is incorrect"); + function _checkDstConfig(uint256 _chainId) internal { + (uint112 gasDropMax, uint112 gasUnitsRcvMsg, , ) = gasFeePricing.dstConfig(_chainId); + assertEq(gasDropMax, dstVars[_chainId].gasDropMax, "dstMaxGasDrop is incorrect"); + assertEq(gasUnitsRcvMsg, dstVars[_chainId].gasUnitsRcvMsg, "dstGasUnitsRcvMsg is incorrect"); } - function _checkDstInfo(uint256 _dstChainId) internal { - (uint128 gasTokenPrice, uint128 gasUnitPrice) = gasFeePricing.dstInfo(_dstChainId); - assertEq(gasTokenPrice, dstVars[_dstChainId].gasTokenPrice, "dstGasTokenPrice is incorrect"); - assertEq(gasUnitPrice, dstVars[_dstChainId].gasUnitPrice, "dstGasUnitPrice is incorrect"); + function _checkDstInfo(uint256 _chainId) internal { + (uint128 gasTokenPrice, uint128 gasUnitPrice) = gasFeePricing.dstInfo(_chainId); + assertEq(gasTokenPrice, dstVars[_chainId].gasTokenPrice, "dstGasTokenPrice is incorrect"); + assertEq(gasUnitPrice, dstVars[_chainId].gasUnitPrice, "dstGasUnitPrice is incorrect"); } - function _checkDstMarkups(uint256 _dstChainId) internal { - (, , uint16 markupGasDrop, uint16 markupGasUsage) = gasFeePricing.dstConfig(_dstChainId); - assertEq(markupGasDrop, dstVars[_dstChainId].markupGasDrop, "dstMarkupGasDrop is incorrect"); - assertEq(markupGasUsage, dstVars[_dstChainId].markupGasUsage, "dstMarkupGasUsage is incorrect"); + function _checkDstMarkups(uint256 _chainId) internal { + (, , uint16 markupGasDrop, uint16 markupGasUsage) = gasFeePricing.dstConfig(_chainId); + assertEq(markupGasDrop, dstVars[_chainId].markupGasDrop, "dstMarkupGasDrop is incorrect"); + assertEq(markupGasUsage, dstVars[_chainId].markupGasUsage, "dstMarkupGasUsage is incorrect"); } - function _checkDstRatios(uint256 _dstChainId) internal { - (uint96 gasTokenPriceRatio, uint160 gasUnitPriceRatio) = gasFeePricing.dstRatios(_dstChainId); - uint256 _gasTokenPriceRatio = (dstVars[_dstChainId].gasTokenPrice * 10**18) / srcVars.gasTokenPrice; - uint256 _gasUnitPriceRatio = (dstVars[_dstChainId].gasUnitPrice * dstVars[_dstChainId].gasTokenPrice * 10**18) / + function _checkDstRatios(uint256 _chainId) internal { + (uint96 gasTokenPriceRatio, uint160 gasUnitPriceRatio) = gasFeePricing.dstRatios(_chainId); + uint256 _gasTokenPriceRatio = (dstVars[_chainId].gasTokenPrice * 10**18) / srcVars.gasTokenPrice; + uint256 _gasUnitPriceRatio = (dstVars[_chainId].gasUnitPrice * dstVars[_chainId].gasTokenPrice * 10**18) / srcVars.gasTokenPrice; assertEq(gasTokenPriceRatio, _gasTokenPriceRatio, "gasTokenPriceRatio is incorrect"); assertEq(gasUnitPriceRatio, _gasUnitPriceRatio, "gasUnitPriceRatio is incorrect"); @@ -414,9 +415,9 @@ contract GasFeePricingUpgradeableTest is Test { } function _checkSrcConfig() internal { - (uint112 gasAmountNeeded, uint112 maxGasDrop, , ) = gasFeePricing.srcConfig(); - assertEq(gasAmountNeeded, srcVars.gasAmountNeeded, "srcGasAmountNeeded is incorrect"); - assertEq(maxGasDrop, srcVars.maxGasDrop, "srcMaxGasDrop is incorrect"); + (uint112 gasDropMax, uint112 gasUnitsRcvMsg, , ) = gasFeePricing.srcConfig(); + assertEq(gasDropMax, srcVars.gasDropMax, "srcMaxGasDrop is incorrect"); + assertEq(gasUnitsRcvMsg, srcVars.gasUnitsRcvMsg, "srcGasUnitsRcvMsg is incorrect"); } function _checkSrcInfo() internal { @@ -430,46 +431,46 @@ contract GasFeePricingUpgradeableTest is Test { \*╚══════════════════════════════════════════════════════════════════════╝*/ function _setDstConfig( - uint256[] memory _dstChainIds, - uint256[] memory _gasAmountsNeeded, - uint256[] memory _maxGasDrops + uint256[] memory _chainIds, + uint256[] memory _gasDropMax, + uint256[] memory _gasUnitsRcvMsg ) internal { - for (uint256 i = 0; i < _dstChainIds.length; ++i) { - dstVars[_dstChainIds[i]].gasAmountNeeded = _gasAmountsNeeded[i]; - dstVars[_dstChainIds[i]].maxGasDrop = _maxGasDrops[i]; + for (uint256 i = 0; i < _chainIds.length; ++i) { + dstVars[_chainIds[i]].gasDropMax = _gasDropMax[i]; + dstVars[_chainIds[i]].gasUnitsRcvMsg = _gasUnitsRcvMsg[i]; } - gasFeePricing.setDstConfig(_dstChainIds, _gasAmountsNeeded, _maxGasDrops); + gasFeePricing.setDstConfig(_chainIds, _gasDropMax, _gasUnitsRcvMsg); } function _setDstInfo( - uint256[] memory _dstChainIds, - uint256[] memory _gasTokenPrices, - uint256[] memory _gasUnitPrices + uint256[] memory _chainIds, + uint256[] memory _gasTokenPrice, + uint256[] memory _gasUnitPrice ) internal { - for (uint256 i = 0; i < _dstChainIds.length; ++i) { - dstVars[_dstChainIds[i]].gasTokenPrice = _gasTokenPrices[i]; - dstVars[_dstChainIds[i]].gasUnitPrice = _gasUnitPrices[i]; + for (uint256 i = 0; i < _chainIds.length; ++i) { + dstVars[_chainIds[i]].gasTokenPrice = _gasTokenPrice[i]; + dstVars[_chainIds[i]].gasUnitPrice = _gasUnitPrice[i]; } - gasFeePricing.setDstInfo(_dstChainIds, _gasTokenPrices, _gasUnitPrices); + gasFeePricing.setDstInfo(_chainIds, _gasTokenPrice, _gasUnitPrice); } function _setDstMarkups( - uint256[] memory _dstChainIds, - uint16[] memory _markupsGasDrop, - uint16[] memory _markupsGasUsage + uint256[] memory _chainIds, + uint16[] memory _markupGasDrop, + uint16[] memory _markupGasUsage ) internal { - for (uint256 i = 0; i < _dstChainIds.length; ++i) { - dstVars[_dstChainIds[i]].markupGasDrop = _markupsGasDrop[i]; - dstVars[_dstChainIds[i]].markupGasUsage = _markupsGasUsage[i]; + for (uint256 i = 0; i < _chainIds.length; ++i) { + dstVars[_chainIds[i]].markupGasDrop = _markupGasDrop[i]; + dstVars[_chainIds[i]].markupGasUsage = _markupGasUsage[i]; } - gasFeePricing.setDstMarkups(_dstChainIds, _markupsGasDrop, _markupsGasUsage); + gasFeePricing.setDstMarkups(_chainIds, _markupGasDrop, _markupGasUsage); } - function _updateSrcConfig(uint256 _gasAmountNeeded, uint256 _maxGasDrop) internal { - srcVars.gasAmountNeeded = _gasAmountNeeded; - srcVars.maxGasDrop = _maxGasDrop; + function _updateSrcConfig(uint256 _gasDropMax, uint256 _gasUnitsRcvMsg) internal { + srcVars.gasDropMax = _gasDropMax; + srcVars.gasUnitsRcvMsg = _gasUnitsRcvMsg; uint256 fee = gasFeePricing.estimateUpdateFees(); - gasFeePricing.updateSrcConfig{value: fee}(_gasAmountNeeded, _maxGasDrop); + gasFeePricing.updateSrcConfig{value: fee}(_gasDropMax, _gasUnitsRcvMsg); } function _updateSrcInfo(uint256 _gasTokenPrice, uint256 _gasUnitPrice) internal { From ec5a5ec9cf87e82d766b239194ebb034c6760202 Mon Sep 17 00:00:00 2001 From: ChiTimesChi <88190723+ChiTimesChi@users.noreply.github.com> Date: Mon, 23 May 2022 15:08:21 +0300 Subject: [PATCH 29/47] refactor: src/dst -> local/remote --- .../messaging/GasFeePricingUpgradeable.sol | 265 +++++++++--------- test/messaging/GasFeePricingUpgradeable.t.sol | 224 +++++++-------- 2 files changed, 249 insertions(+), 240 deletions(-) diff --git a/contracts/messaging/GasFeePricingUpgradeable.sol b/contracts/messaging/GasFeePricingUpgradeable.sol index 8fb67e33a..4b67ff26e 100644 --- a/contracts/messaging/GasFeePricingUpgradeable.sol +++ b/contracts/messaging/GasFeePricingUpgradeable.sol @@ -14,9 +14,9 @@ contract GasFeePricingUpgradeable is SynMessagingReceiverUpgradeable { /** * @notice Whenever the messaging fee is calculated, it takes into account things as: - * gas token prices on src and dst chain, gas limit for executing message on dst chain - * and gas unit price on dst chain. In other words, message sender is paying dst chain - * gas fees (to cover gas usage and gasdrop), but in src chain gas token. + * gas token prices on local and remote chain, gas limit for executing message on remote chain + * and gas unit price on remote chain. In other words, message sender is paying remote chain + * gas fees (to cover gas usage and gasdrop), but in local chain gas token. * The price values are static, though are supposed to be updated in the event of high * volatility. It is implied that gas token/unit prices reflect respective latest * average prices. @@ -28,7 +28,7 @@ contract GasFeePricingUpgradeable is SynMessagingReceiverUpgradeable { * will result in fee that is 50% higher than "projected", etc. * * There are separate markups for gasDrop and gasUsage. gasDropFee is calculated only using - * src and dst gas token prices, while gasUsageFee also takes into account dst chain gas + * local and remote gas token prices, while gasUsageFee also takes into account remote chain gas * unit price, which is an extra source of volatility. * * Generally, markupGasUsage >= markupGasDrop >= 0%. While markups can be set to 0%, @@ -38,7 +38,7 @@ contract GasFeePricingUpgradeable is SynMessagingReceiverUpgradeable { /** * @dev Chain's Config is supposed to be PARTLY synchronized cross-chain, i.e. * GasFeePricing contracts on different chain will have the SAME values for - * the same dst chain: + * the same remote chain: * - gasDropMax: maximum gas airdrop available on chain * uint112 => max value ~= 5 * 10**33 * - gasUnitsRcvMsg: Amount of gas units needed for GasFeePricing contract @@ -47,17 +47,17 @@ contract GasFeePricingUpgradeable is SynMessagingReceiverUpgradeable { * These are universal values, and they should be the same on all GasFeePricing * contracts. * - * Some of the values, however, are set unique for every "src-dst" chain combination: + * Some of the values, however, are set unique for every "local-remote" chain combination: * - markupGasDrop: Markup for gas airdrop * uint16 => max value = 65535 * - markupGasUsage: Markup for gas usage * uint16 => max value = 65535 - * These values depend on correlation between src and dst chains. For instance, + * These values depend on correlation between local and remote chains. For instance, * if both chains have the same gas token (like ETH), markup for the gas drop * can be set to 0, as gasDrop is limited, and the slight price difference between ETH - * on src and dst chain can not be taken advantage of. + * on local and remote chain can not be taken advantage of. * - * On the contrary, if src and dst gas tokens have proven to be not that correlated + * On the contrary, if local and remote gas tokens have proven to be not that correlated * in terms of their price, higher markup is needed to compensate potential price spikes. * * ChainConfig is optimized to fit into one word of storage. @@ -68,7 +68,7 @@ contract GasFeePricingUpgradeable is SynMessagingReceiverUpgradeable { /// @dev Values below are synchronized cross-chain uint112 gasDropMax; uint112 gasUnitsRcvMsg; - /// @dev Values below are src-chain specific + /// @dev Values below are local-chain specific uint16 markupGasDrop; uint16 markupGasUsage; } @@ -76,7 +76,7 @@ contract GasFeePricingUpgradeable is SynMessagingReceiverUpgradeable { /** * @dev Chain's Info is supposed to be FULLY synchronized cross-chain, i.e. * GasFeePricing contracts on different chain will have the SAME values for - * the same dst chain: + * the same remote chain: * - gasTokenPrice: Price of chain's gas token in USD, scaled to wei * uint128 => max value ~= 3 * 10**38 * - gasUnitPrice: Price of chain's 1 gas unit in wei @@ -98,28 +98,28 @@ contract GasFeePricingUpgradeable is SynMessagingReceiverUpgradeable { /** * @dev Chain's Ratios are supposed to be FULLY chain-specific, i.e. * GasFeePricing contracts on different chain will have different values for - * the same dst chain: - * - gasTokenPriceRatio: USD price ratio of dstGasToken / srcGasToken, scaled to wei + * the same remote chain: + * - gasTokenPriceRatio: USD price ratio of remoteGasToken / localGasToken, scaled to wei * uint96 => max value ~= 8 * 10**28 - * - gasUnitPriceRatio: How much 1 gas unit on dst chain is worth, expressed in src chain wei, + * - gasUnitPriceRatio: How much 1 gas unit on remote chain is worth, expressed in local chain wei, * multiplied by 10**18 (aka in attoWei = 10^-18 wei) * uint160 => max value ~= 10**48 - * These values are updated whenever "gas information" is updated for either source or destination chain. + * These values are updated whenever "gas information" is updated for either local or remote chain. * * ChainRatios is optimized to fit into one word of storage. */ /** - * @dev Chain's Ratios are used for calculating a fee for sending a msg from src to dst chain. + * @dev Chain's Ratios are used for calculating a fee for sending a msg from local to remote chain. * To calculate cost of tx gas airdrop (assuming gasDrop airdrop value): * (gasDrop * gasTokenPriceRatio) / 10**18 - * To calculate cost of tx gas usage on dst chain (assuming gasAmount gas units): + * To calculate cost of tx gas usage on remote chain (assuming gasAmount gas units): * (gasAmount * gasUnitPriceRatio) / 10**18 * - * Both numbers are expressed in src chain wei. + * Both numbers are expressed in local chain wei. */ struct ChainRatios { - /// @dev Values below are src-chain specific + /// @dev Values below are local-chain specific uint96 gasTokenPriceRatio; uint160 gasUnitPriceRatio; } @@ -134,27 +134,27 @@ contract GasFeePricingUpgradeable is SynMessagingReceiverUpgradeable { event MarkupsUpdated(uint256 indexed chainId, uint256 markupGasDrop, uint256 markupGasUsage); /*╔══════════════════════════════════════════════════════════════════════╗*\ - ▏*║ DESTINATION CHAINS STORAGE ║*▕ + ▏*║ REMOTE CHAINS STORAGE ║*▕ \*╚══════════════════════════════════════════════════════════════════════╝*/ - /// @dev dstChainId => Info - mapping(uint256 => ChainInfo) public dstInfo; - /// @dev dstChainId => Ratios - mapping(uint256 => ChainRatios) public dstRatios; - /// @dev dstChainId => Config - mapping(uint256 => ChainConfig) public dstConfig; - /// @dev list of all dst chain ids - uint256[] internal dstChainIds; + /// @dev remoteChainId => Info + mapping(uint256 => ChainInfo) public remoteInfo; + /// @dev remoteChainId => Ratios + mapping(uint256 => ChainRatios) public remoteRatios; + /// @dev remoteChainId => Config + mapping(uint256 => ChainConfig) public remoteConfig; + /// @dev list of all remote chain ids + uint256[] internal remoteChainIds; /*╔══════════════════════════════════════════════════════════════════════╗*\ ▏*║ SOURCE CHAIN STORAGE ║*▕ \*╚══════════════════════════════════════════════════════════════════════╝*/ /// @dev See "Structs" docs - /// srcConfig.markupGasDrop and srcConfig.markupGasUsage values are not used - ChainConfig public srcConfig; - ChainInfo public srcInfo; - /// @dev Minimum fee related to gas usage on dst chain + /// localConfig.markupGasDrop and localConfig.markupGasUsage values are not used + ChainConfig public localConfig; + ChainInfo public localInfo; + /// @dev Minimum fee related to gas usage on remote chain uint256 public minGasUsageFee; /*╔══════════════════════════════════════════════════════════════════════╗*\ @@ -170,37 +170,37 @@ contract GasFeePricingUpgradeable is SynMessagingReceiverUpgradeable { ▏*║ INITIALIZER ║*▕ \*╚══════════════════════════════════════════════════════════════════════╝*/ - function initialize(address _messageBus, uint256 _srcGasTokenPrice) external initializer { + function initialize(address _messageBus, uint256 _localGasTokenPrice) external initializer { __Ownable_init_unchained(); messageBus = _messageBus; - srcInfo.gasTokenPrice = uint96(_srcGasTokenPrice); - minGasUsageFee = _calculateMinGasUsageFee(DEFAULT_MIN_FEE_USD, _srcGasTokenPrice); + localInfo.gasTokenPrice = uint96(_localGasTokenPrice); + minGasUsageFee = _calculateMinGasUsageFee(DEFAULT_MIN_FEE_USD, _localGasTokenPrice); } /*╔══════════════════════════════════════════════════════════════════════╗*\ ▏*║ VIEWS ║*▕ \*╚══════════════════════════════════════════════════════════════════════╝*/ - /// @notice Get the fee for sending a message to dst chain with given options - function estimateGasFee(uint256 _dstChainId, bytes calldata _options) external view returns (uint256 fee) { - fee = _estimateGasFee(_dstChainId, _options); + /// @notice Get the fee for sending a message to remote chain with given options + function estimateGasFee(uint256 _remoteChainId, bytes calldata _options) external view returns (uint256 fee) { + fee = _estimateGasFee(_remoteChainId, _options); } /// @notice Get the fee for sending a message to a bunch of chains with given options - function estimateGasFees(uint256[] calldata _dstChainIds, bytes[] calldata _options) + function estimateGasFees(uint256[] calldata _remoteChainIds, bytes[] calldata _options) external view returns (uint256 fee) { - require(_dstChainIds.length == _options.length, "!arrays"); - for (uint256 i = 0; i < _dstChainIds.length; ++i) { - fee = fee + _estimateGasFee(_dstChainIds[i], _options[i]); + require(_remoteChainIds.length == _options.length, "!arrays"); + for (uint256 i = 0; i < _remoteChainIds.length; ++i) { + fee = fee + _estimateGasFee(_remoteChainIds[i], _options[i]); } } /// @dev Extracts the gas information from options and calculates the messaging fee - function _estimateGasFee(uint256 _dstChainId, bytes calldata _options) internal view returns (uint256 fee) { - ChainConfig memory config = dstConfig[_dstChainId]; + function _estimateGasFee(uint256 _remoteChainId, bytes calldata _options) internal view returns (uint256 fee) { + ChainConfig memory config = remoteConfig[_remoteChainId]; uint256 gasAirdrop; uint256 gasLimit; if (_options.length != 0) { @@ -212,24 +212,24 @@ contract GasFeePricingUpgradeable is SynMessagingReceiverUpgradeable { gasLimit = DEFAULT_GAS_LIMIT; } - fee = _estimateGasFee(_dstChainId, gasAirdrop, gasLimit, config.markupGasDrop, config.markupGasUsage); + fee = _estimateGasFee(_remoteChainId, gasAirdrop, gasLimit, config.markupGasDrop, config.markupGasUsage); } - /// @dev Returns a gas fee for sending a message to dst chain, given the amount of gas to airdrop, - /// and amount of gas units for message execution on dst chain. + /// @dev Returns a gas fee for sending a message to remote chain, given the amount of gas to airdrop, + /// and amount of gas units for message execution on remote chain. function _estimateGasFee( - uint256 _dstChainId, + uint256 _remoteChainId, uint256 _gasAirdrop, uint256 _gasLimit, uint256 _markupGasDrop, uint256 _markupGasUsage ) internal view returns (uint256 fee) { - ChainRatios memory dstRatio = dstRatios[_dstChainId]; + ChainRatios memory remoteRatio = remoteRatios[_remoteChainId]; - // Calculate how much gas airdrop is worth in src chain wei - uint256 feeGasDrop = (_gasAirdrop * dstRatio.gasTokenPriceRatio) / 10**18; - // Calculate how much gas usage is worth in src chain wei - uint256 feeGasUsage = (_gasLimit * dstRatio.gasUnitPriceRatio) / 10**18; + // Calculate how much gas airdrop is worth in local chain wei + uint256 feeGasDrop = (_gasAirdrop * remoteRatio.gasTokenPriceRatio) / 10**18; + // Calculate how much gas usage is worth in local chain wei + uint256 feeGasUsage = (_gasLimit * remoteRatio.gasUnitPriceRatio) / 10**18; // Sum up the fees multiplied by their respective markups feeGasDrop = (feeGasDrop * (_markupGasDrop + MARKUP_DENOMINATOR)) / MARKUP_DENOMINATOR; @@ -246,13 +246,13 @@ contract GasFeePricingUpgradeable is SynMessagingReceiverUpgradeable { } /// @dev Returns total gas fee for calling updateChainInfo(), as well as - /// fee for each dst chain. + /// fee for each remote chain. function _estimateUpdateFees() internal view returns (uint256 totalFee, uint256[] memory fees) { - uint256[] memory _chainIds = dstChainIds; + uint256[] memory _chainIds = remoteChainIds; fees = new uint256[](_chainIds.length); for (uint256 i = 0; i < _chainIds.length; ++i) { uint256 chainId = _chainIds[i]; - ChainConfig memory config = dstConfig[chainId]; + ChainConfig memory config = remoteConfig[chainId]; uint256 gasLimit = config.gasUnitsRcvMsg; if (gasLimit == 0) gasLimit = DEFAULT_GAS_LIMIT; @@ -275,68 +275,77 @@ contract GasFeePricingUpgradeable is SynMessagingReceiverUpgradeable { \*╚══════════════════════════════════════════════════════════════════════╝*/ /// @dev Update config (gasLimit for sending messages to chain, max gas airdrop) for a bunch of chains. - function setDstConfig( - uint256[] memory _dstChainId, + function setRemoteConfig( + uint256[] memory _remoteChainId, uint256[] memory _gasDropMax, uint256[] memory _gasUnitsRcvMsg ) external onlyOwner { - require(_dstChainId.length == _gasDropMax.length && _dstChainId.length == _gasUnitsRcvMsg.length, "!arrays"); - for (uint256 i = 0; i < _dstChainId.length; ++i) { - _updateDstChainConfig(_dstChainId[i], _gasDropMax[i], _gasUnitsRcvMsg[i]); + require( + _remoteChainId.length == _gasDropMax.length && _remoteChainId.length == _gasUnitsRcvMsg.length, + "!arrays" + ); + for (uint256 i = 0; i < _remoteChainId.length; ++i) { + _updateRemoteChainConfig(_remoteChainId[i], _gasDropMax[i], _gasUnitsRcvMsg[i]); } } /// @notice Update information about gas unit/token price for a bunch of chains. /// Handy for initial setup. - function setDstInfo( - uint256[] memory _dstChainId, + function setRemoteInfo( + uint256[] memory _remoteChainId, uint256[] memory _gasTokenPrice, uint256[] memory _gasUnitPrice ) external onlyOwner { - require(_dstChainId.length == _gasTokenPrice.length && _dstChainId.length == _gasUnitPrice.length, "!arrays"); - for (uint256 i = 0; i < _dstChainId.length; ++i) { - _updateDstChainInfo(_dstChainId[i], _gasTokenPrice[i], _gasUnitPrice[i]); + require( + _remoteChainId.length == _gasTokenPrice.length && _remoteChainId.length == _gasUnitPrice.length, + "!arrays" + ); + for (uint256 i = 0; i < _remoteChainId.length; ++i) { + _updateRemoteChainInfo(_remoteChainId[i], _gasTokenPrice[i], _gasUnitPrice[i]); } } /// @notice Sets markups (see "Structs" docs) for a bunch of chains. Markups are used for determining /// how much fee to charge on top of "projected gas cost" of delivering the message. - function setDstMarkups( - uint256[] memory _dstChainId, + function setRemoteMarkups( + uint256[] memory _remoteChainId, uint16[] memory _markupGasDrop, uint16[] memory _markupGasUsage ) external onlyOwner { - require(_dstChainId.length == _markupGasDrop.length && _dstChainId.length == _markupGasUsage.length, "!arrays"); - for (uint256 i = 0; i < _dstChainId.length; ++i) { - _updateMarkups(_dstChainId[i], _markupGasDrop[i], _markupGasUsage[i]); + require( + _remoteChainId.length == _markupGasDrop.length && _remoteChainId.length == _markupGasUsage.length, + "!arrays" + ); + for (uint256 i = 0; i < _remoteChainId.length; ++i) { + _updateMarkups(_remoteChainId[i], _markupGasDrop[i], _markupGasUsage[i]); } } - /// @notice Update the minimum fee for gas usage on message delivery. Quoted in src chain wei. + /// @notice Update the minimum fee for gas usage on message delivery. Quoted in local chain wei. function setMinFee(uint256 _minGasUsageFee) external onlyOwner { minGasUsageFee = _minGasUsageFee; } /// @notice Update the minimum fee for gas usage on message delivery. Quoted in USD, scaled to wei. function setMinFeeUsd(uint256 _minGasUsageFeeUsd) external onlyOwner { - minGasUsageFee = _calculateMinGasUsageFee(_minGasUsageFeeUsd, srcInfo.gasTokenPrice); + minGasUsageFee = _calculateMinGasUsageFee(_minGasUsageFeeUsd, localInfo.gasTokenPrice); } - /// @notice Update information about source chain config: - /// amount of gas needed to do _updateDstChainInfo() + /// @notice Update information about local chain config: + /// amount of gas needed to do _updateRemoteChainInfo() /// and maximum airdrop available on this chain - function updateSrcConfig(uint256 _gasDropMax, uint256 _gasUnitsRcvMsg) external payable onlyOwner { + function updateLocalConfig(uint256 _gasDropMax, uint256 _gasUnitsRcvMsg) external payable onlyOwner { require(_gasUnitsRcvMsg != 0, "Gas amount is not set"); _sendUpdateMessages(uint8(GasFeePricingUpdates.MsgType.UPDATE_CONFIG), _gasDropMax, _gasUnitsRcvMsg); - ChainConfig memory config = srcConfig; + ChainConfig memory config = localConfig; config.gasDropMax = uint112(_gasDropMax); config.gasUnitsRcvMsg = uint112(_gasUnitsRcvMsg); - srcConfig = config; + localConfig = config; } - /// @notice Update information about source chain gas token/unit price on all configured dst chains, - /// as well as on the source chain itself. - function updateSrcInfo(uint256 _gasTokenPrice, uint256 _gasUnitPrice) external payable onlyOwner { + /// @notice Update information about local chain gas token/unit price on all configured remote chains, + /// as well as on the local chain itself. + function updateLocalInfo(uint256 _gasTokenPrice, uint256 _gasUnitPrice) external payable onlyOwner { /** * @dev Some chains (i.e. Aurora) allow free transactions, * so we're not checking gasUnitPrice for being zero. @@ -347,51 +356,51 @@ contract GasFeePricingUpgradeable is SynMessagingReceiverUpgradeable { // send messages before updating the values, so that it's possible to use // estimateUpdateFees() to calculate the needed fee for the update _sendUpdateMessages(uint8(GasFeePricingUpdates.MsgType.UPDATE_INFO), _gasTokenPrice, _gasUnitPrice); - _updateSrcChainInfo(_gasTokenPrice, _gasUnitPrice); + _updateLocalChainInfo(_gasTokenPrice, _gasUnitPrice); } /*╔══════════════════════════════════════════════════════════════════════╗*\ ▏*║ UPDATE STATE LOGIC ║*▕ \*╚══════════════════════════════════════════════════════════════════════╝*/ - /// @dev Updates information about src chain gas token/unit price. - /// All the dst chain ratios are updated as well, if gas token price changed - function _updateSrcChainInfo(uint256 _gasTokenPrice, uint256 _gasUnitPrice) internal { - if (srcInfo.gasTokenPrice != _gasTokenPrice) { + /// @dev Updates information about local chain gas token/unit price. + /// All the remote chain ratios are updated as well, if gas token price changed + function _updateLocalChainInfo(uint256 _gasTokenPrice, uint256 _gasUnitPrice) internal { + if (localInfo.gasTokenPrice != _gasTokenPrice) { // update ratios only if gas token price has changed - uint256[] memory chainIds = dstChainIds; + uint256[] memory chainIds = remoteChainIds; for (uint256 i = 0; i < chainIds.length; ++i) { uint256 chainId = chainIds[i]; - ChainInfo memory info = dstInfo[chainId]; - _updateDstChainRatios(_gasTokenPrice, chainId, info.gasTokenPrice, info.gasUnitPrice); + ChainInfo memory info = remoteInfo[chainId]; + _updateRemoteChainRatios(_gasTokenPrice, chainId, info.gasTokenPrice, info.gasUnitPrice); } } - srcInfo = ChainInfo({gasTokenPrice: uint128(_gasTokenPrice), gasUnitPrice: uint128(_gasUnitPrice)}); + localInfo = ChainInfo({gasTokenPrice: uint128(_gasTokenPrice), gasUnitPrice: uint128(_gasUnitPrice)}); // TODO: use context chainid here emit ChainInfoUpdated(block.chainid, _gasTokenPrice, _gasUnitPrice); } - /// @dev Updates dst chain config: - /// Amount of gas needed to do _updateDstChainInfo() + /// @dev Updates remote chain config: + /// Amount of gas needed to do _updateRemoteChainInfo() /// Maximum airdrop available on this chain - function _updateDstChainConfig( - uint256 _dstChainId, + function _updateRemoteChainConfig( + uint256 _remoteChainId, uint256 _gasDropMax, uint256 _gasUnitsRcvMsg ) internal { require(_gasUnitsRcvMsg != 0, "Gas amount is not set"); - ChainConfig memory config = dstConfig[_dstChainId]; + ChainConfig memory config = remoteConfig[_remoteChainId]; config.gasDropMax = uint112(_gasDropMax); config.gasUnitsRcvMsg = uint112(_gasUnitsRcvMsg); - dstConfig[_dstChainId] = config; + remoteConfig[_remoteChainId] = config; } - /// @dev Updates information about dst chain gas token/unit price. - /// Dst chain ratios are updated as well. - function _updateDstChainInfo( - uint256 _dstChainId, + /// @dev Updates information about remote chain gas token/unit price. + /// Remote chain ratios are updated as well. + function _updateRemoteChainInfo( + uint256 _remoteChainId, uint256 _gasTokenPrice, uint256 _gasUnitPrice ) internal { @@ -401,56 +410,56 @@ contract GasFeePricingUpgradeable is SynMessagingReceiverUpgradeable { * gasUnitPrice is never used as denominator, and there's * a minimum fee for gas usage, so this can't be taken advantage of. */ - require(_gasTokenPrice != 0, "Dst gas token price is not set"); - uint256 _srcGasTokenPrice = srcInfo.gasTokenPrice; - require(_srcGasTokenPrice != 0, "Src gas token price is not set"); + require(_gasTokenPrice != 0, "Remote gas token price is not set"); + uint256 _localGasTokenPrice = localInfo.gasTokenPrice; + require(_localGasTokenPrice != 0, "Local gas token price is not set"); - if (dstInfo[_dstChainId].gasTokenPrice == 0) { - // store dst chainId only if it wasn't added already - dstChainIds.push(_dstChainId); + if (remoteInfo[_remoteChainId].gasTokenPrice == 0) { + // store remote chainId only if it wasn't added already + remoteChainIds.push(_remoteChainId); } - dstInfo[_dstChainId] = ChainInfo({ + remoteInfo[_remoteChainId] = ChainInfo({ gasTokenPrice: uint128(_gasTokenPrice), gasUnitPrice: uint128(_gasUnitPrice) }); - _updateDstChainRatios(_srcGasTokenPrice, _dstChainId, _gasTokenPrice, _gasUnitPrice); + _updateRemoteChainRatios(_localGasTokenPrice, _remoteChainId, _gasTokenPrice, _gasUnitPrice); - emit ChainInfoUpdated(_dstChainId, _gasTokenPrice, _gasUnitPrice); + emit ChainInfoUpdated(_remoteChainId, _gasTokenPrice, _gasUnitPrice); } - /// @dev Updates gas token/unit ratios for a given dst chain - function _updateDstChainRatios( - uint256 _srcGasTokenPrice, - uint256 _dstChainId, - uint256 _dstGasTokenPrice, - uint256 _dstGasUnitPrice + /// @dev Updates gas token/unit ratios for a given remote chain + function _updateRemoteChainRatios( + uint256 _localGasTokenPrice, + uint256 _remoteChainId, + uint256 _remoteGasTokenPrice, + uint256 _remoteGasUnitPrice ) internal { - dstRatios[_dstChainId] = ChainRatios({ - gasTokenPriceRatio: uint96((_dstGasTokenPrice * 10**18) / _srcGasTokenPrice), - gasUnitPriceRatio: uint160((_dstGasUnitPrice * _dstGasTokenPrice * 10**18) / _srcGasTokenPrice) + remoteRatios[_remoteChainId] = ChainRatios({ + gasTokenPriceRatio: uint96((_remoteGasTokenPrice * 10**18) / _localGasTokenPrice), + gasUnitPriceRatio: uint160((_remoteGasUnitPrice * _remoteGasTokenPrice * 10**18) / _localGasTokenPrice) }); } /// @dev Updates the markups (see "Structs" docs). /// Markup = 0% means exactly the "projected gas cost" will be charged. function _updateMarkups( - uint256 _dstChainId, + uint256 _remoteChainId, uint16 _markupGasDrop, uint16 _markupGasUsage ) internal { - ChainConfig memory config = dstConfig[_dstChainId]; + ChainConfig memory config = remoteConfig[_remoteChainId]; config.markupGasDrop = _markupGasDrop; config.markupGasUsage = _markupGasUsage; - dstConfig[_dstChainId] = config; - emit MarkupsUpdated(_dstChainId, _markupGasDrop, _markupGasUsage); + remoteConfig[_remoteChainId] = config; + emit MarkupsUpdated(_remoteChainId, _markupGasDrop, _markupGasUsage); } /*╔══════════════════════════════════════════════════════════════════════╗*\ ▏*║ MESSAGING LOGIC ║*▕ \*╚══════════════════════════════════════════════════════════════════════╝*/ - /// @dev Sends "something updated" messages to all registered dst chains + /// @dev Sends "something updated" messages to all registered remote chains function _sendUpdateMessages( uint8 _msgType, uint256 _newValueA, @@ -460,13 +469,13 @@ contract GasFeePricingUpgradeable is SynMessagingReceiverUpgradeable { require(msg.value >= totalFee, "msg.value doesn't cover all the fees"); bytes memory message = GasFeePricingUpdates.encode(_msgType, uint128(_newValueA), uint128(_newValueB)); - uint256[] memory chainIds = dstChainIds; + uint256[] memory chainIds = remoteChainIds; bytes32[] memory receivers = new bytes32[](chainIds.length); bytes[] memory options = new bytes[](chainIds.length); for (uint256 i = 0; i < chainIds.length; ++i) { uint256 chainId = chainIds[i]; - uint256 gasLimit = dstConfig[chainId].gasUnitsRcvMsg; + uint256 gasLimit = remoteConfig[chainId].gasUnitsRcvMsg; if (gasLimit == 0) gasLimit = DEFAULT_GAS_LIMIT; receivers[i] = trustedRemoteLookup[chainId]; @@ -480,15 +489,15 @@ contract GasFeePricingUpgradeable is SynMessagingReceiverUpgradeable { /// @dev Handles the received message. function _handleMessage( bytes32, - uint256 _srcChainId, + uint256 _localChainId, bytes memory _message, address ) internal override { (uint8 msgType, uint128 newValueA, uint128 newValueB) = GasFeePricingUpdates.decode(_message); if (msgType == uint8(GasFeePricingUpdates.MsgType.UPDATE_CONFIG)) { - _updateDstChainConfig(_srcChainId, newValueA, newValueB); + _updateRemoteChainConfig(_localChainId, newValueA, newValueB); } else if (msgType == uint8(GasFeePricingUpdates.MsgType.UPDATE_INFO)) { - _updateDstChainInfo(_srcChainId, newValueA, newValueB); + _updateRemoteChainInfo(_localChainId, newValueA, newValueB); } else { revert("Unknown message type"); } diff --git a/test/messaging/GasFeePricingUpgradeable.t.sol b/test/messaging/GasFeePricingUpgradeable.t.sol index efc5377f7..8a995ff23 100644 --- a/test/messaging/GasFeePricingUpgradeable.t.sol +++ b/test/messaging/GasFeePricingUpgradeable.t.sol @@ -27,11 +27,11 @@ contract GasFeePricingUpgradeableTest is Test { GasFeePricingUpgradeable internal gasFeePricing; MessageBusUpgradeable internal messageBus; - ChainVars internal srcVars; + ChainVars internal localVars; - mapping(uint256 => ChainVars) internal dstVars; + mapping(uint256 => ChainVars) internal remoteVars; - uint256[] internal dstChainIds; + uint256[] internal remoteChainIds; uint256 internal constant TEST_CHAINS = 5; address public constant NODE = address(1337); @@ -49,8 +49,8 @@ contract GasFeePricingUpgradeableTest is Test { utils = new Utilities(); authVerifier = new AuthVerifier(NODE); - // src gas token is worth exactly 1 USD - srcVars.gasTokenPrice = 10**18; + // local gas token is worth exactly 1 USD + localVars.gasTokenPrice = 10**18; MessageBusUpgradeable busImpl = new MessageBusUpgradeable(); GasFeePricingUpgradeable pricingImpl = new GasFeePricingUpgradeable(); @@ -60,15 +60,15 @@ contract GasFeePricingUpgradeableTest is Test { // I don't have extra 10M laying around, so let's initialize those proxies messageBus.initialize(address(gasFeePricing), address(authVerifier)); - gasFeePricing.initialize(address(messageBus), srcVars.gasTokenPrice); + gasFeePricing.initialize(address(messageBus), localVars.gasTokenPrice); - dstChainIds = new uint256[](TEST_CHAINS); + remoteChainIds = new uint256[](TEST_CHAINS); for (uint256 i = 0; i < TEST_CHAINS; ++i) { - uint256 dstChainId = i + 1; - dstChainIds[i] = dstChainId; - address dstGasFeePricing = utils.getNextUserAddress(); - dstVars[dstChainId].gasFeePricing = dstGasFeePricing; - gasFeePricing.setTrustedRemote(dstChainId, utils.addressToBytes32(dstGasFeePricing)); + uint256 remoteChainId = i + 1; + remoteChainIds[i] = remoteChainId; + address remoteGasFeePricing = utils.getNextUserAddress(); + remoteVars[remoteChainId].gasFeePricing = remoteGasFeePricing; + gasFeePricing.setTrustedRemote(remoteChainId, utils.addressToBytes32(remoteGasFeePricing)); } } @@ -95,7 +95,7 @@ contract GasFeePricingUpgradeableTest is Test { utils.checkAccess( _gfp, abi.encodeWithSelector( - GasFeePricingUpgradeable.setDstConfig.selector, + GasFeePricingUpgradeable.setRemoteConfig.selector, new uint256[](1), new uint256[](1), new uint256[](1) @@ -105,7 +105,7 @@ contract GasFeePricingUpgradeableTest is Test { utils.checkAccess( _gfp, abi.encodeWithSelector( - GasFeePricingUpgradeable.setDstInfo.selector, + GasFeePricingUpgradeable.setRemoteInfo.selector, new uint256[](1), new uint256[](1), new uint256[](1) @@ -115,7 +115,7 @@ contract GasFeePricingUpgradeableTest is Test { utils.checkAccess( _gfp, abi.encodeWithSelector( - GasFeePricingUpgradeable.setDstMarkups.selector, + GasFeePricingUpgradeable.setRemoteMarkups.selector, new uint256[](1), new uint16[](1), new uint16[](1) @@ -136,13 +136,13 @@ contract GasFeePricingUpgradeableTest is Test { utils.checkAccess( _gfp, - abi.encodeWithSelector(GasFeePricingUpgradeable.updateSrcConfig.selector, 0, 0), + abi.encodeWithSelector(GasFeePricingUpgradeable.updateLocalConfig.selector, 0, 0), "Ownable: caller is not the owner" ); utils.checkAccess( _gfp, - abi.encodeWithSelector(GasFeePricingUpgradeable.updateSrcInfo.selector, 0, 0), + abi.encodeWithSelector(GasFeePricingUpgradeable.updateLocalInfo.selector, 0, 0), "Ownable: caller is not the owner" ); } @@ -168,39 +168,39 @@ contract GasFeePricingUpgradeableTest is Test { \*╚══════════════════════════════════════════════════════════════════════╝*/ function testInitializedCorrectly() public { - (uint128 _gasTokenPrice, ) = gasFeePricing.srcInfo(); - assertEq(_gasTokenPrice, srcVars.gasTokenPrice, "Failed to init: gasTokenPrice"); + (uint128 _gasTokenPrice, ) = gasFeePricing.localInfo(); + assertEq(_gasTokenPrice, localVars.gasTokenPrice, "Failed to init: gasTokenPrice"); assertEq(gasFeePricing.messageBus(), address(messageBus), "Failed to init: messageBus"); _checkMinFeeUsd(10**18); } - function testSetDstConfig() public { + function testSetRemoteConfig() public { uint256[] memory gasUnitsRcvMsg = new uint256[](TEST_CHAINS); uint256[] memory gasDropMax = new uint256[](TEST_CHAINS); for (uint256 i = 0; i < TEST_CHAINS; ++i) { gasUnitsRcvMsg[i] = (i + 1) * 420420; gasDropMax[i] = (i + 1) * 10**18; } - _setDstConfig(dstChainIds, gasDropMax, gasUnitsRcvMsg); + _setRemoteConfig(remoteChainIds, gasDropMax, gasUnitsRcvMsg); for (uint256 i = 0; i < TEST_CHAINS; ++i) { - _checkDstConfig(dstChainIds[i]); + _checkRemoteConfig(remoteChainIds[i]); } } - function testSetDstConfigZeroDropSucceeds() public { + function testSetRemoteConfigZeroDropSucceeds() public { uint256[] memory gasDropMax = new uint256[](TEST_CHAINS); uint256[] memory gasUnitsRcvMsg = new uint256[](TEST_CHAINS); for (uint256 i = 0; i < TEST_CHAINS; ++i) { gasDropMax[i] = i * 10**18; gasUnitsRcvMsg[i] = (i + 1) * 133769; } - _setDstConfig(dstChainIds, gasDropMax, gasUnitsRcvMsg); + _setRemoteConfig(remoteChainIds, gasDropMax, gasUnitsRcvMsg); for (uint256 i = 0; i < TEST_CHAINS; ++i) { - _checkDstConfig(dstChainIds[i]); + _checkRemoteConfig(remoteChainIds[i]); } } - function testSetDstConfigZeroGasReverts() public { + function testSetRemoteConfigZeroGasReverts() public { uint256[] memory gasDropMax = new uint256[](TEST_CHAINS); uint256[] memory gasUnitsRcvMsg = new uint256[](TEST_CHAINS); for (uint256 i = 0; i < TEST_CHAINS; ++i) { @@ -211,8 +211,8 @@ contract GasFeePricingUpgradeableTest is Test { address(this), address(gasFeePricing), abi.encodeWithSelector( - GasFeePricingUpgradeable.setDstConfig.selector, - dstChainIds, + GasFeePricingUpgradeable.setRemoteConfig.selector, + remoteChainIds, gasDropMax, gasUnitsRcvMsg ), @@ -220,41 +220,41 @@ contract GasFeePricingUpgradeableTest is Test { ); } - function testSetDstInfo() public { + function testSetRemoteInfo() public { (uint256[] memory gasTokenPrices, uint256[] memory gasUnitPrices) = _generateTestInfoValues(); - _setDstInfo(dstChainIds, gasTokenPrices, gasUnitPrices); + _setRemoteInfo(remoteChainIds, gasTokenPrices, gasUnitPrices); for (uint256 i = 0; i < TEST_CHAINS; ++i) { - uint256 chainId = dstChainIds[i]; - _checkDstInfo(chainId); - _checkDstRatios(chainId); + uint256 chainId = remoteChainIds[i]; + _checkRemoteInfo(chainId); + _checkRemoteRatios(chainId); } } - function testSetDstInfoZeroTokenPriceReverts() public { + function testSetRemoteInfoZeroTokenPriceReverts() public { (uint256[] memory gasTokenPrices, uint256[] memory gasUnitPrices) = _generateTestInfoValues(); gasTokenPrices[2] = 0; utils.checkRevert( address(this), address(gasFeePricing), abi.encodeWithSelector( - GasFeePricingUpgradeable.setDstInfo.selector, - dstChainIds, + GasFeePricingUpgradeable.setRemoteInfo.selector, + remoteChainIds, gasTokenPrices, gasUnitPrices ), - "Dst gas token price is not set" + "Remote gas token price is not set" ); } - function testSetDstInfoZeroUnitPriceSucceeds() public { + function testSetRemoteInfoZeroUnitPriceSucceeds() public { (uint256[] memory gasTokenPrices, uint256[] memory gasUnitPrices) = _generateTestInfoValues(); gasTokenPrices[2] = 100 * 10**18; gasUnitPrices[3] = 0; - _setDstInfo(dstChainIds, gasTokenPrices, gasUnitPrices); + _setRemoteInfo(remoteChainIds, gasTokenPrices, gasUnitPrices); for (uint256 i = 0; i < TEST_CHAINS; ++i) { - uint256 chainId = dstChainIds[i]; - _checkDstInfo(chainId); - _checkDstRatios(chainId); + uint256 chainId = remoteChainIds[i]; + _checkRemoteInfo(chainId); + _checkRemoteRatios(chainId); } } @@ -282,7 +282,7 @@ contract GasFeePricingUpgradeableTest is Test { gasUnitPrices[4] = (4 * 10**9) / 100; } - function testSetDstMarkups() public { + function testSetRemoteMarkups() public { uint16[] memory markupGasDrop = new uint16[](TEST_CHAINS); uint16[] memory markupGasUsage = new uint16[](TEST_CHAINS); for (uint16 i = 0; i < TEST_CHAINS; ++i) { @@ -290,9 +290,9 @@ contract GasFeePricingUpgradeableTest is Test { markupGasDrop[i] = i * 13; markupGasUsage[i] = i * 42; } - _setDstMarkups(dstChainIds, markupGasDrop, markupGasUsage); + _setRemoteMarkups(remoteChainIds, markupGasDrop, markupGasUsage); for (uint256 i = 0; i < TEST_CHAINS; ++i) { - _checkDstMarkups(dstChainIds[i]); + _checkRemoteMarkups(remoteChainIds[i]); } } @@ -308,22 +308,22 @@ contract GasFeePricingUpgradeableTest is Test { _checkMinFeeUsd(minGasFeeUsageUsd); } - function testUpdateSrcConfig() public { + function testUpdateLocalConfig() public { uint256 gasDropMax = 10 * 10**18; uint256 gasUnitsRcvMsg = 10**6; - _updateSrcConfig(gasDropMax, gasUnitsRcvMsg); - _checkSrcConfig(); + _updateLocalConfig(gasDropMax, gasUnitsRcvMsg); + _checkLocalConfig(); } - function testUpdateSrcConfigZeroDropSucceeds() public { + function testUpdateLocalConfigZeroDropSucceeds() public { // should be able to set to zero uint256 gasDropMax = 0; uint256 gasUnitsRcvMsg = 2 * 10**6; - _updateSrcConfig(gasDropMax, gasUnitsRcvMsg); - _checkSrcConfig(); + _updateLocalConfig(gasDropMax, gasUnitsRcvMsg); + _checkLocalConfig(); } - function testUpdateSrcConfigZeroGasReverts() public { + function testUpdateLocalConfigZeroGasReverts() public { uint256 gasDropMax = 10**18; // should NOT be able to set to zero uint256 gasUnitsRcvMsg = 0; @@ -331,25 +331,25 @@ contract GasFeePricingUpgradeableTest is Test { utils.checkRevert( address(this), address(gasFeePricing), - abi.encodeWithSelector(GasFeePricingUpgradeable.updateSrcConfig.selector, gasDropMax, gasUnitsRcvMsg), + abi.encodeWithSelector(GasFeePricingUpgradeable.updateLocalConfig.selector, gasDropMax, gasUnitsRcvMsg), "Gas amount is not set" ); } - function testUpdateSrcInfo() public { - testSetDstInfo(); + function testUpdateLocalInfo() public { + testSetRemoteInfo(); uint256 gasTokenPrice = 2 * 10**18; uint256 gasUnitPrice = 10 * 10**9; - _updateSrcInfo(gasTokenPrice, gasUnitPrice); - _checkSrcInfo(); + _updateLocalInfo(gasTokenPrice, gasUnitPrice); + _checkLocalInfo(); for (uint256 i = 0; i < TEST_CHAINS; ++i) { - _checkDstRatios(i + 1); + _checkRemoteRatios(i + 1); } } - function testUpdateSrcInfoZeroTokenPriceReverts() public { - testSetDstInfo(); + function testUpdateLocalInfoZeroTokenPriceReverts() public { + testSetRemoteInfo(); uint256 gasTokenPrice = 0; uint256 gasUnitPrice = 10 * 10**9; @@ -357,20 +357,20 @@ contract GasFeePricingUpgradeableTest is Test { utils.checkRevert( address(this), address(gasFeePricing), - abi.encodeWithSelector(GasFeePricingUpgradeable.updateSrcInfo.selector, gasTokenPrice, gasUnitPrice), + abi.encodeWithSelector(GasFeePricingUpgradeable.updateLocalInfo.selector, gasTokenPrice, gasUnitPrice), "Gas token price is not set" ); } - function testUpdateSrcInfoZeroUnitPriceSucceeds() public { - testSetDstInfo(); + function testUpdateLocalInfoZeroUnitPriceSucceeds() public { + testSetRemoteInfo(); uint256 gasTokenPrice = 4 * 10**17; uint256 gasUnitPrice = 0; - _updateSrcInfo(gasTokenPrice, gasUnitPrice); - _checkSrcInfo(); + _updateLocalInfo(gasTokenPrice, gasUnitPrice); + _checkLocalInfo(); for (uint256 i = 0; i < TEST_CHAINS; ++i) { - _checkDstRatios(i + 1); + _checkRemoteRatios(i + 1); } } @@ -378,29 +378,29 @@ contract GasFeePricingUpgradeableTest is Test { ▏*║ INTERNAL CHECKERS ║*▕ \*╚══════════════════════════════════════════════════════════════════════╝*/ - function _checkDstConfig(uint256 _chainId) internal { - (uint112 gasDropMax, uint112 gasUnitsRcvMsg, , ) = gasFeePricing.dstConfig(_chainId); - assertEq(gasDropMax, dstVars[_chainId].gasDropMax, "dstMaxGasDrop is incorrect"); - assertEq(gasUnitsRcvMsg, dstVars[_chainId].gasUnitsRcvMsg, "dstGasUnitsRcvMsg is incorrect"); + function _checkRemoteConfig(uint256 _chainId) internal { + (uint112 gasDropMax, uint112 gasUnitsRcvMsg, , ) = gasFeePricing.remoteConfig(_chainId); + assertEq(gasDropMax, remoteVars[_chainId].gasDropMax, "remoteMaxGasDrop is incorrect"); + assertEq(gasUnitsRcvMsg, remoteVars[_chainId].gasUnitsRcvMsg, "remoteGasUnitsRcvMsg is incorrect"); } - function _checkDstInfo(uint256 _chainId) internal { - (uint128 gasTokenPrice, uint128 gasUnitPrice) = gasFeePricing.dstInfo(_chainId); - assertEq(gasTokenPrice, dstVars[_chainId].gasTokenPrice, "dstGasTokenPrice is incorrect"); - assertEq(gasUnitPrice, dstVars[_chainId].gasUnitPrice, "dstGasUnitPrice is incorrect"); + function _checkRemoteInfo(uint256 _chainId) internal { + (uint128 gasTokenPrice, uint128 gasUnitPrice) = gasFeePricing.remoteInfo(_chainId); + assertEq(gasTokenPrice, remoteVars[_chainId].gasTokenPrice, "remoteGasTokenPrice is incorrect"); + assertEq(gasUnitPrice, remoteVars[_chainId].gasUnitPrice, "remoteGasUnitPrice is incorrect"); } - function _checkDstMarkups(uint256 _chainId) internal { - (, , uint16 markupGasDrop, uint16 markupGasUsage) = gasFeePricing.dstConfig(_chainId); - assertEq(markupGasDrop, dstVars[_chainId].markupGasDrop, "dstMarkupGasDrop is incorrect"); - assertEq(markupGasUsage, dstVars[_chainId].markupGasUsage, "dstMarkupGasUsage is incorrect"); + function _checkRemoteMarkups(uint256 _chainId) internal { + (, , uint16 markupGasDrop, uint16 markupGasUsage) = gasFeePricing.remoteConfig(_chainId); + assertEq(markupGasDrop, remoteVars[_chainId].markupGasDrop, "remoteMarkupGasDrop is incorrect"); + assertEq(markupGasUsage, remoteVars[_chainId].markupGasUsage, "remoteMarkupGasUsage is incorrect"); } - function _checkDstRatios(uint256 _chainId) internal { - (uint96 gasTokenPriceRatio, uint160 gasUnitPriceRatio) = gasFeePricing.dstRatios(_chainId); - uint256 _gasTokenPriceRatio = (dstVars[_chainId].gasTokenPrice * 10**18) / srcVars.gasTokenPrice; - uint256 _gasUnitPriceRatio = (dstVars[_chainId].gasUnitPrice * dstVars[_chainId].gasTokenPrice * 10**18) / - srcVars.gasTokenPrice; + function _checkRemoteRatios(uint256 _chainId) internal { + (uint96 gasTokenPriceRatio, uint160 gasUnitPriceRatio) = gasFeePricing.remoteRatios(_chainId); + uint256 _gasTokenPriceRatio = (remoteVars[_chainId].gasTokenPrice * 10**18) / localVars.gasTokenPrice; + uint256 _gasUnitPriceRatio = (remoteVars[_chainId].gasUnitPrice * remoteVars[_chainId].gasTokenPrice * 10**18) / + localVars.gasTokenPrice; assertEq(gasTokenPriceRatio, _gasTokenPriceRatio, "gasTokenPriceRatio is incorrect"); assertEq(gasUnitPriceRatio, _gasUnitPriceRatio, "gasUnitPriceRatio is incorrect"); } @@ -410,73 +410,73 @@ contract GasFeePricingUpgradeableTest is Test { } function _checkMinFeeUsd(uint256 _expectedMinFeeUsd) internal { - uint256 _expectedMinFee = (_expectedMinFeeUsd * 10**18) / srcVars.gasTokenPrice; + uint256 _expectedMinFee = (_expectedMinFeeUsd * 10**18) / localVars.gasTokenPrice; _checkMinFee(_expectedMinFee); } - function _checkSrcConfig() internal { - (uint112 gasDropMax, uint112 gasUnitsRcvMsg, , ) = gasFeePricing.srcConfig(); - assertEq(gasDropMax, srcVars.gasDropMax, "srcMaxGasDrop is incorrect"); - assertEq(gasUnitsRcvMsg, srcVars.gasUnitsRcvMsg, "srcGasUnitsRcvMsg is incorrect"); + function _checkLocalConfig() internal { + (uint112 gasDropMax, uint112 gasUnitsRcvMsg, , ) = gasFeePricing.localConfig(); + assertEq(gasDropMax, localVars.gasDropMax, "localMaxGasDrop is incorrect"); + assertEq(gasUnitsRcvMsg, localVars.gasUnitsRcvMsg, "localGasUnitsRcvMsg is incorrect"); } - function _checkSrcInfo() internal { - (uint128 gasTokenPrice, uint128 gasUnitPrice) = gasFeePricing.srcInfo(); - assertEq(gasTokenPrice, srcVars.gasTokenPrice, "srcGasTokenPrice is incorrect"); - assertEq(gasUnitPrice, srcVars.gasUnitPrice, "gasUnitPrice is incorrect"); + function _checkLocalInfo() internal { + (uint128 gasTokenPrice, uint128 gasUnitPrice) = gasFeePricing.localInfo(); + assertEq(gasTokenPrice, localVars.gasTokenPrice, "localGasTokenPrice is incorrect"); + assertEq(gasUnitPrice, localVars.gasUnitPrice, "gasUnitPrice is incorrect"); } /*╔══════════════════════════════════════════════════════════════════════╗*\ ▏*║ INTERNAL SETTERS ║*▕ \*╚══════════════════════════════════════════════════════════════════════╝*/ - function _setDstConfig( + function _setRemoteConfig( uint256[] memory _chainIds, uint256[] memory _gasDropMax, uint256[] memory _gasUnitsRcvMsg ) internal { for (uint256 i = 0; i < _chainIds.length; ++i) { - dstVars[_chainIds[i]].gasDropMax = _gasDropMax[i]; - dstVars[_chainIds[i]].gasUnitsRcvMsg = _gasUnitsRcvMsg[i]; + remoteVars[_chainIds[i]].gasDropMax = _gasDropMax[i]; + remoteVars[_chainIds[i]].gasUnitsRcvMsg = _gasUnitsRcvMsg[i]; } - gasFeePricing.setDstConfig(_chainIds, _gasDropMax, _gasUnitsRcvMsg); + gasFeePricing.setRemoteConfig(_chainIds, _gasDropMax, _gasUnitsRcvMsg); } - function _setDstInfo( + function _setRemoteInfo( uint256[] memory _chainIds, uint256[] memory _gasTokenPrice, uint256[] memory _gasUnitPrice ) internal { for (uint256 i = 0; i < _chainIds.length; ++i) { - dstVars[_chainIds[i]].gasTokenPrice = _gasTokenPrice[i]; - dstVars[_chainIds[i]].gasUnitPrice = _gasUnitPrice[i]; + remoteVars[_chainIds[i]].gasTokenPrice = _gasTokenPrice[i]; + remoteVars[_chainIds[i]].gasUnitPrice = _gasUnitPrice[i]; } - gasFeePricing.setDstInfo(_chainIds, _gasTokenPrice, _gasUnitPrice); + gasFeePricing.setRemoteInfo(_chainIds, _gasTokenPrice, _gasUnitPrice); } - function _setDstMarkups( + function _setRemoteMarkups( uint256[] memory _chainIds, uint16[] memory _markupGasDrop, uint16[] memory _markupGasUsage ) internal { for (uint256 i = 0; i < _chainIds.length; ++i) { - dstVars[_chainIds[i]].markupGasDrop = _markupGasDrop[i]; - dstVars[_chainIds[i]].markupGasUsage = _markupGasUsage[i]; + remoteVars[_chainIds[i]].markupGasDrop = _markupGasDrop[i]; + remoteVars[_chainIds[i]].markupGasUsage = _markupGasUsage[i]; } - gasFeePricing.setDstMarkups(_chainIds, _markupGasDrop, _markupGasUsage); + gasFeePricing.setRemoteMarkups(_chainIds, _markupGasDrop, _markupGasUsage); } - function _updateSrcConfig(uint256 _gasDropMax, uint256 _gasUnitsRcvMsg) internal { - srcVars.gasDropMax = _gasDropMax; - srcVars.gasUnitsRcvMsg = _gasUnitsRcvMsg; + function _updateLocalConfig(uint256 _gasDropMax, uint256 _gasUnitsRcvMsg) internal { + localVars.gasDropMax = _gasDropMax; + localVars.gasUnitsRcvMsg = _gasUnitsRcvMsg; uint256 fee = gasFeePricing.estimateUpdateFees(); - gasFeePricing.updateSrcConfig{value: fee}(_gasDropMax, _gasUnitsRcvMsg); + gasFeePricing.updateLocalConfig{value: fee}(_gasDropMax, _gasUnitsRcvMsg); } - function _updateSrcInfo(uint256 _gasTokenPrice, uint256 _gasUnitPrice) internal { - srcVars.gasTokenPrice = _gasTokenPrice; - srcVars.gasUnitPrice = _gasUnitPrice; + function _updateLocalInfo(uint256 _gasTokenPrice, uint256 _gasUnitPrice) internal { + localVars.gasTokenPrice = _gasTokenPrice; + localVars.gasUnitPrice = _gasUnitPrice; uint256 fee = gasFeePricing.estimateUpdateFees(); - gasFeePricing.updateSrcInfo{value: fee}(_gasTokenPrice, _gasUnitPrice); + gasFeePricing.updateLocalInfo{value: fee}(_gasTokenPrice, _gasUnitPrice); } } From 4059476702913d20fd8336d168cf5cfd841d2409 Mon Sep 17 00:00:00 2001 From: ChiTimesChi <88190723+ChiTimesChi@users.noreply.github.com> Date: Mon, 23 May 2022 18:38:53 +0300 Subject: [PATCH 30/47] GFP: introduce remote chain-specific min fee --- .../messaging/GasFeePricingUpgradeable.sol | 106 ++++----- .../libraries/GasFeePricingUpdates.sol | 58 ++++- test/messaging/GasFeePricingUpgradeable.t.sol | 223 ++++++++++-------- 3 files changed, 225 insertions(+), 162 deletions(-) diff --git a/contracts/messaging/GasFeePricingUpgradeable.sol b/contracts/messaging/GasFeePricingUpgradeable.sol index 4b67ff26e..cfe54b39c 100644 --- a/contracts/messaging/GasFeePricingUpgradeable.sol +++ b/contracts/messaging/GasFeePricingUpgradeable.sol @@ -43,10 +43,14 @@ contract GasFeePricingUpgradeable is SynMessagingReceiverUpgradeable { * uint112 => max value ~= 5 * 10**33 * - gasUnitsRcvMsg: Amount of gas units needed for GasFeePricing contract * to receive "update chain Config/Info" message - * uint112 => max value ~= 5 * 10**33 + * uint80 => max value ~= 10**24 + * - minGasUsageFeeUsd: minimum amount of "gas usage" part of total messaging fee, + * when sending message to given remote chain. + * Quoted in USD, multiplied by USD_DENOMINATOR + * uint32 => max value ~= 4 * 10**9 * These are universal values, and they should be the same on all GasFeePricing * contracts. - * + * ═══════════════════════════════════════════════════════════════════════════════════════ * Some of the values, however, are set unique for every "local-remote" chain combination: * - markupGasDrop: Markup for gas airdrop * uint16 => max value = 65535 @@ -67,7 +71,8 @@ contract GasFeePricingUpgradeable is SynMessagingReceiverUpgradeable { struct ChainConfig { /// @dev Values below are synchronized cross-chain uint112 gasDropMax; - uint112 gasUnitsRcvMsg; + uint80 gasUnitsRcvMsg; + uint32 minGasUsageFeeUsd; /// @dev Values below are local-chain specific uint16 markupGasDrop; uint16 markupGasUsage; @@ -154,17 +159,14 @@ contract GasFeePricingUpgradeable is SynMessagingReceiverUpgradeable { /// localConfig.markupGasDrop and localConfig.markupGasUsage values are not used ChainConfig public localConfig; ChainInfo public localInfo; - /// @dev Minimum fee related to gas usage on remote chain - uint256 public minGasUsageFee; /*╔══════════════════════════════════════════════════════════════════════╗*\ ▏*║ CONSTANTS ║*▕ \*╚══════════════════════════════════════════════════════════════════════╝*/ - uint256 public constant DEFAULT_MIN_FEE_USD = 10**18; - uint256 public constant DEFAULT_GAS_LIMIT = 200000; uint256 public constant MARKUP_DENOMINATOR = 100; + uint256 public constant USD_DENOMINATOR = 10000; /*╔══════════════════════════════════════════════════════════════════════╗*\ ▏*║ INITIALIZER ║*▕ @@ -174,7 +176,6 @@ contract GasFeePricingUpgradeable is SynMessagingReceiverUpgradeable { __Ownable_init_unchained(); messageBus = _messageBus; localInfo.gasTokenPrice = uint96(_localGasTokenPrice); - minGasUsageFee = _calculateMinGasUsageFee(DEFAULT_MIN_FEE_USD, _localGasTokenPrice); } /*╔══════════════════════════════════════════════════════════════════════╗*\ @@ -234,9 +235,10 @@ contract GasFeePricingUpgradeable is SynMessagingReceiverUpgradeable { // Sum up the fees multiplied by their respective markups feeGasDrop = (feeGasDrop * (_markupGasDrop + MARKUP_DENOMINATOR)) / MARKUP_DENOMINATOR; feeGasUsage = (feeGasUsage * (_markupGasUsage + MARKUP_DENOMINATOR)) / MARKUP_DENOMINATOR; + // TODO: implement remote-chain-specific minGasUsageFee // Check if gas usage fee is lower than minimum - uint256 _minGasUsageFee = minGasUsageFee; - if (feeGasUsage < _minGasUsageFee) feeGasUsage = _minGasUsageFee; + // uint256 _minGasUsageFee = minGasUsageFee; + // if (feeGasUsage < _minGasUsageFee) feeGasUsage = _minGasUsageFee; fee = feeGasDrop + feeGasUsage; } @@ -277,15 +279,18 @@ contract GasFeePricingUpgradeable is SynMessagingReceiverUpgradeable { /// @dev Update config (gasLimit for sending messages to chain, max gas airdrop) for a bunch of chains. function setRemoteConfig( uint256[] memory _remoteChainId, - uint256[] memory _gasDropMax, - uint256[] memory _gasUnitsRcvMsg + uint112[] memory _gasDropMax, + uint80[] memory _gasUnitsRcvMsg, + uint32[] memory _minGasUsageFeeUsd ) external onlyOwner { require( - _remoteChainId.length == _gasDropMax.length && _remoteChainId.length == _gasUnitsRcvMsg.length, + _remoteChainId.length == _gasDropMax.length && + _remoteChainId.length == _gasUnitsRcvMsg.length && + _remoteChainId.length == _minGasUsageFeeUsd.length, "!arrays" ); for (uint256 i = 0; i < _remoteChainId.length; ++i) { - _updateRemoteChainConfig(_remoteChainId[i], _gasDropMax[i], _gasUnitsRcvMsg[i]); + _updateRemoteChainConfig(_remoteChainId[i], _gasDropMax[i], _gasUnitsRcvMsg[i], _minGasUsageFeeUsd[i]); } } @@ -293,8 +298,8 @@ contract GasFeePricingUpgradeable is SynMessagingReceiverUpgradeable { /// Handy for initial setup. function setRemoteInfo( uint256[] memory _remoteChainId, - uint256[] memory _gasTokenPrice, - uint256[] memory _gasUnitPrice + uint128[] memory _gasTokenPrice, + uint128[] memory _gasUnitPrice ) external onlyOwner { require( _remoteChainId.length == _gasTokenPrice.length && _remoteChainId.length == _gasUnitPrice.length, @@ -321,31 +326,26 @@ contract GasFeePricingUpgradeable is SynMessagingReceiverUpgradeable { } } - /// @notice Update the minimum fee for gas usage on message delivery. Quoted in local chain wei. - function setMinFee(uint256 _minGasUsageFee) external onlyOwner { - minGasUsageFee = _minGasUsageFee; - } - - /// @notice Update the minimum fee for gas usage on message delivery. Quoted in USD, scaled to wei. - function setMinFeeUsd(uint256 _minGasUsageFeeUsd) external onlyOwner { - minGasUsageFee = _calculateMinGasUsageFee(_minGasUsageFeeUsd, localInfo.gasTokenPrice); - } - /// @notice Update information about local chain config: /// amount of gas needed to do _updateRemoteChainInfo() /// and maximum airdrop available on this chain - function updateLocalConfig(uint256 _gasDropMax, uint256 _gasUnitsRcvMsg) external payable onlyOwner { + function updateLocalConfig( + uint112 _gasDropMax, + uint80 _gasUnitsRcvMsg, + uint32 _minGasUsageFeeUsd + ) external payable onlyOwner { require(_gasUnitsRcvMsg != 0, "Gas amount is not set"); - _sendUpdateMessages(uint8(GasFeePricingUpdates.MsgType.UPDATE_CONFIG), _gasDropMax, _gasUnitsRcvMsg); + _sendUpdateMessages(GasFeePricingUpdates.encodeConfig(_gasDropMax, _gasUnitsRcvMsg, _minGasUsageFeeUsd)); ChainConfig memory config = localConfig; - config.gasDropMax = uint112(_gasDropMax); - config.gasUnitsRcvMsg = uint112(_gasUnitsRcvMsg); + config.gasDropMax = _gasDropMax; + config.gasUnitsRcvMsg = _gasUnitsRcvMsg; + config.minGasUsageFeeUsd = _minGasUsageFeeUsd; localConfig = config; } /// @notice Update information about local chain gas token/unit price on all configured remote chains, /// as well as on the local chain itself. - function updateLocalInfo(uint256 _gasTokenPrice, uint256 _gasUnitPrice) external payable onlyOwner { + function updateLocalInfo(uint128 _gasTokenPrice, uint128 _gasUnitPrice) external payable onlyOwner { /** * @dev Some chains (i.e. Aurora) allow free transactions, * so we're not checking gasUnitPrice for being zero. @@ -355,7 +355,7 @@ contract GasFeePricingUpgradeable is SynMessagingReceiverUpgradeable { require(_gasTokenPrice != 0, "Gas token price is not set"); // send messages before updating the values, so that it's possible to use // estimateUpdateFees() to calculate the needed fee for the update - _sendUpdateMessages(uint8(GasFeePricingUpdates.MsgType.UPDATE_INFO), _gasTokenPrice, _gasUnitPrice); + _sendUpdateMessages(GasFeePricingUpdates.encodeInfo(_gasTokenPrice, _gasUnitPrice)); _updateLocalChainInfo(_gasTokenPrice, _gasUnitPrice); } @@ -365,7 +365,7 @@ contract GasFeePricingUpgradeable is SynMessagingReceiverUpgradeable { /// @dev Updates information about local chain gas token/unit price. /// All the remote chain ratios are updated as well, if gas token price changed - function _updateLocalChainInfo(uint256 _gasTokenPrice, uint256 _gasUnitPrice) internal { + function _updateLocalChainInfo(uint128 _gasTokenPrice, uint128 _gasUnitPrice) internal { if (localInfo.gasTokenPrice != _gasTokenPrice) { // update ratios only if gas token price has changed uint256[] memory chainIds = remoteChainIds; @@ -376,7 +376,7 @@ contract GasFeePricingUpgradeable is SynMessagingReceiverUpgradeable { } } - localInfo = ChainInfo({gasTokenPrice: uint128(_gasTokenPrice), gasUnitPrice: uint128(_gasUnitPrice)}); + localInfo = ChainInfo({gasTokenPrice: _gasTokenPrice, gasUnitPrice: _gasUnitPrice}); // TODO: use context chainid here emit ChainInfoUpdated(block.chainid, _gasTokenPrice, _gasUnitPrice); @@ -387,13 +387,15 @@ contract GasFeePricingUpgradeable is SynMessagingReceiverUpgradeable { /// Maximum airdrop available on this chain function _updateRemoteChainConfig( uint256 _remoteChainId, - uint256 _gasDropMax, - uint256 _gasUnitsRcvMsg + uint112 _gasDropMax, + uint80 _gasUnitsRcvMsg, + uint32 _minGasUsageFeeUsd ) internal { require(_gasUnitsRcvMsg != 0, "Gas amount is not set"); ChainConfig memory config = remoteConfig[_remoteChainId]; - config.gasDropMax = uint112(_gasDropMax); - config.gasUnitsRcvMsg = uint112(_gasUnitsRcvMsg); + config.gasDropMax = _gasDropMax; + config.gasUnitsRcvMsg = _gasUnitsRcvMsg; + config.minGasUsageFeeUsd = _minGasUsageFeeUsd; remoteConfig[_remoteChainId] = config; } @@ -401,8 +403,8 @@ contract GasFeePricingUpgradeable is SynMessagingReceiverUpgradeable { /// Remote chain ratios are updated as well. function _updateRemoteChainInfo( uint256 _remoteChainId, - uint256 _gasTokenPrice, - uint256 _gasUnitPrice + uint128 _gasTokenPrice, + uint128 _gasUnitPrice ) internal { /** * @dev Some chains (i.e. Aurora) allow free transactions, @@ -419,10 +421,7 @@ contract GasFeePricingUpgradeable is SynMessagingReceiverUpgradeable { remoteChainIds.push(_remoteChainId); } - remoteInfo[_remoteChainId] = ChainInfo({ - gasTokenPrice: uint128(_gasTokenPrice), - gasUnitPrice: uint128(_gasUnitPrice) - }); + remoteInfo[_remoteChainId] = ChainInfo({gasTokenPrice: _gasTokenPrice, gasUnitPrice: _gasUnitPrice}); _updateRemoteChainRatios(_localGasTokenPrice, _remoteChainId, _gasTokenPrice, _gasUnitPrice); emit ChainInfoUpdated(_remoteChainId, _gasTokenPrice, _gasUnitPrice); @@ -460,15 +459,10 @@ contract GasFeePricingUpgradeable is SynMessagingReceiverUpgradeable { \*╚══════════════════════════════════════════════════════════════════════╝*/ /// @dev Sends "something updated" messages to all registered remote chains - function _sendUpdateMessages( - uint8 _msgType, - uint256 _newValueA, - uint256 _newValueB - ) internal { + function _sendUpdateMessages(bytes memory _message) internal { (uint256 totalFee, uint256[] memory fees) = _estimateUpdateFees(); require(msg.value >= totalFee, "msg.value doesn't cover all the fees"); - bytes memory message = GasFeePricingUpdates.encode(_msgType, uint128(_newValueA), uint128(_newValueB)); uint256[] memory chainIds = remoteChainIds; bytes32[] memory receivers = new bytes32[](chainIds.length); bytes[] memory options = new bytes[](chainIds.length); @@ -482,7 +476,7 @@ contract GasFeePricingUpgradeable is SynMessagingReceiverUpgradeable { options[i] = Options.encode(gasLimit); } - _send(receivers, chainIds, message, options, fees, payable(msg.sender)); + _send(receivers, chainIds, _message, options, fees, payable(msg.sender)); if (msg.value > totalFee) payable(msg.sender).transfer(msg.value - totalFee); } @@ -493,11 +487,15 @@ contract GasFeePricingUpgradeable is SynMessagingReceiverUpgradeable { bytes memory _message, address ) internal override { - (uint8 msgType, uint128 newValueA, uint128 newValueB) = GasFeePricingUpdates.decode(_message); + uint8 msgType = GasFeePricingUpdates.messageType(_message); if (msgType == uint8(GasFeePricingUpdates.MsgType.UPDATE_CONFIG)) { - _updateRemoteChainConfig(_localChainId, newValueA, newValueB); + (uint112 gasDropMax, uint80 gasUnitsRcvMsg, uint32 minGasUsageFeeUsd) = GasFeePricingUpdates.decodeConfig( + _message + ); + _updateRemoteChainConfig(_localChainId, gasDropMax, gasUnitsRcvMsg, minGasUsageFeeUsd); } else if (msgType == uint8(GasFeePricingUpdates.MsgType.UPDATE_INFO)) { - _updateRemoteChainInfo(_localChainId, newValueA, newValueB); + (uint128 gasTokenPrice, uint128 gasUnitPrice) = GasFeePricingUpdates.decodeInfo(_message); + _updateRemoteChainInfo(_localChainId, gasTokenPrice, gasUnitPrice); } else { revert("Unknown message type"); } diff --git a/contracts/messaging/libraries/GasFeePricingUpdates.sol b/contracts/messaging/libraries/GasFeePricingUpdates.sol index e148f2239..5eb9a1359 100644 --- a/contracts/messaging/libraries/GasFeePricingUpdates.sol +++ b/contracts/messaging/libraries/GasFeePricingUpdates.sol @@ -9,29 +9,61 @@ library GasFeePricingUpdates { UPDATE_INFO } - function encode( - uint8 _txType, - uint128 _newValueA, - uint128 _newValueB + function encodeConfig( + uint112 _gasDropMax, + uint80 _gasUnitsRcvMsg, + uint32 _minGasUsageFeeUsd ) internal pure returns (bytes memory) { - return abi.encodePacked(_txType, _newValueA, _newValueB); + return abi.encodePacked(MsgType.UPDATE_CONFIG, _gasDropMax, _gasUnitsRcvMsg, _minGasUsageFeeUsd); } - function decode(bytes memory _message) + function encodeInfo(uint128 _gasTokenPrice, uint128 _gasUnitPrice) internal pure returns (bytes memory) { + return abi.encodePacked(MsgType.UPDATE_INFO, _gasTokenPrice, _gasUnitPrice); + } + + function decodeConfig(bytes memory _message) internal pure returns ( - uint8 txType, - uint128 newValueA, - uint128 newValueB + uint112 gasDropMax, + uint80 gasUnitsRcvMsg, + uint32 minGasUsageFeeUsd ) { - require(_message.length == 33, "Unknown message format"); + // message: (uint8, uint112, uint80, uint32) + // length: (1, 14, 10, 4) + // offset: (1, 15, 25, 29) + require(_message.length == 29, "Wrong message length"); + uint8 msgType; + // solhint-disable-next-line + assembly { + msgType := mload(add(_message, 1)) + gasDropMax := mload(add(_message, 15)) + gasUnitsRcvMsg := mload(add(_message, 25)) + minGasUsageFeeUsd := mload(add(_message, 29)) + } + require(msgType == uint8(MsgType.UPDATE_CONFIG), "Wrong msgType"); + } + + function decodeInfo(bytes memory _message) internal pure returns (uint128 gasTokenPrice, uint128 gasUnitPrice) { + // message: (uint8, uint128, uint128) + // length: (1, 16, 16) + // offset: (1, 17, 33) + require(_message.length == 33, "Wrong message length"); + uint8 msgType; + // solhint-disable-next-line + assembly { + msgType := mload(add(_message, 1)) + gasTokenPrice := mload(add(_message, 17)) + gasUnitPrice := mload(add(_message, 33)) + } + require(msgType == uint8(MsgType.UPDATE_INFO), "Wrong msgType"); + } + + function messageType(bytes memory _message) internal pure returns (uint8 msgType) { // solhint-disable-next-line assembly { - txType := mload(add(_message, 1)) - newValueA := mload(add(_message, 17)) - newValueB := mload(add(_message, 33)) + msgType := mload(add(_message, 1)) } } } diff --git a/test/messaging/GasFeePricingUpgradeable.t.sol b/test/messaging/GasFeePricingUpgradeable.t.sol index 8a995ff23..920288b59 100644 --- a/test/messaging/GasFeePricingUpgradeable.t.sol +++ b/test/messaging/GasFeePricingUpgradeable.t.sol @@ -16,6 +16,7 @@ contract GasFeePricingUpgradeableTest is Test { uint256 gasUnitPrice; uint256 gasDropMax; uint256 gasUnitsRcvMsg; + uint256 minGasUsageFeeUsd; uint256 markupGasDrop; uint256 markupGasUsage; address gasFeePricing; @@ -97,8 +98,9 @@ contract GasFeePricingUpgradeableTest is Test { abi.encodeWithSelector( GasFeePricingUpgradeable.setRemoteConfig.selector, new uint256[](1), - new uint256[](1), - new uint256[](1) + new uint112[](1), + new uint80[](1), + new uint32[](1) ), "Ownable: caller is not the owner" ); @@ -107,8 +109,8 @@ contract GasFeePricingUpgradeableTest is Test { abi.encodeWithSelector( GasFeePricingUpgradeable.setRemoteInfo.selector, new uint256[](1), - new uint256[](1), - new uint256[](1) + new uint128[](1), + new uint128[](1) ), "Ownable: caller is not the owner" ); @@ -125,18 +127,7 @@ contract GasFeePricingUpgradeableTest is Test { utils.checkAccess( _gfp, - abi.encodeWithSelector(GasFeePricingUpgradeable.setMinFee.selector, 0), - "Ownable: caller is not the owner" - ); - utils.checkAccess( - _gfp, - abi.encodeWithSelector(GasFeePricingUpgradeable.setMinFeeUsd.selector, 0), - "Ownable: caller is not the owner" - ); - - utils.checkAccess( - _gfp, - abi.encodeWithSelector(GasFeePricingUpgradeable.updateLocalConfig.selector, 0, 0), + abi.encodeWithSelector(GasFeePricingUpgradeable.updateLocalConfig.selector, 0, 0, 0), "Ownable: caller is not the owner" ); @@ -151,14 +142,25 @@ contract GasFeePricingUpgradeableTest is Test { ▏*║ ENCODING TESTS ║*▕ \*╚══════════════════════════════════════════════════════════════════════╝*/ - function testEncodeDecode( - uint8 msgType, - uint128 newValueA, - uint128 newValueB + function testEncodeConfig( + uint112 newValueA, + uint80 newValueB, + uint32 newValueC ) public { - bytes memory message = GasFeePricingUpdates.encode(msgType, newValueA, newValueB); - (uint8 _msgType, uint128 _newValueA, uint128 _newValueB) = GasFeePricingUpdates.decode(message); - assertEq(_msgType, msgType, "Failed to encode msgType"); + bytes memory message = GasFeePricingUpdates.encodeConfig(newValueA, newValueB, newValueC); + uint8 _msgType = GasFeePricingUpdates.messageType(message); + (uint112 _newValueA, uint80 _newValueB, uint32 _newValueC) = GasFeePricingUpdates.decodeConfig(message); + assertEq(_msgType, uint8(GasFeePricingUpdates.MsgType.UPDATE_CONFIG), "Failed to encode msgType"); + assertEq(_newValueA, newValueA, "Failed to encode newValueA"); + assertEq(_newValueB, newValueB, "Failed to encode newValueB"); + assertEq(_newValueC, newValueC, "Failed to encode newValueC"); + } + + function testEncodeInfo(uint128 newValueA, uint128 newValueB) public { + bytes memory message = GasFeePricingUpdates.encodeInfo(newValueA, newValueB); + uint8 _msgType = GasFeePricingUpdates.messageType(message); + (uint128 _newValueA, uint128 _newValueB) = GasFeePricingUpdates.decodeInfo(message); + assertEq(_msgType, uint8(GasFeePricingUpdates.MsgType.UPDATE_INFO), "Failed to encode msgType"); assertEq(_newValueA, newValueA, "Failed to encode newValueA"); assertEq(_newValueB, newValueB, "Failed to encode newValueB"); } @@ -171,41 +173,61 @@ contract GasFeePricingUpgradeableTest is Test { (uint128 _gasTokenPrice, ) = gasFeePricing.localInfo(); assertEq(_gasTokenPrice, localVars.gasTokenPrice, "Failed to init: gasTokenPrice"); assertEq(gasFeePricing.messageBus(), address(messageBus), "Failed to init: messageBus"); - _checkMinFeeUsd(10**18); } function testSetRemoteConfig() public { - uint256[] memory gasUnitsRcvMsg = new uint256[](TEST_CHAINS); - uint256[] memory gasDropMax = new uint256[](TEST_CHAINS); + uint112[] memory gasDropMax = new uint112[](TEST_CHAINS); + uint80[] memory gasUnitsRcvMsg = new uint80[](TEST_CHAINS); + uint32[] memory minGasUsageFeeUsd = new uint32[](TEST_CHAINS); for (uint256 i = 0; i < TEST_CHAINS; ++i) { - gasUnitsRcvMsg[i] = (i + 1) * 420420; - gasDropMax[i] = (i + 1) * 10**18; + gasDropMax[i] = uint112((i + 1) * 10**18); + gasUnitsRcvMsg[i] = uint80((i + 1) * 420420); + minGasUsageFeeUsd[i] = uint32((i + 1) * 10000); } - _setRemoteConfig(remoteChainIds, gasDropMax, gasUnitsRcvMsg); + _setRemoteConfig(remoteChainIds, gasDropMax, gasUnitsRcvMsg, minGasUsageFeeUsd); for (uint256 i = 0; i < TEST_CHAINS; ++i) { _checkRemoteConfig(remoteChainIds[i]); } } function testSetRemoteConfigZeroDropSucceeds() public { - uint256[] memory gasDropMax = new uint256[](TEST_CHAINS); - uint256[] memory gasUnitsRcvMsg = new uint256[](TEST_CHAINS); + uint112[] memory gasDropMax = new uint112[](TEST_CHAINS); + uint80[] memory gasUnitsRcvMsg = new uint80[](TEST_CHAINS); + uint32[] memory minGasUsageFeeUsd = new uint32[](TEST_CHAINS); + for (uint256 i = 0; i < TEST_CHAINS; ++i) { + gasDropMax[i] = uint112(i * 10**17); + gasUnitsRcvMsg[i] = uint80((i + 1) * 133769); + minGasUsageFeeUsd[i] = uint32((i + 1) * 1000); + } + _setRemoteConfig(remoteChainIds, gasDropMax, gasUnitsRcvMsg, minGasUsageFeeUsd); + for (uint256 i = 0; i < TEST_CHAINS; ++i) { + _checkRemoteConfig(remoteChainIds[i]); + } + } + + function testSetRemoteConfigZeroFeeSucceeds() public { + uint112[] memory gasDropMax = new uint112[](TEST_CHAINS); + uint80[] memory gasUnitsRcvMsg = new uint80[](TEST_CHAINS); + uint32[] memory minGasUsageFeeUsd = new uint32[](TEST_CHAINS); for (uint256 i = 0; i < TEST_CHAINS; ++i) { - gasDropMax[i] = i * 10**18; - gasUnitsRcvMsg[i] = (i + 1) * 133769; + gasDropMax[i] = uint112((i + 1) * 10**16); + gasUnitsRcvMsg[i] = uint80((i + 1) * 696969); + minGasUsageFeeUsd[i] = uint32(i * 5000); } - _setRemoteConfig(remoteChainIds, gasDropMax, gasUnitsRcvMsg); + _setRemoteConfig(remoteChainIds, gasDropMax, gasUnitsRcvMsg, minGasUsageFeeUsd); for (uint256 i = 0; i < TEST_CHAINS; ++i) { _checkRemoteConfig(remoteChainIds[i]); } } function testSetRemoteConfigZeroGasReverts() public { - uint256[] memory gasDropMax = new uint256[](TEST_CHAINS); - uint256[] memory gasUnitsRcvMsg = new uint256[](TEST_CHAINS); + uint112[] memory gasDropMax = new uint112[](TEST_CHAINS); + uint80[] memory gasUnitsRcvMsg = new uint80[](TEST_CHAINS); + uint32[] memory minGasUsageFeeUsd = new uint32[](TEST_CHAINS); for (uint256 i = 0; i < TEST_CHAINS; ++i) { - gasDropMax[i] = (i + 1) * 10**18; - gasUnitsRcvMsg[i] = i * 133769; + gasDropMax[i] = uint112((i + 1) * 10**15); + gasUnitsRcvMsg[i] = uint80(i * 123456); + minGasUsageFeeUsd[i] = uint32((i + 1) * 5000); } utils.checkRevert( address(this), @@ -214,14 +236,15 @@ contract GasFeePricingUpgradeableTest is Test { GasFeePricingUpgradeable.setRemoteConfig.selector, remoteChainIds, gasDropMax, - gasUnitsRcvMsg + gasUnitsRcvMsg, + minGasUsageFeeUsd ), "Gas amount is not set" ); } function testSetRemoteInfo() public { - (uint256[] memory gasTokenPrices, uint256[] memory gasUnitPrices) = _generateTestInfoValues(); + (uint128[] memory gasTokenPrices, uint128[] memory gasUnitPrices) = _generateTestInfoValues(); _setRemoteInfo(remoteChainIds, gasTokenPrices, gasUnitPrices); for (uint256 i = 0; i < TEST_CHAINS; ++i) { uint256 chainId = remoteChainIds[i]; @@ -231,7 +254,7 @@ contract GasFeePricingUpgradeableTest is Test { } function testSetRemoteInfoZeroTokenPriceReverts() public { - (uint256[] memory gasTokenPrices, uint256[] memory gasUnitPrices) = _generateTestInfoValues(); + (uint128[] memory gasTokenPrices, uint128[] memory gasUnitPrices) = _generateTestInfoValues(); gasTokenPrices[2] = 0; utils.checkRevert( address(this), @@ -247,7 +270,7 @@ contract GasFeePricingUpgradeableTest is Test { } function testSetRemoteInfoZeroUnitPriceSucceeds() public { - (uint256[] memory gasTokenPrices, uint256[] memory gasUnitPrices) = _generateTestInfoValues(); + (uint128[] memory gasTokenPrices, uint128[] memory gasUnitPrices) = _generateTestInfoValues(); gasTokenPrices[2] = 100 * 10**18; gasUnitPrices[3] = 0; _setRemoteInfo(remoteChainIds, gasTokenPrices, gasUnitPrices); @@ -261,10 +284,10 @@ contract GasFeePricingUpgradeableTest is Test { function _generateTestInfoValues() internal pure - returns (uint256[] memory gasTokenPrices, uint256[] memory gasUnitPrices) + returns (uint128[] memory gasTokenPrices, uint128[] memory gasUnitPrices) { - gasTokenPrices = new uint256[](TEST_CHAINS); - gasUnitPrices = new uint256[](TEST_CHAINS); + gasTokenPrices = new uint128[](TEST_CHAINS); + gasUnitPrices = new uint128[](TEST_CHAINS); // 100 gwei, gasToken = $2000 gasTokenPrices[0] = 2000 * 10**18; gasUnitPrices[0] = 100 * 10**9; @@ -296,42 +319,48 @@ contract GasFeePricingUpgradeableTest is Test { } } - function testSetMinFee() public { - uint256 minGasUsageFee = 1234567890; - gasFeePricing.setMinFee(minGasUsageFee); - _checkMinFee(minGasUsageFee); - } + function testUpdateLocalConfig() public { + uint112 gasDropMax = 10 * 10**18; + uint80 gasUnitsRcvMsg = 10**6; + uint32 minGasUsageFeeUsd = 10**4; - function testSetMinFeeUsd(uint16 alphaUsd) public { - uint256 minGasFeeUsageUsd = uint256(alphaUsd) * 10**16; - gasFeePricing.setMinFeeUsd(minGasFeeUsageUsd); - _checkMinFeeUsd(minGasFeeUsageUsd); + _updateLocalConfig(gasDropMax, gasUnitsRcvMsg, minGasUsageFeeUsd); + _checkLocalConfig(); } - function testUpdateLocalConfig() public { - uint256 gasDropMax = 10 * 10**18; - uint256 gasUnitsRcvMsg = 10**6; - _updateLocalConfig(gasDropMax, gasUnitsRcvMsg); + function testUpdateLocalConfigZeroDropSucceeds() public { + // should be able to set to zero + uint112 gasDropMax = 0; + uint80 gasUnitsRcvMsg = 2 * 10**6; + uint32 minGasUsageFeeUsd = 2 * 10**4; + _updateLocalConfig(gasDropMax, gasUnitsRcvMsg, minGasUsageFeeUsd); _checkLocalConfig(); } - function testUpdateLocalConfigZeroDropSucceeds() public { + function testUpdateLocalConfigZeroFeeSucceeds() public { + uint112 gasDropMax = 5 * 10**18; + uint80 gasUnitsRcvMsg = 2 * 10**6; // should be able to set to zero - uint256 gasDropMax = 0; - uint256 gasUnitsRcvMsg = 2 * 10**6; - _updateLocalConfig(gasDropMax, gasUnitsRcvMsg); + uint32 minGasUsageFeeUsd = 0; + _updateLocalConfig(gasDropMax, gasUnitsRcvMsg, minGasUsageFeeUsd); _checkLocalConfig(); } function testUpdateLocalConfigZeroGasReverts() public { - uint256 gasDropMax = 10**18; + uint112 gasDropMax = 10**18; // should NOT be able to set to zero - uint256 gasUnitsRcvMsg = 0; + uint80 gasUnitsRcvMsg = 0; + uint32 minGasUsageFeeUsd = 3 * 10**4; utils.checkRevert( address(this), address(gasFeePricing), - abi.encodeWithSelector(GasFeePricingUpgradeable.updateLocalConfig.selector, gasDropMax, gasUnitsRcvMsg), + abi.encodeWithSelector( + GasFeePricingUpgradeable.updateLocalConfig.selector, + gasDropMax, + gasUnitsRcvMsg, + minGasUsageFeeUsd + ), "Gas amount is not set" ); } @@ -339,8 +368,8 @@ contract GasFeePricingUpgradeableTest is Test { function testUpdateLocalInfo() public { testSetRemoteInfo(); - uint256 gasTokenPrice = 2 * 10**18; - uint256 gasUnitPrice = 10 * 10**9; + uint128 gasTokenPrice = 2 * 10**18; + uint128 gasUnitPrice = 10 * 10**9; _updateLocalInfo(gasTokenPrice, gasUnitPrice); _checkLocalInfo(); for (uint256 i = 0; i < TEST_CHAINS; ++i) { @@ -351,8 +380,8 @@ contract GasFeePricingUpgradeableTest is Test { function testUpdateLocalInfoZeroTokenPriceReverts() public { testSetRemoteInfo(); - uint256 gasTokenPrice = 0; - uint256 gasUnitPrice = 10 * 10**9; + uint128 gasTokenPrice = 0; + uint128 gasUnitPrice = 10 * 10**9; utils.checkRevert( address(this), @@ -365,8 +394,8 @@ contract GasFeePricingUpgradeableTest is Test { function testUpdateLocalInfoZeroUnitPriceSucceeds() public { testSetRemoteInfo(); - uint256 gasTokenPrice = 4 * 10**17; - uint256 gasUnitPrice = 0; + uint128 gasTokenPrice = 4 * 10**17; + uint128 gasUnitPrice = 0; _updateLocalInfo(gasTokenPrice, gasUnitPrice); _checkLocalInfo(); for (uint256 i = 0; i < TEST_CHAINS; ++i) { @@ -379,9 +408,12 @@ contract GasFeePricingUpgradeableTest is Test { \*╚══════════════════════════════════════════════════════════════════════╝*/ function _checkRemoteConfig(uint256 _chainId) internal { - (uint112 gasDropMax, uint112 gasUnitsRcvMsg, , ) = gasFeePricing.remoteConfig(_chainId); + (uint112 gasDropMax, uint80 gasUnitsRcvMsg, uint32 minGasUsageFeeUsd, , ) = gasFeePricing.remoteConfig( + _chainId + ); assertEq(gasDropMax, remoteVars[_chainId].gasDropMax, "remoteMaxGasDrop is incorrect"); assertEq(gasUnitsRcvMsg, remoteVars[_chainId].gasUnitsRcvMsg, "remoteGasUnitsRcvMsg is incorrect"); + assertEq(minGasUsageFeeUsd, remoteVars[_chainId].minGasUsageFeeUsd, "remoteMinGasUsageFeeUsd is incorrect"); } function _checkRemoteInfo(uint256 _chainId) internal { @@ -391,7 +423,7 @@ contract GasFeePricingUpgradeableTest is Test { } function _checkRemoteMarkups(uint256 _chainId) internal { - (, , uint16 markupGasDrop, uint16 markupGasUsage) = gasFeePricing.remoteConfig(_chainId); + (, , , uint16 markupGasDrop, uint16 markupGasUsage) = gasFeePricing.remoteConfig(_chainId); assertEq(markupGasDrop, remoteVars[_chainId].markupGasDrop, "remoteMarkupGasDrop is incorrect"); assertEq(markupGasUsage, remoteVars[_chainId].markupGasUsage, "remoteMarkupGasUsage is incorrect"); } @@ -405,19 +437,11 @@ contract GasFeePricingUpgradeableTest is Test { assertEq(gasUnitPriceRatio, _gasUnitPriceRatio, "gasUnitPriceRatio is incorrect"); } - function _checkMinFee(uint256 _expectedMinFee) internal { - assertEq(gasFeePricing.minGasUsageFee(), _expectedMinFee, "minGasUsageFee is incorrect"); - } - - function _checkMinFeeUsd(uint256 _expectedMinFeeUsd) internal { - uint256 _expectedMinFee = (_expectedMinFeeUsd * 10**18) / localVars.gasTokenPrice; - _checkMinFee(_expectedMinFee); - } - function _checkLocalConfig() internal { - (uint112 gasDropMax, uint112 gasUnitsRcvMsg, , ) = gasFeePricing.localConfig(); + (uint112 gasDropMax, uint80 gasUnitsRcvMsg, uint32 minGasUsageFeeUsd, , ) = gasFeePricing.localConfig(); assertEq(gasDropMax, localVars.gasDropMax, "localMaxGasDrop is incorrect"); assertEq(gasUnitsRcvMsg, localVars.gasUnitsRcvMsg, "localGasUnitsRcvMsg is incorrect"); + assertEq(minGasUsageFeeUsd, localVars.minGasUsageFeeUsd, "localMinGasUsageFeeUsd is incorrect"); } function _checkLocalInfo() internal { @@ -432,24 +456,28 @@ contract GasFeePricingUpgradeableTest is Test { function _setRemoteConfig( uint256[] memory _chainIds, - uint256[] memory _gasDropMax, - uint256[] memory _gasUnitsRcvMsg + uint112[] memory _gasDropMax, + uint80[] memory _gasUnitsRcvMsg, + uint32[] memory _minGasUsageFeeUsd ) internal { for (uint256 i = 0; i < _chainIds.length; ++i) { - remoteVars[_chainIds[i]].gasDropMax = _gasDropMax[i]; - remoteVars[_chainIds[i]].gasUnitsRcvMsg = _gasUnitsRcvMsg[i]; + uint256 chainId = _chainIds[i]; + remoteVars[chainId].gasDropMax = _gasDropMax[i]; + remoteVars[chainId].gasUnitsRcvMsg = _gasUnitsRcvMsg[i]; + remoteVars[chainId].minGasUsageFeeUsd = _minGasUsageFeeUsd[i]; } - gasFeePricing.setRemoteConfig(_chainIds, _gasDropMax, _gasUnitsRcvMsg); + gasFeePricing.setRemoteConfig(_chainIds, _gasDropMax, _gasUnitsRcvMsg, _minGasUsageFeeUsd); } function _setRemoteInfo( uint256[] memory _chainIds, - uint256[] memory _gasTokenPrice, - uint256[] memory _gasUnitPrice + uint128[] memory _gasTokenPrice, + uint128[] memory _gasUnitPrice ) internal { for (uint256 i = 0; i < _chainIds.length; ++i) { - remoteVars[_chainIds[i]].gasTokenPrice = _gasTokenPrice[i]; - remoteVars[_chainIds[i]].gasUnitPrice = _gasUnitPrice[i]; + uint256 chainId = _chainIds[i]; + remoteVars[chainId].gasTokenPrice = _gasTokenPrice[i]; + remoteVars[chainId].gasUnitPrice = _gasUnitPrice[i]; } gasFeePricing.setRemoteInfo(_chainIds, _gasTokenPrice, _gasUnitPrice); } @@ -466,14 +494,19 @@ contract GasFeePricingUpgradeableTest is Test { gasFeePricing.setRemoteMarkups(_chainIds, _markupGasDrop, _markupGasUsage); } - function _updateLocalConfig(uint256 _gasDropMax, uint256 _gasUnitsRcvMsg) internal { + function _updateLocalConfig( + uint112 _gasDropMax, + uint80 _gasUnitsRcvMsg, + uint32 _minGasUsageFeeUsd + ) internal { localVars.gasDropMax = _gasDropMax; localVars.gasUnitsRcvMsg = _gasUnitsRcvMsg; + localVars.minGasUsageFeeUsd = _minGasUsageFeeUsd; uint256 fee = gasFeePricing.estimateUpdateFees(); - gasFeePricing.updateLocalConfig{value: fee}(_gasDropMax, _gasUnitsRcvMsg); + gasFeePricing.updateLocalConfig{value: fee}(_gasDropMax, _gasUnitsRcvMsg, _minGasUsageFeeUsd); } - function _updateLocalInfo(uint256 _gasTokenPrice, uint256 _gasUnitPrice) internal { + function _updateLocalInfo(uint128 _gasTokenPrice, uint128 _gasUnitPrice) internal { localVars.gasTokenPrice = _gasTokenPrice; localVars.gasUnitPrice = _gasUnitPrice; uint256 fee = gasFeePricing.estimateUpdateFees(); From ae006eec24d9c146a41a4777e93a4da04940ade3 Mon Sep 17 00:00:00 2001 From: ChiTimesChi <88190723+ChiTimesChi@users.noreply.github.com> Date: Mon, 23 May 2022 19:06:30 +0300 Subject: [PATCH 31/47] tests: split GFP tests into categories --- test/messaging/GasFeePricingUpgradeable.t.sol | 515 ------------------ .../gfp/GasFeePricingMessaging.t.sol | 34 ++ .../messaging/gfp/GasFeePricingSecurity.t.sol | 78 +++ test/messaging/gfp/GasFeePricingSetters.t.sol | 215 ++++++++ test/messaging/gfp/GasFeePricingSetup.t.sol | 213 ++++++++ 5 files changed, 540 insertions(+), 515 deletions(-) delete mode 100644 test/messaging/GasFeePricingUpgradeable.t.sol create mode 100644 test/messaging/gfp/GasFeePricingMessaging.t.sol create mode 100644 test/messaging/gfp/GasFeePricingSecurity.t.sol create mode 100644 test/messaging/gfp/GasFeePricingSetters.t.sol create mode 100644 test/messaging/gfp/GasFeePricingSetup.t.sol diff --git a/test/messaging/GasFeePricingUpgradeable.t.sol b/test/messaging/GasFeePricingUpgradeable.t.sol deleted file mode 100644 index 920288b59..000000000 --- a/test/messaging/GasFeePricingUpgradeable.t.sol +++ /dev/null @@ -1,515 +0,0 @@ -// SPDX-License-Identifier: MIT - -pragma solidity 0.8.13; - -import "forge-std/Test.sol"; -import {Utilities} from "../utils/Utilities.sol"; - -import "src-messaging/AuthVerifier.sol"; -import "src-messaging/GasFeePricingUpgradeable.sol"; -import "src-messaging/MessageBusUpgradeable.sol"; -import "src-messaging/libraries/GasFeePricingUpdates.sol"; - -contract GasFeePricingUpgradeableTest is Test { - struct ChainVars { - uint256 gasTokenPrice; - uint256 gasUnitPrice; - uint256 gasDropMax; - uint256 gasUnitsRcvMsg; - uint256 minGasUsageFeeUsd; - uint256 markupGasDrop; - uint256 markupGasUsage; - address gasFeePricing; - } - - Utilities internal utils; - - AuthVerifier internal authVerifier; - GasFeePricingUpgradeable internal gasFeePricing; - MessageBusUpgradeable internal messageBus; - - ChainVars internal localVars; - - mapping(uint256 => ChainVars) internal remoteVars; - - uint256[] internal remoteChainIds; - uint256 internal constant TEST_CHAINS = 5; - - address public constant NODE = address(1337); - - // enable receiving overpaid fees - receive() external payable { - this; - } - - /*╔══════════════════════════════════════════════════════════════════════╗*\ - ▏*║ SETUP ║*▕ - \*╚══════════════════════════════════════════════════════════════════════╝*/ - - function setUp() public { - utils = new Utilities(); - authVerifier = new AuthVerifier(NODE); - - // local gas token is worth exactly 1 USD - localVars.gasTokenPrice = 10**18; - - MessageBusUpgradeable busImpl = new MessageBusUpgradeable(); - GasFeePricingUpgradeable pricingImpl = new GasFeePricingUpgradeable(); - - messageBus = MessageBusUpgradeable(utils.deployTransparentProxy(address(busImpl))); - gasFeePricing = GasFeePricingUpgradeable(utils.deployTransparentProxy(address(pricingImpl))); - - // I don't have extra 10M laying around, so let's initialize those proxies - messageBus.initialize(address(gasFeePricing), address(authVerifier)); - gasFeePricing.initialize(address(messageBus), localVars.gasTokenPrice); - - remoteChainIds = new uint256[](TEST_CHAINS); - for (uint256 i = 0; i < TEST_CHAINS; ++i) { - uint256 remoteChainId = i + 1; - remoteChainIds[i] = remoteChainId; - address remoteGasFeePricing = utils.getNextUserAddress(); - remoteVars[remoteChainId].gasFeePricing = remoteGasFeePricing; - gasFeePricing.setTrustedRemote(remoteChainId, utils.addressToBytes32(remoteGasFeePricing)); - } - } - - /*╔══════════════════════════════════════════════════════════════════════╗*\ - ▏*║ SECURITY TESTS ║*▕ - \*╚══════════════════════════════════════════════════════════════════════╝*/ - - function testIsInitialized() public { - utils.checkAccess( - address(messageBus), - abi.encodeWithSelector(MessageBusUpgradeable.initialize.selector, address(0), address(0)), - "Initializable: contract is already initialized" - ); - - utils.checkAccess( - address(gasFeePricing), - abi.encodeWithSelector(GasFeePricingUpgradeable.initialize.selector, address(0), 0, 0, 0), - "Initializable: contract is already initialized" - ); - } - - function testCheckAccessControl() public { - address _gfp = address(gasFeePricing); - utils.checkAccess( - _gfp, - abi.encodeWithSelector( - GasFeePricingUpgradeable.setRemoteConfig.selector, - new uint256[](1), - new uint112[](1), - new uint80[](1), - new uint32[](1) - ), - "Ownable: caller is not the owner" - ); - utils.checkAccess( - _gfp, - abi.encodeWithSelector( - GasFeePricingUpgradeable.setRemoteInfo.selector, - new uint256[](1), - new uint128[](1), - new uint128[](1) - ), - "Ownable: caller is not the owner" - ); - utils.checkAccess( - _gfp, - abi.encodeWithSelector( - GasFeePricingUpgradeable.setRemoteMarkups.selector, - new uint256[](1), - new uint16[](1), - new uint16[](1) - ), - "Ownable: caller is not the owner" - ); - - utils.checkAccess( - _gfp, - abi.encodeWithSelector(GasFeePricingUpgradeable.updateLocalConfig.selector, 0, 0, 0), - "Ownable: caller is not the owner" - ); - - utils.checkAccess( - _gfp, - abi.encodeWithSelector(GasFeePricingUpgradeable.updateLocalInfo.selector, 0, 0), - "Ownable: caller is not the owner" - ); - } - - /*╔══════════════════════════════════════════════════════════════════════╗*\ - ▏*║ ENCODING TESTS ║*▕ - \*╚══════════════════════════════════════════════════════════════════════╝*/ - - function testEncodeConfig( - uint112 newValueA, - uint80 newValueB, - uint32 newValueC - ) public { - bytes memory message = GasFeePricingUpdates.encodeConfig(newValueA, newValueB, newValueC); - uint8 _msgType = GasFeePricingUpdates.messageType(message); - (uint112 _newValueA, uint80 _newValueB, uint32 _newValueC) = GasFeePricingUpdates.decodeConfig(message); - assertEq(_msgType, uint8(GasFeePricingUpdates.MsgType.UPDATE_CONFIG), "Failed to encode msgType"); - assertEq(_newValueA, newValueA, "Failed to encode newValueA"); - assertEq(_newValueB, newValueB, "Failed to encode newValueB"); - assertEq(_newValueC, newValueC, "Failed to encode newValueC"); - } - - function testEncodeInfo(uint128 newValueA, uint128 newValueB) public { - bytes memory message = GasFeePricingUpdates.encodeInfo(newValueA, newValueB); - uint8 _msgType = GasFeePricingUpdates.messageType(message); - (uint128 _newValueA, uint128 _newValueB) = GasFeePricingUpdates.decodeInfo(message); - assertEq(_msgType, uint8(GasFeePricingUpdates.MsgType.UPDATE_INFO), "Failed to encode msgType"); - assertEq(_newValueA, newValueA, "Failed to encode newValueA"); - assertEq(_newValueB, newValueB, "Failed to encode newValueB"); - } - - /*╔══════════════════════════════════════════════════════════════════════╗*\ - ▏*║ GETTERS/SETTERS TESTS ║*▕ - \*╚══════════════════════════════════════════════════════════════════════╝*/ - - function testInitializedCorrectly() public { - (uint128 _gasTokenPrice, ) = gasFeePricing.localInfo(); - assertEq(_gasTokenPrice, localVars.gasTokenPrice, "Failed to init: gasTokenPrice"); - assertEq(gasFeePricing.messageBus(), address(messageBus), "Failed to init: messageBus"); - } - - function testSetRemoteConfig() public { - uint112[] memory gasDropMax = new uint112[](TEST_CHAINS); - uint80[] memory gasUnitsRcvMsg = new uint80[](TEST_CHAINS); - uint32[] memory minGasUsageFeeUsd = new uint32[](TEST_CHAINS); - for (uint256 i = 0; i < TEST_CHAINS; ++i) { - gasDropMax[i] = uint112((i + 1) * 10**18); - gasUnitsRcvMsg[i] = uint80((i + 1) * 420420); - minGasUsageFeeUsd[i] = uint32((i + 1) * 10000); - } - _setRemoteConfig(remoteChainIds, gasDropMax, gasUnitsRcvMsg, minGasUsageFeeUsd); - for (uint256 i = 0; i < TEST_CHAINS; ++i) { - _checkRemoteConfig(remoteChainIds[i]); - } - } - - function testSetRemoteConfigZeroDropSucceeds() public { - uint112[] memory gasDropMax = new uint112[](TEST_CHAINS); - uint80[] memory gasUnitsRcvMsg = new uint80[](TEST_CHAINS); - uint32[] memory minGasUsageFeeUsd = new uint32[](TEST_CHAINS); - for (uint256 i = 0; i < TEST_CHAINS; ++i) { - gasDropMax[i] = uint112(i * 10**17); - gasUnitsRcvMsg[i] = uint80((i + 1) * 133769); - minGasUsageFeeUsd[i] = uint32((i + 1) * 1000); - } - _setRemoteConfig(remoteChainIds, gasDropMax, gasUnitsRcvMsg, minGasUsageFeeUsd); - for (uint256 i = 0; i < TEST_CHAINS; ++i) { - _checkRemoteConfig(remoteChainIds[i]); - } - } - - function testSetRemoteConfigZeroFeeSucceeds() public { - uint112[] memory gasDropMax = new uint112[](TEST_CHAINS); - uint80[] memory gasUnitsRcvMsg = new uint80[](TEST_CHAINS); - uint32[] memory minGasUsageFeeUsd = new uint32[](TEST_CHAINS); - for (uint256 i = 0; i < TEST_CHAINS; ++i) { - gasDropMax[i] = uint112((i + 1) * 10**16); - gasUnitsRcvMsg[i] = uint80((i + 1) * 696969); - minGasUsageFeeUsd[i] = uint32(i * 5000); - } - _setRemoteConfig(remoteChainIds, gasDropMax, gasUnitsRcvMsg, minGasUsageFeeUsd); - for (uint256 i = 0; i < TEST_CHAINS; ++i) { - _checkRemoteConfig(remoteChainIds[i]); - } - } - - function testSetRemoteConfigZeroGasReverts() public { - uint112[] memory gasDropMax = new uint112[](TEST_CHAINS); - uint80[] memory gasUnitsRcvMsg = new uint80[](TEST_CHAINS); - uint32[] memory minGasUsageFeeUsd = new uint32[](TEST_CHAINS); - for (uint256 i = 0; i < TEST_CHAINS; ++i) { - gasDropMax[i] = uint112((i + 1) * 10**15); - gasUnitsRcvMsg[i] = uint80(i * 123456); - minGasUsageFeeUsd[i] = uint32((i + 1) * 5000); - } - utils.checkRevert( - address(this), - address(gasFeePricing), - abi.encodeWithSelector( - GasFeePricingUpgradeable.setRemoteConfig.selector, - remoteChainIds, - gasDropMax, - gasUnitsRcvMsg, - minGasUsageFeeUsd - ), - "Gas amount is not set" - ); - } - - function testSetRemoteInfo() public { - (uint128[] memory gasTokenPrices, uint128[] memory gasUnitPrices) = _generateTestInfoValues(); - _setRemoteInfo(remoteChainIds, gasTokenPrices, gasUnitPrices); - for (uint256 i = 0; i < TEST_CHAINS; ++i) { - uint256 chainId = remoteChainIds[i]; - _checkRemoteInfo(chainId); - _checkRemoteRatios(chainId); - } - } - - function testSetRemoteInfoZeroTokenPriceReverts() public { - (uint128[] memory gasTokenPrices, uint128[] memory gasUnitPrices) = _generateTestInfoValues(); - gasTokenPrices[2] = 0; - utils.checkRevert( - address(this), - address(gasFeePricing), - abi.encodeWithSelector( - GasFeePricingUpgradeable.setRemoteInfo.selector, - remoteChainIds, - gasTokenPrices, - gasUnitPrices - ), - "Remote gas token price is not set" - ); - } - - function testSetRemoteInfoZeroUnitPriceSucceeds() public { - (uint128[] memory gasTokenPrices, uint128[] memory gasUnitPrices) = _generateTestInfoValues(); - gasTokenPrices[2] = 100 * 10**18; - gasUnitPrices[3] = 0; - _setRemoteInfo(remoteChainIds, gasTokenPrices, gasUnitPrices); - for (uint256 i = 0; i < TEST_CHAINS; ++i) { - uint256 chainId = remoteChainIds[i]; - _checkRemoteInfo(chainId); - _checkRemoteRatios(chainId); - } - } - - function _generateTestInfoValues() - internal - pure - returns (uint128[] memory gasTokenPrices, uint128[] memory gasUnitPrices) - { - gasTokenPrices = new uint128[](TEST_CHAINS); - gasUnitPrices = new uint128[](TEST_CHAINS); - // 100 gwei, gasToken = $2000 - gasTokenPrices[0] = 2000 * 10**18; - gasUnitPrices[0] = 100 * 10**9; - // 5 gwei, gasToken = $1000 - gasTokenPrices[1] = 1000 * 10**18; - gasUnitPrices[1] = 5 * 10**9; - // 2000 gwei, gasToken = $0.5 - gasTokenPrices[2] = (5 * 10**18) / 10; - gasUnitPrices[2] = 2000 * 10**9; - // 1 gwei, gasToken = $2000 - gasTokenPrices[3] = 2000 * 10**18; - gasUnitPrices[3] = 10**9; - // 0.04 gwei, gasToken = $0.01 - gasTokenPrices[4] = (1 * 10**18) / 100; - gasUnitPrices[4] = (4 * 10**9) / 100; - } - - function testSetRemoteMarkups() public { - uint16[] memory markupGasDrop = new uint16[](TEST_CHAINS); - uint16[] memory markupGasUsage = new uint16[](TEST_CHAINS); - for (uint16 i = 0; i < TEST_CHAINS; ++i) { - // this will set the first chain markups to [0, 0] - markupGasDrop[i] = i * 13; - markupGasUsage[i] = i * 42; - } - _setRemoteMarkups(remoteChainIds, markupGasDrop, markupGasUsage); - for (uint256 i = 0; i < TEST_CHAINS; ++i) { - _checkRemoteMarkups(remoteChainIds[i]); - } - } - - function testUpdateLocalConfig() public { - uint112 gasDropMax = 10 * 10**18; - uint80 gasUnitsRcvMsg = 10**6; - uint32 minGasUsageFeeUsd = 10**4; - - _updateLocalConfig(gasDropMax, gasUnitsRcvMsg, minGasUsageFeeUsd); - _checkLocalConfig(); - } - - function testUpdateLocalConfigZeroDropSucceeds() public { - // should be able to set to zero - uint112 gasDropMax = 0; - uint80 gasUnitsRcvMsg = 2 * 10**6; - uint32 minGasUsageFeeUsd = 2 * 10**4; - _updateLocalConfig(gasDropMax, gasUnitsRcvMsg, minGasUsageFeeUsd); - _checkLocalConfig(); - } - - function testUpdateLocalConfigZeroFeeSucceeds() public { - uint112 gasDropMax = 5 * 10**18; - uint80 gasUnitsRcvMsg = 2 * 10**6; - // should be able to set to zero - uint32 minGasUsageFeeUsd = 0; - _updateLocalConfig(gasDropMax, gasUnitsRcvMsg, minGasUsageFeeUsd); - _checkLocalConfig(); - } - - function testUpdateLocalConfigZeroGasReverts() public { - uint112 gasDropMax = 10**18; - // should NOT be able to set to zero - uint80 gasUnitsRcvMsg = 0; - uint32 minGasUsageFeeUsd = 3 * 10**4; - - utils.checkRevert( - address(this), - address(gasFeePricing), - abi.encodeWithSelector( - GasFeePricingUpgradeable.updateLocalConfig.selector, - gasDropMax, - gasUnitsRcvMsg, - minGasUsageFeeUsd - ), - "Gas amount is not set" - ); - } - - function testUpdateLocalInfo() public { - testSetRemoteInfo(); - - uint128 gasTokenPrice = 2 * 10**18; - uint128 gasUnitPrice = 10 * 10**9; - _updateLocalInfo(gasTokenPrice, gasUnitPrice); - _checkLocalInfo(); - for (uint256 i = 0; i < TEST_CHAINS; ++i) { - _checkRemoteRatios(i + 1); - } - } - - function testUpdateLocalInfoZeroTokenPriceReverts() public { - testSetRemoteInfo(); - - uint128 gasTokenPrice = 0; - uint128 gasUnitPrice = 10 * 10**9; - - utils.checkRevert( - address(this), - address(gasFeePricing), - abi.encodeWithSelector(GasFeePricingUpgradeable.updateLocalInfo.selector, gasTokenPrice, gasUnitPrice), - "Gas token price is not set" - ); - } - - function testUpdateLocalInfoZeroUnitPriceSucceeds() public { - testSetRemoteInfo(); - - uint128 gasTokenPrice = 4 * 10**17; - uint128 gasUnitPrice = 0; - _updateLocalInfo(gasTokenPrice, gasUnitPrice); - _checkLocalInfo(); - for (uint256 i = 0; i < TEST_CHAINS; ++i) { - _checkRemoteRatios(i + 1); - } - } - - /*╔══════════════════════════════════════════════════════════════════════╗*\ - ▏*║ INTERNAL CHECKERS ║*▕ - \*╚══════════════════════════════════════════════════════════════════════╝*/ - - function _checkRemoteConfig(uint256 _chainId) internal { - (uint112 gasDropMax, uint80 gasUnitsRcvMsg, uint32 minGasUsageFeeUsd, , ) = gasFeePricing.remoteConfig( - _chainId - ); - assertEq(gasDropMax, remoteVars[_chainId].gasDropMax, "remoteMaxGasDrop is incorrect"); - assertEq(gasUnitsRcvMsg, remoteVars[_chainId].gasUnitsRcvMsg, "remoteGasUnitsRcvMsg is incorrect"); - assertEq(minGasUsageFeeUsd, remoteVars[_chainId].minGasUsageFeeUsd, "remoteMinGasUsageFeeUsd is incorrect"); - } - - function _checkRemoteInfo(uint256 _chainId) internal { - (uint128 gasTokenPrice, uint128 gasUnitPrice) = gasFeePricing.remoteInfo(_chainId); - assertEq(gasTokenPrice, remoteVars[_chainId].gasTokenPrice, "remoteGasTokenPrice is incorrect"); - assertEq(gasUnitPrice, remoteVars[_chainId].gasUnitPrice, "remoteGasUnitPrice is incorrect"); - } - - function _checkRemoteMarkups(uint256 _chainId) internal { - (, , , uint16 markupGasDrop, uint16 markupGasUsage) = gasFeePricing.remoteConfig(_chainId); - assertEq(markupGasDrop, remoteVars[_chainId].markupGasDrop, "remoteMarkupGasDrop is incorrect"); - assertEq(markupGasUsage, remoteVars[_chainId].markupGasUsage, "remoteMarkupGasUsage is incorrect"); - } - - function _checkRemoteRatios(uint256 _chainId) internal { - (uint96 gasTokenPriceRatio, uint160 gasUnitPriceRatio) = gasFeePricing.remoteRatios(_chainId); - uint256 _gasTokenPriceRatio = (remoteVars[_chainId].gasTokenPrice * 10**18) / localVars.gasTokenPrice; - uint256 _gasUnitPriceRatio = (remoteVars[_chainId].gasUnitPrice * remoteVars[_chainId].gasTokenPrice * 10**18) / - localVars.gasTokenPrice; - assertEq(gasTokenPriceRatio, _gasTokenPriceRatio, "gasTokenPriceRatio is incorrect"); - assertEq(gasUnitPriceRatio, _gasUnitPriceRatio, "gasUnitPriceRatio is incorrect"); - } - - function _checkLocalConfig() internal { - (uint112 gasDropMax, uint80 gasUnitsRcvMsg, uint32 minGasUsageFeeUsd, , ) = gasFeePricing.localConfig(); - assertEq(gasDropMax, localVars.gasDropMax, "localMaxGasDrop is incorrect"); - assertEq(gasUnitsRcvMsg, localVars.gasUnitsRcvMsg, "localGasUnitsRcvMsg is incorrect"); - assertEq(minGasUsageFeeUsd, localVars.minGasUsageFeeUsd, "localMinGasUsageFeeUsd is incorrect"); - } - - function _checkLocalInfo() internal { - (uint128 gasTokenPrice, uint128 gasUnitPrice) = gasFeePricing.localInfo(); - assertEq(gasTokenPrice, localVars.gasTokenPrice, "localGasTokenPrice is incorrect"); - assertEq(gasUnitPrice, localVars.gasUnitPrice, "gasUnitPrice is incorrect"); - } - - /*╔══════════════════════════════════════════════════════════════════════╗*\ - ▏*║ INTERNAL SETTERS ║*▕ - \*╚══════════════════════════════════════════════════════════════════════╝*/ - - function _setRemoteConfig( - uint256[] memory _chainIds, - uint112[] memory _gasDropMax, - uint80[] memory _gasUnitsRcvMsg, - uint32[] memory _minGasUsageFeeUsd - ) internal { - for (uint256 i = 0; i < _chainIds.length; ++i) { - uint256 chainId = _chainIds[i]; - remoteVars[chainId].gasDropMax = _gasDropMax[i]; - remoteVars[chainId].gasUnitsRcvMsg = _gasUnitsRcvMsg[i]; - remoteVars[chainId].minGasUsageFeeUsd = _minGasUsageFeeUsd[i]; - } - gasFeePricing.setRemoteConfig(_chainIds, _gasDropMax, _gasUnitsRcvMsg, _minGasUsageFeeUsd); - } - - function _setRemoteInfo( - uint256[] memory _chainIds, - uint128[] memory _gasTokenPrice, - uint128[] memory _gasUnitPrice - ) internal { - for (uint256 i = 0; i < _chainIds.length; ++i) { - uint256 chainId = _chainIds[i]; - remoteVars[chainId].gasTokenPrice = _gasTokenPrice[i]; - remoteVars[chainId].gasUnitPrice = _gasUnitPrice[i]; - } - gasFeePricing.setRemoteInfo(_chainIds, _gasTokenPrice, _gasUnitPrice); - } - - function _setRemoteMarkups( - uint256[] memory _chainIds, - uint16[] memory _markupGasDrop, - uint16[] memory _markupGasUsage - ) internal { - for (uint256 i = 0; i < _chainIds.length; ++i) { - remoteVars[_chainIds[i]].markupGasDrop = _markupGasDrop[i]; - remoteVars[_chainIds[i]].markupGasUsage = _markupGasUsage[i]; - } - gasFeePricing.setRemoteMarkups(_chainIds, _markupGasDrop, _markupGasUsage); - } - - function _updateLocalConfig( - uint112 _gasDropMax, - uint80 _gasUnitsRcvMsg, - uint32 _minGasUsageFeeUsd - ) internal { - localVars.gasDropMax = _gasDropMax; - localVars.gasUnitsRcvMsg = _gasUnitsRcvMsg; - localVars.minGasUsageFeeUsd = _minGasUsageFeeUsd; - uint256 fee = gasFeePricing.estimateUpdateFees(); - gasFeePricing.updateLocalConfig{value: fee}(_gasDropMax, _gasUnitsRcvMsg, _minGasUsageFeeUsd); - } - - function _updateLocalInfo(uint128 _gasTokenPrice, uint128 _gasUnitPrice) internal { - localVars.gasTokenPrice = _gasTokenPrice; - localVars.gasUnitPrice = _gasUnitPrice; - uint256 fee = gasFeePricing.estimateUpdateFees(); - gasFeePricing.updateLocalInfo{value: fee}(_gasTokenPrice, _gasUnitPrice); - } -} diff --git a/test/messaging/gfp/GasFeePricingMessaging.t.sol b/test/messaging/gfp/GasFeePricingMessaging.t.sol new file mode 100644 index 000000000..494dc4639 --- /dev/null +++ b/test/messaging/gfp/GasFeePricingMessaging.t.sol @@ -0,0 +1,34 @@ +// SPDX-License-Identifier: MIT + +pragma solidity 0.8.13; + +import "./GasFeePricingSetup.t.sol"; + +contract GasFeePricingUpgradeableMessagingTest is GasFeePricingSetup { + /*╔══════════════════════════════════════════════════════════════════════╗*\ + ▏*║ ENCODING TESTS ║*▕ + \*╚══════════════════════════════════════════════════════════════════════╝*/ + + function testEncodeConfig( + uint112 newValueA, + uint80 newValueB, + uint32 newValueC + ) public { + bytes memory message = GasFeePricingUpdates.encodeConfig(newValueA, newValueB, newValueC); + uint8 _msgType = GasFeePricingUpdates.messageType(message); + (uint112 _newValueA, uint80 _newValueB, uint32 _newValueC) = GasFeePricingUpdates.decodeConfig(message); + assertEq(_msgType, uint8(GasFeePricingUpdates.MsgType.UPDATE_CONFIG), "Failed to encode msgType"); + assertEq(_newValueA, newValueA, "Failed to encode newValueA"); + assertEq(_newValueB, newValueB, "Failed to encode newValueB"); + assertEq(_newValueC, newValueC, "Failed to encode newValueC"); + } + + function testEncodeInfo(uint128 newValueA, uint128 newValueB) public { + bytes memory message = GasFeePricingUpdates.encodeInfo(newValueA, newValueB); + uint8 _msgType = GasFeePricingUpdates.messageType(message); + (uint128 _newValueA, uint128 _newValueB) = GasFeePricingUpdates.decodeInfo(message); + assertEq(_msgType, uint8(GasFeePricingUpdates.MsgType.UPDATE_INFO), "Failed to encode msgType"); + assertEq(_newValueA, newValueA, "Failed to encode newValueA"); + assertEq(_newValueB, newValueB, "Failed to encode newValueB"); + } +} diff --git a/test/messaging/gfp/GasFeePricingSecurity.t.sol b/test/messaging/gfp/GasFeePricingSecurity.t.sol new file mode 100644 index 000000000..0708d66d5 --- /dev/null +++ b/test/messaging/gfp/GasFeePricingSecurity.t.sol @@ -0,0 +1,78 @@ +// SPDX-License-Identifier: MIT + +pragma solidity 0.8.13; + +import "./GasFeePricingSetup.t.sol"; + +contract GasFeePricingUpgradeableSecurityTest is GasFeePricingSetup { + /*╔══════════════════════════════════════════════════════════════════════╗*\ + ▏*║ SECURITY TESTS ║*▕ + \*╚══════════════════════════════════════════════════════════════════════╝*/ + + function testInitializedCorrectly() public { + (uint128 _gasTokenPrice, ) = gasFeePricing.localInfo(); + assertEq(_gasTokenPrice, localVars.gasTokenPrice, "Failed to init: gasTokenPrice"); + assertEq(gasFeePricing.messageBus(), address(messageBus), "Failed to init: messageBus"); + } + + function testIsInitialized() public { + utils.checkAccess( + address(messageBus), + abi.encodeWithSelector(MessageBusUpgradeable.initialize.selector, address(0), address(0)), + "Initializable: contract is already initialized" + ); + + utils.checkAccess( + address(gasFeePricing), + abi.encodeWithSelector(GasFeePricingUpgradeable.initialize.selector, address(0), 0, 0, 0), + "Initializable: contract is already initialized" + ); + } + + function testCheckAccessControl() public { + address _gfp = address(gasFeePricing); + utils.checkAccess( + _gfp, + abi.encodeWithSelector( + GasFeePricingUpgradeable.setRemoteConfig.selector, + new uint256[](1), + new uint112[](1), + new uint80[](1), + new uint32[](1) + ), + "Ownable: caller is not the owner" + ); + utils.checkAccess( + _gfp, + abi.encodeWithSelector( + GasFeePricingUpgradeable.setRemoteInfo.selector, + new uint256[](1), + new uint128[](1), + new uint128[](1) + ), + "Ownable: caller is not the owner" + ); + utils.checkAccess( + _gfp, + abi.encodeWithSelector( + GasFeePricingUpgradeable.setRemoteMarkups.selector, + new uint256[](1), + new uint16[](1), + new uint16[](1) + ), + "Ownable: caller is not the owner" + ); + + utils.checkAccess( + _gfp, + abi.encodeWithSelector(GasFeePricingUpgradeable.updateLocalConfig.selector, 0, 0, 0), + "Ownable: caller is not the owner" + ); + + utils.checkAccess( + _gfp, + abi.encodeWithSelector(GasFeePricingUpgradeable.updateLocalInfo.selector, 0, 0), + "Ownable: caller is not the owner" + ); + } +} diff --git a/test/messaging/gfp/GasFeePricingSetters.t.sol b/test/messaging/gfp/GasFeePricingSetters.t.sol new file mode 100644 index 000000000..17da2f1f5 --- /dev/null +++ b/test/messaging/gfp/GasFeePricingSetters.t.sol @@ -0,0 +1,215 @@ +// SPDX-License-Identifier: MIT + +pragma solidity 0.8.13; + +import "./GasFeePricingSetup.t.sol"; + +contract GasFeePricingUpgradeableSettersTest is GasFeePricingSetup { + /*╔══════════════════════════════════════════════════════════════════════╗*\ + ▏*║ GETTERS/SETTERS TESTS ║*▕ + \*╚══════════════════════════════════════════════════════════════════════╝*/ + + function testSetRemoteConfig() public { + uint112[] memory gasDropMax = new uint112[](TEST_CHAINS); + uint80[] memory gasUnitsRcvMsg = new uint80[](TEST_CHAINS); + uint32[] memory minGasUsageFeeUsd = new uint32[](TEST_CHAINS); + for (uint256 i = 0; i < TEST_CHAINS; ++i) { + gasDropMax[i] = uint112((i + 1) * 10**18); + gasUnitsRcvMsg[i] = uint80((i + 1) * 420420); + minGasUsageFeeUsd[i] = uint32((i + 1) * 10000); + } + _setRemoteConfig(remoteChainIds, gasDropMax, gasUnitsRcvMsg, minGasUsageFeeUsd); + for (uint256 i = 0; i < TEST_CHAINS; ++i) { + _checkRemoteConfig(remoteChainIds[i]); + } + } + + function testSetRemoteConfigZeroDropSucceeds() public { + uint112[] memory gasDropMax = new uint112[](TEST_CHAINS); + uint80[] memory gasUnitsRcvMsg = new uint80[](TEST_CHAINS); + uint32[] memory minGasUsageFeeUsd = new uint32[](TEST_CHAINS); + for (uint256 i = 0; i < TEST_CHAINS; ++i) { + gasDropMax[i] = uint112(i * 10**17); + gasUnitsRcvMsg[i] = uint80((i + 1) * 133769); + minGasUsageFeeUsd[i] = uint32((i + 1) * 1000); + } + _setRemoteConfig(remoteChainIds, gasDropMax, gasUnitsRcvMsg, minGasUsageFeeUsd); + for (uint256 i = 0; i < TEST_CHAINS; ++i) { + _checkRemoteConfig(remoteChainIds[i]); + } + } + + function testSetRemoteConfigZeroFeeSucceeds() public { + uint112[] memory gasDropMax = new uint112[](TEST_CHAINS); + uint80[] memory gasUnitsRcvMsg = new uint80[](TEST_CHAINS); + uint32[] memory minGasUsageFeeUsd = new uint32[](TEST_CHAINS); + for (uint256 i = 0; i < TEST_CHAINS; ++i) { + gasDropMax[i] = uint112((i + 1) * 10**16); + gasUnitsRcvMsg[i] = uint80((i + 1) * 696969); + minGasUsageFeeUsd[i] = uint32(i * 5000); + } + _setRemoteConfig(remoteChainIds, gasDropMax, gasUnitsRcvMsg, minGasUsageFeeUsd); + for (uint256 i = 0; i < TEST_CHAINS; ++i) { + _checkRemoteConfig(remoteChainIds[i]); + } + } + + function testSetRemoteConfigZeroGasReverts() public { + uint112[] memory gasDropMax = new uint112[](TEST_CHAINS); + uint80[] memory gasUnitsRcvMsg = new uint80[](TEST_CHAINS); + uint32[] memory minGasUsageFeeUsd = new uint32[](TEST_CHAINS); + for (uint256 i = 0; i < TEST_CHAINS; ++i) { + gasDropMax[i] = uint112((i + 1) * 10**15); + gasUnitsRcvMsg[i] = uint80(i * 123456); + minGasUsageFeeUsd[i] = uint32((i + 1) * 5000); + } + utils.checkRevert( + address(this), + address(gasFeePricing), + abi.encodeWithSelector( + GasFeePricingUpgradeable.setRemoteConfig.selector, + remoteChainIds, + gasDropMax, + gasUnitsRcvMsg, + minGasUsageFeeUsd + ), + "Gas amount is not set" + ); + } + + function testSetRemoteInfo() public { + (uint128[] memory gasTokenPrices, uint128[] memory gasUnitPrices) = _generateTestInfoValues(); + _setRemoteInfo(remoteChainIds, gasTokenPrices, gasUnitPrices); + for (uint256 i = 0; i < TEST_CHAINS; ++i) { + uint256 chainId = remoteChainIds[i]; + _checkRemoteInfo(chainId); + _checkRemoteRatios(chainId); + } + } + + function testSetRemoteInfoZeroTokenPriceReverts() public { + (uint128[] memory gasTokenPrices, uint128[] memory gasUnitPrices) = _generateTestInfoValues(); + gasTokenPrices[2] = 0; + utils.checkRevert( + address(this), + address(gasFeePricing), + abi.encodeWithSelector( + GasFeePricingUpgradeable.setRemoteInfo.selector, + remoteChainIds, + gasTokenPrices, + gasUnitPrices + ), + "Remote gas token price is not set" + ); + } + + function testSetRemoteInfoZeroUnitPriceSucceeds() public { + (uint128[] memory gasTokenPrices, uint128[] memory gasUnitPrices) = _generateTestInfoValues(); + gasTokenPrices[2] = 100 * 10**18; + gasUnitPrices[3] = 0; + _setRemoteInfo(remoteChainIds, gasTokenPrices, gasUnitPrices); + for (uint256 i = 0; i < TEST_CHAINS; ++i) { + uint256 chainId = remoteChainIds[i]; + _checkRemoteInfo(chainId); + _checkRemoteRatios(chainId); + } + } + + function testSetRemoteMarkups() public { + uint16[] memory markupGasDrop = new uint16[](TEST_CHAINS); + uint16[] memory markupGasUsage = new uint16[](TEST_CHAINS); + for (uint16 i = 0; i < TEST_CHAINS; ++i) { + // this will set the first chain markups to [0, 0] + markupGasDrop[i] = i * 13; + markupGasUsage[i] = i * 42; + } + _setRemoteMarkups(remoteChainIds, markupGasDrop, markupGasUsage); + for (uint256 i = 0; i < TEST_CHAINS; ++i) { + _checkRemoteMarkups(remoteChainIds[i]); + } + } + + function testUpdateLocalConfig() public { + uint112 gasDropMax = 10 * 10**18; + uint80 gasUnitsRcvMsg = 10**6; + uint32 minGasUsageFeeUsd = 10**4; + + _updateLocalConfig(gasDropMax, gasUnitsRcvMsg, minGasUsageFeeUsd); + _checkLocalConfig(); + } + + function testUpdateLocalConfigZeroDropSucceeds() public { + // should be able to set to zero + uint112 gasDropMax = 0; + uint80 gasUnitsRcvMsg = 2 * 10**6; + uint32 minGasUsageFeeUsd = 2 * 10**4; + _updateLocalConfig(gasDropMax, gasUnitsRcvMsg, minGasUsageFeeUsd); + _checkLocalConfig(); + } + + function testUpdateLocalConfigZeroFeeSucceeds() public { + uint112 gasDropMax = 5 * 10**18; + uint80 gasUnitsRcvMsg = 2 * 10**6; + // should be able to set to zero + uint32 minGasUsageFeeUsd = 0; + _updateLocalConfig(gasDropMax, gasUnitsRcvMsg, minGasUsageFeeUsd); + _checkLocalConfig(); + } + + function testUpdateLocalConfigZeroGasReverts() public { + uint112 gasDropMax = 10**18; + // should NOT be able to set to zero + uint80 gasUnitsRcvMsg = 0; + uint32 minGasUsageFeeUsd = 3 * 10**4; + + utils.checkRevert( + address(this), + address(gasFeePricing), + abi.encodeWithSelector( + GasFeePricingUpgradeable.updateLocalConfig.selector, + gasDropMax, + gasUnitsRcvMsg, + minGasUsageFeeUsd + ), + "Gas amount is not set" + ); + } + + function testUpdateLocalInfo() public { + testSetRemoteInfo(); + + uint128 gasTokenPrice = 2 * 10**18; + uint128 gasUnitPrice = 10 * 10**9; + _updateLocalInfo(gasTokenPrice, gasUnitPrice); + _checkLocalInfo(); + for (uint256 i = 0; i < TEST_CHAINS; ++i) { + _checkRemoteRatios(i + 1); + } + } + + function testUpdateLocalInfoZeroTokenPriceReverts() public { + testSetRemoteInfo(); + + uint128 gasTokenPrice = 0; + uint128 gasUnitPrice = 10 * 10**9; + + utils.checkRevert( + address(this), + address(gasFeePricing), + abi.encodeWithSelector(GasFeePricingUpgradeable.updateLocalInfo.selector, gasTokenPrice, gasUnitPrice), + "Gas token price is not set" + ); + } + + function testUpdateLocalInfoZeroUnitPriceSucceeds() public { + testSetRemoteInfo(); + + uint128 gasTokenPrice = 4 * 10**17; + uint128 gasUnitPrice = 0; + _updateLocalInfo(gasTokenPrice, gasUnitPrice); + _checkLocalInfo(); + for (uint256 i = 0; i < TEST_CHAINS; ++i) { + _checkRemoteRatios(i + 1); + } + } +} diff --git a/test/messaging/gfp/GasFeePricingSetup.t.sol b/test/messaging/gfp/GasFeePricingSetup.t.sol new file mode 100644 index 000000000..b5ae5233e --- /dev/null +++ b/test/messaging/gfp/GasFeePricingSetup.t.sol @@ -0,0 +1,213 @@ +// SPDX-License-Identifier: MIT + +pragma solidity 0.8.13; + +import "forge-std/Test.sol"; +import {Utilities} from "../../utils/Utilities.sol"; + +import "src-messaging/AuthVerifier.sol"; +import "src-messaging/GasFeePricingUpgradeable.sol"; +import "src-messaging/MessageBusUpgradeable.sol"; +import "src-messaging/libraries/GasFeePricingUpdates.sol"; + +abstract contract GasFeePricingSetup is Test { + struct ChainVars { + uint256 gasTokenPrice; + uint256 gasUnitPrice; + uint256 gasDropMax; + uint256 gasUnitsRcvMsg; + uint256 minGasUsageFeeUsd; + uint256 markupGasDrop; + uint256 markupGasUsage; + address gasFeePricing; + } + + Utilities internal utils; + + AuthVerifier internal authVerifier; + GasFeePricingUpgradeable internal gasFeePricing; + MessageBusUpgradeable internal messageBus; + + ChainVars internal localVars; + + mapping(uint256 => ChainVars) internal remoteVars; + + uint256[] internal remoteChainIds; + uint256 internal constant TEST_CHAINS = 5; + + address internal constant NODE = address(1337); + + // enable receiving overpaid fees + receive() external payable { + this; + } + + /*╔══════════════════════════════════════════════════════════════════════╗*\ + ▏*║ SETUP ║*▕ + \*╚══════════════════════════════════════════════════════════════════════╝*/ + + function setUp() public { + utils = new Utilities(); + authVerifier = new AuthVerifier(NODE); + + // local gas token is worth exactly 1 USD + localVars.gasTokenPrice = 10**18; + + MessageBusUpgradeable busImpl = new MessageBusUpgradeable(); + GasFeePricingUpgradeable pricingImpl = new GasFeePricingUpgradeable(); + + messageBus = MessageBusUpgradeable(utils.deployTransparentProxy(address(busImpl))); + gasFeePricing = GasFeePricingUpgradeable(utils.deployTransparentProxy(address(pricingImpl))); + + // I don't have extra 10M laying around, so let's initialize those proxies + messageBus.initialize(address(gasFeePricing), address(authVerifier)); + gasFeePricing.initialize(address(messageBus), localVars.gasTokenPrice); + + remoteChainIds = new uint256[](TEST_CHAINS); + for (uint256 i = 0; i < TEST_CHAINS; ++i) { + uint256 remoteChainId = i + 1; + remoteChainIds[i] = remoteChainId; + address remoteGasFeePricing = utils.getNextUserAddress(); + remoteVars[remoteChainId].gasFeePricing = remoteGasFeePricing; + gasFeePricing.setTrustedRemote(remoteChainId, utils.addressToBytes32(remoteGasFeePricing)); + } + } + + /*╔══════════════════════════════════════════════════════════════════════╗*\ + ▏*║ VALUES GENERATORS ║*▕ + \*╚══════════════════════════════════════════════════════════════════════╝*/ + + function _generateTestInfoValues() + internal + pure + returns (uint128[] memory gasTokenPrices, uint128[] memory gasUnitPrices) + { + gasTokenPrices = new uint128[](TEST_CHAINS); + gasUnitPrices = new uint128[](TEST_CHAINS); + // 100 gwei, gasToken = $2000 + gasTokenPrices[0] = 2000 * 10**18; + gasUnitPrices[0] = 100 * 10**9; + // 5 gwei, gasToken = $1000 + gasTokenPrices[1] = 1000 * 10**18; + gasUnitPrices[1] = 5 * 10**9; + // 2000 gwei, gasToken = $0.5 + gasTokenPrices[2] = (5 * 10**18) / 10; + gasUnitPrices[2] = 2000 * 10**9; + // 1 gwei, gasToken = $2000 + gasTokenPrices[3] = 2000 * 10**18; + gasUnitPrices[3] = 10**9; + // 0.04 gwei, gasToken = $0.01 + gasTokenPrices[4] = (1 * 10**18) / 100; + gasUnitPrices[4] = (4 * 10**9) / 100; + } + + /*╔══════════════════════════════════════════════════════════════════════╗*\ + ▏*║ CHECKERS ║*▕ + \*╚══════════════════════════════════════════════════════════════════════╝*/ + + function _checkRemoteConfig(uint256 _chainId) internal { + (uint112 gasDropMax, uint80 gasUnitsRcvMsg, uint32 minGasUsageFeeUsd, , ) = gasFeePricing.remoteConfig( + _chainId + ); + assertEq(gasDropMax, remoteVars[_chainId].gasDropMax, "remoteMaxGasDrop is incorrect"); + assertEq(gasUnitsRcvMsg, remoteVars[_chainId].gasUnitsRcvMsg, "remoteGasUnitsRcvMsg is incorrect"); + assertEq(minGasUsageFeeUsd, remoteVars[_chainId].minGasUsageFeeUsd, "remoteMinGasUsageFeeUsd is incorrect"); + } + + function _checkRemoteInfo(uint256 _chainId) internal { + (uint128 gasTokenPrice, uint128 gasUnitPrice) = gasFeePricing.remoteInfo(_chainId); + assertEq(gasTokenPrice, remoteVars[_chainId].gasTokenPrice, "remoteGasTokenPrice is incorrect"); + assertEq(gasUnitPrice, remoteVars[_chainId].gasUnitPrice, "remoteGasUnitPrice is incorrect"); + } + + function _checkRemoteMarkups(uint256 _chainId) internal { + (, , , uint16 markupGasDrop, uint16 markupGasUsage) = gasFeePricing.remoteConfig(_chainId); + assertEq(markupGasDrop, remoteVars[_chainId].markupGasDrop, "remoteMarkupGasDrop is incorrect"); + assertEq(markupGasUsage, remoteVars[_chainId].markupGasUsage, "remoteMarkupGasUsage is incorrect"); + } + + function _checkRemoteRatios(uint256 _chainId) internal { + (uint96 gasTokenPriceRatio, uint160 gasUnitPriceRatio) = gasFeePricing.remoteRatios(_chainId); + uint256 _gasTokenPriceRatio = (remoteVars[_chainId].gasTokenPrice * 10**18) / localVars.gasTokenPrice; + uint256 _gasUnitPriceRatio = (remoteVars[_chainId].gasUnitPrice * remoteVars[_chainId].gasTokenPrice * 10**18) / + localVars.gasTokenPrice; + assertEq(gasTokenPriceRatio, _gasTokenPriceRatio, "gasTokenPriceRatio is incorrect"); + assertEq(gasUnitPriceRatio, _gasUnitPriceRatio, "gasUnitPriceRatio is incorrect"); + } + + function _checkLocalConfig() internal { + (uint112 gasDropMax, uint80 gasUnitsRcvMsg, uint32 minGasUsageFeeUsd, , ) = gasFeePricing.localConfig(); + assertEq(gasDropMax, localVars.gasDropMax, "localMaxGasDrop is incorrect"); + assertEq(gasUnitsRcvMsg, localVars.gasUnitsRcvMsg, "localGasUnitsRcvMsg is incorrect"); + assertEq(minGasUsageFeeUsd, localVars.minGasUsageFeeUsd, "localMinGasUsageFeeUsd is incorrect"); + } + + function _checkLocalInfo() internal { + (uint128 gasTokenPrice, uint128 gasUnitPrice) = gasFeePricing.localInfo(); + assertEq(gasTokenPrice, localVars.gasTokenPrice, "localGasTokenPrice is incorrect"); + assertEq(gasUnitPrice, localVars.gasUnitPrice, "gasUnitPrice is incorrect"); + } + + /*╔══════════════════════════════════════════════════════════════════════╗*\ + ▏*║ SETTERS ║*▕ + \*╚══════════════════════════════════════════════════════════════════════╝*/ + + function _setRemoteConfig( + uint256[] memory _chainIds, + uint112[] memory _gasDropMax, + uint80[] memory _gasUnitsRcvMsg, + uint32[] memory _minGasUsageFeeUsd + ) internal { + for (uint256 i = 0; i < _chainIds.length; ++i) { + uint256 chainId = _chainIds[i]; + remoteVars[chainId].gasDropMax = _gasDropMax[i]; + remoteVars[chainId].gasUnitsRcvMsg = _gasUnitsRcvMsg[i]; + remoteVars[chainId].minGasUsageFeeUsd = _minGasUsageFeeUsd[i]; + } + gasFeePricing.setRemoteConfig(_chainIds, _gasDropMax, _gasUnitsRcvMsg, _minGasUsageFeeUsd); + } + + function _setRemoteInfo( + uint256[] memory _chainIds, + uint128[] memory _gasTokenPrice, + uint128[] memory _gasUnitPrice + ) internal { + for (uint256 i = 0; i < _chainIds.length; ++i) { + uint256 chainId = _chainIds[i]; + remoteVars[chainId].gasTokenPrice = _gasTokenPrice[i]; + remoteVars[chainId].gasUnitPrice = _gasUnitPrice[i]; + } + gasFeePricing.setRemoteInfo(_chainIds, _gasTokenPrice, _gasUnitPrice); + } + + function _setRemoteMarkups( + uint256[] memory _chainIds, + uint16[] memory _markupGasDrop, + uint16[] memory _markupGasUsage + ) internal { + for (uint256 i = 0; i < _chainIds.length; ++i) { + remoteVars[_chainIds[i]].markupGasDrop = _markupGasDrop[i]; + remoteVars[_chainIds[i]].markupGasUsage = _markupGasUsage[i]; + } + gasFeePricing.setRemoteMarkups(_chainIds, _markupGasDrop, _markupGasUsage); + } + + function _updateLocalConfig( + uint112 _gasDropMax, + uint80 _gasUnitsRcvMsg, + uint32 _minGasUsageFeeUsd + ) internal { + localVars.gasDropMax = _gasDropMax; + localVars.gasUnitsRcvMsg = _gasUnitsRcvMsg; + localVars.minGasUsageFeeUsd = _minGasUsageFeeUsd; + uint256 fee = gasFeePricing.estimateUpdateFees(); + gasFeePricing.updateLocalConfig{value: fee}(_gasDropMax, _gasUnitsRcvMsg, _minGasUsageFeeUsd); + } + + function _updateLocalInfo(uint128 _gasTokenPrice, uint128 _gasUnitPrice) internal { + localVars.gasTokenPrice = _gasTokenPrice; + localVars.gasUnitPrice = _gasUnitPrice; + uint256 fee = gasFeePricing.estimateUpdateFees(); + gasFeePricing.updateLocalInfo{value: fee}(_gasTokenPrice, _gasUnitPrice); + } +} From 94675e92f47047122e0aefb9ac4e4d8edf2a1d7f Mon Sep 17 00:00:00 2001 From: ChiTimesChi <88190723+ChiTimesChi@users.noreply.github.com> Date: Mon, 23 May 2022 19:42:32 +0300 Subject: [PATCH 32/47] GFP: implement (remote chain)-specific min fee --- .../messaging/GasFeePricingUpgradeable.sol | 53 ++++++++++--------- 1 file changed, 27 insertions(+), 26 deletions(-) diff --git a/contracts/messaging/GasFeePricingUpgradeable.sol b/contracts/messaging/GasFeePricingUpgradeable.sol index cfe54b39c..af55c0d85 100644 --- a/contracts/messaging/GasFeePricingUpgradeable.sol +++ b/contracts/messaging/GasFeePricingUpgradeable.sol @@ -45,7 +45,7 @@ contract GasFeePricingUpgradeable is SynMessagingReceiverUpgradeable { * to receive "update chain Config/Info" message * uint80 => max value ~= 10**24 * - minGasUsageFeeUsd: minimum amount of "gas usage" part of total messaging fee, - * when sending message to given remote chain. + * when sending message to a given chain. * Quoted in USD, multiplied by USD_DENOMINATOR * uint32 => max value ~= 4 * 10**9 * These are universal values, and they should be the same on all GasFeePricing @@ -201,44 +201,46 @@ contract GasFeePricingUpgradeable is SynMessagingReceiverUpgradeable { /// @dev Extracts the gas information from options and calculates the messaging fee function _estimateGasFee(uint256 _remoteChainId, bytes calldata _options) internal view returns (uint256 fee) { - ChainConfig memory config = remoteConfig[_remoteChainId]; uint256 gasAirdrop; uint256 gasLimit; if (_options.length != 0) { (gasLimit, gasAirdrop, ) = Options.decode(_options); - if (gasAirdrop != 0) { - require(gasAirdrop <= config.gasDropMax, "GasDrop higher than max"); - } } else { gasLimit = DEFAULT_GAS_LIMIT; } - - fee = _estimateGasFee(_remoteChainId, gasAirdrop, gasLimit, config.markupGasDrop, config.markupGasUsage); + fee = _estimateGasFee(_remoteChainId, gasAirdrop, gasLimit); } /// @dev Returns a gas fee for sending a message to remote chain, given the amount of gas to airdrop, /// and amount of gas units for message execution on remote chain. function _estimateGasFee( - uint256 _remoteChainId, + uint256 _chainId, uint256 _gasAirdrop, - uint256 _gasLimit, - uint256 _markupGasDrop, - uint256 _markupGasUsage + uint256 _gasLimit ) internal view returns (uint256 fee) { - ChainRatios memory remoteRatio = remoteRatios[_remoteChainId]; - - // Calculate how much gas airdrop is worth in local chain wei - uint256 feeGasDrop = (_gasAirdrop * remoteRatio.gasTokenPriceRatio) / 10**18; - // Calculate how much gas usage is worth in local chain wei - uint256 feeGasUsage = (_gasLimit * remoteRatio.gasUnitPriceRatio) / 10**18; + // Read config/info for destination (remote) chain + ChainConfig memory dstConfig = remoteConfig[_chainId]; + ChainInfo memory dstInfo = remoteInfo[_chainId]; + // Read info for source (local) chain + ChainInfo memory srcInfo = localInfo; + require(_gasAirdrop <= dstConfig.markupGasDrop, "GasDrop higher than max"); + + // Calculate how much [gas airdrop] is worth in [local chain wei] + uint256 feeGasDrop = (_gasAirdrop * dstInfo.gasTokenPrice) / srcInfo.gasTokenPrice; + // Calculate how much [gas usage] is worth in [local chain wei] + uint256 feeGasUsage = (_gasLimit * dstInfo.gasUnitPrice * dstInfo.gasTokenPrice) / srcInfo.gasTokenPrice; // Sum up the fees multiplied by their respective markups - feeGasDrop = (feeGasDrop * (_markupGasDrop + MARKUP_DENOMINATOR)) / MARKUP_DENOMINATOR; - feeGasUsage = (feeGasUsage * (_markupGasUsage + MARKUP_DENOMINATOR)) / MARKUP_DENOMINATOR; - // TODO: implement remote-chain-specific minGasUsageFee - // Check if gas usage fee is lower than minimum - // uint256 _minGasUsageFee = minGasUsageFee; - // if (feeGasUsage < _minGasUsageFee) feeGasUsage = _minGasUsageFee; + feeGasDrop = (feeGasDrop * (dstConfig.markupGasDrop + MARKUP_DENOMINATOR)) / MARKUP_DENOMINATOR; + feeGasUsage = (feeGasUsage * (dstConfig.markupGasUsage + MARKUP_DENOMINATOR)) / MARKUP_DENOMINATOR; + + // Calculate min fee (specific to destination chain) + // Multiply by 10**18 to convert to wei + // Multiply by 10**18 again, as gasTokenPrice is scaled by 10**18 + // Divide by USD_DENOMINATOR, as minGasUsageFeeUsd is scaled by USD_DENOMINATOR + uint256 minFee = (dstConfig.minGasUsageFeeUsd * 10**36) / (srcInfo.gasTokenPrice * USD_DENOMINATOR); + if (feeGasUsage < minFee) feeGasUsage = minFee; + fee = feeGasDrop + feeGasUsage; } @@ -254,11 +256,10 @@ contract GasFeePricingUpgradeable is SynMessagingReceiverUpgradeable { fees = new uint256[](_chainIds.length); for (uint256 i = 0; i < _chainIds.length; ++i) { uint256 chainId = _chainIds[i]; - ChainConfig memory config = remoteConfig[chainId]; - uint256 gasLimit = config.gasUnitsRcvMsg; + uint256 gasLimit = remoteConfig[chainId].gasUnitsRcvMsg; if (gasLimit == 0) gasLimit = DEFAULT_GAS_LIMIT; - uint256 fee = _estimateGasFee(chainId, 0, gasLimit, config.markupGasDrop, config.markupGasUsage); + uint256 fee = _estimateGasFee(chainId, 0, gasLimit); totalFee += fee; fees[i] = fee; } From aa44c1555ad002c90f47043fe0f23a592af4bc58 Mon Sep 17 00:00:00 2001 From: ChiTimesChi <88190723+ChiTimesChi@users.noreply.github.com> Date: Mon, 23 May 2022 20:35:06 +0300 Subject: [PATCH 33/47] gas fee pricing explained --- contracts/messaging/GasFeePricing.md | 63 ++++++++++++++++++++++++++++ 1 file changed, 63 insertions(+) create mode 100644 contracts/messaging/GasFeePricing.md diff --git a/contracts/messaging/GasFeePricing.md b/contracts/messaging/GasFeePricing.md new file mode 100644 index 000000000..d1c23a36c --- /dev/null +++ b/contracts/messaging/GasFeePricing.md @@ -0,0 +1,63 @@ +# Gas Fee Pricing for Message Bridge + +## Setup + +On every chain `MessageBusUpgradeable` and `GasFeePricingUpgradeable` are deployed. `GasFeePricing` contracts are using `MessageBus` to communicate with each other, so they are set up as the "trusted remote" for one another. + +For every chain we're considering following information: + +- `gasTokenPrice`: price of chain's gas token (ETH, AVAX, BNB, ...) +- `gasUnitPrice`: price of a single gas unit (usually referred as chain's "gwei gas price") + +> Both values are supposed to reflect the latest "average" price. + +`GasFeePricing` contract is storing this information both for the local chain, as well as for all known remote chains. Whenever information for a **local chain** is updated on any of the `GasFeePricing` contracts, it sends messages to all the remote `GasFeePricing` contracts, so they can update it as well. + +This way, the information about chain's gas token/unit prices is synchronized across all chains. + +## Message Passing + +Any contract can interact with `MessageBus` to send a message to a remote chain, specifying both a gas airdrop and a gas limit on the remote chain. +Every remote chain has a different setting for the maximum amount of gas available for the airdrop. That way, the airdrop is fully flexible, and can not be taken advantage of by the bad actors. This value (for every remote chain) is also synchronized across all the chains. + +## Fee calculation + +### Basic calculation + +The fee is split into two parts. Both parts are quoted in the local gas token. + +- Fee for providing gas airdrop on a remote chain. Assuming dropping `gasDrop` worth of the remote gas token. + +```go +feeGasDrop = gasDrop * remoteGasTokenPrice / localGasTokenPrice +``` + +- Fee for executing message on a remote chain. Assuming providing `gasLimit` gas units on the remote chain. + +```go +feeGasUsage = max( + minRemoteFeeUsd / localGasTokenPrice, + gasLimit * remoteGasUnitPrice * remoteGasTokenPrice / localGasTokenPrice +) +``` + +> `minRemoteFeeUsd` is the minimum fee (in $), taken for the gas usage on a remote chain. It is specific to the remote chain, and does not take a local chain into account. `minRemoteFeeUsd` (for every remote chain) is also synchronized across all the chains. + +Note that both fees will express the _exact expected costs_ of delivering the message. + +### Monetizing the messaging. + +Introduce markup. Markup is a value of `0%`, or higher. Markup means how much more is `MessageBus` charging, compared to [expected costs](#basic-calculation). + +Markups are separate for the gas airdrop, and the gas usage. This means that the final fee formula is + +```go +fee = (100% + markupGasDrop) * feeGasDrop + (100% + markupGasUsage) * feeGasUsage +``` + +- Markups are specific to a "local chain - remote chain" pair. +- `markupGasUsage` should be set higher for the remote chains, known to have gas spikes (Ethereum, Fantom). +- `markupGasDrop` can be set to 0, when both local and remote chain are using the same gas token. + > Gas airdrop amount is limited, so it's not possible to bridge gas by sending an empty message with a huge airdrop. +- `markupGasDrop <= markupGasUsage` makes sense, as the price ratio for the gas usage is more volatile, since it's taking into account the gas unit price as well. +- `markupGasDrop` and `markupGasUsage` should be set higher for such a "local chain - remote chain" pair, where gas local token price is less correlated with the remote gas token price. From 0c8e461dd3fe5b6743cce45b3d29e69132701d18 Mon Sep 17 00:00:00 2001 From: ChiTimesChi <88190723+ChiTimesChi@users.noreply.github.com> Date: Mon, 23 May 2022 20:49:31 +0300 Subject: [PATCH 34/47] GFP: remote ratios are no longer used --- .../messaging/GasFeePricingUpgradeable.sol | 57 ------------------- test/messaging/gfp/GasFeePricingSetters.t.sol | 8 --- test/messaging/gfp/GasFeePricingSetup.t.sol | 9 --- 3 files changed, 74 deletions(-) diff --git a/contracts/messaging/GasFeePricingUpgradeable.sol b/contracts/messaging/GasFeePricingUpgradeable.sol index af55c0d85..cc31bdf90 100644 --- a/contracts/messaging/GasFeePricingUpgradeable.sol +++ b/contracts/messaging/GasFeePricingUpgradeable.sol @@ -100,35 +100,6 @@ contract GasFeePricingUpgradeable is SynMessagingReceiverUpgradeable { uint128 gasUnitPrice; } - /** - * @dev Chain's Ratios are supposed to be FULLY chain-specific, i.e. - * GasFeePricing contracts on different chain will have different values for - * the same remote chain: - * - gasTokenPriceRatio: USD price ratio of remoteGasToken / localGasToken, scaled to wei - * uint96 => max value ~= 8 * 10**28 - * - gasUnitPriceRatio: How much 1 gas unit on remote chain is worth, expressed in local chain wei, - * multiplied by 10**18 (aka in attoWei = 10^-18 wei) - * uint160 => max value ~= 10**48 - * These values are updated whenever "gas information" is updated for either local or remote chain. - * - * ChainRatios is optimized to fit into one word of storage. - */ - - /** - * @dev Chain's Ratios are used for calculating a fee for sending a msg from local to remote chain. - * To calculate cost of tx gas airdrop (assuming gasDrop airdrop value): - * (gasDrop * gasTokenPriceRatio) / 10**18 - * To calculate cost of tx gas usage on remote chain (assuming gasAmount gas units): - * (gasAmount * gasUnitPriceRatio) / 10**18 - * - * Both numbers are expressed in local chain wei. - */ - struct ChainRatios { - /// @dev Values below are local-chain specific - uint96 gasTokenPriceRatio; - uint160 gasUnitPriceRatio; - } - /*╔══════════════════════════════════════════════════════════════════════╗*\ ▏*║ EVENTS ║*▕ \*╚══════════════════════════════════════════════════════════════════════╝*/ @@ -144,8 +115,6 @@ contract GasFeePricingUpgradeable is SynMessagingReceiverUpgradeable { /// @dev remoteChainId => Info mapping(uint256 => ChainInfo) public remoteInfo; - /// @dev remoteChainId => Ratios - mapping(uint256 => ChainRatios) public remoteRatios; /// @dev remoteChainId => Config mapping(uint256 => ChainConfig) public remoteConfig; /// @dev list of all remote chain ids @@ -365,18 +334,7 @@ contract GasFeePricingUpgradeable is SynMessagingReceiverUpgradeable { \*╚══════════════════════════════════════════════════════════════════════╝*/ /// @dev Updates information about local chain gas token/unit price. - /// All the remote chain ratios are updated as well, if gas token price changed function _updateLocalChainInfo(uint128 _gasTokenPrice, uint128 _gasUnitPrice) internal { - if (localInfo.gasTokenPrice != _gasTokenPrice) { - // update ratios only if gas token price has changed - uint256[] memory chainIds = remoteChainIds; - for (uint256 i = 0; i < chainIds.length; ++i) { - uint256 chainId = chainIds[i]; - ChainInfo memory info = remoteInfo[chainId]; - _updateRemoteChainRatios(_gasTokenPrice, chainId, info.gasTokenPrice, info.gasUnitPrice); - } - } - localInfo = ChainInfo({gasTokenPrice: _gasTokenPrice, gasUnitPrice: _gasUnitPrice}); // TODO: use context chainid here @@ -423,24 +381,9 @@ contract GasFeePricingUpgradeable is SynMessagingReceiverUpgradeable { } remoteInfo[_remoteChainId] = ChainInfo({gasTokenPrice: _gasTokenPrice, gasUnitPrice: _gasUnitPrice}); - _updateRemoteChainRatios(_localGasTokenPrice, _remoteChainId, _gasTokenPrice, _gasUnitPrice); - emit ChainInfoUpdated(_remoteChainId, _gasTokenPrice, _gasUnitPrice); } - /// @dev Updates gas token/unit ratios for a given remote chain - function _updateRemoteChainRatios( - uint256 _localGasTokenPrice, - uint256 _remoteChainId, - uint256 _remoteGasTokenPrice, - uint256 _remoteGasUnitPrice - ) internal { - remoteRatios[_remoteChainId] = ChainRatios({ - gasTokenPriceRatio: uint96((_remoteGasTokenPrice * 10**18) / _localGasTokenPrice), - gasUnitPriceRatio: uint160((_remoteGasUnitPrice * _remoteGasTokenPrice * 10**18) / _localGasTokenPrice) - }); - } - /// @dev Updates the markups (see "Structs" docs). /// Markup = 0% means exactly the "projected gas cost" will be charged. function _updateMarkups( diff --git a/test/messaging/gfp/GasFeePricingSetters.t.sol b/test/messaging/gfp/GasFeePricingSetters.t.sol index 17da2f1f5..984a9530b 100644 --- a/test/messaging/gfp/GasFeePricingSetters.t.sol +++ b/test/messaging/gfp/GasFeePricingSetters.t.sol @@ -83,7 +83,6 @@ contract GasFeePricingUpgradeableSettersTest is GasFeePricingSetup { for (uint256 i = 0; i < TEST_CHAINS; ++i) { uint256 chainId = remoteChainIds[i]; _checkRemoteInfo(chainId); - _checkRemoteRatios(chainId); } } @@ -111,7 +110,6 @@ contract GasFeePricingUpgradeableSettersTest is GasFeePricingSetup { for (uint256 i = 0; i < TEST_CHAINS; ++i) { uint256 chainId = remoteChainIds[i]; _checkRemoteInfo(chainId); - _checkRemoteRatios(chainId); } } @@ -182,9 +180,6 @@ contract GasFeePricingUpgradeableSettersTest is GasFeePricingSetup { uint128 gasUnitPrice = 10 * 10**9; _updateLocalInfo(gasTokenPrice, gasUnitPrice); _checkLocalInfo(); - for (uint256 i = 0; i < TEST_CHAINS; ++i) { - _checkRemoteRatios(i + 1); - } } function testUpdateLocalInfoZeroTokenPriceReverts() public { @@ -208,8 +203,5 @@ contract GasFeePricingUpgradeableSettersTest is GasFeePricingSetup { uint128 gasUnitPrice = 0; _updateLocalInfo(gasTokenPrice, gasUnitPrice); _checkLocalInfo(); - for (uint256 i = 0; i < TEST_CHAINS; ++i) { - _checkRemoteRatios(i + 1); - } } } diff --git a/test/messaging/gfp/GasFeePricingSetup.t.sol b/test/messaging/gfp/GasFeePricingSetup.t.sol index b5ae5233e..31ea60331 100644 --- a/test/messaging/gfp/GasFeePricingSetup.t.sol +++ b/test/messaging/gfp/GasFeePricingSetup.t.sol @@ -126,15 +126,6 @@ abstract contract GasFeePricingSetup is Test { assertEq(markupGasUsage, remoteVars[_chainId].markupGasUsage, "remoteMarkupGasUsage is incorrect"); } - function _checkRemoteRatios(uint256 _chainId) internal { - (uint96 gasTokenPriceRatio, uint160 gasUnitPriceRatio) = gasFeePricing.remoteRatios(_chainId); - uint256 _gasTokenPriceRatio = (remoteVars[_chainId].gasTokenPrice * 10**18) / localVars.gasTokenPrice; - uint256 _gasUnitPriceRatio = (remoteVars[_chainId].gasUnitPrice * remoteVars[_chainId].gasTokenPrice * 10**18) / - localVars.gasTokenPrice; - assertEq(gasTokenPriceRatio, _gasTokenPriceRatio, "gasTokenPriceRatio is incorrect"); - assertEq(gasUnitPriceRatio, _gasUnitPriceRatio, "gasUnitPriceRatio is incorrect"); - } - function _checkLocalConfig() internal { (uint112 gasDropMax, uint80 gasUnitsRcvMsg, uint32 minGasUsageFeeUsd, , ) = gasFeePricing.localConfig(); assertEq(gasDropMax, localVars.gasDropMax, "localMaxGasDrop is incorrect"); From 635d9841ab03f9a0b2eeec157e83cf8356b47357 Mon Sep 17 00:00:00 2001 From: ChiTimesChi <88190723+ChiTimesChi@users.noreply.github.com> Date: Mon, 23 May 2022 23:49:52 +0300 Subject: [PATCH 35/47] tests: sending/receiving messages by GasFeePricing --- .../gfp/GasFeePricingMessaging.t.sol | 131 ++++++++++++++++++ 1 file changed, 131 insertions(+) diff --git a/test/messaging/gfp/GasFeePricingMessaging.t.sol b/test/messaging/gfp/GasFeePricingMessaging.t.sol index 494dc4639..9a9164785 100644 --- a/test/messaging/gfp/GasFeePricingMessaging.t.sol +++ b/test/messaging/gfp/GasFeePricingMessaging.t.sol @@ -3,8 +3,21 @@ pragma solidity 0.8.13; import "./GasFeePricingSetup.t.sol"; +import "src-messaging/libraries/Options.sol"; contract GasFeePricingUpgradeableMessagingTest is GasFeePricingSetup { + event MessageSent( + address indexed sender, + uint256 srcChainID, + bytes32 receiver, + uint256 indexed dstChainId, + bytes message, + uint64 nonce, + bytes options, + uint256 fee, + bytes32 indexed messageId + ); + /*╔══════════════════════════════════════════════════════════════════════╗*\ ▏*║ ENCODING TESTS ║*▕ \*╚══════════════════════════════════════════════════════════════════════╝*/ @@ -31,4 +44,122 @@ contract GasFeePricingUpgradeableMessagingTest is GasFeePricingSetup { assertEq(_newValueA, newValueA, "Failed to encode newValueA"); assertEq(_newValueB, newValueB, "Failed to encode newValueB"); } + + /*╔══════════════════════════════════════════════════════════════════════╗*\ + ▏*║ MESSAGING TESTS ║*▕ + \*╚══════════════════════════════════════════════════════════════════════╝*/ + + function testSendUpdateConfig( + uint112 _gasDropMax, + uint80 _gasUnitsRcvMsg, + uint32 _minGasUsageFeeUsd + ) public { + vm.assume(_gasUnitsRcvMsg != 0); + _prepareSendingTests(); + uint256 totalFee = gasFeePricing.estimateUpdateFees(); + bytes memory message = GasFeePricingUpdates.encodeConfig(_gasDropMax, _gasUnitsRcvMsg, _minGasUsageFeeUsd); + + _expectMessagingEmits(message); + gasFeePricing.updateLocalConfig{value: totalFee}(_gasDropMax, _gasUnitsRcvMsg, _minGasUsageFeeUsd); + } + + function testSendUpdateInfo(uint128 _gasTokenPrice, uint128 _gasUnitPrice) public { + vm.assume(_gasTokenPrice != 0); + _prepareSendingTests(); + uint256 totalFee = gasFeePricing.estimateUpdateFees(); + bytes memory message = GasFeePricingUpdates.encodeInfo(_gasTokenPrice, _gasUnitPrice); + + _expectMessagingEmits(message); + gasFeePricing.updateLocalInfo{value: totalFee}(_gasTokenPrice, _gasUnitPrice); + } + + function testRcvUpdateConfig( + uint8 _chainIndex, + uint112 _gasDropMax, + uint80 _gasUnitsRcvMsg, + uint32 _minGasUsageFeeUsd + ) public { + vm.assume(_gasUnitsRcvMsg != 0); + uint256 chainId = remoteChainIds[_chainIndex % TEST_CHAINS]; + bytes32 messageId = utils.getNextKappa(); + bytes32 srcAddress = utils.addressToBytes32(remoteVars[chainId].gasFeePricing); + + bytes memory message = GasFeePricingUpdates.encodeConfig(_gasDropMax, _gasUnitsRcvMsg, _minGasUsageFeeUsd); + hoax(NODE); + messageBus.executeMessage(chainId, srcAddress, address(gasFeePricing), 100000, 0, message, messageId); + + remoteVars[chainId].gasDropMax = _gasDropMax; + remoteVars[chainId].gasUnitsRcvMsg = _gasUnitsRcvMsg; + remoteVars[chainId].minGasUsageFeeUsd = _minGasUsageFeeUsd; + _checkRemoteConfig(chainId); + } + + function testRcvUpdateInfo( + uint8 _chainIndex, + uint128 _gasTokenPrice, + uint128 _gasUnitPrice + ) public { + vm.assume(_gasTokenPrice != 0); + uint256 chainId = remoteChainIds[_chainIndex % TEST_CHAINS]; + bytes32 messageId = utils.getNextKappa(); + bytes32 srcAddress = utils.addressToBytes32(remoteVars[chainId].gasFeePricing); + + bytes memory message = GasFeePricingUpdates.encodeInfo(_gasTokenPrice, _gasUnitPrice); + hoax(NODE); + messageBus.executeMessage(chainId, srcAddress, address(gasFeePricing), 100000, 0, message, messageId); + + remoteVars[chainId].gasTokenPrice = _gasTokenPrice; + remoteVars[chainId].gasUnitPrice = _gasUnitPrice; + _checkRemoteInfo(chainId); + } + + /*╔══════════════════════════════════════════════════════════════════════╗*\ + ▏*║ INTERNAL STUFF ║*▕ + \*╚══════════════════════════════════════════════════════════════════════╝*/ + + function _expectMessagingEmits(bytes memory message) internal { + for (uint256 i = 0; i < TEST_CHAINS; ++i) { + uint256 chainId = remoteChainIds[i]; + bytes memory options = Options.encode(remoteVars[chainId].gasUnitsRcvMsg); + uint256 fee = messageBus.estimateFee(chainId, options); + bytes32 receiver = utils.addressToBytes32(remoteVars[chainId].gasFeePricing); + uint64 nonce = uint64(i); + bytes32 messageId = messageBus.computeMessageId( + address(gasFeePricing), + block.chainid, + receiver, + chainId, + nonce, + message + ); + + vm.expectEmit(true, true, true, true); + emit MessageSent( + address(gasFeePricing), + block.chainid, + receiver, + chainId, + message, + nonce, + options, + fee, + messageId + ); + } + } + + function _prepareSendingTests() internal { + (uint128[] memory gasTokenPrices, uint128[] memory gasUnitPrices) = _generateTestInfoValues(); + _setRemoteInfo(remoteChainIds, gasTokenPrices, gasUnitPrices); + + uint112[] memory gasDropMax = new uint112[](TEST_CHAINS); + uint80[] memory gasUnitsRcvMsg = new uint80[](TEST_CHAINS); + uint32[] memory minGasUsageFeeUsd = new uint32[](TEST_CHAINS); + for (uint256 i = 0; i < TEST_CHAINS; ++i) { + gasDropMax[i] = 0; + gasUnitsRcvMsg[i] = uint80((i + 1) * 105000); + minGasUsageFeeUsd[i] = 0; + } + _setRemoteConfig(remoteChainIds, gasDropMax, gasUnitsRcvMsg, minGasUsageFeeUsd); + } } From 753058824c12142bfaade1e01137d48d4ec195d4 Mon Sep 17 00:00:00 2001 From: ChiTimesChi <88190723+ChiTimesChi@users.noreply.github.com> Date: Tue, 24 May 2022 16:04:34 +0300 Subject: [PATCH 36/47] GFP: fix bugs in estimateGasFee: - wrong value was used to check for maxGasDrop - some values were not cast to uin256, resulting in overflows --- .../messaging/GasFeePricingUpgradeable.sol | 5 +- .../gfp/GasFeePricingMessaging.t.sol | 66 ++++++++++++++++++- test/messaging/gfp/GasFeePricingSetup.t.sol | 2 +- 3 files changed, 69 insertions(+), 4 deletions(-) diff --git a/contracts/messaging/GasFeePricingUpgradeable.sol b/contracts/messaging/GasFeePricingUpgradeable.sol index cc31bdf90..508b4ec40 100644 --- a/contracts/messaging/GasFeePricingUpgradeable.sol +++ b/contracts/messaging/GasFeePricingUpgradeable.sol @@ -192,7 +192,7 @@ contract GasFeePricingUpgradeable is SynMessagingReceiverUpgradeable { ChainInfo memory dstInfo = remoteInfo[_chainId]; // Read info for source (local) chain ChainInfo memory srcInfo = localInfo; - require(_gasAirdrop <= dstConfig.markupGasDrop, "GasDrop higher than max"); + require(_gasAirdrop <= dstConfig.gasDropMax, "GasDrop higher than max"); // Calculate how much [gas airdrop] is worth in [local chain wei] uint256 feeGasDrop = (_gasAirdrop * dstInfo.gasTokenPrice) / srcInfo.gasTokenPrice; @@ -207,7 +207,8 @@ contract GasFeePricingUpgradeable is SynMessagingReceiverUpgradeable { // Multiply by 10**18 to convert to wei // Multiply by 10**18 again, as gasTokenPrice is scaled by 10**18 // Divide by USD_DENOMINATOR, as minGasUsageFeeUsd is scaled by USD_DENOMINATOR - uint256 minFee = (dstConfig.minGasUsageFeeUsd * 10**36) / (srcInfo.gasTokenPrice * USD_DENOMINATOR); + uint256 minFee = (uint256(dstConfig.minGasUsageFeeUsd) * 10**36) / + (uint256(srcInfo.gasTokenPrice) * USD_DENOMINATOR); if (feeGasUsage < minFee) feeGasUsage = minFee; fee = feeGasDrop + feeGasUsage; diff --git a/test/messaging/gfp/GasFeePricingMessaging.t.sol b/test/messaging/gfp/GasFeePricingMessaging.t.sol index 9a9164785..b50928f97 100644 --- a/test/messaging/gfp/GasFeePricingMessaging.t.sol +++ b/test/messaging/gfp/GasFeePricingMessaging.t.sol @@ -18,6 +18,15 @@ contract GasFeePricingUpgradeableMessagingTest is GasFeePricingSetup { bytes32 indexed messageId ); + // set this to true to do fee refund test + bool internal allowFeeRefund; + + receive() external payable override { + // making sure that all fee calculations are correct + // i.e. there are no fee refunds + if (!allowFeeRefund) revert("Received ether"); + } + /*╔══════════════════════════════════════════════════════════════════════╗*\ ▏*║ ENCODING TESTS ║*▕ \*╚══════════════════════════════════════════════════════════════════════╝*/ @@ -60,6 +69,7 @@ contract GasFeePricingUpgradeableMessagingTest is GasFeePricingSetup { bytes memory message = GasFeePricingUpdates.encodeConfig(_gasDropMax, _gasUnitsRcvMsg, _minGasUsageFeeUsd); _expectMessagingEmits(message); + // receive() is disabled, so this will also check if the totalFee is exactly the needed fee gasFeePricing.updateLocalConfig{value: totalFee}(_gasDropMax, _gasUnitsRcvMsg, _minGasUsageFeeUsd); } @@ -70,6 +80,7 @@ contract GasFeePricingUpgradeableMessagingTest is GasFeePricingSetup { bytes memory message = GasFeePricingUpdates.encodeInfo(_gasTokenPrice, _gasUnitPrice); _expectMessagingEmits(message); + // receive() is disabled, so this will also check if the totalFee is exactly the needed fee gasFeePricing.updateLocalInfo{value: totalFee}(_gasTokenPrice, _gasUnitPrice); } @@ -113,10 +124,63 @@ contract GasFeePricingUpgradeableMessagingTest is GasFeePricingSetup { _checkRemoteInfo(chainId); } + function testGasDropMaxSucceeds() public { + uint112 gasDropMax = 10**18; + _testGasDrop(gasDropMax, gasDropMax); + } + + function testGasDropTooBigReverts() public { + uint112 gasDropMax = 10**18; + _testGasDrop(gasDropMax + 1, gasDropMax); + } + + function _testGasDrop(uint112 gasDropAmount, uint112 gasDropMax) internal { + uint256 chainId = remoteChainIds[0]; + uint256 gasLimit = 100000; + uint128 gasUnitPrice = 10**9; + _setupSingleChain(chainId, gasDropMax, 0, uint128(localVars.gasTokenPrice), gasUnitPrice); + uint256 fee = gasDropAmount + gasLimit * gasUnitPrice; + + bytes32 receiver = keccak256("Not a fake address"); + + bytes memory options = Options.encode(100000, gasDropAmount, receiver); + + if (gasDropAmount > gasDropMax) { + vm.expectRevert("GasDrop higher than max"); + } + + messageBus.sendMessage{value: fee}(receiver, chainId, bytes(""), options); + } + /*╔══════════════════════════════════════════════════════════════════════╗*\ - ▏*║ INTERNAL STUFF ║*▕ + ▏*║ INTERNAL HELPERS ║*▕ \*╚══════════════════════════════════════════════════════════════════════╝*/ + function _setupSingleChain( + uint256 _chainId, + uint112 _gasDropMax, + uint32 _minGasUsageFeeUsd, + uint128 _gasTokenPrice, + uint128 _gasUnitPrice + ) internal { + uint256[] memory chainIds = new uint256[](1); + uint112[] memory gasDropMax = new uint112[](1); + uint80[] memory gasUnitsRcvMsg = new uint80[](1); + uint32[] memory minGasUsageFeeUsd = new uint32[](1); + + chainIds[0] = _chainId; + gasDropMax[0] = _gasDropMax; + gasUnitsRcvMsg[0] = 100000; + minGasUsageFeeUsd[0] = _minGasUsageFeeUsd; + _setRemoteConfig(chainIds, gasDropMax, gasUnitsRcvMsg, minGasUsageFeeUsd); + + uint128[] memory gasTokenPrice = new uint128[](1); + uint128[] memory gasUnitPrice = new uint128[](1); + gasTokenPrice[0] = _gasTokenPrice; + gasUnitPrice[0] = _gasUnitPrice; + _setRemoteInfo(chainIds, gasTokenPrice, gasUnitPrice); + } + function _expectMessagingEmits(bytes memory message) internal { for (uint256 i = 0; i < TEST_CHAINS; ++i) { uint256 chainId = remoteChainIds[i]; diff --git a/test/messaging/gfp/GasFeePricingSetup.t.sol b/test/messaging/gfp/GasFeePricingSetup.t.sol index 31ea60331..43bd5bb73 100644 --- a/test/messaging/gfp/GasFeePricingSetup.t.sol +++ b/test/messaging/gfp/GasFeePricingSetup.t.sol @@ -38,7 +38,7 @@ abstract contract GasFeePricingSetup is Test { address internal constant NODE = address(1337); // enable receiving overpaid fees - receive() external payable { + receive() external payable virtual { this; } From 66dc56e8213194b681c845043df8a4543708bd00 Mon Sep 17 00:00:00 2001 From: ChiTimesChi <88190723+ChiTimesChi@users.noreply.github.com> Date: Tue, 24 May 2022 16:40:56 +0300 Subject: [PATCH 37/47] tests: GFP minFee, applying markups --- .../gfp/GasFeePricingMessaging.t.sol | 108 ++++++++++++++++++ 1 file changed, 108 insertions(+) diff --git a/test/messaging/gfp/GasFeePricingMessaging.t.sol b/test/messaging/gfp/GasFeePricingMessaging.t.sol index b50928f97..0b598d3f3 100644 --- a/test/messaging/gfp/GasFeePricingMessaging.t.sol +++ b/test/messaging/gfp/GasFeePricingMessaging.t.sol @@ -58,6 +58,100 @@ contract GasFeePricingUpgradeableMessagingTest is GasFeePricingSetup { ▏*║ MESSAGING TESTS ║*▕ \*╚══════════════════════════════════════════════════════════════════════╝*/ + function testMinGasUsageFee() public { + uint256 chainId = remoteChainIds[0]; + uint128 gasUnitPrice = 10 * 10**9; + uint128 gasTokenPrice = uint128(localVars.gasTokenPrice * 1000); + // min fee = $2 + uint32 minGasUsageFeeUsd = 20000; + _setupSingleChain(chainId, 0, minGasUsageFeeUsd, gasTokenPrice, gasUnitPrice); + + // This gasLimit will result in gasUsage fee exactly $2 + uint256 gasLimit = 200000; + uint256 expectedFee = 2 * 10**18; + + assertEq( + gasFeePricing.estimateGasFee(chainId, Options.encode(gasLimit / 2)), + expectedFee, + "Wrong fee for 100,000 gas" + ); + assertEq( + gasFeePricing.estimateGasFee(chainId, Options.encode(gasLimit - 1)), + expectedFee, + "Wrong fee for 199,999 gas" + ); + assertEq( + gasFeePricing.estimateGasFee(chainId, Options.encode(gasLimit)), + expectedFee, + "Wrong fee for 200,000 gas" + ); + assertEq( + gasFeePricing.estimateGasFee(chainId, Options.encode(gasLimit + 1)), + (expectedFee * (gasLimit + 1)) / gasLimit, + "Wrong fee for 200,001 gas" + ); + assertEq( + gasFeePricing.estimateGasFee(chainId, Options.encode(gasLimit * 2)), + expectedFee * 2, + "Wrong fee for 400,000 gas" + ); + } + + function testMarkupGasDrop() public { + uint256 chainId = remoteChainIds[0]; + // set to 0, so that estimateFee would be only the airdrop cost + uint128 gasUnitPrice = 0; + uint128 gasTokenPrice = uint128(localVars.gasTokenPrice * 5); + uint32 minGasUsageFeeUsd = 0; + uint112 gasDropMax = 10**20; + + uint16 markupGasDrop = 69; + uint16 markupGasUsage = 100; + + _setupSingleChain(chainId, gasDropMax, minGasUsageFeeUsd, gasTokenPrice, gasUnitPrice); + _setupSingleChainMarkups(chainId, markupGasDrop, markupGasUsage); + + bytes32 receiver = keccak256("receiver"); + + // (2 * 10**18) remoteGas = (20 * 10**17) remoteGas = (100 * 10**17) localGas; + // +69% -> (169 * 10**17) localGas + assertEq( + gasFeePricing.estimateGasFee(chainId, Options.encode(0, 2 * 10**18, receiver)), + 169 * 10**17, + "Wrong markup for 2 * 10**18 gasDrop" + ); + + // 2 remoteGas = 10 localGas; +69% = 16 (rounded down) + assertEq( + gasFeePricing.estimateGasFee(chainId, Options.encode(0, 2, receiver)), + 16, + "Wrong markup for 2 gasDrop" + ); + } + + function testMarkupGasUsage() public { + uint256 chainId = remoteChainIds[0]; + uint128 gasUnitPrice = 2 * 10**9; + uint128 gasTokenPrice = uint128(localVars.gasTokenPrice * 5); + // set to 0, to check the markup being applied + uint32 minGasUsageFeeUsd = 0; + uint112 gasDropMax = 0; + + uint16 markupGasDrop = 42; + uint16 markupGasUsage = 69; + + _setupSingleChain(chainId, gasDropMax, minGasUsageFeeUsd, gasTokenPrice, gasUnitPrice); + _setupSingleChainMarkups(chainId, markupGasDrop, markupGasUsage); + + // (10**6 gasLimit) => (2 * 10**15 remoteGas cost) => (10**16 localGas) + // +69% -> 1.69 * 10**16 = 169 * 10**14 + assertEq( + gasFeePricing.estimateGasFee(chainId, Options.encode(10**6)), + 169 * 10**14, + "Wrong markup for 10**6 gasLimit" + ); + } + function testSendUpdateConfig( uint112 _gasDropMax, uint80 _gasUnitsRcvMsg, @@ -181,6 +275,20 @@ contract GasFeePricingUpgradeableMessagingTest is GasFeePricingSetup { _setRemoteInfo(chainIds, gasTokenPrice, gasUnitPrice); } + function _setupSingleChainMarkups( + uint256 _chainId, + uint16 _markupGasDrop, + uint16 _markupGasUsage + ) internal { + uint256[] memory chainIds = new uint256[](1); + uint16[] memory markupGasDrop = new uint16[](1); + uint16[] memory markupGasUsage = new uint16[](1); + chainIds[0] = _chainId; + markupGasDrop[0] = _markupGasDrop; + markupGasUsage[0] = _markupGasUsage; + _setRemoteMarkups(chainIds, markupGasDrop, markupGasUsage); + } + function _expectMessagingEmits(bytes memory message) internal { for (uint256 i = 0; i < TEST_CHAINS; ++i) { uint256 chainId = remoteChainIds[i]; From 6f1d6974975482d420c5cb49b9a39e8672d76f80 Mon Sep 17 00:00:00 2001 From: ChiTimesChi <88190723+ChiTimesChi@users.noreply.github.com> Date: Wed, 25 May 2022 14:40:41 +0300 Subject: [PATCH 38/47] MB: immutable contracts --- contracts/messaging/ContextChainId.sol | 15 ++ contracts/messaging/HarmonyMessageBus.sol | 21 +++ contracts/messaging/MessageBus.sol | 30 ++++ contracts/messaging/MessageBusReceiver.sol | 122 +++++++++++++ contracts/messaging/MessageBusSender.sol | 162 ++++++++++++++++++ .../messaging/interfaces/IMessageExecutor.sol | 13 ++ 6 files changed, 363 insertions(+) create mode 100644 contracts/messaging/ContextChainId.sol create mode 100644 contracts/messaging/HarmonyMessageBus.sol create mode 100644 contracts/messaging/MessageBus.sol create mode 100644 contracts/messaging/MessageBusReceiver.sol create mode 100644 contracts/messaging/MessageBusSender.sol create mode 100644 contracts/messaging/interfaces/IMessageExecutor.sol diff --git a/contracts/messaging/ContextChainId.sol b/contracts/messaging/ContextChainId.sol new file mode 100644 index 000000000..46711b841 --- /dev/null +++ b/contracts/messaging/ContextChainId.sol @@ -0,0 +1,15 @@ +// SPDX-License-Identifier: MIT + +pragma solidity 0.8.13; + +abstract contract ContextChainId { + uint256 internal immutable localChainId; + + constructor() { + localChainId = _chainId(); + } + + function _chainId() internal view virtual returns (uint256) { + return block.chainid; + } +} diff --git a/contracts/messaging/HarmonyMessageBus.sol b/contracts/messaging/HarmonyMessageBus.sol new file mode 100644 index 000000000..732cae929 --- /dev/null +++ b/contracts/messaging/HarmonyMessageBus.sol @@ -0,0 +1,21 @@ +// SPDX-License-Identifier: MIT + +pragma solidity 0.8.13; + +import "./MessageBus.sol"; + +contract HarmonyMessageBus is MessageBus { + uint256 private constant CHAIN_ID = 1666600000; + + constructor( + IGasFeePricing _pricing, + IAuthVerifier _verifier, + IMessageExecutor _executor + ) MessageBus(_pricing, _verifier, _executor) { + this; + } + + function _chainId() internal pure override returns (uint256) { + return CHAIN_ID; + } +} diff --git a/contracts/messaging/MessageBus.sol b/contracts/messaging/MessageBus.sol new file mode 100644 index 000000000..5c405dbef --- /dev/null +++ b/contracts/messaging/MessageBus.sol @@ -0,0 +1,30 @@ +// SPDX-License-Identifier: MIT + +pragma solidity 0.8.13; + +import "./MessageBusSender.sol"; +import "./MessageBusReceiver.sol"; + +contract MessageBus is MessageBusSender, MessageBusReceiver { + constructor( + IGasFeePricing _pricing, + IAuthVerifier _verifier, + IMessageExecutor _executor + ) { + pricing = _pricing; + verifier = _verifier; + executor = _executor; + } + + /*╔══════════════════════════════════════════════════════════════════════╗*\ + ▏*║ PAUSABLE ║*▕ + \*╚══════════════════════════════════════════════════════════════════════╝*/ + + function pause() external onlyOwner { + _pause(); + } + + function unpause() external onlyOwner { + _unpause(); + } +} diff --git a/contracts/messaging/MessageBusReceiver.sol b/contracts/messaging/MessageBusReceiver.sol new file mode 100644 index 000000000..281dd2ff3 --- /dev/null +++ b/contracts/messaging/MessageBusReceiver.sol @@ -0,0 +1,122 @@ +// SPDX-License-Identifier: MIT + +pragma solidity 0.8.13; + +import "@openzeppelin/contracts-4.5.0/access/Ownable.sol"; +import "@openzeppelin/contracts-4.5.0/security/Pausable.sol"; + +import "./interfaces/IAuthVerifier.sol"; +import "./interfaces/IMessageExecutor.sol"; + +contract MessageBusReceiver is Ownable, Pausable { + enum TxStatus { + Null, + Success, + Fail + } + + event Executed( + bytes32 indexed messageId, + TxStatus status, + address indexed dstAddress, + uint64 indexed srcChainId, + uint64 srcNonce + ); + + event CallReverted(string reason); + + /*╔══════════════════════════════════════════════════════════════════════╗*\ + ▏*║ STORAGE ║*▕ + \*╚══════════════════════════════════════════════════════════════════════╝*/ + + /// @dev Contract used for authenticating validator address + IAuthVerifier public verifier; + /// @dev Contract used for executing received messages + IMessageExecutor public executor; + /// @dev Status of all executed messages + mapping(bytes32 => TxStatus) public executedMessages; + + /*╔══════════════════════════════════════════════════════════════════════╗*\ + ▏*║ ONLY OWNER ║*▕ + \*╚══════════════════════════════════════════════════════════════════════╝*/ + + function updateAuthVerifier(IAuthVerifier _verifier) external onlyOwner { + require(address(_verifier) != address(0), "Cannot set to 0"); + verifier = _verifier; + } + + function updateMessageExecutor(IMessageExecutor _executor) external onlyOwner { + require(address(_executor) != address(0), "Cannot set to 0"); + executor = _executor; + } + + // TODO: how useful is that, if contract is immutable? + function updateMessageStatus(bytes32 _messageId, TxStatus _status) external onlyOwner { + executedMessages[_messageId] = _status; + } + + /*╔══════════════════════════════════════════════════════════════════════╗*\ + ▏*║ MESSAGING LOGIC ║*▕ + \*╚══════════════════════════════════════════════════════════════════════╝*/ + + /** + * @notice Relayer executes messages through an authenticated method to the destination receiver + * based on the originating transaction on source chain + * @param _srcChainId Originating chain ID - typically a standard EVM chain ID, + * but may refer to a Synapse-specific chain ID on nonEVM chains + * @param _srcAddress Originating bytes32 address of the message sender on the srcChain + * @param _dstAddress Destination address that the arbitrary message will be passed to + * @param _message Arbitrary message payload to pass to the destination chain receiver + * @param _srcNonce Nonce of the message on the originating chain + * @param _options Versioned struct used to instruct message executor on how to proceed with gas limits, + * gas airdrop, etc + * @param _messageId Unique message identifier, computed when sending message on originating chain + * @param _proof Byte string containing proof that message was sent from originating chain + */ + function executeMessage( + uint256 _srcChainId, + bytes32 _srcAddress, + address _dstAddress, + bytes calldata _message, + uint256 _srcNonce, + bytes calldata _options, + bytes32 _messageId, + bytes calldata _proof + ) external whenNotPaused { + // In order to guarantee that an individual message is only executed once, a messageId is passed + // enforce that this message ID hasn't already been tried ever + require(executedMessages[_messageId] == TxStatus.Null, "Message already executed"); + // Authenticate executeMessage, will revert if not authenticated + IAuthVerifier(verifier).msgAuth(abi.encode(msg.sender, _messageId, _proof)); + + TxStatus status; + try executor.executeMessage(_srcChainId, _srcAddress, _dstAddress, _message, _options) { + // Assuming success state if no revert + status = TxStatus.Success; + } catch (bytes memory reason) { + // call hard reverted & failed + emit CallReverted(_getRevertMsg(reason)); + status = TxStatus.Fail; + } + + executedMessages[_messageId] = status; + emit Executed(_messageId, status, _dstAddress, uint64(_srcChainId), uint64(_srcNonce)); + } + + /*╔══════════════════════════════════════════════════════════════════════╗*\ + ▏*║ INTERNAL HELPERS ║*▕ + \*╚══════════════════════════════════════════════════════════════════════╝*/ + + // https://ethereum.stackexchange.com/a/83577 + // https://github.com/Uniswap/v3-periphery/blob/v1.0.0/contracts/base/Multicall.sol + function _getRevertMsg(bytes memory _returnData) internal pure returns (string memory) { + // If the _res length is less than 68, then the transaction failed silently (without a revert message) + if (_returnData.length < 68) return "Transaction reverted silently"; + // solhint-disable-next-line + assembly { + // Slice the sighash. + _returnData := add(_returnData, 0x04) + } + return abi.decode(_returnData, (string)); // All that remains is the revert string + } +} diff --git a/contracts/messaging/MessageBusSender.sol b/contracts/messaging/MessageBusSender.sol new file mode 100644 index 000000000..00056ed79 --- /dev/null +++ b/contracts/messaging/MessageBusSender.sol @@ -0,0 +1,162 @@ +// SPDX-License-Identifier: MIT + +pragma solidity 0.8.13; + +import "./interfaces/IGasFeePricing.sol"; +import "./ContextChainId.sol"; + +import "@openzeppelin/contracts-4.5.0/access/Ownable.sol"; +import "@openzeppelin/contracts-4.5.0/security/Pausable.sol"; + +contract MessageBusSender is Ownable, Pausable, ContextChainId { + event MessageSent( + address indexed sender, + uint256 srcChainID, + bytes32 receiver, + uint256 indexed dstChainId, + bytes message, + uint64 nonce, + bytes options, + uint256 fee, + bytes32 indexed messageId + ); + + /*╔══════════════════════════════════════════════════════════════════════╗*\ + ▏*║ STORAGE ║*▕ + \*╚══════════════════════════════════════════════════════════════════════╝*/ + + /// @dev Contract used for calculating fee for sending a message + IGasFeePricing public pricing; + /// @dev Nonce of the next send message (in other words, amount of messages sent) + uint64 public nonce; + /// @dev Accrued messaging fees. Withdrawable by the owner. + uint256 public fees; + + /*╔══════════════════════════════════════════════════════════════════════╗*\ + ▏*║ VIEWS ║*▕ + \*╚══════════════════════════════════════════════════════════════════════╝*/ + + function computeMessageId( + address _srcAddress, + uint256 _srcChainId, + bytes32 _dstAddress, + uint256 _dstChainId, + uint256 _srcNonce, + bytes calldata _message + ) public pure returns (bytes32) { + return keccak256(abi.encode(_srcAddress, _srcChainId, _dstAddress, _dstChainId, _srcNonce, _message)); + } + + function estimateFee(uint256 _dstChainId, bytes calldata _options) public returns (uint256) { + uint256 fee = pricing.estimateGasFee(_dstChainId, _options); + require(fee != 0, "Fee not set"); + return fee; + } + + /*╔══════════════════════════════════════════════════════════════════════╗*\ + ▏*║ ONLY OWNER ║*▕ + \*╚══════════════════════════════════════════════════════════════════════╝*/ + + /** + * @notice Rescues any gas in contract, aside from fees + * @param to Address to which to rescue gas to + */ + function rescueGas(address payable to) external onlyOwner { + uint256 withdrawAmount = address(this).balance - fees; + to.transfer(withdrawAmount); + } + + /** + * @notice Withdraws accumulated fees in native gas token, based on fees variable. + * @param to Address to withdraw gas fees to, which can be specified in the event owner() can't receive native gas + */ + function withdrawGasFees(address payable to) external onlyOwner { + to.transfer(fees); + delete fees; + } + + function updateGasFeePricing(IGasFeePricing _pricing) external onlyOwner { + require(address(_pricing) != address(0), "Cannot set to 0"); + pricing = _pricing; + } + + /*╔══════════════════════════════════════════════════════════════════════╗*\ + ▏*║ MESSAGING LOGIC ║*▕ + \*╚══════════════════════════════════════════════════════════════════════╝*/ + + /** + * @notice Sends a message to a receiving contract address on another chain. + * Sender must make sure that the message is unique and not a duplicate message. + * Unspent gas fees would be transferred back to tx.origin. + * @param _receiver The bytes32 address of the destination contract to be called + * @param _dstChainId The destination chain ID - typically, standard EVM chain ID, but differs on nonEVM chains + * @param _message The arbitrary payload to pass to the destination chain receiver + * @param _options Versioned struct used to instruct relayer on how to proceed with gas limits + */ + function sendMessage( + bytes32 _receiver, + uint256 _dstChainId, + bytes calldata _message, + bytes calldata _options + ) external payable { + /** + * @dev We're using `tx.origin` instead of `msg.sender` here, because + * it's expected that the vast majority of interactions with {MessageBus} + * will be done by the smart contracts. If they truly wanted to receive unspent + * gas back, they should've specified themselves as a refund address. + * + * `tx.origin` is always an EOA that submitted the tx, and paid the gas fees, + * so returning overspent fees to it makes total sense. + * + * Also, some of the contracts interacting with {MessageBus} might have no way + * to receive gas, causing sendMessage to revert in case of overpayment, + * `msg.sender` was used by default. + */ + // solhint-disable-next-line + _sendMessage(_receiver, _dstChainId, _message, _options, payable(tx.origin)); + } + + /** + * @notice Sends a message to a receiving contract address on another chain. + * Sender must make sure that the message is unique and not a duplicate message. + * Unspent gas fees will be refunded to specified address. + * @param _receiver The bytes32 address of the destination contract to be called + * @param _dstChainId The destination chain ID - typically, standard EVM chain ID, but differs on nonEVM chains + * @param _message The arbitrary payload to pass to the destination chain receiver + * @param _options Versioned struct used to instruct relayer on how to proceed with gas limits + * @param _refundAddress Address that will receive unspent gas fees + */ + function sendMessage( + bytes32 _receiver, + uint256 _dstChainId, + bytes calldata _message, + bytes calldata _options, + address payable _refundAddress + ) external payable { + _sendMessage(_receiver, _dstChainId, _message, _options, _refundAddress); + } + + /// @dev Sending messages is disabled, when {MessageBus} is paused. + function _sendMessage( + bytes32 _receiver, + uint256 _dstChainId, + bytes calldata _message, + bytes calldata _options, + address payable _refundAddress + ) internal whenNotPaused { + // Check that we're not sending to the local chain + require(_dstChainId != localChainId, "Invalid chainId"); + // Check that messaging fee is fully covered + uint256 fee = estimateFee(_dstChainId, _options); + require(msg.value >= fee, "Insufficient gas fee"); + // Compute individual message identifier + bytes32 msgId = computeMessageId(msg.sender, localChainId, _receiver, _dstChainId, nonce, _message); + emit MessageSent(msg.sender, localChainId, _receiver, _dstChainId, _message, nonce, _options, fee, msgId); + fees += fee; + ++nonce; + // refund gas fees in case of overpayment + if (msg.value > fee) { + _refundAddress.transfer(msg.value - fee); + } + } +} diff --git a/contracts/messaging/interfaces/IMessageExecutor.sol b/contracts/messaging/interfaces/IMessageExecutor.sol new file mode 100644 index 000000000..b3dbe4805 --- /dev/null +++ b/contracts/messaging/interfaces/IMessageExecutor.sol @@ -0,0 +1,13 @@ +// SPDX-License-Identifier: MIT + +pragma solidity 0.8.13; + +interface IMessageExecutor { + function executeMessage( + uint256 _srcChainId, + bytes32 _srcAddress, + address _dstAddress, + bytes calldata _message, + bytes calldata _options + ) external; +} From c47eca80cae65ba32004826a7c1df8db32fc9a68 Mon Sep 17 00:00:00 2001 From: ChiTimesChi <88190723+ChiTimesChi@users.noreply.github.com> Date: Thu, 26 May 2022 15:39:13 +0300 Subject: [PATCH 39/47] slightly better docs --- contracts/messaging/MessageBusReceiver.sol | 1 + contracts/messaging/MessageBusSender.sol | 10 ++++++---- 2 files changed, 7 insertions(+), 4 deletions(-) diff --git a/contracts/messaging/MessageBusReceiver.sol b/contracts/messaging/MessageBusReceiver.sol index 281dd2ff3..473825ef8 100644 --- a/contracts/messaging/MessageBusReceiver.sol +++ b/contracts/messaging/MessageBusReceiver.sol @@ -83,6 +83,7 @@ contract MessageBusReceiver is Ownable, Pausable { bytes32 _messageId, bytes calldata _proof ) external whenNotPaused { + /// @dev Sending messages is disabled, when {MessageBus} is paused. // In order to guarantee that an individual message is only executed once, a messageId is passed // enforce that this message ID hasn't already been tried ever require(executedMessages[_messageId] == TxStatus.Null, "Message already executed"); diff --git a/contracts/messaging/MessageBusSender.sol b/contracts/messaging/MessageBusSender.sol index 00056ed79..2d9a863f1 100644 --- a/contracts/messaging/MessageBusSender.sol +++ b/contracts/messaging/MessageBusSender.sol @@ -91,7 +91,7 @@ contract MessageBusSender is Ownable, Pausable, ContextChainId { * @param _receiver The bytes32 address of the destination contract to be called * @param _dstChainId The destination chain ID - typically, standard EVM chain ID, but differs on nonEVM chains * @param _message The arbitrary payload to pass to the destination chain receiver - * @param _options Versioned struct used to instruct relayer on how to proceed with gas limits + * @param _options Versioned struct used to instruct message executor on how to proceed with gas limits */ function sendMessage( bytes32 _receiver, @@ -106,10 +106,12 @@ contract MessageBusSender is Ownable, Pausable, ContextChainId { * gas back, they should've specified themselves as a refund address. * * `tx.origin` is always an EOA that submitted the tx, and paid the gas fees, - * so returning overspent fees to it makes total sense. + * so returning overspent fees to it by default makes sense. This address is + * only going to be used for receiving unspent gas, so the usual + * "do not use tx.origin" approach can not be applied here. * * Also, some of the contracts interacting with {MessageBus} might have no way - * to receive gas, causing sendMessage to revert in case of overpayment, + * to receive gas, causing sendMessage to revert in case of overpayment, if * `msg.sender` was used by default. */ // solhint-disable-next-line @@ -123,7 +125,7 @@ contract MessageBusSender is Ownable, Pausable, ContextChainId { * @param _receiver The bytes32 address of the destination contract to be called * @param _dstChainId The destination chain ID - typically, standard EVM chain ID, but differs on nonEVM chains * @param _message The arbitrary payload to pass to the destination chain receiver - * @param _options Versioned struct used to instruct relayer on how to proceed with gas limits + * @param _options Versioned struct used to instruct message executor on how to proceed with gas limits * @param _refundAddress Address that will receive unspent gas fees */ function sendMessage( From 3757cd51eb32f8720206acc5eb3c366207d9eba4 Mon Sep 17 00:00:00 2001 From: ChiTimesChi <88190723+ChiTimesChi@users.noreply.github.com> Date: Thu, 26 May 2022 16:53:24 +0300 Subject: [PATCH 40/47] remove extra casting --- contracts/messaging/MessageBusReceiver.sol | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/contracts/messaging/MessageBusReceiver.sol b/contracts/messaging/MessageBusReceiver.sol index 473825ef8..ee61452d8 100644 --- a/contracts/messaging/MessageBusReceiver.sol +++ b/contracts/messaging/MessageBusReceiver.sol @@ -83,12 +83,13 @@ contract MessageBusReceiver is Ownable, Pausable { bytes32 _messageId, bytes calldata _proof ) external whenNotPaused { - /// @dev Sending messages is disabled, when {MessageBus} is paused. + /// @dev Executing messages is disabled, when {MessageBus} is paused. + // In order to guarantee that an individual message is only executed once, a messageId is passed // enforce that this message ID hasn't already been tried ever require(executedMessages[_messageId] == TxStatus.Null, "Message already executed"); // Authenticate executeMessage, will revert if not authenticated - IAuthVerifier(verifier).msgAuth(abi.encode(msg.sender, _messageId, _proof)); + verifier.msgAuth(abi.encode(msg.sender, _messageId, _proof)); TxStatus status; try executor.executeMessage(_srcChainId, _srcAddress, _dstAddress, _message, _options) { From 4830921b87294172e031598bab8ddd478c4adfb8 Mon Sep 17 00:00:00 2001 From: ChiTimesChi <88190723+ChiTimesChi@users.noreply.github.com> Date: Thu, 26 May 2022 16:56:22 +0300 Subject: [PATCH 41/47] remove unnecessary cast to uint64 --- contracts/messaging/MessageBusReceiver.sol | 6 +++--- contracts/messaging/MessageBusSender.sol | 4 ++-- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/contracts/messaging/MessageBusReceiver.sol b/contracts/messaging/MessageBusReceiver.sol index ee61452d8..8935cba6e 100644 --- a/contracts/messaging/MessageBusReceiver.sol +++ b/contracts/messaging/MessageBusReceiver.sol @@ -19,8 +19,8 @@ contract MessageBusReceiver is Ownable, Pausable { bytes32 indexed messageId, TxStatus status, address indexed dstAddress, - uint64 indexed srcChainId, - uint64 srcNonce + uint256 indexed srcChainId, + uint256 srcNonce ); event CallReverted(string reason); @@ -102,7 +102,7 @@ contract MessageBusReceiver is Ownable, Pausable { } executedMessages[_messageId] = status; - emit Executed(_messageId, status, _dstAddress, uint64(_srcChainId), uint64(_srcNonce)); + emit Executed(_messageId, status, _dstAddress, _srcChainId, _srcNonce); } /*╔══════════════════════════════════════════════════════════════════════╗*\ diff --git a/contracts/messaging/MessageBusSender.sol b/contracts/messaging/MessageBusSender.sol index 2d9a863f1..134768fab 100644 --- a/contracts/messaging/MessageBusSender.sol +++ b/contracts/messaging/MessageBusSender.sol @@ -15,7 +15,7 @@ contract MessageBusSender is Ownable, Pausable, ContextChainId { bytes32 receiver, uint256 indexed dstChainId, bytes message, - uint64 nonce, + uint256 nonce, bytes options, uint256 fee, bytes32 indexed messageId @@ -28,7 +28,7 @@ contract MessageBusSender is Ownable, Pausable, ContextChainId { /// @dev Contract used for calculating fee for sending a message IGasFeePricing public pricing; /// @dev Nonce of the next send message (in other words, amount of messages sent) - uint64 public nonce; + uint256 public nonce; /// @dev Accrued messaging fees. Withdrawable by the owner. uint256 public fees; From 88e4a801bdd5de879eccb5fae00b902d11ef6dbf Mon Sep 17 00:00:00 2001 From: ChiTimesChi <88190723+ChiTimesChi@users.noreply.github.com> Date: Fri, 27 May 2022 14:53:01 +0300 Subject: [PATCH 42/47] chore: rename new libraries --- .../messaging/GasFeePricingUpgradeable.sol | 22 ++++----- .../libraries/{Options.sol => OptionsLib.sol} | 2 +- ...ricingUpdates.sol => PricingUpdateLib.sol} | 2 +- .../gfp/GasFeePricingMessaging.t.sol | 46 +++++++++---------- test/messaging/gfp/GasFeePricingSetup.t.sol | 2 +- 5 files changed, 37 insertions(+), 37 deletions(-) rename contracts/messaging/libraries/{Options.sol => OptionsLib.sol} (98%) rename contracts/messaging/libraries/{GasFeePricingUpdates.sol => PricingUpdateLib.sol} (98%) diff --git a/contracts/messaging/GasFeePricingUpgradeable.sol b/contracts/messaging/GasFeePricingUpgradeable.sol index 508b4ec40..ca3b26a82 100644 --- a/contracts/messaging/GasFeePricingUpgradeable.sol +++ b/contracts/messaging/GasFeePricingUpgradeable.sol @@ -4,8 +4,8 @@ pragma solidity 0.8.13; import "./framework/SynMessagingReceiverUpgradeable.sol"; import "./interfaces/IGasFeePricing.sol"; -import "./libraries/Options.sol"; -import "./libraries/GasFeePricingUpdates.sol"; +import "./libraries/OptionsLib.sol"; +import "./libraries/PricingUpdateLib.sol"; contract GasFeePricingUpgradeable is SynMessagingReceiverUpgradeable { /*╔══════════════════════════════════════════════════════════════════════╗*\ @@ -173,7 +173,7 @@ contract GasFeePricingUpgradeable is SynMessagingReceiverUpgradeable { uint256 gasAirdrop; uint256 gasLimit; if (_options.length != 0) { - (gasLimit, gasAirdrop, ) = Options.decode(_options); + (gasLimit, gasAirdrop, ) = OptionsLib.decode(_options); } else { gasLimit = DEFAULT_GAS_LIMIT; } @@ -306,7 +306,7 @@ contract GasFeePricingUpgradeable is SynMessagingReceiverUpgradeable { uint32 _minGasUsageFeeUsd ) external payable onlyOwner { require(_gasUnitsRcvMsg != 0, "Gas amount is not set"); - _sendUpdateMessages(GasFeePricingUpdates.encodeConfig(_gasDropMax, _gasUnitsRcvMsg, _minGasUsageFeeUsd)); + _sendUpdateMessages(PricingUpdateLib.encodeConfig(_gasDropMax, _gasUnitsRcvMsg, _minGasUsageFeeUsd)); ChainConfig memory config = localConfig; config.gasDropMax = _gasDropMax; config.gasUnitsRcvMsg = _gasUnitsRcvMsg; @@ -326,7 +326,7 @@ contract GasFeePricingUpgradeable is SynMessagingReceiverUpgradeable { require(_gasTokenPrice != 0, "Gas token price is not set"); // send messages before updating the values, so that it's possible to use // estimateUpdateFees() to calculate the needed fee for the update - _sendUpdateMessages(GasFeePricingUpdates.encodeInfo(_gasTokenPrice, _gasUnitPrice)); + _sendUpdateMessages(PricingUpdateLib.encodeInfo(_gasTokenPrice, _gasUnitPrice)); _updateLocalChainInfo(_gasTokenPrice, _gasUnitPrice); } @@ -418,7 +418,7 @@ contract GasFeePricingUpgradeable is SynMessagingReceiverUpgradeable { if (gasLimit == 0) gasLimit = DEFAULT_GAS_LIMIT; receivers[i] = trustedRemoteLookup[chainId]; - options[i] = Options.encode(gasLimit); + options[i] = OptionsLib.encode(gasLimit); } _send(receivers, chainIds, _message, options, fees, payable(msg.sender)); @@ -432,14 +432,14 @@ contract GasFeePricingUpgradeable is SynMessagingReceiverUpgradeable { bytes memory _message, address ) internal override { - uint8 msgType = GasFeePricingUpdates.messageType(_message); - if (msgType == uint8(GasFeePricingUpdates.MsgType.UPDATE_CONFIG)) { - (uint112 gasDropMax, uint80 gasUnitsRcvMsg, uint32 minGasUsageFeeUsd) = GasFeePricingUpdates.decodeConfig( + uint8 msgType = PricingUpdateLib.messageType(_message); + if (msgType == uint8(PricingUpdateLib.MsgType.UPDATE_CONFIG)) { + (uint112 gasDropMax, uint80 gasUnitsRcvMsg, uint32 minGasUsageFeeUsd) = PricingUpdateLib.decodeConfig( _message ); _updateRemoteChainConfig(_localChainId, gasDropMax, gasUnitsRcvMsg, minGasUsageFeeUsd); - } else if (msgType == uint8(GasFeePricingUpdates.MsgType.UPDATE_INFO)) { - (uint128 gasTokenPrice, uint128 gasUnitPrice) = GasFeePricingUpdates.decodeInfo(_message); + } else if (msgType == uint8(PricingUpdateLib.MsgType.UPDATE_INFO)) { + (uint128 gasTokenPrice, uint128 gasUnitPrice) = PricingUpdateLib.decodeInfo(_message); _updateRemoteChainInfo(_localChainId, gasTokenPrice, gasUnitPrice); } else { revert("Unknown message type"); diff --git a/contracts/messaging/libraries/Options.sol b/contracts/messaging/libraries/OptionsLib.sol similarity index 98% rename from contracts/messaging/libraries/Options.sol rename to contracts/messaging/libraries/OptionsLib.sol index 913abfd2c..47fc9465b 100644 --- a/contracts/messaging/libraries/Options.sol +++ b/contracts/messaging/libraries/OptionsLib.sol @@ -2,7 +2,7 @@ pragma solidity ^0.8.0; -library Options { +library OptionsLib { enum TxType { UNKNOWN, DEFAULT, diff --git a/contracts/messaging/libraries/GasFeePricingUpdates.sol b/contracts/messaging/libraries/PricingUpdateLib.sol similarity index 98% rename from contracts/messaging/libraries/GasFeePricingUpdates.sol rename to contracts/messaging/libraries/PricingUpdateLib.sol index 5eb9a1359..3e53f01ea 100644 --- a/contracts/messaging/libraries/GasFeePricingUpdates.sol +++ b/contracts/messaging/libraries/PricingUpdateLib.sol @@ -2,7 +2,7 @@ pragma solidity ^0.8.0; -library GasFeePricingUpdates { +library PricingUpdateLib { enum MsgType { UNKNOWN, UPDATE_CONFIG, diff --git a/test/messaging/gfp/GasFeePricingMessaging.t.sol b/test/messaging/gfp/GasFeePricingMessaging.t.sol index 0b598d3f3..08632ab92 100644 --- a/test/messaging/gfp/GasFeePricingMessaging.t.sol +++ b/test/messaging/gfp/GasFeePricingMessaging.t.sol @@ -3,7 +3,7 @@ pragma solidity 0.8.13; import "./GasFeePricingSetup.t.sol"; -import "src-messaging/libraries/Options.sol"; +import "src-messaging/libraries/OptionsLib.sol"; contract GasFeePricingUpgradeableMessagingTest is GasFeePricingSetup { event MessageSent( @@ -36,20 +36,20 @@ contract GasFeePricingUpgradeableMessagingTest is GasFeePricingSetup { uint80 newValueB, uint32 newValueC ) public { - bytes memory message = GasFeePricingUpdates.encodeConfig(newValueA, newValueB, newValueC); - uint8 _msgType = GasFeePricingUpdates.messageType(message); - (uint112 _newValueA, uint80 _newValueB, uint32 _newValueC) = GasFeePricingUpdates.decodeConfig(message); - assertEq(_msgType, uint8(GasFeePricingUpdates.MsgType.UPDATE_CONFIG), "Failed to encode msgType"); + bytes memory message = PricingUpdateLib.encodeConfig(newValueA, newValueB, newValueC); + uint8 _msgType = PricingUpdateLib.messageType(message); + (uint112 _newValueA, uint80 _newValueB, uint32 _newValueC) = PricingUpdateLib.decodeConfig(message); + assertEq(_msgType, uint8(PricingUpdateLib.MsgType.UPDATE_CONFIG), "Failed to encode msgType"); assertEq(_newValueA, newValueA, "Failed to encode newValueA"); assertEq(_newValueB, newValueB, "Failed to encode newValueB"); assertEq(_newValueC, newValueC, "Failed to encode newValueC"); } function testEncodeInfo(uint128 newValueA, uint128 newValueB) public { - bytes memory message = GasFeePricingUpdates.encodeInfo(newValueA, newValueB); - uint8 _msgType = GasFeePricingUpdates.messageType(message); - (uint128 _newValueA, uint128 _newValueB) = GasFeePricingUpdates.decodeInfo(message); - assertEq(_msgType, uint8(GasFeePricingUpdates.MsgType.UPDATE_INFO), "Failed to encode msgType"); + bytes memory message = PricingUpdateLib.encodeInfo(newValueA, newValueB); + uint8 _msgType = PricingUpdateLib.messageType(message); + (uint128 _newValueA, uint128 _newValueB) = PricingUpdateLib.decodeInfo(message); + assertEq(_msgType, uint8(PricingUpdateLib.MsgType.UPDATE_INFO), "Failed to encode msgType"); assertEq(_newValueA, newValueA, "Failed to encode newValueA"); assertEq(_newValueB, newValueB, "Failed to encode newValueB"); } @@ -71,27 +71,27 @@ contract GasFeePricingUpgradeableMessagingTest is GasFeePricingSetup { uint256 expectedFee = 2 * 10**18; assertEq( - gasFeePricing.estimateGasFee(chainId, Options.encode(gasLimit / 2)), + gasFeePricing.estimateGasFee(chainId, OptionsLib.encode(gasLimit / 2)), expectedFee, "Wrong fee for 100,000 gas" ); assertEq( - gasFeePricing.estimateGasFee(chainId, Options.encode(gasLimit - 1)), + gasFeePricing.estimateGasFee(chainId, OptionsLib.encode(gasLimit - 1)), expectedFee, "Wrong fee for 199,999 gas" ); assertEq( - gasFeePricing.estimateGasFee(chainId, Options.encode(gasLimit)), + gasFeePricing.estimateGasFee(chainId, OptionsLib.encode(gasLimit)), expectedFee, "Wrong fee for 200,000 gas" ); assertEq( - gasFeePricing.estimateGasFee(chainId, Options.encode(gasLimit + 1)), + gasFeePricing.estimateGasFee(chainId, OptionsLib.encode(gasLimit + 1)), (expectedFee * (gasLimit + 1)) / gasLimit, "Wrong fee for 200,001 gas" ); assertEq( - gasFeePricing.estimateGasFee(chainId, Options.encode(gasLimit * 2)), + gasFeePricing.estimateGasFee(chainId, OptionsLib.encode(gasLimit * 2)), expectedFee * 2, "Wrong fee for 400,000 gas" ); @@ -116,14 +116,14 @@ contract GasFeePricingUpgradeableMessagingTest is GasFeePricingSetup { // (2 * 10**18) remoteGas = (20 * 10**17) remoteGas = (100 * 10**17) localGas; // +69% -> (169 * 10**17) localGas assertEq( - gasFeePricing.estimateGasFee(chainId, Options.encode(0, 2 * 10**18, receiver)), + gasFeePricing.estimateGasFee(chainId, OptionsLib.encode(0, 2 * 10**18, receiver)), 169 * 10**17, "Wrong markup for 2 * 10**18 gasDrop" ); // 2 remoteGas = 10 localGas; +69% = 16 (rounded down) assertEq( - gasFeePricing.estimateGasFee(chainId, Options.encode(0, 2, receiver)), + gasFeePricing.estimateGasFee(chainId, OptionsLib.encode(0, 2, receiver)), 16, "Wrong markup for 2 gasDrop" ); @@ -146,7 +146,7 @@ contract GasFeePricingUpgradeableMessagingTest is GasFeePricingSetup { // (10**6 gasLimit) => (2 * 10**15 remoteGas cost) => (10**16 localGas) // +69% -> 1.69 * 10**16 = 169 * 10**14 assertEq( - gasFeePricing.estimateGasFee(chainId, Options.encode(10**6)), + gasFeePricing.estimateGasFee(chainId, OptionsLib.encode(10**6)), 169 * 10**14, "Wrong markup for 10**6 gasLimit" ); @@ -160,7 +160,7 @@ contract GasFeePricingUpgradeableMessagingTest is GasFeePricingSetup { vm.assume(_gasUnitsRcvMsg != 0); _prepareSendingTests(); uint256 totalFee = gasFeePricing.estimateUpdateFees(); - bytes memory message = GasFeePricingUpdates.encodeConfig(_gasDropMax, _gasUnitsRcvMsg, _minGasUsageFeeUsd); + bytes memory message = PricingUpdateLib.encodeConfig(_gasDropMax, _gasUnitsRcvMsg, _minGasUsageFeeUsd); _expectMessagingEmits(message); // receive() is disabled, so this will also check if the totalFee is exactly the needed fee @@ -171,7 +171,7 @@ contract GasFeePricingUpgradeableMessagingTest is GasFeePricingSetup { vm.assume(_gasTokenPrice != 0); _prepareSendingTests(); uint256 totalFee = gasFeePricing.estimateUpdateFees(); - bytes memory message = GasFeePricingUpdates.encodeInfo(_gasTokenPrice, _gasUnitPrice); + bytes memory message = PricingUpdateLib.encodeInfo(_gasTokenPrice, _gasUnitPrice); _expectMessagingEmits(message); // receive() is disabled, so this will also check if the totalFee is exactly the needed fee @@ -189,7 +189,7 @@ contract GasFeePricingUpgradeableMessagingTest is GasFeePricingSetup { bytes32 messageId = utils.getNextKappa(); bytes32 srcAddress = utils.addressToBytes32(remoteVars[chainId].gasFeePricing); - bytes memory message = GasFeePricingUpdates.encodeConfig(_gasDropMax, _gasUnitsRcvMsg, _minGasUsageFeeUsd); + bytes memory message = PricingUpdateLib.encodeConfig(_gasDropMax, _gasUnitsRcvMsg, _minGasUsageFeeUsd); hoax(NODE); messageBus.executeMessage(chainId, srcAddress, address(gasFeePricing), 100000, 0, message, messageId); @@ -209,7 +209,7 @@ contract GasFeePricingUpgradeableMessagingTest is GasFeePricingSetup { bytes32 messageId = utils.getNextKappa(); bytes32 srcAddress = utils.addressToBytes32(remoteVars[chainId].gasFeePricing); - bytes memory message = GasFeePricingUpdates.encodeInfo(_gasTokenPrice, _gasUnitPrice); + bytes memory message = PricingUpdateLib.encodeInfo(_gasTokenPrice, _gasUnitPrice); hoax(NODE); messageBus.executeMessage(chainId, srcAddress, address(gasFeePricing), 100000, 0, message, messageId); @@ -237,7 +237,7 @@ contract GasFeePricingUpgradeableMessagingTest is GasFeePricingSetup { bytes32 receiver = keccak256("Not a fake address"); - bytes memory options = Options.encode(100000, gasDropAmount, receiver); + bytes memory options = OptionsLib.encode(100000, gasDropAmount, receiver); if (gasDropAmount > gasDropMax) { vm.expectRevert("GasDrop higher than max"); @@ -292,7 +292,7 @@ contract GasFeePricingUpgradeableMessagingTest is GasFeePricingSetup { function _expectMessagingEmits(bytes memory message) internal { for (uint256 i = 0; i < TEST_CHAINS; ++i) { uint256 chainId = remoteChainIds[i]; - bytes memory options = Options.encode(remoteVars[chainId].gasUnitsRcvMsg); + bytes memory options = OptionsLib.encode(remoteVars[chainId].gasUnitsRcvMsg); uint256 fee = messageBus.estimateFee(chainId, options); bytes32 receiver = utils.addressToBytes32(remoteVars[chainId].gasFeePricing); uint64 nonce = uint64(i); diff --git a/test/messaging/gfp/GasFeePricingSetup.t.sol b/test/messaging/gfp/GasFeePricingSetup.t.sol index 43bd5bb73..83189335d 100644 --- a/test/messaging/gfp/GasFeePricingSetup.t.sol +++ b/test/messaging/gfp/GasFeePricingSetup.t.sol @@ -8,7 +8,7 @@ import {Utilities} from "../../utils/Utilities.sol"; import "src-messaging/AuthVerifier.sol"; import "src-messaging/GasFeePricingUpgradeable.sol"; import "src-messaging/MessageBusUpgradeable.sol"; -import "src-messaging/libraries/GasFeePricingUpdates.sol"; +import "src-messaging/libraries/PricingUpdateLib.sol"; abstract contract GasFeePricingSetup is Test { struct ChainVars { From 4a836b0e02cbce76b98226309fcd662cdf911cb2 Mon Sep 17 00:00:00 2001 From: ChiTimesChi <88190723+ChiTimesChi@users.noreply.github.com> Date: Fri, 27 May 2022 21:13:00 +0300 Subject: [PATCH 43/47] yoink bytes32 to address library --- .../messaging/libraries/Bytes32AddressLib.sol | 14 ++++++++++++++ 1 file changed, 14 insertions(+) create mode 100644 contracts/messaging/libraries/Bytes32AddressLib.sol diff --git a/contracts/messaging/libraries/Bytes32AddressLib.sol b/contracts/messaging/libraries/Bytes32AddressLib.sol new file mode 100644 index 000000000..bc857be10 --- /dev/null +++ b/contracts/messaging/libraries/Bytes32AddressLib.sol @@ -0,0 +1,14 @@ +// SPDX-License-Identifier: AGPL-3.0-only +pragma solidity >=0.8.0; + +/// @notice Library for converting between addresses and bytes32 values. +/// @author Solmate (https://github.com/Rari-Capital/solmate/blob/main/src/utils/Bytes32AddressLib.sol) +library Bytes32AddressLib { + function fromLast20Bytes(bytes32 bytesValue) internal pure returns (address) { + return address(uint160(uint256(bytesValue))); + } + + function fillLast12Bytes(address addressValue) internal pure returns (bytes32) { + return bytes32(bytes20(addressValue)); + } +} From ca2a4037362c41387aceaa712d9f1787e9f898a2 Mon Sep 17 00:00:00 2001 From: ChiTimesChi <88190723+ChiTimesChi@users.noreply.github.com> Date: Fri, 27 May 2022 21:14:58 +0300 Subject: [PATCH 44/47] revert on encoding msg options, not on decoding --- contracts/messaging/libraries/OptionsLib.sol | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/contracts/messaging/libraries/OptionsLib.sol b/contracts/messaging/libraries/OptionsLib.sol index 47fc9465b..90fdc4f62 100644 --- a/contracts/messaging/libraries/OptionsLib.sol +++ b/contracts/messaging/libraries/OptionsLib.sol @@ -18,6 +18,8 @@ library OptionsLib { uint256 _gasDropAmount, bytes32 _dstReceiver ) internal pure returns (bytes memory) { + require(_gasDropAmount != 0, "gasDropAmount empty"); + require(_dstReceiver != bytes32(0), "dstReceiver empty"); return abi.encodePacked(uint16(TxType.GASDROP), _gasLimit, _gasDropAmount, _dstReceiver); } @@ -44,8 +46,6 @@ library OptionsLib { gasDropAmount := mload(add(_options, 66)) dstReceiver := mload(add(_options, 98)) } - require(gasDropAmount != 0, "gasDropAmount empty"); - require(dstReceiver != bytes32(0), "dstReceiver empty"); } } } From 969607f4d4b320fc70b086022c69b680c309ca50 Mon Sep 17 00:00:00 2001 From: ChiTimesChi <88190723+ChiTimesChi@users.noreply.github.com> Date: Fri, 27 May 2022 21:27:00 +0300 Subject: [PATCH 45/47] GasFeePricing -> MessageExecutor --- ...ble.sol => MessageExecutorUpgradeable.sol} | 40 ++++++++++++++++++- .../messaging/interfaces/IMessageExecutor.sol | 19 +++++++++ .../messaging/gfp/GasFeePricingSecurity.t.sol | 12 +++--- test/messaging/gfp/GasFeePricingSetters.t.sol | 8 ++-- test/messaging/gfp/GasFeePricingSetup.t.sol | 8 ++-- 5 files changed, 72 insertions(+), 15 deletions(-) rename contracts/messaging/{GasFeePricingUpgradeable.sol => MessageExecutorUpgradeable.sol} (91%) create mode 100644 contracts/messaging/interfaces/IMessageExecutor.sol diff --git a/contracts/messaging/GasFeePricingUpgradeable.sol b/contracts/messaging/MessageExecutorUpgradeable.sol similarity index 91% rename from contracts/messaging/GasFeePricingUpgradeable.sol rename to contracts/messaging/MessageExecutorUpgradeable.sol index ca3b26a82..f77eb1834 100644 --- a/contracts/messaging/GasFeePricingUpgradeable.sol +++ b/contracts/messaging/MessageExecutorUpgradeable.sol @@ -4,10 +4,13 @@ pragma solidity 0.8.13; import "./framework/SynMessagingReceiverUpgradeable.sol"; import "./interfaces/IGasFeePricing.sol"; +import "./libraries/Bytes32AddressLib.sol"; import "./libraries/OptionsLib.sol"; import "./libraries/PricingUpdateLib.sol"; -contract GasFeePricingUpgradeable is SynMessagingReceiverUpgradeable { +contract MessageExecutorUpgradeable is SynMessagingReceiverUpgradeable { + using Bytes32AddressLib for bytes32; + /*╔══════════════════════════════════════════════════════════════════════╗*\ ▏*║ STRUCTS ║*▕ \*╚══════════════════════════════════════════════════════════════════════╝*/ @@ -445,4 +448,39 @@ contract GasFeePricingUpgradeable is SynMessagingReceiverUpgradeable { revert("Unknown message type"); } } + + /*╔══════════════════════════════════════════════════════════════════════╗*\ + ▏*║ EXECUTING MESSAGES LOGIC ║*▕ + \*╚══════════════════════════════════════════════════════════════════════╝*/ + + function executeMessage( + uint256 _srcChainId, + bytes32 _srcAddress, + address _dstAddress, + bytes calldata _message, + bytes calldata _options + ) external returns (address gasDropRecipient, uint256 gasDropAmount) { + require(msg.sender == messageBus, "!messageBus"); + + (uint256 gasLimit, uint256 _gasDropAmount, bytes32 _dstReceiver) = OptionsLib.decode(_options); + if (_gasDropAmount != 0) { + // check if requested airdrop is not more than max allowed + uint256 maxGasDropAmount = localConfig.gasDropMax; + // cap gas airdrop to max amount if needed + if (_gasDropAmount > maxGasDropAmount) _gasDropAmount = maxGasDropAmount; + // check airdrop amount again, in case max amount was 0 + if (_gasDropAmount != 0) { + address payable receiver = payable(_dstReceiver.fromLast20Bytes()); + if (receiver != address(0)) { + if (receiver.send(_gasDropAmount)) { + gasDropRecipient = receiver; + gasDropAmount = _gasDropAmount; + } + } + } + } + // tx.origin is in fact the initial message executor on local chain + // TODO: do we need to pass that information though? + ISynMessagingReceiver(_dstAddress).executeMessage{gas: gasLimit}(_srcAddress, _srcChainId, _message, tx.origin); + } } diff --git a/contracts/messaging/interfaces/IMessageExecutor.sol b/contracts/messaging/interfaces/IMessageExecutor.sol new file mode 100644 index 000000000..137c11cde --- /dev/null +++ b/contracts/messaging/interfaces/IMessageExecutor.sol @@ -0,0 +1,19 @@ +// SPDX-License-Identifier: MIT + +pragma solidity ^0.8.0; + +interface IMessageExecutor { + /** + * @notice Returns srcGasToken fee to charge in wei for the cross-chain message based on the gas limit, gas airdrop, etc. + * @param _options Versioned struct used to instruct message executor on how to proceed with gas limit, gas airdrop, etc. + */ + function estimateGasFee(uint256 _dstChainId, bytes calldata _options) external view returns (uint256 fee); + + function executeMessage( + uint256 _srcChainId, + bytes32 _srcAddress, + address _dstAddress, + bytes calldata _message, + bytes calldata _options + ) external returns (address gasDropRecipient, uint256 gasDropAmount); +} diff --git a/test/messaging/gfp/GasFeePricingSecurity.t.sol b/test/messaging/gfp/GasFeePricingSecurity.t.sol index 0708d66d5..c424d9484 100644 --- a/test/messaging/gfp/GasFeePricingSecurity.t.sol +++ b/test/messaging/gfp/GasFeePricingSecurity.t.sol @@ -24,7 +24,7 @@ contract GasFeePricingUpgradeableSecurityTest is GasFeePricingSetup { utils.checkAccess( address(gasFeePricing), - abi.encodeWithSelector(GasFeePricingUpgradeable.initialize.selector, address(0), 0, 0, 0), + abi.encodeWithSelector(MessageExecutorUpgradeable.initialize.selector, address(0), 0, 0, 0), "Initializable: contract is already initialized" ); } @@ -34,7 +34,7 @@ contract GasFeePricingUpgradeableSecurityTest is GasFeePricingSetup { utils.checkAccess( _gfp, abi.encodeWithSelector( - GasFeePricingUpgradeable.setRemoteConfig.selector, + MessageExecutorUpgradeable.setRemoteConfig.selector, new uint256[](1), new uint112[](1), new uint80[](1), @@ -45,7 +45,7 @@ contract GasFeePricingUpgradeableSecurityTest is GasFeePricingSetup { utils.checkAccess( _gfp, abi.encodeWithSelector( - GasFeePricingUpgradeable.setRemoteInfo.selector, + MessageExecutorUpgradeable.setRemoteInfo.selector, new uint256[](1), new uint128[](1), new uint128[](1) @@ -55,7 +55,7 @@ contract GasFeePricingUpgradeableSecurityTest is GasFeePricingSetup { utils.checkAccess( _gfp, abi.encodeWithSelector( - GasFeePricingUpgradeable.setRemoteMarkups.selector, + MessageExecutorUpgradeable.setRemoteMarkups.selector, new uint256[](1), new uint16[](1), new uint16[](1) @@ -65,13 +65,13 @@ contract GasFeePricingUpgradeableSecurityTest is GasFeePricingSetup { utils.checkAccess( _gfp, - abi.encodeWithSelector(GasFeePricingUpgradeable.updateLocalConfig.selector, 0, 0, 0), + abi.encodeWithSelector(MessageExecutorUpgradeable.updateLocalConfig.selector, 0, 0, 0), "Ownable: caller is not the owner" ); utils.checkAccess( _gfp, - abi.encodeWithSelector(GasFeePricingUpgradeable.updateLocalInfo.selector, 0, 0), + abi.encodeWithSelector(MessageExecutorUpgradeable.updateLocalInfo.selector, 0, 0), "Ownable: caller is not the owner" ); } diff --git a/test/messaging/gfp/GasFeePricingSetters.t.sol b/test/messaging/gfp/GasFeePricingSetters.t.sol index 984a9530b..e9bd6b3bb 100644 --- a/test/messaging/gfp/GasFeePricingSetters.t.sol +++ b/test/messaging/gfp/GasFeePricingSetters.t.sol @@ -67,7 +67,7 @@ contract GasFeePricingUpgradeableSettersTest is GasFeePricingSetup { address(this), address(gasFeePricing), abi.encodeWithSelector( - GasFeePricingUpgradeable.setRemoteConfig.selector, + MessageExecutorUpgradeable.setRemoteConfig.selector, remoteChainIds, gasDropMax, gasUnitsRcvMsg, @@ -93,7 +93,7 @@ contract GasFeePricingUpgradeableSettersTest is GasFeePricingSetup { address(this), address(gasFeePricing), abi.encodeWithSelector( - GasFeePricingUpgradeable.setRemoteInfo.selector, + MessageExecutorUpgradeable.setRemoteInfo.selector, remoteChainIds, gasTokenPrices, gasUnitPrices @@ -164,7 +164,7 @@ contract GasFeePricingUpgradeableSettersTest is GasFeePricingSetup { address(this), address(gasFeePricing), abi.encodeWithSelector( - GasFeePricingUpgradeable.updateLocalConfig.selector, + MessageExecutorUpgradeable.updateLocalConfig.selector, gasDropMax, gasUnitsRcvMsg, minGasUsageFeeUsd @@ -191,7 +191,7 @@ contract GasFeePricingUpgradeableSettersTest is GasFeePricingSetup { utils.checkRevert( address(this), address(gasFeePricing), - abi.encodeWithSelector(GasFeePricingUpgradeable.updateLocalInfo.selector, gasTokenPrice, gasUnitPrice), + abi.encodeWithSelector(MessageExecutorUpgradeable.updateLocalInfo.selector, gasTokenPrice, gasUnitPrice), "Gas token price is not set" ); } diff --git a/test/messaging/gfp/GasFeePricingSetup.t.sol b/test/messaging/gfp/GasFeePricingSetup.t.sol index 83189335d..4537df438 100644 --- a/test/messaging/gfp/GasFeePricingSetup.t.sol +++ b/test/messaging/gfp/GasFeePricingSetup.t.sol @@ -6,7 +6,7 @@ import "forge-std/Test.sol"; import {Utilities} from "../../utils/Utilities.sol"; import "src-messaging/AuthVerifier.sol"; -import "src-messaging/GasFeePricingUpgradeable.sol"; +import "src-messaging/MessageExecutorUpgradeable.sol"; import "src-messaging/MessageBusUpgradeable.sol"; import "src-messaging/libraries/PricingUpdateLib.sol"; @@ -25,7 +25,7 @@ abstract contract GasFeePricingSetup is Test { Utilities internal utils; AuthVerifier internal authVerifier; - GasFeePricingUpgradeable internal gasFeePricing; + MessageExecutorUpgradeable internal gasFeePricing; MessageBusUpgradeable internal messageBus; ChainVars internal localVars; @@ -54,10 +54,10 @@ abstract contract GasFeePricingSetup is Test { localVars.gasTokenPrice = 10**18; MessageBusUpgradeable busImpl = new MessageBusUpgradeable(); - GasFeePricingUpgradeable pricingImpl = new GasFeePricingUpgradeable(); + MessageExecutorUpgradeable pricingImpl = new MessageExecutorUpgradeable(); messageBus = MessageBusUpgradeable(utils.deployTransparentProxy(address(busImpl))); - gasFeePricing = GasFeePricingUpgradeable(utils.deployTransparentProxy(address(pricingImpl))); + gasFeePricing = MessageExecutorUpgradeable(utils.deployTransparentProxy(address(pricingImpl))); // I don't have extra 10M laying around, so let's initialize those proxies messageBus.initialize(address(gasFeePricing), address(authVerifier)); From 54e62cc0ea9197090f08517f847593f89a98bca5 Mon Sep 17 00:00:00 2001 From: ChiTimesChi <88190723+ChiTimesChi@users.noreply.github.com> Date: Fri, 27 May 2022 22:23:08 +0300 Subject: [PATCH 46/47] MessageBus restructuring --- contracts/messaging/HarmonyMessageBus.sol | 6 +--- contracts/messaging/MessageBus.sol | 26 ++++++++++++---- contracts/messaging/MessageBusBase.sol | 18 +++++++++++ contracts/messaging/MessageBusReceiver.sol | 35 ++-------------------- contracts/messaging/MessageBusSender.sol | 26 ++++------------ 5 files changed, 47 insertions(+), 64 deletions(-) create mode 100644 contracts/messaging/MessageBusBase.sol diff --git a/contracts/messaging/HarmonyMessageBus.sol b/contracts/messaging/HarmonyMessageBus.sol index 732cae929..0e7a39c12 100644 --- a/contracts/messaging/HarmonyMessageBus.sol +++ b/contracts/messaging/HarmonyMessageBus.sol @@ -7,11 +7,7 @@ import "./MessageBus.sol"; contract HarmonyMessageBus is MessageBus { uint256 private constant CHAIN_ID = 1666600000; - constructor( - IGasFeePricing _pricing, - IAuthVerifier _verifier, - IMessageExecutor _executor - ) MessageBus(_pricing, _verifier, _executor) { + constructor(IAuthVerifier _verifier, IMessageExecutor _executor) MessageBus(_verifier, _executor) { this; } diff --git a/contracts/messaging/MessageBus.sol b/contracts/messaging/MessageBus.sol index 5c405dbef..1781e58af 100644 --- a/contracts/messaging/MessageBus.sol +++ b/contracts/messaging/MessageBus.sol @@ -6,16 +6,30 @@ import "./MessageBusSender.sol"; import "./MessageBusReceiver.sol"; contract MessageBus is MessageBusSender, MessageBusReceiver { - constructor( - IGasFeePricing _pricing, - IAuthVerifier _verifier, - IMessageExecutor _executor - ) { - pricing = _pricing; + constructor(IAuthVerifier _verifier, IMessageExecutor _executor) { verifier = _verifier; executor = _executor; } + /*╔══════════════════════════════════════════════════════════════════════╗*\ + ▏*║ UPDATING: ONLY OWNER ║*▕ + \*╚══════════════════════════════════════════════════════════════════════╝*/ + + function updateAuthVerifier(IAuthVerifier _verifier) external onlyOwner { + require(address(_verifier) != address(0), "Cannot set to 0"); + verifier = _verifier; + } + + function updateMessageExecutor(IMessageExecutor _executor) external onlyOwner { + require(address(_executor) != address(0), "Cannot set to 0"); + executor = _executor; + } + + // TODO: how useful is that, if contract is immutable? + function updateMessageStatus(bytes32 _messageId, TxStatus _status) external onlyOwner { + executedMessages[_messageId] = _status; + } + /*╔══════════════════════════════════════════════════════════════════════╗*\ ▏*║ PAUSABLE ║*▕ \*╚══════════════════════════════════════════════════════════════════════╝*/ diff --git a/contracts/messaging/MessageBusBase.sol b/contracts/messaging/MessageBusBase.sol new file mode 100644 index 000000000..bab8df2d1 --- /dev/null +++ b/contracts/messaging/MessageBusBase.sol @@ -0,0 +1,18 @@ +// SPDX-License-Identifier: MIT + +pragma solidity 0.8.13; + +import "./interfaces/IAuthVerifier.sol"; +import "./interfaces/IMessageExecutor.sol"; + +import "@openzeppelin/contracts-4.5.0/access/Ownable.sol"; +import "@openzeppelin/contracts-4.5.0/security/Pausable.sol"; + +contract MessageBusBase is Ownable, Pausable { + /// @dev Contract used for executing received messages, + /// and for calculating a fee for sending a message + IMessageExecutor public executor; + + /// @dev Contract used for authenticating validator address + IAuthVerifier public verifier; +} diff --git a/contracts/messaging/MessageBusReceiver.sol b/contracts/messaging/MessageBusReceiver.sol index 8935cba6e..de1cac3e0 100644 --- a/contracts/messaging/MessageBusReceiver.sol +++ b/contracts/messaging/MessageBusReceiver.sol @@ -2,13 +2,9 @@ pragma solidity 0.8.13; -import "@openzeppelin/contracts-4.5.0/access/Ownable.sol"; -import "@openzeppelin/contracts-4.5.0/security/Pausable.sol"; +import "./MessageBusBase.sol"; -import "./interfaces/IAuthVerifier.sol"; -import "./interfaces/IMessageExecutor.sol"; - -contract MessageBusReceiver is Ownable, Pausable { +contract MessageBusReceiver is MessageBusBase { enum TxStatus { Null, Success, @@ -25,36 +21,9 @@ contract MessageBusReceiver is Ownable, Pausable { event CallReverted(string reason); - /*╔══════════════════════════════════════════════════════════════════════╗*\ - ▏*║ STORAGE ║*▕ - \*╚══════════════════════════════════════════════════════════════════════╝*/ - - /// @dev Contract used for authenticating validator address - IAuthVerifier public verifier; - /// @dev Contract used for executing received messages - IMessageExecutor public executor; /// @dev Status of all executed messages mapping(bytes32 => TxStatus) public executedMessages; - /*╔══════════════════════════════════════════════════════════════════════╗*\ - ▏*║ ONLY OWNER ║*▕ - \*╚══════════════════════════════════════════════════════════════════════╝*/ - - function updateAuthVerifier(IAuthVerifier _verifier) external onlyOwner { - require(address(_verifier) != address(0), "Cannot set to 0"); - verifier = _verifier; - } - - function updateMessageExecutor(IMessageExecutor _executor) external onlyOwner { - require(address(_executor) != address(0), "Cannot set to 0"); - executor = _executor; - } - - // TODO: how useful is that, if contract is immutable? - function updateMessageStatus(bytes32 _messageId, TxStatus _status) external onlyOwner { - executedMessages[_messageId] = _status; - } - /*╔══════════════════════════════════════════════════════════════════════╗*\ ▏*║ MESSAGING LOGIC ║*▕ \*╚══════════════════════════════════════════════════════════════════════╝*/ diff --git a/contracts/messaging/MessageBusSender.sol b/contracts/messaging/MessageBusSender.sol index 134768fab..571496be5 100644 --- a/contracts/messaging/MessageBusSender.sol +++ b/contracts/messaging/MessageBusSender.sol @@ -2,13 +2,10 @@ pragma solidity 0.8.13; -import "./interfaces/IGasFeePricing.sol"; import "./ContextChainId.sol"; +import "./MessageBusBase.sol"; -import "@openzeppelin/contracts-4.5.0/access/Ownable.sol"; -import "@openzeppelin/contracts-4.5.0/security/Pausable.sol"; - -contract MessageBusSender is Ownable, Pausable, ContextChainId { +contract MessageBusSender is MessageBusBase, ContextChainId { event MessageSent( address indexed sender, uint256 srcChainID, @@ -21,15 +18,9 @@ contract MessageBusSender is Ownable, Pausable, ContextChainId { bytes32 indexed messageId ); - /*╔══════════════════════════════════════════════════════════════════════╗*\ - ▏*║ STORAGE ║*▕ - \*╚══════════════════════════════════════════════════════════════════════╝*/ - - /// @dev Contract used for calculating fee for sending a message - IGasFeePricing public pricing; - /// @dev Nonce of the next send message (in other words, amount of messages sent) + /// @dev Nonce of the next sent message (amount of messages already sent) uint256 public nonce; - /// @dev Accrued messaging fees. Withdrawable by the owner. + /// @dev Collected messaging fees. Withdrawable by the owner. uint256 public fees; /*╔══════════════════════════════════════════════════════════════════════╗*\ @@ -47,8 +38,8 @@ contract MessageBusSender is Ownable, Pausable, ContextChainId { return keccak256(abi.encode(_srcAddress, _srcChainId, _dstAddress, _dstChainId, _srcNonce, _message)); } - function estimateFee(uint256 _dstChainId, bytes calldata _options) public returns (uint256) { - uint256 fee = pricing.estimateGasFee(_dstChainId, _options); + function estimateFee(uint256 _dstChainId, bytes calldata _options) public view returns (uint256) { + uint256 fee = executor.estimateGasFee(_dstChainId, _options); require(fee != 0, "Fee not set"); return fee; } @@ -75,11 +66,6 @@ contract MessageBusSender is Ownable, Pausable, ContextChainId { delete fees; } - function updateGasFeePricing(IGasFeePricing _pricing) external onlyOwner { - require(address(_pricing) != address(0), "Cannot set to 0"); - pricing = _pricing; - } - /*╔══════════════════════════════════════════════════════════════════════╗*\ ▏*║ MESSAGING LOGIC ║*▕ \*╚══════════════════════════════════════════════════════════════════════╝*/ From 7fdf2eb21ef5096e761f2b3e8b2d793c03c6965b Mon Sep 17 00:00:00 2001 From: ChiTimesChi <88190723+ChiTimesChi@users.noreply.github.com> Date: Fri, 27 May 2022 22:27:16 +0300 Subject: [PATCH 47/47] MessageBus: emit gas airdrop info --- contracts/messaging/MessageBusReceiver.sol | 15 ++++++++++++--- 1 file changed, 12 insertions(+), 3 deletions(-) diff --git a/contracts/messaging/MessageBusReceiver.sol b/contracts/messaging/MessageBusReceiver.sol index de1cac3e0..4f6708795 100644 --- a/contracts/messaging/MessageBusReceiver.sol +++ b/contracts/messaging/MessageBusReceiver.sol @@ -16,7 +16,9 @@ contract MessageBusReceiver is MessageBusBase { TxStatus status, address indexed dstAddress, uint256 indexed srcChainId, - uint256 srcNonce + uint256 srcNonce, + address gasDropRecipient, + uint256 gasDropAmount ); event CallReverted(string reason); @@ -61,9 +63,16 @@ contract MessageBusReceiver is MessageBusBase { verifier.msgAuth(abi.encode(msg.sender, _messageId, _proof)); TxStatus status; - try executor.executeMessage(_srcChainId, _srcAddress, _dstAddress, _message, _options) { + address gasDropRecipient; + uint256 gasDropAmount; + try executor.executeMessage(_srcChainId, _srcAddress, _dstAddress, _message, _options) returns ( + address _gasDropRecipient, + uint256 _gasDropAmount + ) { // Assuming success state if no revert status = TxStatus.Success; + gasDropRecipient = _gasDropRecipient; + gasDropAmount = _gasDropAmount; } catch (bytes memory reason) { // call hard reverted & failed emit CallReverted(_getRevertMsg(reason)); @@ -71,7 +80,7 @@ contract MessageBusReceiver is MessageBusBase { } executedMessages[_messageId] = status; - emit Executed(_messageId, status, _dstAddress, _srcChainId, _srcNonce); + emit Executed(_messageId, status, _dstAddress, _srcChainId, _srcNonce, gasDropRecipient, gasDropAmount); } /*╔══════════════════════════════════════════════════════════════════════╗*\