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

MessageBus restructure #162

Draft
wants to merge 49 commits into
base: master
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
49 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
6f1d697
MB: immutable contracts
ChiTimesChi May 25, 2022
c47eca8
slightly better docs
ChiTimesChi May 26, 2022
3757cd5
remove extra casting
ChiTimesChi May 26, 2022
4830921
remove unnecessary cast to uint64
ChiTimesChi May 26, 2022
88e4a80
chore: rename new libraries
ChiTimesChi May 27, 2022
4a836b0
yoink bytes32 to address library
ChiTimesChi May 27, 2022
ca2a403
revert on encoding msg options, not on decoding
ChiTimesChi May 27, 2022
969607f
GasFeePricing -> MessageExecutor
ChiTimesChi May 27, 2022
c75f2e1
Merge branch 'feature/message-bus-immutable' into feature/message-bus…
ChiTimesChi May 27, 2022
5bb3cf2
Merge branch 'feature/messaging-gas-pricing' into feature/message-bus…
ChiTimesChi May 27, 2022
54e62cc
MessageBus restructuring
ChiTimesChi May 27, 2022
7fdf2eb
MessageBus: emit gas airdrop info
ChiTimesChi May 27, 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
15 changes: 15 additions & 0 deletions contracts/messaging/ContextChainId.sol
Original file line number Diff line number Diff line change
@@ -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;
}
}
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.
17 changes: 17 additions & 0 deletions contracts/messaging/HarmonyMessageBus.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
// SPDX-License-Identifier: MIT

pragma solidity 0.8.13;

import "./MessageBus.sol";

contract HarmonyMessageBus is MessageBus {
uint256 private constant CHAIN_ID = 1666600000;

constructor(IAuthVerifier _verifier, IMessageExecutor _executor) MessageBus(_verifier, _executor) {
this;
}

function _chainId() internal pure override returns (uint256) {
return CHAIN_ID;
}
}
44 changes: 44 additions & 0 deletions contracts/messaging/MessageBus.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
// SPDX-License-Identifier: MIT

pragma solidity 0.8.13;

import "./MessageBusSender.sol";
import "./MessageBusReceiver.sol";

contract MessageBus is MessageBusSender, MessageBusReceiver {
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 ║*▕
\*╚══════════════════════════════════════════════════════════════════════╝*/

function pause() external onlyOwner {
_pause();
}

function unpause() external onlyOwner {
_unpause();
}
}
18 changes: 18 additions & 0 deletions contracts/messaging/MessageBusBase.sol
Original file line number Diff line number Diff line change
@@ -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;
}
102 changes: 102 additions & 0 deletions contracts/messaging/MessageBusReceiver.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,102 @@
// SPDX-License-Identifier: MIT

pragma solidity 0.8.13;

import "./MessageBusBase.sol";

contract MessageBusReceiver is MessageBusBase {
enum TxStatus {
Null,
Success,
Fail
}

event Executed(
bytes32 indexed messageId,
TxStatus status,
address indexed dstAddress,
uint256 indexed srcChainId,
uint256 srcNonce,
address gasDropRecipient,
uint256 gasDropAmount
);

event CallReverted(string reason);

/// @dev Status of all executed messages
mapping(bytes32 => TxStatus) public executedMessages;

/*╔══════════════════════════════════════════════════════════════════════╗*\
▏*║ 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 {
/// @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
verifier.msgAuth(abi.encode(msg.sender, _messageId, _proof));

TxStatus status;
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));
status = TxStatus.Fail;
}

executedMessages[_messageId] = status;
emit Executed(_messageId, status, _dstAddress, _srcChainId, _srcNonce, gasDropRecipient, gasDropAmount);
}

/*╔══════════════════════════════════════════════════════════════════════╗*\
▏*║ 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
}
}
Loading