Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

GasFeePricingUpgradeable #158

Closed
wants to merge 37 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
37 commits
Select commit Hold shift + click to select a range
6c6e9f5
fix GasFeePricing interface
ChiTimesChi May 20, 2022
0b21dbb
GasFeePricing: first draft
ChiTimesChi May 20, 2022
6562105
chore: formatting + docs
ChiTimesChi May 21, 2022
36c0372
estimateGasFees for a bunch of messages
ChiTimesChi May 21, 2022
ba0cb18
_send(): multiple messages at a time
ChiTimesChi May 21, 2022
03e53e2
Cross-chain gas price setup: first draft
ChiTimesChi May 21, 2022
69dc324
GasFeePricing: messages to update chain info on all other chains
ChiTimesChi May 21, 2022
dbfbd43
GasFeePricing: manual setDstChainConfig
ChiTimesChi May 21, 2022
775783a
estaimateFee should be view
ChiTimesChi May 21, 2022
42c5c47
add src-messaging to remappings
ChiTimesChi May 21, 2022
548766b
update test Utilities
ChiTimesChi May 21, 2022
b1249fa
tests: access, initialized for GasFeePricing
ChiTimesChi May 21, 2022
42c5cf0
tests: prepare for setters/getters testing
ChiTimesChi May 21, 2022
b6c6b31
I like pretty boxes
ChiTimesChi May 21, 2022
25829f5
GasFeePricing: min fee for gas usage on dst chain
ChiTimesChi May 21, 2022
73d8273
GasFeePricing: better docs
ChiTimesChi May 21, 2022
a6f1783
markups now start from 0%, not 100%
ChiTimesChi May 22, 2022
4e3ff05
I strive for perfection
ChiTimesChi May 22, 2022
869d3ab
GFP: markups are now chain-specific
ChiTimesChi May 22, 2022
e17a6ed
GFP: fix encoding
ChiTimesChi May 22, 2022
44ec353
GFP: remove duplicated functions, refactor names
ChiTimesChi May 22, 2022
8e44415
tests: check minGasUsageFee post-initialization
ChiTimesChi May 22, 2022
5580db9
setTrustedRemotes for a bunch of chains
ChiTimesChi May 22, 2022
74fe440
GFP: remove dstGasFeePricing(was shadowing trustedRemoteLookup)
ChiTimesChi May 22, 2022
a58544c
GFP: allow to set zero gas unit price (hi Aurora)
ChiTimesChi May 22, 2022
5d14361
GFP: add missing checks, unify ordering
ChiTimesChi May 22, 2022
cd88583
tests: GFP setters
ChiTimesChi May 22, 2022
221e204
GFP: refactoring + better docs
ChiTimesChi May 23, 2022
ec5a5ec
refactor: src/dst -> local/remote
ChiTimesChi May 23, 2022
4059476
GFP: introduce remote chain-specific min fee
ChiTimesChi May 23, 2022
ae006ee
tests: split GFP tests into categories
ChiTimesChi May 23, 2022
94675e9
GFP: implement (remote chain)-specific min fee
ChiTimesChi May 23, 2022
aa44c15
gas fee pricing explained
ChiTimesChi May 23, 2022
0c8e461
GFP: remote ratios are no longer used
ChiTimesChi May 23, 2022
635d984
tests: sending/receiving messages by GasFeePricing
ChiTimesChi May 23, 2022
7530588
GFP: fix bugs in estimateGasFee:
ChiTimesChi May 24, 2022
66dc56e
tests: GFP minFee, applying markups
ChiTimesChi May 24, 2022
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
63 changes: 63 additions & 0 deletions contracts/messaging/GasFeePricing.md
Original file line number Diff line number Diff line change
@@ -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.
448 changes: 448 additions & 0 deletions contracts/messaging/GasFeePricingUpgradeable.sol

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion contracts/messaging/MessageBusSenderUpgradeable.sol
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
75 changes: 66 additions & 9 deletions contracts/messaging/framework/SynMessagingReceiverUpgradeable.sol
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down Expand Up @@ -44,30 +44,76 @@ 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,
bytes memory _message,
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 */
Expand All @@ -77,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);
}
Expand Down
2 changes: 1 addition & 1 deletion contracts/messaging/interfaces/IGasFeePricing.sol
Original file line number Diff line number Diff line change
Expand Up @@ -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);
}
69 changes: 69 additions & 0 deletions contracts/messaging/libraries/GasFeePricingUpdates.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
// SPDX-License-Identifier: MIT

pragma solidity ^0.8.0;

library GasFeePricingUpdates {
enum MsgType {
UNKNOWN,
UPDATE_CONFIG,
UPDATE_INFO
}

function encodeConfig(
uint112 _gasDropMax,
uint80 _gasUnitsRcvMsg,
uint32 _minGasUsageFeeUsd
) internal pure returns (bytes memory) {
return abi.encodePacked(MsgType.UPDATE_CONFIG, _gasDropMax, _gasUnitsRcvMsg, _minGasUsageFeeUsd);
}

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 (
uint112 gasDropMax,
uint80 gasUnitsRcvMsg,
uint32 minGasUsageFeeUsd
)
{
// 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 {
msgType := mload(add(_message, 1))
}
}
}
51 changes: 51 additions & 0 deletions contracts/messaging/libraries/Options.sol
Original file line number Diff line number Diff line change
@@ -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");
}
}
}
4 changes: 3 additions & 1 deletion remappings.txt
Original file line number Diff line number Diff line change
@@ -1,2 +1,4 @@
ds-test=lib/ds-test/src
forge-std=lib/forge-std/src
forge-std=lib/forge-std/src

src-messaging=contracts/messaging
26 changes: 13 additions & 13 deletions test/dfk/HeroBridgeTest.sol
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
2 changes: 1 addition & 1 deletion test/dfk/HeroEncoding.t.sol
Original file line number Diff line number Diff line change
@@ -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 {
Expand Down
10 changes: 5 additions & 5 deletions test/dfk/TearBridge.t.sol
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
2 changes: 1 addition & 1 deletion test/messaging/AuthVerifier.t.sol
Original file line number Diff line number Diff line change
@@ -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;
Expand Down
Loading