Skip to content

Commit

Permalink
(feat) Break out fixtures
Browse files Browse the repository at this point in the history
(feat) Start of cross-compatibility tests.
  • Loading branch information
rrw-zilliqa committed Jul 15, 2024
1 parent cf3d62d commit c79d509
Show file tree
Hide file tree
Showing 3 changed files with 335 additions and 139 deletions.
110 changes: 110 additions & 0 deletions smart-contracts/test/zilbridge/ZilBridgeTokenBridge.compat.t.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,110 @@
// SPDX-License-Identifier: MIT OR Apache-2.0
pragma solidity 0.8.20;

import "forge-std/console.sol";
import {Tester, Vm} from "test/Tester.sol";
import {ITokenManagerStructs, TokenManagerUpgradeable} from "contracts/periphery/TokenManagerUpgradeable.sol";
import {LockAndReleaseTokenManagerUpgradeableV3} from "contracts/periphery/TokenManagerV3/LockAndReleaseTokenManagerUpgradeableV3.sol";
import {MintAndBurnTokenManagerUpgradeableV3} from "contracts/periphery/TokenManagerV3/MintAndBurnTokenManagerUpgradeableV3.sol";
import {BridgedToken} from "contracts/periphery/BridgedToken.sol";
import {CallMetadata, IRelayerEvents} from "contracts/core/Relayer.sol";
import {ValidatorManager} from "contracts/core/ValidatorManager.sol";
import {ChainGateway} from "contracts/core/ChainGateway.sol";
import {MessageHashUtils} from "@openzeppelin/contracts/utils/cryptography/MessageHashUtils.sol";
import {TestToken} from "test/Helpers.sol";
import {LockProxyTokenManagerUpgradeableV3} from "contracts/zilbridge/2/LockProxyTokenManagerUpgradeableV3.sol";
import {LockProxyTokenManagerDeployer} from "test/zilbridge/TokenManagerDeployers/LockProxyTokenManagerDeployer.sol";
import {MintAndBurnTokenManagerDeployer} from "test/periphery/TokenManagerDeployers/MintAndBurnTokenManagerDeployer.sol";
import {LockAndReleaseTokenManagerDeployer} from "test/periphery/TokenManagerDeployers/LockAndReleaseTokenManagerDeployer.sol";
import { SwitcheoToken } from "contracts/zilbridge/token/tokens/SwitcheoTokenETH.sol";
import { ZilBridgeFixture } from "test/zilbridge/DeployZilBridge.t.sol";
import { MockLockProxy } from "./MockLockProxy.sol";
import { ZilBridgeTokenBridgeIntegrationFixture } from "./ZilBridgeTokenIntegrationFixture.t.sol";
import { IERC20 } from "openzeppelin-contracts/contracts/interfaces/IERC20.sol";

/*** @notice provides utility routines for the compatibility tests. These are very similar to the
* orutines in the integration tests.
*/
contract ZilBridgeTokenBridgeCompatibilityFixture is ZilBridgeTokenBridgeIntegrationFixture {
using MessageHashUtils for bytes;

/// @notice send amount wrapped tokens to the remote user.
function transferToRemoteUser(address sourceToken_, address remoteToken_,
address sourceUser_, address remoteUser_, uint256 amount_) public {
startHoax(sourceUser_);
uint sourceChainId_ = block.chainid;
uint remoteChainId_ = block.chainid;
uint valueToSend = fees;
uint sourceBalance;
if (sourceToken_ == address(0)) {
// Check for native
assertGe(sourceUser_.balance, amount_);
sourceBalance = sourceUser_.balance;
valueToSend += amount_;
} else {
// Check that the source user has the funds.
sourceBalance = IERC20(sourceToken_).balanceOf(sourceUser_);
assertGe(sourceBalance, amount_);
// Set up an allowance
IERC20(sourceToken_).approve(address(sourceTokenManager), amount_);
}
uint remoteBalance;
if (remoteToken_ == address(0)) {
remoteBalance = remoteUser_.balance;
} else {
remoteBalance = IERC20(remoteToken_).balanceOf(remoteUser_);
}

bytes memory data = abi.encodeWithSelector(
TokenManagerUpgradeable.accept.selector,
// From
CallMetadata(sourceChainId_, address(sourceTokenManager)),
// To
abi.encode(ITokenManagerStructs.AcceptArgs(address(remoteToken_), remoteUser_, amount_)));

vm.expectEmit(address(sourceChainGateway));
emit IRelayerEvents.Relayed(remoteChainId_,
address(remoteTokenManager), data, 1_000_000, 0);
sourceTokenManager.transfer{value: valueToSend} (
address(sourceToken_), remoteChainId_, remoteUser_, amount_);
vm.startPrank(validator);
bytes[] memory signatures = new bytes[](1);
signatures[0] = sign(validatorWallet, abi.encode(sourceChainId_, remoteChainId_,
address(remoteTokenManager), data,
1_000_000, 0)
.toEthSignedMessageHash());
remoteChainGateway.dispatch(sourceChainId_,
address(remoteTokenManager),
data, 1_000_000, 0, signatures);
if (remoteToken_ == address(0)) {
assertGe(remoteUser_.balance, remoteBalance + amount_);
} else {
assertEq(IERC20(remoteToken_).balanceOf(remoteUser_), remoteBalance + amount_);
}
if (sourceToken_ == address(0)) {
assertLe(sourceUser_.balance, sourceBalance - amount_);
} else {
assertEq(IERC20(sourceToken_).balanceOf(sourceUser_), sourceBalance - amount_);
}
}
}

