-
Notifications
You must be signed in to change notification settings - Fork 389
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
1 parent
41ff083
commit 9c38a7a
Showing
2 changed files
with
295 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,78 @@ | ||
// SPDX-License-Identifier: Apache-2.0 | ||
pragma solidity >=0.8.0; | ||
|
||
import {HypNative} from "../HypNative.sol"; | ||
import {TokenRouter} from "../libs/TokenRouter.sol"; | ||
|
||
/** | ||
* @title Hyperlane Native Token that scales native value by a fixed factor for consistency with other tokens. | ||
* @dev The scale factor multiplies the `message.amount` to the local native token amount. | ||
* Conversely, it divides the local native `msg.value` amount by `scale` to encode the `message.amount`. | ||
* @author Abacus Works | ||
*/ | ||
contract HypNativeScaled is HypNative { | ||
uint256 public immutable scale; | ||
|
||
constructor(uint256 _scale, address _mailbox) HypNative(_mailbox) { | ||
scale = _scale; | ||
} | ||
|
||
/** | ||
* @inheritdoc HypNative | ||
* @dev Sends scaled `msg.value` (divided by `scale`) to `_recipient`. | ||
*/ | ||
function transferRemote( | ||
uint32 _destination, | ||
bytes32 _recipient, | ||
uint256 _amount | ||
) external payable override returns (bytes32 messageId) { | ||
require(msg.value >= _amount, "Native: amount exceeds msg.value"); | ||
uint256 _hookPayment = msg.value - _amount; | ||
uint256 _scaledAmount = _amount / scale; | ||
return | ||
_transferRemote( | ||
_destination, | ||
_recipient, | ||
_scaledAmount, | ||
_hookPayment | ||
); | ||
} | ||
|
||
/** | ||
* @inheritdoc TokenRouter | ||
* @dev uses (`msg.value` - `_amount`) as hook payment. | ||
*/ | ||
function transferRemote( | ||
uint32 _destination, | ||
bytes32 _recipient, | ||
uint256 _amount, | ||
bytes calldata _hookMetadata, | ||
address _hook | ||
) external payable override returns (bytes32 messageId) { | ||
require(msg.value >= _amount, "Native: amount exceeds msg.value"); | ||
uint256 _hookPayment = msg.value - _amount; | ||
uint256 _scaledAmount = _amount / scale; | ||
return | ||
_transferRemote( | ||
_destination, | ||
_recipient, | ||
_scaledAmount, | ||
_hookPayment, | ||
_hookMetadata, | ||
_hook | ||
); | ||
} | ||
|
||
/** | ||
* @dev Sends scaled `_amount` (multiplied by `scale`) to `_recipient`. | ||
* @inheritdoc TokenRouter | ||
*/ | ||
function _transferTo( | ||
address _recipient, | ||
uint256 _amount, | ||
bytes calldata metadata // no metadata | ||
) internal override { | ||
uint256 scaledAmount = _amount * scale; | ||
HypNative._transferTo(_recipient, scaledAmount, metadata); | ||
} | ||
} |
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,217 @@ | ||
// SPDX-License-Identifier: Apache-2.0 | ||
pragma solidity >=0.8.0; | ||
|
||
import "forge-std/Test.sol"; | ||
import {TransparentUpgradeableProxy} from "@openzeppelin/contracts/proxy/transparent/TransparentUpgradeableProxy.sol"; | ||
|
||
import {TestPostDispatchHook} from "../../contracts/test/TestPostDispatchHook.sol"; | ||
import {HypNativeScaled} from "../../contracts/token/extensions/HypNativeScaled.sol"; | ||
import {HypERC20} from "../../contracts/token/HypERC20.sol"; | ||
import {HypNative} from "../../contracts/token/HypNative.sol"; | ||
import {TypeCasts} from "../../contracts/libs/TypeCasts.sol"; | ||
import {MockHyperlaneEnvironment} from "../../contracts/mock/MockHyperlaneEnvironment.sol"; | ||
|
||
contract HypNativeScaledTest is Test { | ||
uint32 nativeDomain = 1; | ||
uint32 synthDomain = 2; | ||
|
||
address internal constant ALICE = address(0x1); | ||
|
||
uint8 decimals = 9; | ||
uint256 mintAmount = 123456789; | ||
uint256 nativeDecimals = 18; | ||
uint256 scale = 10 ** (nativeDecimals - decimals); | ||
|
||
event Donation(address indexed sender, uint256 amount); | ||
event SentTransferRemote( | ||
uint32 indexed destination, | ||
bytes32 indexed recipient, | ||
uint256 amount | ||
); | ||
event ReceivedTransferRemote( | ||
uint32 indexed origin, | ||
bytes32 indexed recipient, | ||
uint256 amount | ||
); | ||
|
||
HypNativeScaled native; | ||
HypERC20 synth; | ||
|
||
MockHyperlaneEnvironment environment; | ||
|
||
function setUp() public { | ||
environment = new MockHyperlaneEnvironment(synthDomain, nativeDomain); | ||
|
||
HypERC20 implementationSynth = new HypERC20( | ||
decimals, | ||
address(environment.mailboxes(synthDomain)) | ||
); | ||
TransparentUpgradeableProxy proxySynth = new TransparentUpgradeableProxy( | ||
address(implementationSynth), | ||
address(9), | ||
abi.encodeWithSelector( | ||
HypERC20.initialize.selector, | ||
mintAmount * (10 ** decimals), | ||
"Zebec BSC Token", | ||
"ZBC", | ||
address(0), | ||
address(0), | ||
address(this) | ||
) | ||
); | ||
synth = HypERC20(address(proxySynth)); | ||
|
||
HypNativeScaled implementationNative = new HypNativeScaled( | ||
scale, | ||
address(environment.mailboxes(nativeDomain)) | ||
); | ||
TransparentUpgradeableProxy proxyNative = new TransparentUpgradeableProxy( | ||
address(implementationNative), | ||
address(9), | ||
abi.encodeWithSelector( | ||
HypNative.initialize.selector, | ||
address(0), | ||
address(0), | ||
address(this) | ||
) | ||
); | ||
|
||
native = HypNativeScaled(payable(address(proxyNative))); | ||
|
||
native.enrollRemoteRouter( | ||
synthDomain, | ||
TypeCasts.addressToBytes32(address(synth)) | ||
); | ||
synth.enrollRemoteRouter( | ||
nativeDomain, | ||
TypeCasts.addressToBytes32(address(native)) | ||
); | ||
} | ||
|
||
function test_constructor() public { | ||
assertEq(native.scale(), scale); | ||
} | ||
|
||
uint256 receivedValue; | ||
|
||
receive() external payable { | ||
receivedValue = msg.value; | ||
} | ||
|
||
function test_receive(uint256 amount) public { | ||
vm.assume(amount < address(this).balance); | ||
vm.expectEmit(true, true, true, true); | ||
emit Donation(address(this), amount); | ||
(bool success, bytes memory returnData) = address(native).call{ | ||
value: amount | ||
}(""); | ||
assert(success); | ||
assertEq(returnData.length, 0); | ||
} | ||
|
||
function test_handle(uint256 amount) public { | ||
vm.assume(amount <= mintAmount); | ||
|
||
uint256 synthAmount = amount * (10 ** decimals); | ||
uint256 nativeAmount = amount * (10 ** nativeDecimals); | ||
|
||
vm.deal(address(native), nativeAmount); | ||
|
||
bytes32 recipient = TypeCasts.addressToBytes32(address(this)); | ||
synth.transferRemote(nativeDomain, recipient, synthAmount); | ||
|
||
vm.expectEmit(true, true, true, true); | ||
emit ReceivedTransferRemote(synthDomain, recipient, synthAmount); | ||
environment.processNextPendingMessage(); | ||
|
||
assertEq(receivedValue, nativeAmount); | ||
} | ||
|
||
function test_handle_reverts_whenAmountExceedsSupply( | ||
uint256 amount | ||
) public { | ||
vm.assume(amount <= mintAmount); | ||
|
||
bytes32 recipient = TypeCasts.addressToBytes32(address(this)); | ||
synth.transferRemote(nativeDomain, recipient, amount); | ||
|
||
uint256 nativeValue = amount * scale; | ||
vm.deal(address(native), nativeValue / 2); | ||
|
||
if (amount > 0) { | ||
vm.expectRevert(bytes("Address: insufficient balance")); | ||
} | ||
environment.processNextPendingMessage(); | ||
} | ||
|
||
function test_tranferRemote(uint256 amount) public { | ||
vm.assume(amount <= mintAmount); | ||
|
||
uint256 nativeValue = amount * (10 ** nativeDecimals); | ||
uint256 synthAmount = amount * (10 ** decimals); | ||
address recipient = address(0xdeadbeef); | ||
bytes32 bRecipient = TypeCasts.addressToBytes32(recipient); | ||
|
||
vm.deal(address(this), nativeValue); | ||
vm.expectEmit(true, true, true, true); | ||
emit SentTransferRemote(synthDomain, bRecipient, synthAmount); | ||
native.transferRemote{value: nativeValue}( | ||
synthDomain, | ||
bRecipient, | ||
nativeValue | ||
); | ||
environment.processNextPendingMessageFromDestination(); | ||
assertEq(synth.balanceOf(recipient), synthAmount); | ||
} | ||
|
||
function testTransfer_withHookSpecified( | ||
uint256 amount, | ||
bytes calldata metadata | ||
) public { | ||
vm.assume(amount <= mintAmount); | ||
|
||
uint256 nativeValue = amount * (10 ** nativeDecimals); | ||
uint256 synthAmount = amount * (10 ** decimals); | ||
address recipient = address(0xdeadbeef); | ||
bytes32 bRecipient = TypeCasts.addressToBytes32(recipient); | ||
|
||
TestPostDispatchHook hook = new TestPostDispatchHook(); | ||
|
||
vm.deal(address(this), nativeValue); | ||
vm.expectEmit(true, true, true, true); | ||
emit SentTransferRemote(synthDomain, bRecipient, synthAmount); | ||
native.transferRemote{value: nativeValue}( | ||
synthDomain, | ||
bRecipient, | ||
nativeValue, | ||
metadata, | ||
address(hook) | ||
); | ||
environment.processNextPendingMessageFromDestination(); | ||
assertEq(synth.balanceOf(recipient), synthAmount); | ||
} | ||
|
||
function test_transferRemote_reverts_whenAmountExceedsValue( | ||
uint256 nativeValue | ||
) public { | ||
vm.assume(nativeValue < address(this).balance); | ||
|
||
address recipient = address(0xdeadbeef); | ||
bytes32 bRecipient = TypeCasts.addressToBytes32(recipient); | ||
vm.expectRevert("Native: amount exceeds msg.value"); | ||
native.transferRemote{value: nativeValue}( | ||
synthDomain, | ||
bRecipient, | ||
nativeValue + 1 | ||
); | ||
|
||
vm.expectRevert("Native: amount exceeds msg.value"); | ||
native.transferRemote{value: nativeValue}( | ||
synthDomain, | ||
bRecipient, | ||
nativeValue + 1, | ||
bytes(""), | ||
address(0) | ||
); | ||
} | ||
} |