Add `quoteDispatch` to `IPostDispatchHook` (#2660)

- `quoteDispatch` added to `IPostDispatchHook` interface and the
relevant hooks:
     - StaticProtocolFee
     - OPStackHook
     - InterchainGasPaymaster
     - MerkleTreeHook
     - PausableHook (no tests)
     - DomainRoutingHook (no tests)
     - ConfigFallbackDomainRoutingHook
     - ConfigurableDomainRoutingHook (no tests)

- `expectEmit` -> `expectCall`

- Quote in V3

Yes

Uint tests
pull/2736/head
Kunal Arora 1 year ago committed by Yorke Rhodes
parent 6a32287f00
commit 2b7ecfc312
No known key found for this signature in database
GPG Key ID: 9EEACF1DA75C5627
  1. 34
      solidity/contracts/hooks/ConfigFallbackDomainRoutingHook.sol
  2. 22
      solidity/contracts/hooks/DomainRoutingHook.sol
  3. 9
      solidity/contracts/hooks/ERC5164Hook.sol
  4. 7
      solidity/contracts/hooks/MerkleTreeHook.sol
  5. 12
      solidity/contracts/hooks/OPStackHook.sol
  6. 10
      solidity/contracts/hooks/PausableHook.sol
  7. 10
      solidity/contracts/hooks/StaticProtocolFee.sol
  8. 40
      solidity/contracts/igps/InterchainGasPaymaster.sol
  9. 16
      solidity/contracts/interfaces/hooks/IPostDispatchHook.sol
  10. 15
      solidity/contracts/libs/hooks/IGPMetadata.sol
  11. 12
      solidity/contracts/test/TestPostDispatchHook.sol
  12. 39
      solidity/test/hooks/FallbackDomainRoutingHook.t.sol
  13. 4
      solidity/test/hooks/StaticProtocolFee.t.sol
  14. 34
      solidity/test/igps/InterchainGasPaymaster.t.sol
  15. 10
      solidity/test/isms/OPStackIsm.t.sol

@ -35,14 +35,18 @@ contract ConfigFallbackDomainRoutingHook is IPostDispatchHook {
payable
override
{
IPostDispatchHook configuredHook = customHooks[message.senderAddress()][
message.destination()
][message.recipient()];
if (address(configuredHook) == address(0)) {
configuredHook = mailbox.defaultHook();
}
_getConfiguredHook(message).postDispatch{value: msg.value}(
metadata,
message
);
}
configuredHook.postDispatch{value: msg.value}(metadata, message);
function quoteDispatch(bytes calldata metadata, bytes calldata message)
public
view
returns (uint256)
{
return _getConfiguredHook(message).quoteDispatch(metadata, message);
}
function setHook(
@ -52,4 +56,20 @@ contract ConfigFallbackDomainRoutingHook is IPostDispatchHook {
) external {
customHooks[msg.sender][destinationDomain][recipient] = hook;
}
// ============ Internal Functions ============
function _getConfiguredHook(bytes calldata message)
internal
view
returns (IPostDispatchHook)
{
IPostDispatchHook configuredHook = customHooks[message.senderAddress()][
message.destination()
][message.recipient()];
if (address(configuredHook) == address(0)) {
configuredHook = mailbox.defaultHook();
}
return configuredHook;
}
}

@ -38,9 +38,29 @@ contract DomainRoutingHook is IPostDispatchHook, Ownable {
virtual
override
{
hooks[message.destination()].postDispatch{value: msg.value}(
_getConfiguredHook(message).postDispatch{value: msg.value}(
metadata,
message
);
}
function quoteDispatch(bytes calldata metadata, bytes calldata message)
public
view
virtual
override
returns (uint256)
{
return _getConfiguredHook(message).quoteDispatch(metadata, message);
}
// ============ Internal Functions ============
function _getConfiguredHook(bytes calldata message)
internal
view
returns (IPostDispatchHook)
{
return hooks[message.destination()];
}
}

@ -43,6 +43,15 @@ contract ERC5164Hook is AbstractMessageIdAuthHook {
dispatcher = IMessageDispatcher(_dispatcher);
}
function quoteDispatch(bytes calldata, bytes calldata)
external
pure
override
returns (uint256)
{
revert("not implemented");
}
function _sendMessageId(
bytes calldata, /* metadata */
bytes memory payload

@ -43,4 +43,11 @@ contract MerkleTreeHook is IPostDispatchHook, MailboxClient {
require(isLatestDispatched(id), "message not dispatching");
_tree.insert(id);
}
function quoteDispatch(
bytes calldata, /*metadata*/
bytes calldata /*message*/
) external pure override returns (uint256) {
return 0;
}
}

@ -57,6 +57,18 @@ contract OPStackHook is AbstractMessageIdAuthHook {
l1Messenger = ICrossDomainMessenger(_messenger);
}
// ============ External functions ============
/// @inheritdoc IPostDispatchHook
function quoteDispatch(bytes calldata, bytes calldata)
external
pure
override
returns (uint256)
{
return 0; // gas subsidized by the L2
}
// ============ Internal functions ============
/// @inheritdoc AbstractMessageIdAuthHook

@ -13,6 +13,16 @@ contract PausableHook is IPostDispatchHook, Ownable, Pausable {
whenNotPaused
{}
/// @inheritdoc IPostDispatchHook
function quoteDispatch(bytes calldata, bytes calldata)
external
pure
override
returns (uint256)
{
return 0;
}
function pause() external onlyOwner {
_pause();
}

@ -74,6 +74,16 @@ contract StaticProtocolFee is IPostDispatchHook, Ownable {
if (refund > 0) payable(message.senderAddress()).sendValue(refund);
}
/// @inheritdoc IPostDispatchHook
function quoteDispatch(bytes calldata, bytes calldata)
external
view
override
returns (uint256)
{
return protocolFee;
}
/**
* @notice Sets the protocol fee.
* @param _protocolFee The new protocol fee.

@ -1,6 +1,18 @@
// SPDX-License-Identifier: MIT OR Apache-2.0
pragma solidity >=0.8.0;
/*@@@@@@@ @@@@@@@@@
@@@@@@@@@ @@@@@@@@@
@@@@@@@@@ @@@@@@@@@
@@@@@@@@@ @@@@@@@@@
@@@@@@@@@@@@@@@@@@@@@@@@@
@@@@@ HYPERLANE @@@@@@@
@@@@@@@@@@@@@@@@@@@@@@@@@
@@@@@@@@@ @@@@@@@@@
@@@@@@@@@ @@@@@@@@@
@@@@@@@@@ @@@@@@@@@
@@@@@@@@@ @@@@@@@@*/
// ============ Internal Imports ============
import {Message} from "../libs/Message.sol";
import {IGPMetadata} from "../libs/hooks/IGPMetadata.sol";
@ -86,20 +98,26 @@ contract InterchainGasPaymaster is
payable
override
{
uint256 gasLimit;
address refundAddress;
if (metadata.length == 0) {
gasLimit = DEFAULT_GAS_USAGE;
refundAddress = message.senderAddress();
} else {
gasLimit = metadata.gasLimit();
refundAddress = metadata.refundAddress();
if (refundAddress == address(0))
refundAddress = message.senderAddress();
}
uint256 gasLimit = metadata.gasLimit(DEFAULT_GAS_USAGE);
address refundAddress = metadata.refundAddress(message.senderAddress());
payForGas(message.id(), message.destination(), gasLimit, refundAddress);
}
/**
* @notice Quote gas payment for a hook call.
* @param metadata The metadata as gasConfig.
* @param message The message to pay for.
*/
function quoteDispatch(bytes calldata metadata, bytes calldata message)
external
view
override
returns (uint256)
{
uint256 gasLimit = metadata.gasLimit(DEFAULT_GAS_USAGE);
return quoteGasPayment(message.destination(), gasLimit);
}
/**
* @notice Transfers the entire native token balance to the beneficiary.
* @dev The beneficiary must be able to receive native tokens.

@ -2,7 +2,23 @@
pragma solidity >=0.8.0;
interface IPostDispatchHook {
/**
* @notice Post action afte a message is dispatched via the Mailbox
* @param metadata The metadata required for the hook
* @param message The message passed from the Mailbox.dispatch() call
*/
function postDispatch(bytes calldata metadata, bytes calldata message)
external
payable;
/**
* @notice Estimate the amount of gas consumed by the postDispatch call
* @param metadata The metadata required for the hook
* @param message The message passed from the Mailbox.dispatch() call
* @return Gas quote for the postDispatch call
*/
function quoteDispatch(bytes calldata metadata, bytes calldata message)
external
view
returns (uint256);
}

@ -28,11 +28,12 @@ library IGPMetadata {
* @param _metadata ABI encoded IGP hook metadata.
* @return Gas limit for the message as uint256.
*/
function gasLimit(bytes calldata _metadata)
function gasLimit(bytes calldata _metadata, uint256 _default)
internal
pure
returns (uint256)
{
if (_metadata.length < GAS_LIMIT_OFFSET + 32) return _default;
return
uint256(bytes32(_metadata[GAS_LIMIT_OFFSET:GAS_LIMIT_OFFSET + 32]));
}
@ -42,17 +43,23 @@ library IGPMetadata {
* @param _metadata ABI encoded IGP hook metadata.
* @return Refund address for the message as address.
*/
function refundAddress(bytes calldata _metadata)
function refundAddress(bytes calldata _metadata, address _default)
internal
pure
returns (address)
{
return
address(
address _refundAddress;
if (_metadata.length < REFUND_ADDRESS_OFFSET + 20) {
_refundAddress = _default;
} else {
_refundAddress = address(
bytes20(
_metadata[REFUND_ADDRESS_OFFSET:REFUND_ADDRESS_OFFSET + 20]
)
);
if (_refundAddress == address(0)) _refundAddress = _default;
}
return _refundAddress;
}
/**

@ -4,13 +4,19 @@ pragma solidity >=0.8.0;
import {IPostDispatchHook} from "../interfaces/hooks/IPostDispatchHook.sol";
contract TestPostDispatchHook is IPostDispatchHook {
event PostDispatchHookCalled();
uint256 public mockGasQuote = 25000;
function postDispatch(
bytes calldata, /*metadata*/
bytes calldata /*message*/
) external payable override {
// test - emit event
emit PostDispatchHookCalled();
// test - empty
}
function quoteDispatch(
bytes calldata, /*metadata*/
bytes calldata /*message*/
) external view override returns (uint256) {
return mockGasQuote;
}
}

@ -34,22 +34,51 @@ contract FallbackDomainRoutingHookTest is Test {
mailbox.setDefaultHook(address(mailboxDefaultHook));
}
function test_postDispatchHook_configured() public payable {
/* ============ hook.quoteDispatch ============ */
function test_quoteDispatchHook_configured() public {
fallbackHook.setHook(
TEST_DESTINATION_DOMAIN,
address(testRecipient).addressToBytes32(),
configuredTestHook
);
vm.expectEmit(false, false, false, false, address(configuredTestHook));
emit PostDispatchHookCalled();
vm.expectCall(
address(configuredTestHook),
abi.encodeCall(configuredTestHook.quoteDispatch, ("", testMessage))
);
assertEq(fallbackHook.quoteDispatch("", testMessage), 25000);
}
function test_quoteDispatch_default() public payable {
vm.expectCall(
address(mailboxDefaultHook),
abi.encodeCall(mailboxDefaultHook.quoteDispatch, ("", testMessage))
);
fallbackHook.quoteDispatch("", testMessage);
}
/* ============ hook.postDispatch ============ */
function test_postDispatchHook_configured() public payable {
fallbackHook.setHook(
TEST_DESTINATION_DOMAIN,
address(testRecipient).addressToBytes32(),
configuredTestHook
);
vm.expectCall(
address(configuredTestHook),
abi.encodeCall(configuredTestHook.postDispatch, ("", testMessage))
);
fallbackHook.postDispatch{value: msg.value}("", testMessage);
}
function test_postDispatch_default() public payable {
vm.expectEmit(false, false, false, false, address(mailboxDefaultHook));
emit PostDispatchHookCalled();
vm.expectCall(
address(mailboxDefaultHook),
abi.encodeCall(mailboxDefaultHook.postDispatch, ("", testMessage))
);
fallbackHook.postDispatch{value: msg.value}("", testMessage);
}

@ -67,6 +67,10 @@ contract StaticProtocolFeeTest is Test {
assertEq(fees.beneficiary(), bob);
}
function testQuoteDispatch() public {
assertEq(fees.quoteDispatch("", testMessage), 1e15);
}
function testFuzz_postDispatch_inusfficientFees(
uint256 feeRequired,
uint256 feeSent

@ -28,6 +28,7 @@ contract InterchainGasPaymasterTest is Test {
bytes32 constant testMessageId =
0x6ae9a99190641b9ed0c07143340612dde0e9cb7deaa5fe07597858ae9ba5fd7f;
address constant testRefundAddress = address(0xc0ffee);
bytes testEncodedMessage;
event GasPayment(
bytes32 indexed messageId,
@ -44,6 +45,8 @@ contract InterchainGasPaymasterTest is Test {
igp.initialize(address(this), beneficiary);
oracle = new StorageGasOracle();
setGasOracle(testDestinationDomain, address(oracle));
testEncodedMessage = _encodeTestMessage();
}
// ============ constructor ============
@ -59,6 +62,34 @@ contract InterchainGasPaymasterTest is Test {
igp.initialize(address(this), beneficiary);
}
// ============ quoteDispatch ============
function testQuoteDispatch_defaultGasLimit() public {
setRemoteGasData(
testDestinationDomain,
1 * 1e10, // 1.0 exchange rate (remote token has exact same value as local)
150 // 1 wei gas price
);
// 150 * 69_420 = 10_413_000
assertEq(igp.quoteDispatch("", testEncodedMessage), 10_413_000);
}
function testQuoteDispatch_customWithMetadata() public {
setRemoteGasData(
testDestinationDomain,
1 * 1e10, // 1.0 exchange rate (remote token has exact same value as local)
150 // 1 wei gas price
);
bytes memory metadata = IGPMetadata.formatMetadata(
uint256(testGasAmount), // gas limit
testRefundAddress // refund address
);
// 150 * 300_000 = 45_000_000
assertEq(igp.quoteDispatch(metadata, testEncodedMessage), 45_000_000);
}
// ============ postDispatch ============
function testPostDispatch_defaultGasLimit() public {
@ -73,9 +104,8 @@ contract InterchainGasPaymasterTest is Test {
uint256 _quote = igp.quoteGasPayment(testDestinationDomain, 69_420);
uint256 _overpayment = 21000;
bytes memory message = _encodeTestMessage();
igp.postDispatch{value: _quote + _overpayment}("", message);
igp.postDispatch{value: _quote + _overpayment}("", testEncodedMessage);
uint256 _igpBalanceAfter = address(igp).balance;
uint256 _refundAddressBalanceAfter = address(this).balance;

@ -132,6 +132,16 @@ contract OPStackIsmTest is Test {
/// FORK TESTS ///
///////////////////////////////////////////////////////////////////
/* ============ hook.quoteDispatch ============ */
function testFork_quoteDispatch() public {
deployAll();
vm.selectFork(mainnetFork);
assertEq(opHook.quoteDispatch(testMetadata, encodedMessage), 0);
}
/* ============ hook.postDispatch ============ */
function testFork_postDispatch() public {

Loading…
Cancel
Save