/// @title Test that assets bridged via ZilBridge can be recovered via XBridge
/// @author rrw
/*** @notice This test is a replica of ZilBridgeTokenBridgeIntegrationTest which bridges assets that we fake up
* ( via MockLockProxy ) to have been locked by the original zilBridge, to prove that we can recover
* them via XBridge.
*/
contract ZilBridgeTokenBridgeCompatibilityTest is ZilBridgeTokenBridgeCompatibilityFixture {
using MessageHashUtils for bytes;

function setUp() external {
installContracts();
}

function test_wrappedCompatibility() external {
// Amusingly, because of the way SwitcheoToken works, we need to do a bridge transfer to get funds to the remote user that they can then
// synthetically lock.
transferToRemoteUser(address(nativelyOnSource), address(remoteNativelyOnSource), sourceUser, remoteUser, originalTokenSupply);
}
}
231 changes: 92 additions & 139 deletions smart-contracts/test/zilbridge/ZilBridgeTokenBridge.integration.t.sol
Original file line number Diff line number Diff line change
Expand Up @@ -19,144 +19,15 @@ import {LockAndReleaseTokenManagerDeployer} from "test/periphery/TokenManagerDep
import { SwitcheoToken } from "contracts/zilbridge/token/tokens/SwitcheoTokenETH.sol";
import { ZilBridgeFixture } from "test/zilbridge/DeployZilBridge.t.sol";
import { MockLockProxy } from "./MockLockProxy.sol";


