mirror of
https://github.com/vacp2p/linea-monorepo.git
synced 2026-01-09 04:08:01 -05:00
* allow tokenbridge overrides * add L1MessageService overrides * refactor L2 MessageService * refactor L2 MessageService V1 * use correct modifier * refactor LineaRollup for overriding * allow other overrides * reinstate general in pause on tokenbridge * add missing NatSpec * sample overrides * add generic bridge and document placeholder * documentation and folder placement * documentation cleanup * use imported references * use variable pragma for inherited contracts * reset pragmas for some * use base abstract contracts with version overrides * use TokenBridgeBase as abstract * Update contracts/src/bridging/token/TokenBridgeBase.sol Co-authored-by: Victorien Gauch <85494462+VGau@users.noreply.github.com> Signed-off-by: The Dark Jester <thedarkjester@users.noreply.github.com> * Update contracts/src/bridging/token/TokenBridgeBase.sol Co-authored-by: Victorien Gauch <85494462+VGau@users.noreply.github.com> Signed-off-by: The Dark Jester <thedarkjester@users.noreply.github.com> * Update contracts/src/bridging/token/TokenBridgeBase.sol Co-authored-by: Victorien Gauch <85494462+VGau@users.noreply.github.com> Signed-off-by: The Dark Jester <thedarkjester@users.noreply.github.com> * Update contracts/src/bridging/token/interfaces/ITokenBridge.sol Co-authored-by: Victorien Gauch <85494462+VGau@users.noreply.github.com> Signed-off-by: The Dark Jester <thedarkjester@users.noreply.github.com> * Update contracts/src/messaging/l2/L2MessageServiceBase.sol Co-authored-by: Victorien Gauch <85494462+VGau@users.noreply.github.com> Signed-off-by: The Dark Jester <thedarkjester@users.noreply.github.com> * Update contracts/src/messaging/l2/v1/interfaces/IL2MessageServiceV1.sol Co-authored-by: Victorien Gauch <85494462+VGau@users.noreply.github.com> Signed-off-by: The Dark Jester <thedarkjester@users.noreply.github.com> * Update contracts/src/rollup/interfaces/ILineaRollup.sol Co-authored-by: Victorien Gauch <85494462+VGau@users.noreply.github.com> Signed-off-by: The Dark Jester <thedarkjester@users.noreply.github.com> * Update contracts/src/rollup/LineaRollupBase.sol Co-authored-by: Victorien Gauch <85494462+VGau@users.noreply.github.com> Signed-off-by: The Dark Jester <thedarkjester@users.noreply.github.com> * Update contracts/src/_testing/unit/bridging/InheritingTokenBridge.sol Co-authored-by: Victorien Gauch <85494462+VGau@users.noreply.github.com> Signed-off-by: The Dark Jester <thedarkjester@users.noreply.github.com> * Update contracts/src/verifiers/PlonkVerifierDev.sol Co-authored-by: Victorien Gauch <85494462+VGau@users.noreply.github.com> Signed-off-by: The Dark Jester <thedarkjester@users.noreply.github.com> * Update contracts/src/verifiers/PlonkVerifierForDataAggregation.sol Co-authored-by: Victorien Gauch <85494462+VGau@users.noreply.github.com> Signed-off-by: The Dark Jester <thedarkjester@users.noreply.github.com> * Update contracts/src/verifiers/PlonkVerifierForMultiTypeDataAggregation.sol Co-authored-by: Victorien Gauch <85494462+VGau@users.noreply.github.com> Signed-off-by: The Dark Jester <thedarkjester@users.noreply.github.com> * Update contracts/src/verifiers/PlonkVerifierMainnetFull.sol Co-authored-by: Victorien Gauch <85494462+VGau@users.noreply.github.com> Signed-off-by: The Dark Jester <thedarkjester@users.noreply.github.com> * Update contracts/src/verifiers/PlonkVerifierSepoliaFull.sol Co-authored-by: Victorien Gauch <85494462+VGau@users.noreply.github.com> Signed-off-by: The Dark Jester <thedarkjester@users.noreply.github.com> * linting * allow submitDataAsCalldata overriding * address missing test coverage * adjust gap name for storage clarity --------- Signed-off-by: The Dark Jester <thedarkjester@users.noreply.github.com> Co-authored-by: Victorien Gauch <85494462+VGau@users.noreply.github.com>
1018 lines
38 KiB
TypeScript
1018 lines
38 KiB
TypeScript
import { loadFixture } from "@nomicfoundation/hardhat-network-helpers";
|
|
import { expect } from "chai";
|
|
import { ethers, upgrades } from "hardhat";
|
|
import { deployTokenBridgeWithMockMessaging } from "../../../../scripts/tokenBridge/test/deployTokenBridges";
|
|
import { deployTokens } from "../../../../scripts/tokenBridge/test/deployTokens";
|
|
import { BridgedToken, TestTokenBridge } from "../../../../typechain-types";
|
|
import { getPermitData } from "./helpers/permitHelper";
|
|
import { Contract } from "ethers";
|
|
import {
|
|
ADDRESS_ZERO,
|
|
COMPLETE_TOKEN_BRIDGING_PAUSE_TYPE,
|
|
INITIALIZED_ALREADY_MESSAGE,
|
|
INITIATE_TOKEN_BRIDGING_PAUSE_TYPE,
|
|
PAUSE_INITIATE_TOKEN_BRIDGING_ROLE,
|
|
REMOVE_RESERVED_TOKEN_ROLE,
|
|
SET_CUSTOM_CONTRACT_ROLE,
|
|
SET_MESSAGE_SERVICE_ROLE,
|
|
SET_RESERVED_TOKEN_ROLE,
|
|
UNPAUSE_INITIATE_TOKEN_BRIDGING_ROLE,
|
|
pauseTypeRoles,
|
|
unpauseTypeRoles,
|
|
} from "../../common/constants";
|
|
import {
|
|
buildAccessErrorMessage,
|
|
expectEvent,
|
|
expectRevertWithCustomError,
|
|
expectRevertWithReason,
|
|
} from "../../common/helpers";
|
|
import { SupportedChainIds } from "contracts/common/supportedNetworks";
|
|
|
|
const initialUserBalance = BigInt(10 ** 9);
|
|
const mockName = "L1 DAI";
|
|
const mockSymbol = "L1DAI";
|
|
const mockDecimals = 18;
|
|
const RESERVED_STATUS = ethers.getAddress("0x0000000000000000000000000000000000000111");
|
|
const PLACEHOLDER_ADDRESS = ethers.getAddress("0x5555555555555555555555555555555555555555");
|
|
const CUSTOM_ADDRESS = ethers.getAddress("0x9999999999999999999999999999999999999999");
|
|
const EMPTY_PERMIT_DATA = "0x";
|
|
|
|
describe("TokenBridge", function () {
|
|
async function deployContractsFixture() {
|
|
const [owner, user] = await ethers.getSigners();
|
|
|
|
// Deploy and configure bridges
|
|
const deploymentFixture = await deployTokenBridgeWithMockMessaging();
|
|
|
|
// Deploy tokens
|
|
const tokens = await deployTokens();
|
|
|
|
// Mint tokens for user and approve bridge
|
|
for (const name in tokens) {
|
|
const token = tokens[name];
|
|
await token.mint(user.address, initialUserBalance);
|
|
|
|
let bridgeAddress;
|
|
if ((await token.name()).includes("L1")) {
|
|
bridgeAddress = await deploymentFixture.l1TokenBridge.getAddress();
|
|
}
|
|
if ((await token.name()).includes("L2")) {
|
|
bridgeAddress = await deploymentFixture.l2TokenBridge.getAddress();
|
|
}
|
|
|
|
await token.connect(user).approve(bridgeAddress!, ethers.MaxUint256);
|
|
}
|
|
const encodedTokenMetadata = ethers.AbiCoder.defaultAbiCoder().encode(
|
|
["string", "string", "uint8"],
|
|
[mockName, mockSymbol, mockDecimals],
|
|
);
|
|
return { owner, user, ...deploymentFixture, tokens, encodedTokenMetadata };
|
|
}
|
|
|
|
describe("initialize", async function () {
|
|
it("Should revert if it has already been intialized", async function () {
|
|
const { user, l1TokenBridge, chainIds } = await loadFixture(deployContractsFixture);
|
|
await expectRevertWithReason(
|
|
l1TokenBridge.connect(user).initialize({
|
|
defaultAdmin: PLACEHOLDER_ADDRESS,
|
|
messageService: PLACEHOLDER_ADDRESS,
|
|
tokenBeacon: PLACEHOLDER_ADDRESS,
|
|
sourceChainId: chainIds[0],
|
|
targetChainId: chainIds[1],
|
|
remoteSender: PLACEHOLDER_ADDRESS,
|
|
reservedTokens: [],
|
|
roleAddresses: [],
|
|
pauseTypeRoles: [],
|
|
unpauseTypeRoles: [],
|
|
}),
|
|
INITIALIZED_ALREADY_MESSAGE,
|
|
);
|
|
});
|
|
|
|
it("Should revert if one of the initializing parameters is address 0", async function () {
|
|
const { user, owner, chainIds } = await loadFixture(deployContractsFixture);
|
|
const TokenBridge = await ethers.getContractFactory("TokenBridge");
|
|
|
|
await expectRevertWithCustomError(
|
|
TokenBridge,
|
|
upgrades.deployProxy(TokenBridge, [
|
|
{
|
|
defaultAdmin: PLACEHOLDER_ADDRESS,
|
|
messageService: ADDRESS_ZERO,
|
|
tokenBeacon: PLACEHOLDER_ADDRESS,
|
|
sourceChainId: chainIds[0],
|
|
targetChainId: chainIds[1],
|
|
remoteSender: PLACEHOLDER_ADDRESS,
|
|
reservedTokens: [],
|
|
roleAddresses: [],
|
|
pauseTypeRoles: [],
|
|
unpauseTypeRoles: [],
|
|
},
|
|
]),
|
|
"ZeroAddressNotAllowed",
|
|
);
|
|
|
|
await expectRevertWithCustomError(
|
|
TokenBridge,
|
|
upgrades.deployProxy(TokenBridge, [
|
|
{
|
|
defaultAdmin: PLACEHOLDER_ADDRESS,
|
|
messageService: PLACEHOLDER_ADDRESS,
|
|
tokenBeacon: ADDRESS_ZERO,
|
|
sourceChainId: chainIds[0],
|
|
targetChainId: chainIds[1],
|
|
remoteSender: PLACEHOLDER_ADDRESS,
|
|
reservedTokens: [],
|
|
roleAddresses: [],
|
|
pauseTypeRoles: [],
|
|
unpauseTypeRoles: [],
|
|
},
|
|
]),
|
|
"ZeroAddressNotAllowed",
|
|
);
|
|
|
|
await expectRevertWithCustomError(
|
|
TokenBridge,
|
|
upgrades.deployProxy(TokenBridge, [
|
|
{
|
|
defaultAdmin: PLACEHOLDER_ADDRESS,
|
|
messageService: PLACEHOLDER_ADDRESS,
|
|
tokenBeacon: PLACEHOLDER_ADDRESS,
|
|
sourceChainId: chainIds[0],
|
|
targetChainId: chainIds[1],
|
|
remoteSender: ADDRESS_ZERO,
|
|
reservedTokens: [],
|
|
roleAddresses: [],
|
|
pauseTypeRoles: [],
|
|
unpauseTypeRoles: [],
|
|
},
|
|
]),
|
|
"ZeroAddressNotAllowed",
|
|
);
|
|
|
|
await expectRevertWithCustomError(
|
|
TokenBridge,
|
|
upgrades.deployProxy(TokenBridge, [
|
|
{
|
|
defaultAdmin: PLACEHOLDER_ADDRESS,
|
|
messageService: PLACEHOLDER_ADDRESS,
|
|
tokenBeacon: PLACEHOLDER_ADDRESS,
|
|
sourceChainId: chainIds[0],
|
|
targetChainId: chainIds[1],
|
|
remoteSender: PLACEHOLDER_ADDRESS,
|
|
reservedTokens: [PLACEHOLDER_ADDRESS, ADDRESS_ZERO],
|
|
roleAddresses: [
|
|
{ addressWithRole: user.address, role: SET_RESERVED_TOKEN_ROLE },
|
|
{ addressWithRole: owner.address, role: SET_RESERVED_TOKEN_ROLE },
|
|
],
|
|
pauseTypeRoles: pauseTypeRoles,
|
|
unpauseTypeRoles: unpauseTypeRoles,
|
|
},
|
|
]),
|
|
"ZeroAddressNotAllowed",
|
|
);
|
|
|
|
await expectRevertWithCustomError(
|
|
TokenBridge,
|
|
upgrades.deployProxy(TokenBridge, [
|
|
{
|
|
defaultAdmin: PLACEHOLDER_ADDRESS,
|
|
messageService: PLACEHOLDER_ADDRESS,
|
|
tokenBeacon: PLACEHOLDER_ADDRESS,
|
|
sourceChainId: chainIds[0],
|
|
targetChainId: chainIds[1],
|
|
remoteSender: PLACEHOLDER_ADDRESS,
|
|
reservedTokens: [PLACEHOLDER_ADDRESS],
|
|
roleAddresses: [
|
|
{ addressWithRole: ADDRESS_ZERO, role: SET_RESERVED_TOKEN_ROLE },
|
|
{ addressWithRole: owner.address, role: SET_RESERVED_TOKEN_ROLE },
|
|
],
|
|
pauseTypeRoles: pauseTypeRoles,
|
|
unpauseTypeRoles: unpauseTypeRoles,
|
|
},
|
|
]),
|
|
"ZeroAddressNotAllowed",
|
|
);
|
|
});
|
|
|
|
it("Should revert if one of the initializing parameters is chainId 0", async function () {
|
|
const { chainIds } = await loadFixture(deployContractsFixture);
|
|
const TokenBridge = await ethers.getContractFactory("TokenBridge");
|
|
|
|
await expectRevertWithCustomError(
|
|
TokenBridge,
|
|
upgrades.deployProxy(TokenBridge, [
|
|
{
|
|
defaultAdmin: PLACEHOLDER_ADDRESS,
|
|
messageService: PLACEHOLDER_ADDRESS,
|
|
tokenBeacon: PLACEHOLDER_ADDRESS,
|
|
sourceChainId: 0,
|
|
targetChainId: chainIds[1],
|
|
remoteSender: PLACEHOLDER_ADDRESS,
|
|
reservedTokens: [],
|
|
roleAddresses: [],
|
|
pauseTypeRoles: [],
|
|
unpauseTypeRoles: [],
|
|
},
|
|
]),
|
|
"ZeroChainIdNotAllowed",
|
|
);
|
|
|
|
await expectRevertWithCustomError(
|
|
TokenBridge,
|
|
upgrades.deployProxy(TokenBridge, [
|
|
{
|
|
defaultAdmin: PLACEHOLDER_ADDRESS,
|
|
messageService: PLACEHOLDER_ADDRESS,
|
|
tokenBeacon: PLACEHOLDER_ADDRESS,
|
|
sourceChainId: chainIds[0],
|
|
targetChainId: 0,
|
|
remoteSender: PLACEHOLDER_ADDRESS,
|
|
reservedTokens: [],
|
|
roleAddresses: [],
|
|
pauseTypeRoles: [],
|
|
unpauseTypeRoles: [],
|
|
},
|
|
]),
|
|
"ZeroChainIdNotAllowed",
|
|
);
|
|
});
|
|
|
|
it("Should revert if the sourceChainId is the same as the targetChainId", async function () {
|
|
const { chainIds } = await loadFixture(deployContractsFixture);
|
|
const TokenBridge = await ethers.getContractFactory("TokenBridge");
|
|
|
|
await expectRevertWithCustomError(
|
|
TokenBridge,
|
|
upgrades.deployProxy(TokenBridge, [
|
|
{
|
|
defaultAdmin: PLACEHOLDER_ADDRESS,
|
|
messageService: PLACEHOLDER_ADDRESS,
|
|
tokenBeacon: PLACEHOLDER_ADDRESS,
|
|
sourceChainId: chainIds[0],
|
|
targetChainId: chainIds[0],
|
|
remoteSender: PLACEHOLDER_ADDRESS,
|
|
reservedTokens: [],
|
|
roleAddresses: [],
|
|
pauseTypeRoles: [],
|
|
unpauseTypeRoles: [],
|
|
},
|
|
]),
|
|
"SourceChainSameAsTargetChain",
|
|
);
|
|
});
|
|
|
|
it("Should revert if the default admin is empty", async function () {
|
|
const { chainIds } = await loadFixture(deployContractsFixture);
|
|
const TokenBridge = await ethers.getContractFactory("TokenBridge");
|
|
|
|
await expectRevertWithCustomError(
|
|
TokenBridge,
|
|
upgrades.deployProxy(TokenBridge, [
|
|
{
|
|
defaultAdmin: ADDRESS_ZERO,
|
|
messageService: PLACEHOLDER_ADDRESS,
|
|
tokenBeacon: PLACEHOLDER_ADDRESS,
|
|
sourceChainId: chainIds[0],
|
|
targetChainId: chainIds[1],
|
|
remoteSender: PLACEHOLDER_ADDRESS,
|
|
reservedTokens: [],
|
|
roleAddresses: [],
|
|
pauseTypeRoles: [],
|
|
unpauseTypeRoles: [],
|
|
},
|
|
]),
|
|
"ZeroAddressNotAllowed",
|
|
);
|
|
});
|
|
|
|
it("Should return 'NOT_VALID_ENCODING' for invalid data in _returnDataToString", async function () {
|
|
const TestTokenBridgeFactory = await ethers.getContractFactory("TestTokenBridge");
|
|
|
|
const initData = {
|
|
defaultAdmin: PLACEHOLDER_ADDRESS,
|
|
messageService: PLACEHOLDER_ADDRESS,
|
|
tokenBeacon: PLACEHOLDER_ADDRESS,
|
|
sourceChainId: SupportedChainIds.SEPOLIA,
|
|
targetChainId: SupportedChainIds.LINEA_TESTNET,
|
|
remoteSender: PLACEHOLDER_ADDRESS,
|
|
reservedTokens: [],
|
|
roleAddresses: [],
|
|
pauseTypeRoles: [],
|
|
unpauseTypeRoles: [],
|
|
};
|
|
|
|
const l1TestTokenBridge = (await upgrades.deployProxy(TestTokenBridgeFactory, [
|
|
initData,
|
|
])) as unknown as TestTokenBridge;
|
|
await l1TestTokenBridge.waitForDeployment();
|
|
|
|
// Test case 1: Data length is not 32 and less than 64
|
|
const invalidData1 = ethers.hexlify(ethers.randomBytes(33)); // 33 bytes
|
|
expect(await l1TestTokenBridge.testReturnDataToString(invalidData1)).to.equal("NOT_VALID_ENCODING");
|
|
|
|
// Test case 2: Data length is 32 but starts with a zero byte
|
|
const invalidData2 = ethers.concat([
|
|
ethers.hexlify(new Uint8Array(1)), // One zero byte
|
|
ethers.hexlify(ethers.randomBytes(31)), // 31 random bytes
|
|
]);
|
|
expect(await l1TestTokenBridge.testReturnDataToString(invalidData2)).to.equal("NOT_VALID_ENCODING");
|
|
|
|
// Test case 3: Valid data for comparison
|
|
const validString = "ValidString";
|
|
const encodedValidData = ethers.AbiCoder.defaultAbiCoder().encode(["string"], [validString]);
|
|
expect(await l1TestTokenBridge.testReturnDataToString(encodedValidData)).to.equal(validString);
|
|
});
|
|
|
|
it("Should have the correct contract version", async () => {
|
|
const { l1TokenBridge, l2TokenBridge } = await loadFixture(deployContractsFixture);
|
|
|
|
expect(await l1TokenBridge.CONTRACT_VERSION()).to.equal("1.1");
|
|
|
|
expect(await l2TokenBridge.CONTRACT_VERSION()).to.equal("1.1");
|
|
});
|
|
});
|
|
|
|
describe("Permissions", function () {
|
|
it("Should revert if completeBridging is not called by the messageService", async function () {
|
|
const {
|
|
user,
|
|
l1TokenBridge,
|
|
tokens: { L1DAI },
|
|
encodedTokenMetadata,
|
|
chainIds,
|
|
} = await loadFixture(deployContractsFixture);
|
|
|
|
await expectRevertWithCustomError(
|
|
l1TokenBridge,
|
|
l1TokenBridge
|
|
.connect(user)
|
|
.completeBridging(await L1DAI.getAddress(), 1, user.address, chainIds[1], encodedTokenMetadata),
|
|
"CallerIsNotMessageService",
|
|
);
|
|
});
|
|
|
|
it("Should revert if completeBridging message does not come from the remote Token Bridge", async function () {
|
|
const {
|
|
user,
|
|
messageService,
|
|
l1TokenBridge,
|
|
l2TokenBridge,
|
|
tokens: { L1DAI },
|
|
encodedTokenMetadata,
|
|
chainIds,
|
|
} = await loadFixture(deployContractsFixture);
|
|
|
|
const sendCalldata = messageService
|
|
.connect(user)
|
|
.sendMessage(
|
|
await l2TokenBridge.getAddress(),
|
|
0,
|
|
l1TokenBridge.interface.encodeFunctionData("completeBridging", [
|
|
await L1DAI.getAddress(),
|
|
1,
|
|
user.address,
|
|
chainIds[1],
|
|
encodedTokenMetadata,
|
|
]),
|
|
);
|
|
await expectRevertWithCustomError(l1TokenBridge, sendCalldata, "SenderNotAuthorized");
|
|
});
|
|
|
|
describe("setMessageService", function () {
|
|
it("Should revert if trying to set message service to zero address", async function () {
|
|
const { owner, l1TokenBridge } = await loadFixture(deployContractsFixture);
|
|
|
|
await expectRevertWithCustomError(
|
|
l1TokenBridge,
|
|
l1TokenBridge.connect(owner).setMessageService(ADDRESS_ZERO),
|
|
"ZeroAddressNotAllowed",
|
|
);
|
|
});
|
|
|
|
it("Should revert if called by non-owner", async function () {
|
|
const { user, l1TokenBridge } = await loadFixture(deployContractsFixture);
|
|
|
|
await expectRevertWithReason(
|
|
l1TokenBridge.connect(user).setMessageService(PLACEHOLDER_ADDRESS),
|
|
buildAccessErrorMessage(user, SET_MESSAGE_SERVICE_ROLE),
|
|
);
|
|
});
|
|
|
|
it("Should successfully set new message service address", async function () {
|
|
const { owner, l1TokenBridge } = await loadFixture(deployContractsFixture);
|
|
const newMessageServiceAddress = ethers.Wallet.createRandom().address;
|
|
|
|
await expect(l1TokenBridge.connect(owner).setMessageService(newMessageServiceAddress))
|
|
.to.emit(l1TokenBridge, "MessageServiceUpdated")
|
|
.withArgs(newMessageServiceAddress, await l1TokenBridge.messageService(), owner.address);
|
|
|
|
expect(await l1TokenBridge.messageService()).to.equal(newMessageServiceAddress);
|
|
});
|
|
});
|
|
|
|
describe("setCustomContract", function () {
|
|
it("Should bridge EIP712-compliant-token with permit", async function () {
|
|
const {
|
|
user,
|
|
l1TokenBridge,
|
|
l2TokenBridge,
|
|
tokens: { L1DAI },
|
|
chainIds,
|
|
} = await loadFixture(deployContractsFixture);
|
|
|
|
const l1Token = L1DAI;
|
|
const bridgeAmount = 70;
|
|
|
|
// Bridge token L1 to L2
|
|
await l1TokenBridge.connect(user).bridgeToken(await L1DAI.getAddress(), bridgeAmount, user.address);
|
|
const l2TokenAddress = await l2TokenBridge.nativeToBridgedToken(chainIds[0], await l1Token.getAddress());
|
|
const BridgedToken = await ethers.getContractFactory("BridgedToken");
|
|
const l2Token = BridgedToken.attach(l2TokenAddress) as BridgedToken;
|
|
|
|
// Check that no allowance exist for l2Token (User => l2TokenBridge)
|
|
expect(await l2Token.allowance(user.address, await l2TokenBridge.getAddress())).to.be.equal(0);
|
|
|
|
// Create EIP712-signature
|
|
const deadline = ethers.MaxUint256;
|
|
const { chainId } = await ethers.provider.getNetwork();
|
|
const nonce = await l2Token.nonces(user.address);
|
|
expect(nonce).to.be.equal(0);
|
|
|
|
// Try to bridge back without permit data
|
|
await expectRevertWithReason(
|
|
l2TokenBridge.connect(user).bridgeToken(await l2Token.getAddress(), bridgeAmount, user.address),
|
|
"ERC20: insufficient allowance",
|
|
);
|
|
|
|
// Capture balances before bridging back
|
|
const l1TokenUserBalanceBefore = await l1Token.balanceOf(user.address);
|
|
const l2TokenUserBalanceBefore = await l2Token.balanceOf(user.address);
|
|
|
|
// Prepare data for permit calldata
|
|
const permitData = await getPermitData(
|
|
user,
|
|
l2Token,
|
|
nonce,
|
|
parseInt(chainId.toString()),
|
|
await l2TokenBridge.getAddress(),
|
|
bridgeAmount,
|
|
deadline,
|
|
);
|
|
|
|
// Bridge back
|
|
await l2TokenBridge
|
|
.connect(user)
|
|
.bridgeTokenWithPermit(await l2Token.getAddress(), bridgeAmount, user.address, permitData);
|
|
|
|
// Capture balances after bridging back
|
|
const l1TokenUserBalanceAfter = await l1Token.balanceOf(user.address);
|
|
const l2TokenUserBalanceAfter = await l2Token.balanceOf(user.address);
|
|
|
|
const diffL1UserBalance = l1TokenUserBalanceAfter - l1TokenUserBalanceBefore;
|
|
const diffL2UserBalance = l2TokenUserBalanceBefore - l2TokenUserBalanceAfter;
|
|
|
|
expect(diffL1UserBalance).to.be.equal(bridgeAmount);
|
|
expect(diffL2UserBalance).to.be.equal(bridgeAmount);
|
|
});
|
|
|
|
it("Should revert if setCustomContract is not called by the owner", async function () {
|
|
const { user, l1TokenBridge } = await loadFixture(deployContractsFixture);
|
|
await expectRevertWithReason(
|
|
l1TokenBridge.connect(user).setCustomContract(CUSTOM_ADDRESS, CUSTOM_ADDRESS),
|
|
buildAccessErrorMessage(user, SET_CUSTOM_CONTRACT_ROLE),
|
|
);
|
|
});
|
|
|
|
it("Should revert if a native token has already been bridged", async function () {
|
|
const {
|
|
user,
|
|
owner,
|
|
l1TokenBridge,
|
|
l2TokenBridge,
|
|
tokens: { L1DAI },
|
|
chainIds,
|
|
} = await loadFixture(deployContractsFixture);
|
|
const L1DAIAddress = await L1DAI.getAddress();
|
|
// First bridge token (user has L1DAI balance set in the fixture)
|
|
await l1TokenBridge.connect(user).bridgeToken(L1DAIAddress, 1, user.address);
|
|
const l2TokenAddress = await l2TokenBridge.nativeToBridgedToken(chainIds[0], L1DAIAddress);
|
|
|
|
await expectRevertWithCustomError(
|
|
l1TokenBridge,
|
|
l1TokenBridge.connect(owner).setCustomContract(L1DAIAddress, CUSTOM_ADDRESS),
|
|
"AlreadyBridgedToken",
|
|
[L1DAIAddress],
|
|
);
|
|
|
|
await expectRevertWithCustomError(
|
|
l2TokenBridge,
|
|
l2TokenBridge.connect(owner).setCustomContract(l2TokenAddress, CUSTOM_ADDRESS),
|
|
"AlreadyBridgedToken",
|
|
[l2TokenAddress],
|
|
);
|
|
});
|
|
|
|
it("Should revert if _nativeToken is zero address", async function () {
|
|
const { owner, l1TokenBridge } = await loadFixture(deployContractsFixture);
|
|
|
|
await expectRevertWithCustomError(
|
|
l1TokenBridge,
|
|
l1TokenBridge.connect(owner).setCustomContract(ADDRESS_ZERO, CUSTOM_ADDRESS),
|
|
"ZeroAddressNotAllowed",
|
|
);
|
|
});
|
|
|
|
it("Should revert if _targetContract is zero address", async function () {
|
|
const { owner, l1TokenBridge } = await loadFixture(deployContractsFixture);
|
|
const validNativeToken = ethers.Wallet.createRandom().address;
|
|
|
|
await expectRevertWithCustomError(
|
|
l1TokenBridge,
|
|
l1TokenBridge.connect(owner).setCustomContract(validNativeToken, ADDRESS_ZERO),
|
|
"ZeroAddressNotAllowed",
|
|
);
|
|
});
|
|
|
|
it("Should successfully set custom contract for valid addresses", async function () {
|
|
const { owner, l1TokenBridge } = await loadFixture(deployContractsFixture);
|
|
const validNativeToken = ethers.Wallet.createRandom().address;
|
|
const validTargetContract = ethers.Wallet.createRandom().address;
|
|
|
|
await expect(l1TokenBridge.connect(owner).setCustomContract(validNativeToken, validTargetContract))
|
|
.to.emit(l1TokenBridge, "CustomContractSet")
|
|
.withArgs(validNativeToken, validTargetContract, owner.address);
|
|
});
|
|
});
|
|
|
|
describe("Pause / unpause", function () {
|
|
it("Should pause the contract when INITIATE_TOKEN_BRIDGING_PAUSE_TYPE() is called", async function () {
|
|
const { owner, l1TokenBridge } = await loadFixture(deployContractsFixture);
|
|
|
|
await l1TokenBridge.connect(owner).pauseByType(INITIATE_TOKEN_BRIDGING_PAUSE_TYPE);
|
|
expect(await l1TokenBridge.isPaused(INITIATE_TOKEN_BRIDGING_PAUSE_TYPE)).to.equal(true);
|
|
});
|
|
|
|
it("Should unpause the contract when unpause() is called", async function () {
|
|
const { owner, l1TokenBridge } = await loadFixture(deployContractsFixture);
|
|
|
|
await l1TokenBridge.connect(owner).pauseByType(INITIATE_TOKEN_BRIDGING_PAUSE_TYPE);
|
|
|
|
await l1TokenBridge.connect(owner).unPauseByType(INITIATE_TOKEN_BRIDGING_PAUSE_TYPE);
|
|
|
|
expect(await l1TokenBridge.isPaused(INITIATE_TOKEN_BRIDGING_PAUSE_TYPE)).to.equal(false);
|
|
});
|
|
it("Should revert bridgeToken if paused", async function () {
|
|
const {
|
|
owner,
|
|
l1TokenBridge,
|
|
tokens: { L1DAI },
|
|
} = await loadFixture(deployContractsFixture);
|
|
|
|
await l1TokenBridge.connect(owner).pauseByType(INITIATE_TOKEN_BRIDGING_PAUSE_TYPE);
|
|
|
|
await expectRevertWithCustomError(
|
|
l1TokenBridge,
|
|
l1TokenBridge.bridgeToken(await L1DAI.getAddress(), 10, owner.address),
|
|
"IsPaused",
|
|
[INITIATE_TOKEN_BRIDGING_PAUSE_TYPE],
|
|
);
|
|
});
|
|
it("Should allow bridgeToken if unpaused", async function () {
|
|
const {
|
|
owner,
|
|
user,
|
|
l1TokenBridge,
|
|
tokens: { L1DAI },
|
|
} = await loadFixture(deployContractsFixture);
|
|
|
|
await l1TokenBridge.connect(owner).pauseByType(INITIATE_TOKEN_BRIDGING_PAUSE_TYPE);
|
|
await l1TokenBridge.connect(owner).unPauseByType(INITIATE_TOKEN_BRIDGING_PAUSE_TYPE);
|
|
await l1TokenBridge.connect(user).bridgeToken(await L1DAI.getAddress(), 10, user.address);
|
|
});
|
|
// TODO: COMPLETE_TOKEN_BRIDGING_PAUSE_TYPE tests
|
|
it("Should pause the contract when pause() is called", async function () {
|
|
const { owner, l1TokenBridge } = await loadFixture(deployContractsFixture);
|
|
|
|
await l1TokenBridge.connect(owner).pauseByType(COMPLETE_TOKEN_BRIDGING_PAUSE_TYPE);
|
|
expect(await l1TokenBridge.isPaused(COMPLETE_TOKEN_BRIDGING_PAUSE_TYPE)).to.equal(true);
|
|
});
|
|
|
|
it("Should unpause the contract when unpause() is called", async function () {
|
|
const { owner, l1TokenBridge } = await loadFixture(deployContractsFixture);
|
|
|
|
await l1TokenBridge.connect(owner).pauseByType(COMPLETE_TOKEN_BRIDGING_PAUSE_TYPE);
|
|
|
|
await l1TokenBridge.connect(owner).unPauseByType(COMPLETE_TOKEN_BRIDGING_PAUSE_TYPE);
|
|
|
|
expect(await l1TokenBridge.isPaused(COMPLETE_TOKEN_BRIDGING_PAUSE_TYPE)).to.equal(false);
|
|
});
|
|
|
|
it("Should emit BridgingInitiatedV2 when bridging", async function () {
|
|
const {
|
|
user,
|
|
l1TokenBridge,
|
|
tokens: { L1DAI },
|
|
} = await loadFixture(deployContractsFixture);
|
|
|
|
const L1DAIAddress = L1DAI.getAddress();
|
|
|
|
await expectEvent(
|
|
l1TokenBridge,
|
|
l1TokenBridge.connect(user).bridgeToken(L1DAIAddress, BigInt(10), user.address),
|
|
"BridgingInitiatedV2",
|
|
[user.address, user.address, L1DAIAddress, 10],
|
|
);
|
|
});
|
|
|
|
it("Should emit BridgingFinalizedV2 event when bridging is complete", async function () {
|
|
const {
|
|
user,
|
|
l1TokenBridge,
|
|
l2TokenBridge,
|
|
tokens: { L1DAI },
|
|
chainIds,
|
|
} = await loadFixture(deployContractsFixture);
|
|
const bridgeAmount = 10;
|
|
|
|
const L1DAIAddress = L1DAI.getAddress();
|
|
|
|
// const initialAmount = await L1DAI.balanceOf(user.address);
|
|
await l1TokenBridge.connect(user).bridgeToken(L1DAIAddress, bridgeAmount, user.address);
|
|
const L2DAIBridgedAddress = await l2TokenBridge.nativeToBridgedToken(chainIds[0], L1DAIAddress);
|
|
await l2TokenBridge.confirmDeployment([L2DAIBridgedAddress]);
|
|
|
|
const bridgedToken = await l2TokenBridge.nativeToBridgedToken(chainIds[0], L1DAIAddress);
|
|
|
|
const abi = [
|
|
"event BridgingFinalizedV2(address indexed nativeToken,address indexed bridgedToken,uint256 amount,address indexed recipient)",
|
|
];
|
|
|
|
const contract = new Contract(await l2TokenBridge.getAddress(), abi, ethers.provider);
|
|
// Filtering for indexed fields by default validates they are correct when events are not null
|
|
const filteredEvents = contract.filters.BridgingFinalizedV2(L1DAIAddress, bridgedToken, null, user.address);
|
|
|
|
const events = await l2TokenBridge.queryFilter(filteredEvents);
|
|
expect(events).to.not.be.null;
|
|
expect(events).to.not.be.empty;
|
|
expect(events[0].args?.[2]).to.equal(10);
|
|
});
|
|
});
|
|
|
|
describe("Owner", function () {
|
|
it("Should revert if setReservedToken is called by a non-owner", async function () {
|
|
const { user, l1TokenBridge } = await loadFixture(deployContractsFixture);
|
|
await expectRevertWithReason(
|
|
l1TokenBridge.connect(user).setReserved(user.address),
|
|
buildAccessErrorMessage(user, SET_RESERVED_TOKEN_ROLE),
|
|
);
|
|
});
|
|
it("Should revert if pause() is called by a non-owner", async function () {
|
|
const { user, l1TokenBridge } = await loadFixture(deployContractsFixture);
|
|
await expectRevertWithReason(
|
|
l1TokenBridge.connect(user).pauseByType(INITIATE_TOKEN_BRIDGING_PAUSE_TYPE),
|
|
buildAccessErrorMessage(user, PAUSE_INITIATE_TOKEN_BRIDGING_ROLE),
|
|
);
|
|
});
|
|
it("Should revert if unpause() is called by a non-owner", async function () {
|
|
const { user, l1TokenBridge } = await loadFixture(deployContractsFixture);
|
|
await expectRevertWithReason(
|
|
l1TokenBridge.connect(user).unPauseByType(INITIATE_TOKEN_BRIDGING_PAUSE_TYPE),
|
|
buildAccessErrorMessage(user, UNPAUSE_INITIATE_TOKEN_BRIDGING_ROLE),
|
|
);
|
|
});
|
|
it("Should revert if removeReserved is called by a non-owner", async function () {
|
|
const {
|
|
user,
|
|
l1TokenBridge,
|
|
tokens: { L1DAI },
|
|
} = await loadFixture(deployContractsFixture);
|
|
await expectRevertWithReason(
|
|
l1TokenBridge.connect(user).removeReserved(await L1DAI.getAddress()),
|
|
buildAccessErrorMessage(user, REMOVE_RESERVED_TOKEN_ROLE),
|
|
);
|
|
});
|
|
});
|
|
});
|
|
|
|
describe("Reserved tokens", function () {
|
|
it("Should be possible for the admin to reserve a token", async function () {
|
|
const {
|
|
owner,
|
|
l1TokenBridge,
|
|
tokens: { L1DAI },
|
|
chainIds,
|
|
} = await loadFixture(deployContractsFixture);
|
|
await expect(l1TokenBridge.connect(owner).setReserved(await L1DAI.getAddress())).not.to.be.revertedWith(
|
|
"TokenBridge: token already bridged",
|
|
);
|
|
expect(await l1TokenBridge.nativeToBridgedToken(chainIds[0], await L1DAI.getAddress())).to.be.equal(
|
|
RESERVED_STATUS,
|
|
);
|
|
});
|
|
|
|
it("Should be possible for the admin to remove token from the reserved list", async function () {
|
|
// @TODO this test can probably be rewritten, avoiding to set the token as reserved in the first place
|
|
|
|
const {
|
|
owner,
|
|
l1TokenBridge,
|
|
tokens: { L1DAI },
|
|
chainIds,
|
|
} = await loadFixture(deployContractsFixture);
|
|
|
|
const L1DAIAddress = await L1DAI.getAddress();
|
|
|
|
await l1TokenBridge.connect(owner).setReserved(L1DAIAddress);
|
|
expect(await l1TokenBridge.nativeToBridgedToken(chainIds[0], L1DAIAddress)).to.be.equal(RESERVED_STATUS);
|
|
await expect(l1TokenBridge.connect(owner).removeReserved(L1DAIAddress))
|
|
.to.emit(l1TokenBridge, "ReservationRemoved")
|
|
.withArgs(L1DAIAddress);
|
|
expect(await l1TokenBridge.nativeToBridgedToken(chainIds[0], L1DAIAddress)).to.be.equal(ADDRESS_ZERO);
|
|
});
|
|
|
|
it("Should not be possible to bridge reserved tokens", async function () {
|
|
const {
|
|
owner,
|
|
user,
|
|
l1TokenBridge,
|
|
tokens: { L1DAI },
|
|
} = await loadFixture(deployContractsFixture);
|
|
await l1TokenBridge.connect(owner).setReserved(await L1DAI.getAddress());
|
|
|
|
await expectRevertWithCustomError(
|
|
l1TokenBridge,
|
|
l1TokenBridge.connect(user).bridgeToken(await L1DAI.getAddress(), 1, user.address),
|
|
"ReservedToken",
|
|
[L1DAI.getAddress()],
|
|
);
|
|
});
|
|
|
|
it("Should only be possible to reserve a token if it has not been bridged before", async function () {
|
|
const {
|
|
owner,
|
|
user,
|
|
l1TokenBridge,
|
|
tokens: { L1DAI },
|
|
} = await loadFixture(deployContractsFixture);
|
|
await l1TokenBridge.connect(user).bridgeToken(await L1DAI.getAddress(), 1, user.address);
|
|
|
|
await expectRevertWithCustomError(
|
|
l1TokenBridge,
|
|
l1TokenBridge.connect(owner).setReserved(await L1DAI.getAddress()),
|
|
"AlreadyBridgedToken",
|
|
[L1DAI.getAddress()],
|
|
);
|
|
});
|
|
|
|
it("Should set reserved tokens in the initializer", async function () {
|
|
const { chainIds } = await loadFixture(deployContractsFixture);
|
|
const TokenBridgeFactory = await ethers.getContractFactory("TokenBridge");
|
|
const l1TokenBridge = await upgrades.deployProxy(TokenBridgeFactory, [
|
|
{
|
|
defaultAdmin: PLACEHOLDER_ADDRESS,
|
|
messageService: PLACEHOLDER_ADDRESS,
|
|
tokenBeacon: PLACEHOLDER_ADDRESS,
|
|
sourceChainId: chainIds[0],
|
|
targetChainId: chainIds[1],
|
|
reservedTokens: [CUSTOM_ADDRESS],
|
|
remoteSender: PLACEHOLDER_ADDRESS,
|
|
roleAddresses: [],
|
|
pauseTypeRoles: pauseTypeRoles,
|
|
unpauseTypeRoles: unpauseTypeRoles,
|
|
},
|
|
]);
|
|
await l1TokenBridge.waitForDeployment();
|
|
expect(await l1TokenBridge.nativeToBridgedToken(chainIds[0], CUSTOM_ADDRESS)).to.be.equal(RESERVED_STATUS);
|
|
});
|
|
|
|
it("Should only be possible to call removeReserved if the token is in the reserved list", async function () {
|
|
const {
|
|
owner,
|
|
l1TokenBridge,
|
|
tokens: { L1DAI },
|
|
} = await loadFixture(deployContractsFixture);
|
|
|
|
await expectRevertWithCustomError(
|
|
l1TokenBridge,
|
|
l1TokenBridge.connect(owner).removeReserved(await L1DAI.getAddress()),
|
|
"NotReserved",
|
|
[L1DAI.getAddress()],
|
|
);
|
|
});
|
|
|
|
it("Should revert if token is the 0 address", async function () {
|
|
const { owner, l1TokenBridge } = await loadFixture(deployContractsFixture);
|
|
await expectRevertWithCustomError(
|
|
l1TokenBridge,
|
|
l1TokenBridge.connect(owner).setReserved(ADDRESS_ZERO),
|
|
"ZeroAddressNotAllowed",
|
|
);
|
|
});
|
|
it("Should revert if trying to remove reservation for zero address", async function () {
|
|
const { owner, l1TokenBridge } = await loadFixture(deployContractsFixture);
|
|
|
|
await expectRevertWithCustomError(
|
|
l1TokenBridge,
|
|
l1TokenBridge.connect(owner).removeReserved(ADDRESS_ZERO),
|
|
"ZeroAddressNotAllowed",
|
|
);
|
|
});
|
|
});
|
|
|
|
describe("bridgeTokenWithPermit", function () {
|
|
it("Should revert if contract is paused", async function () {
|
|
const {
|
|
user,
|
|
tokens: { L1DAI },
|
|
l1TokenBridge,
|
|
} = await loadFixture(deployContractsFixture);
|
|
await l1TokenBridge.pauseByType(INITIATE_TOKEN_BRIDGING_PAUSE_TYPE);
|
|
await expectRevertWithCustomError(
|
|
l1TokenBridge,
|
|
l1TokenBridge.connect(user).bridgeTokenWithPermit(await L1DAI.getAddress(), 1, user.address, EMPTY_PERMIT_DATA),
|
|
"IsPaused",
|
|
[INITIATE_TOKEN_BRIDGING_PAUSE_TYPE],
|
|
);
|
|
});
|
|
|
|
it("Should revert if token is the 0 address", async function () {
|
|
const { user, l1TokenBridge } = await loadFixture(deployContractsFixture);
|
|
await expectRevertWithCustomError(
|
|
l1TokenBridge,
|
|
l1TokenBridge.connect(user).bridgeTokenWithPermit(ADDRESS_ZERO, 1, user.address, EMPTY_PERMIT_DATA),
|
|
"ZeroAddressNotAllowed",
|
|
);
|
|
});
|
|
|
|
it("Should revert if token is the amount is 0", async function () {
|
|
const {
|
|
user,
|
|
l1TokenBridge,
|
|
tokens: { L1DAI },
|
|
} = await loadFixture(deployContractsFixture);
|
|
await expectRevertWithCustomError(
|
|
l1TokenBridge,
|
|
l1TokenBridge.connect(user).bridgeTokenWithPermit(await L1DAI.getAddress(), 0, user.address, EMPTY_PERMIT_DATA),
|
|
"ZeroAmountNotAllowed",
|
|
[0],
|
|
);
|
|
});
|
|
|
|
it("Should not revert if permitData is empty", async function () {
|
|
const {
|
|
user,
|
|
l1TokenBridge,
|
|
tokens: { L1DAI },
|
|
} = await loadFixture(deployContractsFixture);
|
|
await expect(
|
|
l1TokenBridge
|
|
.connect(user)
|
|
.bridgeTokenWithPermit(await L1DAI.getAddress(), 10, user.address, EMPTY_PERMIT_DATA),
|
|
).to.be.not.reverted;
|
|
});
|
|
|
|
it("Should revert if permitData is invalid", async function () {
|
|
const {
|
|
owner,
|
|
user,
|
|
l1TokenBridge,
|
|
l2TokenBridge,
|
|
tokens: { L1DAI },
|
|
chainIds,
|
|
} = await loadFixture(deployContractsFixture);
|
|
// Test when the permitData has an invalid format
|
|
await expect(
|
|
l1TokenBridge.connect(user).bridgeTokenWithPermit(await L1DAI.getAddress(), 10, user.address, "0x111111111111"),
|
|
).to.be.revertedWithCustomError(l1TokenBridge, "InvalidPermitData");
|
|
|
|
// Test when the spender passed is invalid
|
|
// Prepare data for permit calldata
|
|
const bridgeAmount = 70;
|
|
|
|
// Bridge token L1 to L2
|
|
await l1TokenBridge.connect(user).bridgeToken(await L1DAI.getAddress(), bridgeAmount, user.address);
|
|
const l2TokenAddress = await l2TokenBridge.nativeToBridgedToken(chainIds[0], await L1DAI.getAddress());
|
|
const BridgedToken = await ethers.getContractFactory("BridgedToken");
|
|
const l2Token = BridgedToken.attach(l2TokenAddress) as BridgedToken;
|
|
|
|
// Create EIP712-signature
|
|
const deadline = ethers.MaxUint256;
|
|
const { chainId } = await ethers.provider.getNetwork();
|
|
const nonce = await l2Token.nonces(user.address);
|
|
|
|
let permitData = await getPermitData(
|
|
user,
|
|
l2Token,
|
|
nonce,
|
|
parseInt(chainId.toString()),
|
|
user.address,
|
|
bridgeAmount,
|
|
deadline,
|
|
);
|
|
await expect(
|
|
l2TokenBridge
|
|
.connect(user)
|
|
.bridgeTokenWithPermit(await L1DAI.getAddress(), bridgeAmount, user.address, permitData),
|
|
).to.be.revertedWithCustomError(l1TokenBridge, "PermitNotAllowingBridge");
|
|
|
|
// Test when the sender is not the owner of the tokens
|
|
permitData = await getPermitData(
|
|
user,
|
|
l2Token,
|
|
nonce,
|
|
parseInt(chainId.toString()),
|
|
await l2TokenBridge.getAddress(),
|
|
bridgeAmount,
|
|
deadline,
|
|
);
|
|
await expect(
|
|
l1TokenBridge
|
|
.connect(owner)
|
|
.bridgeTokenWithPermit(await L1DAI.getAddress(), bridgeAmount, user.address, permitData),
|
|
).to.be.revertedWithCustomError(l1TokenBridge, "PermitNotFromSender");
|
|
});
|
|
});
|
|
|
|
describe("bridgeToken", function () {
|
|
it("Should not emit event NewToken if the token has already been bridged once", async function () {
|
|
const {
|
|
user,
|
|
tokens: { L1DAI },
|
|
l1TokenBridge,
|
|
} = await loadFixture(deployContractsFixture);
|
|
await expect(l1TokenBridge.connect(user).bridgeToken(await L1DAI.getAddress(), 1, user.address)).to.emit(
|
|
l1TokenBridge,
|
|
"NewToken",
|
|
);
|
|
await expect(l1TokenBridge.connect(user).bridgeToken(await L1DAI.getAddress(), 1, user.address)).to.not.emit(
|
|
l1TokenBridge,
|
|
"NewToken",
|
|
);
|
|
});
|
|
|
|
it("Should revert if recipient is set at 0 address", async function () {
|
|
const {
|
|
user,
|
|
tokens: { L1DAI },
|
|
l1TokenBridge,
|
|
} = await loadFixture(deployContractsFixture);
|
|
await expect(
|
|
l1TokenBridge.connect(user).bridgeToken(await L1DAI.getAddress(), 1, ADDRESS_ZERO),
|
|
).to.revertedWithCustomError(l1TokenBridge, "ZeroAddressNotAllowed");
|
|
});
|
|
|
|
it("Should not be able to call bridgeToken by reentrancy", async function () {
|
|
const { owner, l1TokenBridge } = await loadFixture(deployContractsFixture);
|
|
|
|
const ReentrancyContract = await ethers.getContractFactory("ReentrancyContract");
|
|
const reentrancyContract = await ReentrancyContract.deploy(await l1TokenBridge.getAddress());
|
|
|
|
const MaliciousERC777 = await ethers.getContractFactory("MaliciousERC777");
|
|
const maliciousERC777 = await MaliciousERC777.deploy(await reentrancyContract.getAddress());
|
|
await maliciousERC777.mint(await reentrancyContract.getAddress(), 100);
|
|
await maliciousERC777.mint(owner.address, 100);
|
|
|
|
await reentrancyContract.setToken(maliciousERC777.getAddress());
|
|
|
|
await expectRevertWithReason(
|
|
l1TokenBridge.bridgeToken(await maliciousERC777.getAddress(), 1, owner.address),
|
|
"ReentrancyGuard: reentrant call",
|
|
);
|
|
});
|
|
});
|
|
|
|
describe("setDeployed", function () {
|
|
it("Should revert if not called by the messageService", async function () {
|
|
const { user, l1TokenBridge } = await loadFixture(deployContractsFixture);
|
|
await expectRevertWithCustomError(
|
|
l1TokenBridge,
|
|
l1TokenBridge.connect(user).setDeployed([]),
|
|
"CallerIsNotMessageService",
|
|
);
|
|
});
|
|
|
|
it("Should revert if message does not come from the remote Token Bridge", async function () {
|
|
const { user, messageService, l1TokenBridge, l2TokenBridge } = await loadFixture(deployContractsFixture);
|
|
await expectRevertWithCustomError(
|
|
l2TokenBridge,
|
|
messageService
|
|
.connect(user)
|
|
.sendMessage(
|
|
await l2TokenBridge.getAddress(),
|
|
0,
|
|
l1TokenBridge.interface.encodeFunctionData("setDeployed", [[]]),
|
|
),
|
|
"SenderNotAuthorized",
|
|
);
|
|
});
|
|
});
|
|
|
|
describe("TokenBridge Upgradeable Tests", function () {
|
|
it.skip("Should deploy and manually upgrade the TokenBridge contract", async function () {
|
|
// Deploy V1 from artifact
|
|
// Deploy V-next when we have it
|
|
});
|
|
});
|
|
});
|