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

feat(contracts): ValueRouter #4814

Open
wants to merge 27 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 21 commits
Commits
Show all changes
27 commits
Select commit Hold shift + click to select a range
3e2f9e3
refactor: implement outbound and inbound warp route amount transforms…
yorhodes Oct 22, 2024
a51b288
fix(contracts): check for sufficient msgValue for `AggregationHook` …
aroralanuk Oct 29, 2024
7215577
init
aroralanuk Nov 5, 2024
4556e24
Merge branch 'main' into kunal/value-router
aroralanuk Nov 7, 2024
99647fe
revert
aroralanuk Nov 7, 2024
6876905
more
aroralanuk Nov 7, 2024
2514043
more
aroralanuk Nov 7, 2024
670912b
fixes
aroralanuk Nov 8, 2024
3fec6a0
rename
aroralanuk Nov 11, 2024
1e54bf6
account.balance
aroralanuk Nov 11, 2024
d8d4d2f
resolve sdk imports
aroralanuk Nov 20, 2024
e302e9c
Merge branch 'main' into kunal/value-router
aroralanuk Nov 20, 2024
ecd1e90
override if more
aroralanuk Nov 27, 2024
8fb963c
testpostdispatchhook
aroralanuk Nov 28, 2024
bee6d22
rm quote fetch
aroralanuk Nov 28, 2024
bab9928
Merge branch 'main' into kunal/value-router
aroralanuk Dec 10, 2024
139ba55
rm extra insuff check
aroralanuk Dec 13, 2024
9fcc4d9
rm comment
aroralanuk Dec 13, 2024
41ff083
inherit from hypnativecollateral
aroralanuk Dec 13, 2024
9c38a7a
interchange names
aroralanuk Dec 13, 2024
5c1f2e8
more
aroralanuk Dec 13, 2024
ec225b4
revert
aroralanuk Dec 13, 2024
d996b3a
Merge branch 'main' into kunal/value-router
aroralanuk Dec 13, 2024
4f04099
rm override if higher
aroralanuk Dec 13, 2024
7cf2d75
docs(changeset): Added a new router HypNativeCollateral with a unifie…
aroralanuk Dec 13, 2024
b541129
revrt
aroralanuk Dec 13, 2024
13e79af
Update long-llamas-fly.md
aroralanuk Dec 13, 2024
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
41 changes: 41 additions & 0 deletions solidity/contracts/hooks/libs/StandardHookMetadata.sol
Original file line number Diff line number Diff line change
Expand Up @@ -165,4 +165,45 @@ library StandardHookMetadata {
) internal pure returns (bytes memory) {
return formatMetadata(uint256(0), uint256(0), _refundAddress, "");
}

/**
* @notice Overrides the msg.value in the metadata.
* @param _metadata encoded standard hook metadata.
* @param _msgValue msg.value for the message.
* @return encoded standard hook metadata.
*/
function overrideMsgValue(
bytes calldata _metadata,
uint256 _msgValue
) internal view returns (bytes memory) {
return
formatMetadata(
Fixed Show fixed Hide fixed
_msgValue,
gasLimit(_metadata, 0),
refundAddress(_metadata, msg.sender),
Comment on lines +182 to +183
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I dont understand the 0 and msg.sender here
this will also memcopy for every field iiuc

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

these are just default values in case the specific part of the metadata is null
true but memcopy is cheap

Copy link
Member

@yorhodes yorhodes Nov 9, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

0 gas limit and msg.sender for refund address as defaults does not make sense to me

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

what would you have in their place?

getCustomMetadata(_metadata)
Fixed Show fixed Hide fixed
Fixed Show fixed Hide fixed
);
}

/**
* @notice Overrides the gas limit in the metadata if _gasLimit is higher than the current gas limit.
* @param _metadata encoded standard hook metadata.
* @param _gasLimit gas limit for the message.
* @return encoded standard hook metadata.
*/
function overrideGasLimit(
bytes calldata _metadata,
uint256 _gasLimit
) internal view returns (bytes memory) {
if (gasLimit(_metadata, 0) < _gasLimit) {
aroralanuk marked this conversation as resolved.
Show resolved Hide resolved
return
formatMetadata(
Fixed Show fixed Hide fixed
msgValue(_metadata, 0),
_gasLimit,
refundAddress(_metadata, msg.sender),
getCustomMetadata(_metadata)
Fixed Show fixed Hide fixed
);
}
return _metadata;
}
}
5 changes: 2 additions & 3 deletions solidity/contracts/token/HypNative.sol
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@
pragma solidity >=0.8.0;

import {TokenRouter} from "./libs/TokenRouter.sol";
import {TokenMessage} from "./libs/TokenMessage.sol";
import {Address} from "@openzeppelin/contracts/utils/Address.sol";