// This deploys a bridge that is ZilBridge token manager on one side and lock-and-release on the other.
// We do this so we can test ZilBridge integration end to end, though in fact the "other side" of the bridge will be provided
// by the code in the Scilla half of this repo.
contract ZilBridgeTokenBridgeIntegrationFixture is
Tester, IRelayerEvents, LockAndReleaseTokenManagerDeployer, LockProxyTokenManagerDeployer, ZilBridgeFixture {
using MessageHashUtils for bytes;

ZilBridgeFixture zilBridge;

// Gateway shared between the two chains
Vm.Wallet validatorWallet = vm.createWallet(1);
address validator = validatorWallet.addr;
address[] validators = [ validator ];
address sourceUser = vm.addr(2);
address remoteUser = vm.addr(3);
uint originalTokenSupply = 1000 ether;
uint fees = 0.1 ether;

LockProxyTokenManagerUpgradeableV3 sourceTokenManager;
// There are "actually" three of these - native (which is lock/release), a mint/burn token and a conventional token.
// This means we need a remote lock manager and a remote mint burn manager.
TestToken nativelyOnSource;
SwitcheoToken nativelyOnRemote;
ChainGateway sourceChainGateway;
ValidatorManager sourceValidatorManager;

// see doc/zilbridge.md
MockLockProxy mockRemoteLockProxy;
LockProxyTokenManagerUpgradeableV3 remoteTokenManager;
SwitcheoToken remoteNativelyOnSource;
TestToken remoteBridgedGasToken;

ChainGateway remoteChainGateway;
ValidatorManager remoteValidatorManager;

function installContracts() public {
setUpZilBridgeForTesting();

vm.startPrank(validator);
// Deploy source infra
sourceValidatorManager = new ValidatorManager(validator);
sourceValidatorManager.initialize(validators);
sourceChainGateway = new ChainGateway(address(sourceValidatorManager), validator);

// Deploy target infra
remoteValidatorManager = new ValidatorManager(validator);
remoteValidatorManager.initialize(validators);
remoteChainGateway = new ChainGateway(address(remoteValidatorManager), validator);

// Deploy the token managers
sourceTokenManager = deployLatestLockProxyTokenManager(address(sourceChainGateway), address(lockProxy), fees);
mockRemoteLockProxy = new MockLockProxy();
remoteTokenManager = deployLatestLockProxyTokenManager(address(remoteChainGateway), address(mockRemoteLockProxy), fees);

vm.stopPrank();
// Make the token manager an extension.
installTokenManager(address(sourceTokenManager));

// That involved a prank, so we need to reset our caller to the validator.
vm.startPrank(validator);

// Register contracts to chaingateway
sourceChainGateway.register(address(sourceTokenManager));
remoteChainGateway.register(address(remoteTokenManager));

// Deploy the test tokens.
nativelyOnSource = new TestToken(originalTokenSupply);
nativelyOnSource.transfer(sourceUser, originalTokenSupply);
nativelyOnRemote = new SwitcheoToken(address(lockProxy));

remoteNativelyOnSource = new SwitcheoToken(address(mockRemoteLockProxy));
// When coins arrive at the remote token manager for remoteNativelyOnSource, send them to nativelyOnSource's
// manager at sourceTokenManager.
ITokenManagerStructs.RemoteToken memory sourceNOSStruct = ITokenManagerStructs.RemoteToken({
token: address(nativelyOnSource),
tokenManager: address(sourceTokenManager),
chainId: block.chainid });
remoteTokenManager.registerToken(address(remoteNativelyOnSource), sourceNOSStruct);

// When coins arrive at nativelyOnSource, send them to remoteNativelyOnSource's manager at remoteTokenManager
ITokenManagerStructs.RemoteToken memory remoteNOSStruct = ITokenManagerStructs.RemoteToken({
token: address(remoteNativelyOnSource),
tokenManager: address(remoteTokenManager),
chainId: block.chainid });
sourceTokenManager.registerToken(address(nativelyOnSource), remoteNOSStruct);
vm.stopPrank();
}

/* function test_Debug() external { */
/* console.log("Hello world!"); */
/* setUpZilBridgeForTesting(); */
/* vm.startPrank(validator); */
/* // Deploy source infra */
/* sourceValidatorManager = new ValidatorManager(validator); */
/* sourceValidatorManager.initialize(validators); */
/* sourceChainGateway = new ChainGateway(address(sourceValidatorManager), validator); */

/* // Deploy target infra */
/* remoteValidatorManager = new ValidatorManager(validator); */
/* remoteValidatorManager.initialize(validators); */
/* remoteChainGateway = new ChainGateway(address(remoteValidatorManager), validator); */

/* // Deploy the token managers */
/* sourceTokenManager = deployLatestLockProxyTokenManager(address(sourceChainGateway), address(lockProxy), fees); */
/* remoteMintBurnManager = deployLatestMintAndBurnTokenManager(address(remoteChainGateway), fees); */

/* vm.stopPrank(); */
/* // Make the token manager an extension. */
/* installTokenManager(address(sourceTokenManager)); */

/* // That involved a prank, so we need to reset our caller to the validator. */
/* vm.startPrank(validator); */

/* // Register contracts to chaingateway */
/* sourceChainGateway.register(address(sourceTokenManager)); */
/* remoteChainGateway.register(address(remoteMintBurnManager)); */

/* // Deploy the test tokens. */
/* nativelyOnSource = new TestToken(originalTokenSupply); */
/* nativelyOnSource.transfer(sourceUser, originalTokenSupply); */
/* nativelyOnRemote = new SwitcheoToken(address(lockProxy)); */

/* remoteNativelyOnSource = remoteMintBurnManager.deployToken("NativelyOnSource", "NOST", */
/* address(nativelyOnSource), */
/* address(sourceTokenManager), */
/* block.chainid); */
/* ITokenManagerStructs.RemoteToken memory remoteNOSStruct = ITokenManagerStructs.RemoteToken({ */
/* token: address(remoteNativelyOnSource), */
/* tokenManager: address(remoteMintBurnManager), */
/* chainId: block.chainid }); */
/* sourceTokenManager.registerToken(address(nativelyOnSource), remoteNOSStruct); */
/* vm.stopPrank(); */
/* } */

}

import { ZilBridgeTokenBridgeIntegrationFixture } from "./ZilBridgeTokenIntegrationFixture.t.sol";

