-
Notifications
You must be signed in to change notification settings - Fork 425
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat(contracts): ValueRouter (#4814)
### Description - goal: use a unified `transferRemote` interface to send msg value from A to B agnostic or the hook/ism needed to do so - design doc: https://www.notion.so/hyperlanexyz/Native-bridge-value-transfer-API-1126d35200d680a0aa42d653d335a446 ### Drive-by changes - overrideMsgValue and overrideGasLimit which actually override the respective fields ### Related issues <!-- - Fixes #[issue number here] --> ### Backward compatibility Yes ### Testing Unit --------- Co-authored-by: Yorke Rhodes <[email protected]>
- Loading branch information
1 parent
a51b50c
commit b36bf0c
Showing
4 changed files
with
260 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,5 @@ | ||
--- | ||
'@hyperlane-xyz/core': minor | ||
--- | ||
|
||
Added a new router HypNativeCollateral with a unified interface for sending value hook/ism agnostic |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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; | ||
|
||
/*@@@@@@@ @@@@@@@@@ | ||
@@@@@@@@@ @@@@@@@@@ | ||
@@@@@@@@@ @@@@@@@@@ | ||
@@@@@@@@@ @@@@@@@@@ | ||
@@@@@@@@@@@@@@@@@@@@@@@@@ | ||
@@@@@ 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 { | ||
constructor(address _mailbox) HypNative(_mailbox) {} | ||
|
||
// ============ External Functions ============ | ||
|
||
/// @inheritdoc TokenRouter | ||
function transferRemote( | ||
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( | ||
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 | ||
); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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(); | ||
} | ||
} |