test: XERC20 and XERC20 Lockbox integration tests (#3849)
parent
335310355c
commit
a8a68f6f61
@ -0,0 +1,5 @@ |
||||
--- |
||||
"@hyperlane-xyz/core": patch |
||||
--- |
||||
|
||||
fix: make XERC20 and XERC20 Lockbox proxy-able |
@ -1 +1 @@ |
||||
Subproject commit e8a047e3f40f13fa37af6fe14e6e06283d9a060e |
||||
Subproject commit 52715a217dc51d0de15877878ab8213f6cbbbab5 |
@ -0,0 +1,4 @@ |
||||
export ROUTER_ADDRESS=0xA34ceDf9068C5deE726C67A4e1DCfCc2D6E2A7fD |
||||
export ERC20_ADDRESS=0x2416092f143378750bb29b79eD961ab195CcEea5 |
||||
export XERC20_ADDRESS=0x2416092f143378750bb29b79eD961ab195CcEea5 |
||||
export RPC_URL="https://rpc.blast.io" |
@ -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" |
@ -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 |
||||
); |
||||
} |
||||
} |
@ -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); |
||||
} |
||||
} |
@ -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 |
||||
); |
||||
} |
||||
} |
@ -0,0 +1,107 @@ |
||||
// SPDX-License-Identifier: MIT OR Apache-2.0 |
||||
pragma solidity >=0.8.0; |
||||
|
||||
import "forge-std/Vm.sol"; |
||||
import {Strings} from "@openzeppelin/contracts/utils/Strings.sol"; |
||||
|
||||
// see https://book.getfoundry.sh/reference/anvil/#supported-rpc-methods |
||||
library AnvilRPC { |
||||
using Strings for address; |
||||
using Strings for uint256; |
||||
|
||||
using AnvilRPC for string; |
||||
using AnvilRPC for string[1]; |
||||
using AnvilRPC for string[2]; |
||||
using AnvilRPC for string[3]; |
||||
|
||||
Vm private constant vm = |
||||
Vm(address(uint160(uint256(keccak256("hevm cheat code"))))); |
||||
|
||||
string private constant OPEN_ARRAY = "["; |
||||
string private constant CLOSE_ARRAY = "]"; |
||||
string private constant COMMA = ","; |
||||
string private constant EMPTY_ARRAY = "[]"; |
||||
|
||||
function escaped( |
||||
string memory value |
||||
) internal pure returns (string memory) { |
||||
return string.concat(ESCAPED_QUOTE, value, ESCAPED_QUOTE); |
||||
} |
||||
|
||||
function toString( |
||||
string[1] memory values |
||||
) internal pure returns (string memory) { |
||||
return string.concat(OPEN_ARRAY, values[0], CLOSE_ARRAY); |
||||
} |
||||
|
||||
function toString( |
||||
string[2] memory values |
||||
) internal pure returns (string memory) { |
||||
return |
||||
string.concat(OPEN_ARRAY, values[0], COMMA, values[1], CLOSE_ARRAY); |
||||
} |
||||
|
||||
function toString( |
||||
string[3] memory values |
||||
) internal pure returns (string memory) { |
||||
return |
||||
string.concat( |
||||
OPEN_ARRAY, |
||||
values[0], |
||||
COMMA, |
||||
values[1], |
||||
COMMA, |
||||
values[2], |
||||
CLOSE_ARRAY |
||||
); |
||||
} |
||||
|
||||
function impersonateAccount(address account) internal { |
||||
vm.rpc( |
||||
"anvil_impersonateAccount", |
||||
[account.toHexString().escaped()].toString() |
||||
); |
||||
} |
||||
|
||||
function setBalance(address account, uint256 balance) internal { |
||||
vm.rpc( |
||||
"anvil_setBalance", |
||||
[account.toHexString().escaped(), balance.toString()].toString() |
||||
); |
||||
} |
||||
|
||||
function setCode(address account, bytes memory code) internal { |
||||
vm.rpc( |
||||
"anvil_setCode", |
||||
[account.toHexString().escaped(), string(code).escaped()].toString() |
||||
); |
||||
} |
||||
|
||||
function setStorageAt( |
||||
address account, |
||||
uint256 slot, |
||||
uint256 value |
||||
) internal { |
||||
vm.rpc( |
||||
"anvil_setStorageAt", |
||||
[ |
||||
account.toHexString().escaped(), |
||||
slot.toHexString(), |
||||
value.toHexString() |
||||
].toString() |
||||
); |
||||
} |
||||
|
||||
function resetFork(string memory rpcUrl) internal { |
||||
string memory obj = string.concat( |
||||
// solhint-disable-next-line quotes |
||||
'{"forking":{"jsonRpcUrl":', |
||||
string(rpcUrl).escaped(), |
||||
"}}" |
||||
); |
||||
vm.rpc("anvil_reset", [obj].toString()); |
||||
} |
||||
} |
||||
|
||||
// here to prevent syntax highlighting from breaking |
||||
string constant ESCAPED_QUOTE = '"'; |
Loading…
Reference in new issue