Skip to content

Commit

Permalink
test: XERC20 and XERC20 Lockbox integration tests (#3849)
Browse files Browse the repository at this point in the history
  • Loading branch information
yorhodes authored Jun 5, 2024
1 parent 3353103 commit a8a68f6
Show file tree
Hide file tree
Showing 13 changed files with 485 additions and 14 deletions.
5 changes: 5 additions & 0 deletions .changeset/sweet-pandas-brush.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@hyperlane-xyz/core": patch
---

fix: make XERC20 and XERC20 Lockbox proxy-able
50 changes: 43 additions & 7 deletions solidity/contracts/test/ERC20Test.sol
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ pragma solidity >=0.8.0;

import "@openzeppelin/contracts/token/ERC20/ERC20.sol";

import "../token/interfaces/IXERC20Lockbox.sol";
import "../token/interfaces/IXERC20.sol";
import "../token/interfaces/IFiatToken.sol";

Expand Down Expand Up @@ -66,15 +67,50 @@ contract XERC20Test is ERC20Test, IXERC20 {
_burn(account, amount);
}

function setLimits(
address _bridge,
uint256 _mintingLimit,
uint256 _burningLimit
) external {
require(false);
function setLimits(address, uint256, uint256) external pure {
assert(false);
}

function owner() external returns (address) {
function owner() external pure returns (address) {
return address(0x0);
}
}

contract XERC20LockboxTest is IXERC20Lockbox {
IXERC20 public immutable XERC20;
IERC20 public immutable ERC20;

constructor(
string memory name,
string memory symbol,
uint256 totalSupply,
uint8 __decimals
) {
ERC20Test erc20 = new ERC20Test(name, symbol, totalSupply, __decimals);
erc20.transfer(msg.sender, totalSupply);
ERC20 = erc20;
XERC20 = new XERC20Test(name, symbol, 0, __decimals);
}

function depositTo(address _user, uint256 _amount) public {
ERC20.transferFrom(msg.sender, address(this), _amount);
XERC20.mint(_user, _amount);
}

function deposit(uint256 _amount) external {
depositTo(msg.sender, _amount);
}

function depositNativeTo(address) external payable {
assert(false);
}

function withdrawTo(address _user, uint256 _amount) public {
XERC20.burn(msg.sender, _amount);
ERC20Test(address(ERC20)).mintTo(_user, _amount);
}

function withdraw(uint256 _amount) external {
withdrawTo(msg.sender, _amount);
}
}
27 changes: 24 additions & 3 deletions solidity/contracts/token/extensions/HypXERC20Lockbox.sol
Original file line number Diff line number Diff line change
Expand Up @@ -17,18 +17,39 @@ contract HypXERC20Lockbox is HypERC20Collateral {
) HypERC20Collateral(address(IXERC20Lockbox(_lockbox).ERC20()), _mailbox) {
lockbox = IXERC20Lockbox(_lockbox);
xERC20 = lockbox.XERC20();
approveLockbox();
}

// grant infinite approvals to lockbox
/**
* @notice Approve the lockbox to spend the wrapped token and xERC20
* @dev This function is idempotent and need not be access controlled
*/
function approveLockbox() public {
require(
IERC20(wrappedToken).approve(_lockbox, MAX_INT),
IERC20(wrappedToken).approve(address(lockbox), MAX_INT),
"erc20 lockbox approve failed"
);
require(
xERC20.approve(_lockbox, MAX_INT),
xERC20.approve(address(lockbox), MAX_INT),
"xerc20 lockbox approve failed"
);
}

/**
* @notice Initialize the contract
* @param _hook The address of the hook contract
* @param _ism The address of the interchain security module
* @param _owner The address of the owner
*/
function initialize(
address _hook,
address _ism,
address _owner
) public override initializer {
approveLockbox();
_MailboxClient_initialize(_hook, _ism, _owner);
}

function _transferFromSender(
uint256 _amount
) internal override returns (bytes memory) {
Expand Down
2 changes: 1 addition & 1 deletion solidity/coverage.sh
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ fi
lcov --version

# exclude FastTokenRouter until https://github.com/hyperlane-xyz/hyperlane-monorepo/issues/2806
EXCLUDE="*test* *mock* *node_modules* *FastHyp*"
EXCLUDE="*test* *mock* *node_modules* *script* *FastHyp*"
lcov \
--rc lcov_branch_coverage=1 \
--remove lcov.info $EXCLUDE \
Expand Down
6 changes: 5 additions & 1 deletion solidity/foundry.toml
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,11 @@ fs_permissions = [
{ access = "read", path = "./script/avs/"},
{ access = "write", path = "./fixtures" }
]
ignored_warnings_from = ['fx-portal']
ignored_warnings_from = [
'lib',
'test',
'contracts/test'
]

[profile.ci]
verbosity = 4
Expand Down
4 changes: 4 additions & 0 deletions solidity/script/xerc20/.env.blast
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
export ROUTER_ADDRESS=0xA34ceDf9068C5deE726C67A4e1DCfCc2D6E2A7fD
export ERC20_ADDRESS=0x2416092f143378750bb29b79eD961ab195CcEea5
export XERC20_ADDRESS=0x2416092f143378750bb29b79eD961ab195CcEea5
export RPC_URL="https://rpc.blast.io"
5 changes: 5 additions & 0 deletions solidity/script/xerc20/.env.ethereum
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
export ROUTER_ADDRESS=0x8dfbEA2582F41c8C4Eb25252BbA392fd3c09449A
export ADMIN_ADDRESS=0xa5B0D537CeBE97f087Dc5FE5732d70719caaEc1D
export ERC20_ADDRESS=0xbf5495Efe5DB9ce00f80364C8B423567e58d2110
export XERC20_ADDRESS=0x2416092f143378750bb29b79eD961ab195CcEea5
export RPC_URL="https://eth.merkle.io"
50 changes: 50 additions & 0 deletions solidity/script/xerc20/ApproveLockbox.s.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
// SPDX-License-Identifier: MIT OR Apache-2.0
pragma solidity >=0.8.0;

import "forge-std/Script.sol";

import {AnvilRPC} from "test/AnvilRPC.sol";
import {TypeCasts} from "contracts/libs/TypeCasts.sol";

import {ITransparentUpgradeableProxy} from "@openzeppelin/contracts/proxy/transparent/TransparentUpgradeableProxy.sol";

import {ProxyAdmin} from "contracts/upgrade/ProxyAdmin.sol";

import {HypXERC20Lockbox} from "contracts/token/extensions/HypXERC20Lockbox.sol";
import {IXERC20Lockbox} from "contracts/token/interfaces/IXERC20Lockbox.sol";
import {IXERC20} from "contracts/token/interfaces/IXERC20.sol";
import {IERC20} from "contracts/token/interfaces/IXERC20.sol";

// source .env.<CHAIN>
// forge script ApproveLockbox.s.sol --broadcast --rpc-url localhost:XXXX
contract ApproveLockbox is Script {
address router = vm.envAddress("ROUTER_ADDRESS");
address admin = vm.envAddress("ADMIN_ADDRESS");
uint256 deployerPrivateKey = vm.envUint("PRIVATE_KEY");

ITransparentUpgradeableProxy proxy = ITransparentUpgradeableProxy(router);
HypXERC20Lockbox old = HypXERC20Lockbox(router);
address lockbox = address(old.lockbox());
address mailbox = address(old.mailbox());
ProxyAdmin proxyAdmin = ProxyAdmin(admin);

function run() external {
assert(proxyAdmin.getProxyAdmin(proxy) == admin);

vm.startBroadcast(deployerPrivateKey);
HypXERC20Lockbox logic = new HypXERC20Lockbox(lockbox, mailbox);
proxyAdmin.upgradeAndCall(
proxy,
address(logic),
abi.encodeCall(HypXERC20Lockbox.approveLockbox, ())
);
vm.stopBroadcast();

vm.expectRevert("Initializable: contract is already initialized");
HypXERC20Lockbox(address(proxy)).initialize(
address(0),
address(0),
mailbox
);
}
}
37 changes: 37 additions & 0 deletions solidity/script/xerc20/GrantLimits.s.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
// SPDX-License-Identifier: MIT OR Apache-2.0
pragma solidity >=0.8.0;

import "forge-std/Script.sol";

import {AnvilRPC} from "test/AnvilRPC.sol";

import {IXERC20Lockbox} from "contracts/token/interfaces/IXERC20Lockbox.sol";
import {IXERC20} from "contracts/token/interfaces/IXERC20.sol";
import {IERC20} from "contracts/token/interfaces/IXERC20.sol";

// source .env.<CHAIN>
// anvil --fork-url $RPC_URL --port XXXX
// forge script GrantLimits.s.sol --broadcast --unlocked --rpc-url localhost:XXXX
contract GrantLimits is Script {
address tester = 0xa7ECcdb9Be08178f896c26b7BbD8C3D4E844d9Ba;
uint256 amount = 1 gwei;

address router = vm.envAddress("ROUTER_ADDRESS");
IERC20 erc20 = IERC20(vm.envAddress("ERC20_ADDRESS"));
IXERC20 xerc20 = IXERC20(vm.envAddress("XERC20_ADDRESS"));

function runFrom(address account) internal {
AnvilRPC.setBalance(account, 1 ether);
AnvilRPC.impersonateAccount(account);
vm.broadcast(account);
}

function run() external {
address owner = xerc20.owner();
runFrom(owner);
xerc20.setLimits(router, amount, amount);

runFrom(address(erc20));
erc20.transfer(tester, amount);
}
}
127 changes: 127 additions & 0 deletions solidity/script/xerc20/ezETH.s.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,127 @@
// SPDX-License-Identifier: MIT OR Apache-2.0
pragma solidity >=0.8.0;

import "forge-std/Script.sol";

import {IXERC20Lockbox} from "../../contracts/token/interfaces/IXERC20Lockbox.sol";
import {IXERC20} from "../../contracts/token/interfaces/IXERC20.sol";
import {IERC20} from "../../contracts/token/interfaces/IXERC20.sol";
import {HypXERC20Lockbox} from "../../contracts/token/extensions/HypXERC20Lockbox.sol";
import {HypERC20Collateral} from "../../contracts/token/HypERC20Collateral.sol";
import {HypXERC20} from "../../contracts/token/extensions/HypXERC20.sol";
import {TransparentUpgradeableProxy} from "../../contracts/upgrade/TransparentUpgradeableProxy.sol";
import {ProxyAdmin} from "../../contracts/upgrade/ProxyAdmin.sol";

import {TypeCasts} from "../../contracts/libs/TypeCasts.sol";
import {TokenMessage} from "../../contracts/token/libs/TokenMessage.sol";

contract ezETH is Script {
using TypeCasts for address;

string ETHEREUM_RPC_URL = vm.envString("ETHEREUM_RPC_URL");
string BLAST_RPC_URL = vm.envString("BLAST_RPC_URL");

uint256 ethereumFork;
uint32 ethereumDomainId = 1;
address ethereumMailbox = 0xc005dc82818d67AF737725bD4bf75435d065D239;
address ethereumLockbox = 0xC8140dA31E6bCa19b287cC35531c2212763C2059;

uint256 blastFork;
uint32 blastDomainId = 81457;
address blastXERC20 = 0x2416092f143378750bb29b79eD961ab195CcEea5;
address blastMailbox = 0x3a867fCfFeC2B790970eeBDC9023E75B0a172aa7;

uint256 amount = 100;

function setUp() public {
ethereumFork = vm.createFork(ETHEREUM_RPC_URL);
blastFork = vm.createFork(BLAST_RPC_URL);
}

function run() external {
address deployer = address(this);
bytes32 recipient = deployer.addressToBytes32();
bytes memory tokenMessage = TokenMessage.format(recipient, amount, "");
vm.selectFork(ethereumFork);
HypXERC20Lockbox hypXERC20Lockbox = new HypXERC20Lockbox(
ethereumLockbox,
ethereumMailbox
);
ProxyAdmin ethAdmin = new ProxyAdmin();
TransparentUpgradeableProxy ethProxy = new TransparentUpgradeableProxy(
address(hypXERC20Lockbox),
address(ethAdmin),
abi.encodeCall(
HypXERC20Lockbox.initialize,
(address(0), address(0), deployer)
)
);
hypXERC20Lockbox = HypXERC20Lockbox(address(ethProxy));

vm.selectFork(blastFork);
HypXERC20 hypXERC20 = new HypXERC20(blastXERC20, blastMailbox);
ProxyAdmin blastAdmin = new ProxyAdmin();
TransparentUpgradeableProxy blastProxy = new TransparentUpgradeableProxy(
address(hypXERC20),
address(blastAdmin),
abi.encodeCall(
HypERC20Collateral.initialize,
(address(0), address(0), deployer)
)
);
hypXERC20 = HypXERC20(address(blastProxy));
hypXERC20.enrollRemoteRouter(
ethereumDomainId,
address(hypXERC20Lockbox).addressToBytes32()
);

// grant `amount` mint and burn limit to warp route
vm.prank(IXERC20(blastXERC20).owner());
IXERC20(blastXERC20).setLimits(address(hypXERC20), amount, amount);

// test sending `amount` on warp route
vm.prank(0x7BE481D464CAD7ad99500CE8A637599eB8d0FCDB); // ezETH whale
IXERC20(blastXERC20).transfer(address(this), amount);
IXERC20(blastXERC20).approve(address(hypXERC20), amount);
uint256 value = hypXERC20.quoteGasPayment(ethereumDomainId);
hypXERC20.transferRemote{value: value}(
ethereumDomainId,
recipient,
amount
);

// test receiving `amount` on warp route
vm.prank(blastMailbox);
hypXERC20.handle(
ethereumDomainId,
address(hypXERC20Lockbox).addressToBytes32(),
tokenMessage
);

vm.selectFork(ethereumFork);
hypXERC20Lockbox.enrollRemoteRouter(
blastDomainId,
address(hypXERC20).addressToBytes32()
);

// grant `amount` mint and burn limit to warp route
IXERC20 ethereumXERC20 = hypXERC20Lockbox.xERC20();
vm.prank(ethereumXERC20.owner());
ethereumXERC20.setLimits(address(hypXERC20Lockbox), amount, amount);

// test sending `amount` on warp route
IERC20 erc20 = IXERC20Lockbox(ethereumLockbox).ERC20();
vm.prank(ethereumLockbox);
erc20.transfer(address(this), amount);
erc20.approve(address(hypXERC20Lockbox), amount);
hypXERC20Lockbox.transferRemote(blastDomainId, recipient, amount);

// test receiving `amount` on warp route
vm.prank(ethereumMailbox);
hypXERC20Lockbox.handle(
blastDomainId,
address(hypXERC20).addressToBytes32(),
tokenMessage
);
}
}
Loading

0 comments on commit a8a68f6

Please sign in to comment.