Add `receive` fn to `HypNative` and `Scaled` variant (#2671)
Co-authored-by: Trevor Porter <trkporter@ucdavis.edu> Co-authored-by: Nam Chu Hoai <nambrot@googlemail.com>pull/2726/head
parent
892cc5df9c
commit
06124b441a
@ -1,3 +1,6 @@ |
||||
[submodule "solidity/lib/forge-std"] |
||||
path = solidity/lib/forge-std |
||||
url = https://github.com/foundry-rs/forge-std |
||||
[submodule "typescript/token/lib/forge-std"] |
||||
path = typescript/token/lib/forge-std |
||||
url = https://github.com/foundry-rs/forge-std |
||||
|
@ -0,0 +1,48 @@ |
||||
// 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) { |
||||
scale = _scale; |
||||
} |
||||
|
||||
/** |
||||
* @inheritdoc HypNative |
||||
* @dev Sends scaled `msg.value` (divided by `scale`) to `_recipient`. |
||||
*/ |
||||
function transferRemote( |
||||
uint32 _destination, |
||||
bytes32 _recipient, |
||||
uint256 _amount |
||||
) public payable override returns (bytes32 messageId) { |
||||
require(msg.value >= _amount, "Native: amount exceeds msg.value"); |
||||
uint256 gasPayment = msg.value - _amount; |
||||
uint256 scaledAmount = _amount / scale; |
||||
return |
||||
_transferRemote(_destination, _recipient, scaledAmount, gasPayment); |
||||
} |
||||
|
||||
/** |
||||
* @dev Sends scaled `_amount` (multipled 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); |
||||
} |
||||
} |
@ -0,0 +1 @@ |
||||
Subproject commit 74cfb77e308dd188d2f58864aaf44963ae6b88b1 |
@ -0,0 +1,155 @@ |
||||
// SPDX-License-Identifier: Apache-2.0 |
||||
pragma solidity >=0.8.0; |
||||
|
||||
import "forge-std/Test.sol"; |
||||
|
||||
import {HypNativeScaled} from "../contracts/extensions/HypNativeScaled.sol"; |
||||
import {HypERC20} from "../contracts/HypERC20.sol"; |
||||
import {TypeCasts} from "@hyperlane-xyz/core/contracts/libs/TypeCasts.sol"; |
||||
import {MockHyperlaneEnvironment} from "@hyperlane-xyz/core/contracts/mock/MockHyperlaneEnvironment.sol"; |
||||
|
||||
contract HypNativeScaledTest is Test { |
||||
uint32 nativeDomain = 1; |
||||
uint32 synthDomain = 2; |
||||
|
||||
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); |
||||
|
||||
synth = new HypERC20(decimals); |
||||
synth.initialize( |
||||
address(environment.mailboxes(synthDomain)), |
||||
address(environment.igps(synthDomain)), |
||||
mintAmount * (10**decimals), |
||||
"Zebec BSC Token", |
||||
"ZBC" |
||||
); |
||||
|
||||
native = new HypNativeScaled(scale); |
||||
native.initialize( |
||||
address(environment.mailboxes(nativeDomain)), |
||||
address(environment.igps(nativeDomain)) |
||||
); |
||||
|
||||
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.assume(nativeValue < address(this).balance); |
||||
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 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 |
||||
); |
||||
} |
||||
} |
Loading…
Reference in new issue