/**
Expand All @@ -23,8 +22,8 @@ contract HypNative is TokenRouter {
/**
* @notice Initializes the Hyperlane router
* @param _hook The post-dispatch hook contract.
@param _interchainSecurityModule The interchain security module contract.
@param _owner The this contract.
* @param _interchainSecurityModule The interchain security module contract.
* @param _owner The this contract.
*/
function initialize(
address _hook,
Expand Down
84 changes: 84 additions & 0 deletions solidity/contracts/token/HypNativeCollateral.sol
aroralanuk marked this conversation as resolved.
Show resolved Hide resolved
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
// SPDX-License-Identifier: MIT OR Apache-2.0
pragma solidity >=0.8.0;
Fixed Show fixed Hide fixed

Check notice

Code scanning / Olympix Integrated Security

Using an unbounded pragma for Solidity version may be unsafe if future versions introduce breaking changes. For more information, visit: http://detectors.olympixdevsectools.com/article/web3-vulnerability/unbounded-pragma Low

Using an unbounded pragma for Solidity version may be unsafe if future versions introduce breaking changes. For more information, visit: http://detectors.olympixdevsectools.com/article/web3-vulnerability/unbounded-pragma

/*@@@@@@@ @@@@@@@@@
@@@@@@@@@ @@@@@@@@@
@@@@@@@@@ @@@@@@@@@
@@@@@@@@@ @@@@@@@@@
@@@@@@@@@@@@@@@@@@@@@@@@@
@@@@@ HYPERLANE @@@@@@@
@@@@@@@@@@@@@@@@@@@@@@@@@
@@@@@@@@@ @@@@@@@@@
@@@@@@@@@ @@@@@@@@@
@@@@@@@@@ @@@@@@@@@
@@@@@@@@@ @@@@@@@@*/

// ============ Internal Imports ============
import {TokenRouter} from "./libs/TokenRouter.sol";
import {HypNative} from "./HypNative.sol";
import {StandardHookMetadata} from "../hooks/libs/StandardHookMetadata.sol";

/**
* @title HypNativeCollateral
* @author Abacus Works
* @notice This contract facilitates the transfer of value between chains using value transfer hooks
*/
contract HypNativeCollateral is HypNative {
Dismissed Show dismissed Hide dismissed
constructor(address _mailbox) HypNative(_mailbox) {}

Check notice

Code scanning / Olympix Integrated Security

Test functions fail to thoroughly test all aspects of contract constructors, potentially missing critical initialization issues. For more information, visit: http://detectors.olympixdevsectools.com/article/web3-vulnerability/incomplete-constructor-tests Low

Test functions fail to thoroughly test all aspects of contract constructors, potentially missing critical initialization issues. For more information, visit: http://detectors.olympixdevsectools.com/article/web3-vulnerability/incomplete-constructor-tests

Check notice

Code scanning / Olympix Integrated Security

Parameters passed to a constructor that are not validated for correct values may lead to contract creation in an undesired state. For more information, visit: http://detectors.olympixdevsectools.com/article/web3-vulnerability/no-parameter-validation-in-constructor Low

Parameters passed to a constructor that are not validated for correct values may lead to contract creation in an undesired state. For more information, visit: http://detectors.olympixdevsectools.com/article/web3-vulnerability/no-parameter-validation-in-constructor

// ============ External Functions ============

/// @inheritdoc TokenRouter
function transferRemote(
Dismissed Show dismissed Hide dismissed
uint32 _destination,
bytes32 _recipient,
uint256 _amount
) external payable virtual override returns (bytes32 messageId) {
bytes calldata emptyBytes;
assembly {
emptyBytes.length := 0
emptyBytes.offset := 0
}
return
transferRemote(
_destination,
_recipient,
_amount,
emptyBytes,
address(hook)
);
}

/**
* @inheritdoc TokenRouter
* @dev use _hook with caution, make sure that this hook can handle msg.value transfer using the metadata.msgValue()
*/
function transferRemote(
Dismissed Show dismissed Hide dismissed
uint32 _destination,
bytes32 _recipient,
uint256 _amount,
bytes calldata _hookMetadata,
address _hook
) public payable virtual override returns (bytes32 messageId) {
uint256 quote = _GasRouter_quoteDispatch(
_destination,
_hookMetadata,
_hook
);

bytes memory hookMetadata = StandardHookMetadata.overrideMsgValue(
_hookMetadata,
_amount
);

return
_transferRemote(
_destination,
_recipient,
_amount,
_amount + quote,
hookMetadata,
_hook
);
}
}
1 change: 0 additions & 1 deletion solidity/test/isms/ExternalBridgeTest.sol
Original file line number Diff line number Diff line change
Expand Up @@ -115,7 +115,6 @@ abstract contract ExternalBridgeTest is Test {
function test_postDispatch_revertWhen_insufficientValue() public {
bytes memory encodedHookData = _encodeHookData(messageId, 0);
originMailbox.updateLatestDispatchedId(messageId);
_expectOriginExternalBridgeCall(encodedHookData);
aroralanuk marked this conversation as resolved.
Show resolved Hide resolved

uint256 quote = hook.quoteDispatch(testMetadata, encodedMessage);

Expand Down
23 changes: 20 additions & 3 deletions solidity/test/isms/OPL2ToL1Ism.t.sol
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,9 @@ contract OPL2ToL1IsmTest is ExternalBridgeTest {
0x4200000000000000000000000000000000000007;

uint256 internal constant MOCK_NONCE = 0;

uint256 internal constant GAS_LIMIT = 300_000;
uint256 internal constant GAS_PRICE = 10;
uint256 internal constant OVERRIDE_MULTIPLIER = 2;
TestInterchainGasPaymaster internal mockOverheadIgp;
MockOptimismPortal internal portal;
MockOptimismMessenger internal l1Messenger;
Expand All @@ -40,6 +42,8 @@ contract OPL2ToL1IsmTest is ExternalBridgeTest {

deployAll();
super.setUp();

GAS_QUOTE = GAS_LIMIT * GAS_PRICE;
}

function deployHook() public {
Expand Down Expand Up @@ -76,14 +80,27 @@ contract OPL2ToL1IsmTest is ExternalBridgeTest {
_expectOriginExternalBridgeCall(encodedHookData);

bytes memory igpMetadata = StandardHookMetadata.overrideGasLimit(
78_000
GAS_LIMIT
);

uint256 quote = hook.quoteDispatch(igpMetadata, encodedMessage);
assertEq(quote, mockOverheadIgp.quoteGasPayment(ORIGIN_DOMAIN, 78_000));
assertEq(
quote,
mockOverheadIgp.quoteGasPayment(ORIGIN_DOMAIN, GAS_LIMIT)
);
hook.postDispatch{value: quote}(igpMetadata, encodedMessage);
}

function test_quoteDispatch_dontOverrideGasLimit() public {
bytes memory igpMetadata = StandardHookMetadata.overrideGasLimit(
GAS_LIMIT * OVERRIDE_MULTIPLIER
);
assertEq(
hook.quoteDispatch(igpMetadata, encodedMessage),
GAS_LIMIT * GAS_PRICE * OVERRIDE_MULTIPLIER
);
}

/* ============ helper functions ============ */

function _expectOriginExternalBridgeCall(
Expand Down
10 changes: 5 additions & 5 deletions solidity/test/token/HypERC20.t.sol
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ import {IXERC20} from "../../contracts/token/interfaces/IXERC20.sol";
import {IFiatToken} from "../../contracts/token/interfaces/IFiatToken.sol";
import {HypXERC20} from "../../contracts/token/extensions/HypXERC20.sol";
import {HypFiatToken} from "../../contracts/token/extensions/HypFiatToken.sol";
import {HypNative} from "../../contracts/token/HypNative.sol";
import {HypNativeCollateral} from "../../contracts/token/HypNativeCollateral.sol";
import {TokenRouter} from "../../contracts/token/libs/TokenRouter.sol";
import {TokenMessage} from "../../contracts/token/libs/TokenMessage.sol";
import {Message} from "../../contracts/libs/Message.sol";
Expand Down Expand Up @@ -623,16 +623,16 @@ contract HypFiatTokenTest is HypTokenTest {
}
}

contract HypNativeTest is HypTokenTest {
contract HypNativeCollateralTest is HypTokenTest {
using TypeCasts for address;

HypNative internal nativeToken;
HypNativeCollateral internal nativeToken;

function setUp() public override {
super.setUp();

localToken = new HypNative(address(localMailbox));
nativeToken = HypNative(payable(address(localToken)));
localToken = new HypNativeCollateral(address(localMailbox));
nativeToken = HypNativeCollateral(payable(address(localToken)));

nativeToken.enrollRemoteRouter(
DESTINATION,
Expand Down
152 changes: 152 additions & 0 deletions solidity/test/token/HypNative.t.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,152 @@
// SPDX-License-Identifier: MIT OR Apache-2.0
pragma solidity >=0.8.0;

import {TypeCasts} from "../../contracts/libs/TypeCasts.sol";
import {HypTokenTest} from "./HypERC20.t.sol";
import {HypERC20} from "../../contracts/token/HypERC20.sol";
import {TokenRouter} from "../../contracts/token/libs/TokenRouter.sol";
import {HypNativeCollateral} from "../../contracts/token/HypNativeCollateral.sol";
import {TestPostDispatchHook} from "../../contracts/test/TestPostDispatchHook.sol";
import {TestIsm} from "../../contracts/test/TestIsm.sol";

contract HypNativeCollateralTest is HypTokenTest {
using TypeCasts for address;

HypNativeCollateral internal localValueRouter;
HypNativeCollateral internal remoteValueRouter;
TestPostDispatchHook internal valueHook;
TestIsm internal ism;

function setUp() public override {
super.setUp();

localValueRouter = new HypNativeCollateral(address(localMailbox));
remoteValueRouter = new HypNativeCollateral(address(remoteMailbox));

localToken = TokenRouter(payable(address(localValueRouter)));
remoteToken = HypERC20(payable(address(remoteValueRouter)));

ism = new TestIsm();

valueHook = new TestPostDispatchHook();
valueHook.setFee(1e10);

localValueRouter.initialize(
address(valueHook),
address(ism),
address(this)
);
remoteValueRouter.initialize(
address(valueHook),
address(ism),
address(this)
);

localValueRouter.enrollRemoteRouter(
DESTINATION,
address(remoteToken).addressToBytes32()
);
remoteValueRouter.enrollRemoteRouter(
ORIGIN,
address(localToken).addressToBytes32()
);

vm.deal(ALICE, TRANSFER_AMT * 10);
}

function testRemoteTransfer() public {
uint256 quote = localValueRouter.quoteGasPayment(DESTINATION);
uint256 msgValue = TRANSFER_AMT + quote;

vm.expectEmit(true, true, false, true);
emit TokenRouter.SentTransferRemote(
DESTINATION,
BOB.addressToBytes32(),
TRANSFER_AMT
);

vm.prank(ALICE);
localToken.transferRemote{value: msgValue}(
DESTINATION,
BOB.addressToBytes32(),
TRANSFER_AMT
);

vm.assertEq(address(localToken).balance, 0);
vm.assertEq(address(valueHook).balance, msgValue);

vm.deal(address(remoteToken), TRANSFER_AMT);
vm.prank(address(remoteMailbox));

remoteToken.handle(
ORIGIN,
address(localToken).addressToBytes32(),
abi.encodePacked(BOB.addressToBytes32(), TRANSFER_AMT)
);

assertEq(BOB.balance, TRANSFER_AMT);
assertEq(address(valueHook).balance, msgValue);
}

// when msg.value is >= quote + amount, it should revert in
function testRemoteTransfer_insufficientValue() public {
vm.expectRevert();
vm.prank(ALICE);
localToken.transferRemote{value: TRANSFER_AMT}(
DESTINATION,
BOB.addressToBytes32(),
TRANSFER_AMT
);
}

function testTransfer_withHookSpecified(
uint256 fee,
bytes calldata metadata
) public override {
vm.assume(fee < TRANSFER_AMT);
uint256 msgValue = TRANSFER_AMT + fee;
vm.deal(ALICE, msgValue);

TestPostDispatchHook hook = new TestPostDispatchHook();
hook.setFee(fee);

vm.prank(ALICE);
localToken.transferRemote{value: msgValue}(
DESTINATION,
BOB.addressToBytes32(),
TRANSFER_AMT,
metadata,
address(hook)
);

vm.assertEq(address(localToken).balance, 0);
vm.assertEq(address(valueHook).balance, 0);
}

function testTransfer_withHookSpecified_revertsInsufficientValue(
uint256 fee,
bytes calldata metadata
) public {
vm.assume(fee < TRANSFER_AMT);
uint256 msgValue = TRANSFER_AMT + fee;
vm.deal(ALICE, msgValue);

TestPostDispatchHook hook = new TestPostDispatchHook();
hook.setFee(fee);

vm.prank(ALICE);
vm.expectRevert();
localToken.transferRemote{value: msgValue - 1}(
DESTINATION,
BOB.addressToBytes32(),
TRANSFER_AMT,
metadata,
address(hook)
);
}

function testBenchmark_overheadGasUsage() public override {
vm.deal(address(localValueRouter), TRANSFER_AMT);
super.testBenchmark_overheadGasUsage();
}
}
2 changes: 1 addition & 1 deletion typescript/sdk/src/metadata/warpRouteConfig.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ import { ChainMap } from '../types.js';
const TokenConfigSchema = z.object({
protocolType: z.nativeEnum(ProtocolType),
type: z.nativeEnum(TokenType),
hypAddress: z.string(), // HypERC20Collateral, HypERC20Synthetic, HypNativeToken address
hypAddress: z.string(), // HypERC20Collateral, HypERC20Synthetic, HypNativeCollateralToken address
tokenAddress: z.string().optional(), // external token address needed for collateral type eg tokenAddress.balanceOf(hypAddress)
tokenCoinGeckoId: z.string().optional(), // CoinGecko id for token
name: z.string(),
Expand Down
Loading
Loading