/// @title A general integration test for ZilBridge.
/// @author rrw
/*** @notice Tests some of the basic transfer routes: TestToken -> SwitcheoToken and back,
* and native token -> SwitcheoToken and back. Testing SwitcheoToken -> nativetoken is
* skipped, partly because it is symmetric and partly because it's not possible in zq1
* anyway.
*/
contract ZilBridgeTokenBridgeIntegrationTest is ZilBridgeTokenBridgeIntegrationFixture {
using MessageHashUtils for bytes;

Expand All @@ -165,7 +36,8 @@ contract ZilBridgeTokenBridgeIntegrationTest is ZilBridgeTokenBridgeIntegrationF
installContracts();
}

function test_happyPath() external {
/// @notice test the happy path for a wrapped token to wrapped token exchange from TestToken to SwitcheoToken and back.
function test_happyPathWrappedToRemote() external {
startHoax(sourceUser);
uint amount = originalTokenSupply;
uint sourceChainId = block.chainid;
Expand Down Expand Up @@ -245,4 +117,85 @@ contract ZilBridgeTokenBridgeIntegrationTest is ZilBridgeTokenBridgeIntegrationF
assertEq(remoteNativelyOnSource.balanceOf(remoteUser), 0);
assertEq(nativelyOnSource.balanceOf(sourceUser), amount);
}


/// @notice Test native token to SwitcheoToken and back.
function test_happyPathNativeToken() external {
startHoax(sourceUser);
uint256 amount = originalTokenSupply;

// add some to account for gas.
uint accountForGas = 1 ether;
vm.deal(sourceUser, amount + accountForGas);
vm.deal(remoteUser, accountForGas);
uint256 initialSourceBalance = sourceUser.balance;
uint sourceChainId = block.chainid;
uint remoteChainId = block.chainid;
assertGt(sourceUser.balance, originalTokenSupply);

bytes memory data = abi.encodeWithSelector(
TokenManagerUpgradeable.accept.selector,
// From
CallMetadata(sourceChainId, address(sourceTokenManager)),
// To
abi.encode(ITokenManagerStructs.AcceptArgs(address(remoteBridgedGasToken), remoteUser, amount)));
vm.expectEmit(address(sourceChainGateway));
emit IRelayerEvents.Relayed(remoteChainId,
address(remoteTokenManager), data, 1_000_000, 0);
sourceTokenManager.transfer{ value: fees + originalTokenSupply }(
address(0), remoteChainId, remoteUser, amount);

// Bridge!
vm.startPrank(validator);
bytes[] memory signatures = new bytes[](1);
signatures[0] = sign(validatorWallet, abi.encode(sourceChainId, remoteChainId,
address(remoteTokenManager),
data,
1_000_000, 0)
.toEthSignedMessageHash());

remoteChainGateway.dispatch(sourceChainId, address(remoteTokenManager), data, 1_000_000, 0, signatures);

// The source user should now have less balance then accountForGas
assertLe(sourceUser.balance, initialSourceBalance - amount);
uint256 sourceUserBalanceAfterTransfer = sourceUser.balance;
// The lock proxy should have the balance.
assertEq(address(lockProxy).balance, amount);
// The remote user should have the right number of wrapped tokens
assertEq(remoteBridgedGasToken.balanceOf(sourceUser), 0);
assertEq(remoteBridgedGasToken.balanceOf(remoteUser), amount);
assertEq(remoteBridgedGasToken.totalSupply(), amount);

// ..aaand send them all back again
vm.startPrank(remoteUser);
remoteBridgedGasToken.approve(address(remoteTokenManager), amount);
remoteTokenManager.transfer{value: fees}(
address(remoteBridgedGasToken), sourceChainId, sourceUser, amount);
vm.startPrank(validator);
data = abi.encodeWithSelector(
TokenManagerUpgradeable.accept.selector,
// From
CallMetadata(remoteChainId, address(remoteTokenManager)),
// To
abi.encode(
ITokenManagerStructs.AcceptArgs(address(0), sourceUser, amount)));
signatures[0] = sign(
validatorWallet,
abi.encode(remoteChainId, sourceChainId,
address(sourceTokenManager),
data,
1_000_000, 0).toEthSignedMessageHash());
sourceChainGateway.dispatch(remoteChainId,
address(sourceTokenManager),
data,
1_000_000,
0,
signatures);
// Balances should now be back
assertEq(sourceUser.balance, sourceUserBalanceAfterTransfer + amount);
assertEq(remoteBridgedGasToken.balanceOf(remoteUser), 0);
// Weirdly, the supply is nondecreasing - this seems to be intentional in SwitcheoToken...
assertEq(remoteBridgedGasToken.totalSupply(), amount);
}

}
Loading

0 comments on commit c79d509

Please sign in to comment.