163 lines
5.6 KiB
Solidity
163 lines
5.6 KiB
Solidity
|
// SPDX-License-Identifier: MIT
|
||
|
|
||
|
pragma solidity >=0.8.9;
|
||
|
|
||
|
interface ISigsVerifier {
|
||
|
/**
|
||
|
* @notice Verifies that a message is signed by a quorum among the signers.
|
||
|
* @param _msg signed message
|
||
|
* @param _sigs list of signatures sorted by signer addresses in ascending order
|
||
|
* @param _signers sorted list of current signers
|
||
|
* @param _powers powers of current signers
|
||
|
*/
|
||
|
function verifySigs(
|
||
|
bytes memory _msg,
|
||
|
bytes[] calldata _sigs,
|
||
|
address[] calldata _signers,
|
||
|
uint256[] calldata _powers
|
||
|
) external view;
|
||
|
}
|
||
|
|
||
|
contract MessageBusSender {
|
||
|
ISigsVerifier public immutable sigsVerifier;
|
||
|
|
||
|
uint256 public feeBase;
|
||
|
uint256 public feePerByte;
|
||
|
mapping(address => uint256) public withdrawnFees;
|
||
|
|
||
|
event Message(
|
||
|
address indexed sender,
|
||
|
address receiver,
|
||
|
uint256 dstChainId,
|
||
|
bytes message,
|
||
|
uint256 fee
|
||
|
);
|
||
|
|
||
|
event MessageWithTransfer(
|
||
|
address indexed sender,
|
||
|
address receiver,
|
||
|
uint256 dstChainId,
|
||
|
address bridge,
|
||
|
bytes32 srcTransferId,
|
||
|
bytes message,
|
||
|
uint256 fee
|
||
|
);
|
||
|
|
||
|
event FeeBaseUpdated(uint256 feeBase);
|
||
|
event FeePerByteUpdated(uint256 feePerByte);
|
||
|
|
||
|
constructor(ISigsVerifier _sigsVerifier) {
|
||
|
sigsVerifier = _sigsVerifier;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* @notice Sends a message to a contract on another chain.
|
||
|
* Sender needs to make sure the uniqueness of the message Id, which is computed as
|
||
|
* hash(type.MessageOnly, sender, receiver, srcChainId, srcTxHash, dstChainId, message).
|
||
|
* If messages with the same Id are sent, only one of them will succeed at dst chain.
|
||
|
* A fee is charged in the native gas token.
|
||
|
* @param _receiver The address of the destination app contract.
|
||
|
* @param _dstChainId The destination chain ID.
|
||
|
* @param _message Arbitrary message bytes to be decoded by the destination app contract.
|
||
|
*/
|
||
|
function sendMessage(
|
||
|
address _receiver,
|
||
|
uint256 _dstChainId,
|
||
|
bytes calldata _message
|
||
|
) external payable {
|
||
|
require(_dstChainId != block.chainid, "Invalid chainId");
|
||
|
uint256 minFee = calcFee(_message);
|
||
|
require(msg.value >= minFee, "Insufficient fee");
|
||
|
emit Message(msg.sender, _receiver, _dstChainId, _message, msg.value);
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* @notice Sends a message associated with a transfer to a contract on another chain.
|
||
|
* If messages with the same srcTransferId are sent, only one of them will succeed.
|
||
|
* A fee is charged in the native token.
|
||
|
* @param _receiver The address of the destination app contract.
|
||
|
* @param _dstChainId The destination chain ID.
|
||
|
* @param _srcBridge The bridge contract to send the transfer with.
|
||
|
* @param _srcTransferId The transfer ID.
|
||
|
* @param _dstChainId The destination chain ID.
|
||
|
* @param _message Arbitrary message bytes to be decoded by the destination app contract.
|
||
|
*/
|
||
|
function sendMessageWithTransfer(
|
||
|
address _receiver,
|
||
|
uint256 _dstChainId,
|
||
|
address _srcBridge,
|
||
|
bytes32 _srcTransferId,
|
||
|
bytes calldata _message
|
||
|
) external payable {
|
||
|
require(_dstChainId != block.chainid, "Invalid chainId");
|
||
|
uint256 minFee = calcFee(_message);
|
||
|
require(msg.value >= minFee, "Insufficient fee");
|
||
|
// SGN needs to verify
|
||
|
// 1. msg.sender matches sender of the src transfer
|
||
|
// 2. dstChainId matches dstChainId of the src transfer
|
||
|
// 3. bridge is either liquidity bridge, peg src vault, or peg dst bridge
|
||
|
emit MessageWithTransfer(
|
||
|
msg.sender,
|
||
|
_receiver,
|
||
|
_dstChainId,
|
||
|
_srcBridge,
|
||
|
_srcTransferId,
|
||
|
_message,
|
||
|
msg.value
|
||
|
);
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* @notice Withdraws message fee in the form of native gas token.
|
||
|
* @param _account The address receiving the fee.
|
||
|
* @param _cumulativeFee The cumulative fee credited to the account. Tracked by SGN.
|
||
|
* @param _sigs The list of signatures sorted by signing addresses in ascending order. A withdrawal must be
|
||
|
* signed-off by +2/3 of the sigsVerifier's current signing power to be delivered.
|
||
|
* @param _signers The sorted list of signers.
|
||
|
* @param _powers The signing powers of the signers.
|
||
|
*/
|
||
|
function withdrawFee(
|
||
|
address _account,
|
||
|
uint256 _cumulativeFee,
|
||
|
bytes[] calldata _sigs,
|
||
|
address[] calldata _signers,
|
||
|
uint256[] calldata _powers
|
||
|
) external {
|
||
|
bytes32 domain = keccak256(
|
||
|
abi.encodePacked(block.chainid, address(this), "withdrawFee")
|
||
|
);
|
||
|
sigsVerifier.verifySigs(
|
||
|
abi.encodePacked(domain, _account, _cumulativeFee),
|
||
|
_sigs,
|
||
|
_signers,
|
||
|
_powers
|
||
|
);
|
||
|
uint256 amount = _cumulativeFee - withdrawnFees[_account];
|
||
|
require(amount > 0, "No new amount to withdraw");
|
||
|
withdrawnFees[_account] = _cumulativeFee;
|
||
|
(bool sent, ) = _account.call{value: amount, gas: 50000}("");
|
||
|
require(sent, "failed to withdraw fee");
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* @notice Calculates the required fee for the message.
|
||
|
* @param _message Arbitrary message bytes to be decoded by the destination app contract.
|
||
|
@ @return The required fee.
|
||
|
*/
|
||
|
function calcFee(bytes calldata _message) public view returns (uint256) {
|
||
|
return feeBase + _message.length * feePerByte;
|
||
|
}
|
||
|
|
||
|
// -------------------- Admin --------------------
|
||
|
|
||
|
function setFeePerByte(uint256 _fee) external {
|
||
|
feePerByte = _fee;
|
||
|
emit FeePerByteUpdated(feePerByte);
|
||
|
}
|
||
|
|
||
|
function setFeeBase(uint256 _fee) external {
|
||
|
feeBase = _fee;
|
||
|
emit FeeBaseUpdated(feeBase);
|
||
|
}
|
||
|
}
|