Skip to content

Commit

Permalink
Full testing coverage for sovereign contracts
Browse files Browse the repository at this point in the history
  • Loading branch information
ignasirv committed Sep 19, 2024
1 parent fda25e4 commit f6ee3ba
Show file tree
Hide file tree
Showing 4 changed files with 164 additions and 7 deletions.
5 changes: 5 additions & 0 deletions contracts/interfaces/IBasePolygonZkEVMGlobalExitRoot.sol
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,11 @@ interface IBasePolygonZkEVMGlobalExitRoot {
*/
error OnlyCoinbase();

/**
* @dev Thrown when trying to insert a global exit root that is already set
*/
error GlobalExitRootAlreadySet();

function updateExitRoot(bytes32 newRollupExitRoot) external;

function globalExitRootMap(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,8 @@ contract GlobalExitRootManagerL2SovereignChain is PolygonZkEVMGlobalExitRootL2 {
if (globalExitRootMap[_newRoot] == 0) {
globalExitRootMap[_newRoot] = block.timestamp;
emit InsertGlobalExitRoot(_newRoot);
} else {
revert GlobalExitRootAlreadySet();
}
}
}
140 changes: 138 additions & 2 deletions test/contractsv2/BridgeL2GasTokensSovereignChains.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -117,14 +117,151 @@ describe("SovereignChainBridge Gas tokens tests", () => {
const hashInitCode = ethers.solidityPackedKeccak256(["bytes", "bytes"], [minimalBytecodeProxy, metadataWETH]);
const precalculatedWeth = await ethers.getCreate2Address(
sovereignChainBridgeContract.target as string,
ethers.ZeroHash, // zero only for weth
ethers.ZeroHash, // salt is zero
hashInitCode
);
WETHToken = tokenWrappedFactory.attach(precalculatedWeth) as TokenWrapped;

expect(await sovereignChainBridgeContract.WETHToken()).to.be.equal(WETHToken.target);
});

it("should claim message from not mintable remapped gas (WETH) token", async () => {
// Add a claim leaf to rollup exit tree
const originNetwork = networkIDRollup;
const tokenAddress = polTokenContract.target;
const amount = ethers.parseEther("10");
const destinationNetwork = networkIDRollup2;
const destinationAddress = deployer.address;

const metadata = "0x176923791298713271763697869132"; // since is ether does not have metadata
const metadataHash = ethers.solidityPackedKeccak256(["bytes"], [metadata]);

// deploy sovereign
const maticTokenFactory = await ethers.getContractFactory("ERC20PermitMock");
const sovereignToken = await maticTokenFactory.deploy(
tokenName,
tokenSymbol,
deployer.address,
tokenInitialBalance
);

const mainnetExitRoot = ethers.ZeroHash;

// compute root merkle tree in Js
const height = 32;
const merkleTree = new MerkleTreeBridge(height);
const leafValue = getLeafValue(
LEAF_TYPE_MESSAGE,
originNetwork,
sovereignToken.target,
destinationNetwork,
destinationAddress,
amount,
metadataHash
);
merkleTree.add(leafValue);

// check merkle root with SC
const rootJSRollup = merkleTree.getRoot();
const merkleTreeRollup = new MerkleTreeBridge(height);
merkleTreeRollup.add(rootJSRollup);
const rollupRoot = merkleTreeRollup.getRoot();

// add rollup Merkle root
await ethers.provider.send("hardhat_impersonateAccount", [sovereignChainBridgeContract.target]);
const bridgeMock = await ethers.getSigner(sovereignChainBridgeContract.target as any);
await sovereignChainGlobalExitRoot.connect(bridgeMock).updateExitRoot(rollupRoot, {gasPrice: 0});

// check roots
const rollupExitRootSC = await sovereignChainGlobalExitRoot.lastRollupExitRoot();
expect(rollupExitRootSC).to.be.equal(rollupRoot);

const computedGlobalExitRoot = calculateGlobalExitRoot(mainnetExitRoot, rollupExitRootSC);
// Insert global exit root
expect(await sovereignChainGlobalExitRoot.insertGlobalExitRoot(computedGlobalExitRoot))
.to.emit(sovereignChainGlobalExitRoot, "InsertGlobalExitRoot")
.withArgs(computedGlobalExitRoot);

// Check GER has value in mapping
expect(await sovereignChainGlobalExitRoot.globalExitRootMap(computedGlobalExitRoot)).to.not.be.eq(0);
// check merkle proof
const index = 0;
const proofLocal = merkleTree.getProofTreeByIndex(0);
const proofRollup = merkleTreeRollup.getProofTreeByIndex(0);
const globalIndex = computeGlobalIndex(index, index, false);

// verify merkle proof
expect(verifyMerkleProof(leafValue, proofLocal, index, rootJSRollup)).to.be.equal(true);
expect(
await sovereignChainBridgeContract.verifyMerkleProof(leafValue, proofLocal, index, rootJSRollup)
).to.be.equal(true);
// Remap weth token
await expect(sovereignChainBridgeContract.connect(bridgeManager).setSovereignWETHAddress(sovereignToken.target, true))
.to.emit(sovereignChainBridgeContract, "SetSovereignWETHAddress")
.withArgs(sovereignToken.target, true);
// try claim without balance to transfer (from bridge)
await expect(
sovereignChainBridgeContract.claimMessage(
proofLocal,
proofRollup,
globalIndex,
mainnetExitRoot,
rollupExitRootSC,
originNetwork,
sovereignToken.target,
destinationNetwork,
destinationAddress,
amount,
metadata
)
)
.to.revertedWith("ERC20: transfer amount exceeds balance");
// Transfer tokens to bridge
await sovereignToken.transfer(sovereignChainBridgeContract.target, amount);
const balanceBridge = await sovereignToken.balanceOf(sovereignChainBridgeContract.target);

// Check balances before claim
expect(balanceBridge).to.be.equal(amount);
// Claim message
await expect(
sovereignChainBridgeContract.claimMessage(
proofLocal,
proofRollup,
globalIndex,
mainnetExitRoot,
rollupExitRootSC,
originNetwork,
sovereignToken.target,
destinationNetwork,
destinationAddress,
amount,
metadata
)
)
.to.emit(sovereignChainBridgeContract, "ClaimEvent")
.withArgs(index, originNetwork, sovereignToken.target, destinationAddress, amount);

// Check balances after claim
expect(await sovereignToken.balanceOf(sovereignChainBridgeContract.target)).to.be.equal(ethers.parseEther("0"));

// Can't claim because nullifier
await expect(
sovereignChainBridgeContract.claimMessage(
proofLocal,
proofRollup,
globalIndex,
mainnetExitRoot,
rollupExitRootSC,
originNetwork,
sovereignToken.target,
destinationNetwork,
destinationAddress,
amount,
metadata
)
).to.be.revertedWithCustomError(sovereignChainBridgeContract, "AlreadyClaimed");
});

it("should check the constructor parameters", async () => {
expect(await sovereignChainBridgeContract.globalExitRootManager()).to.be.equal(
sovereignChainGlobalExitRoot.target
Expand Down Expand Up @@ -272,7 +409,6 @@ describe("SovereignChainBridge Gas tokens tests", () => {
expect(
await sovereignChainBridgeContract.verifyMerkleProof(leafValue, proof, index, rootSCMainnet)
).to.be.equal(true);

});

it("should PolygonZkEVMBridge message and verify merkle proof", async () => {
Expand Down
24 changes: 19 additions & 5 deletions test/contractsv2/BridgeL2SovereignChain.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -201,6 +201,10 @@ describe("BridgeL2SovereignChain Contract", () => {
// Check GER has value in mapping
expect(await sovereignChainGlobalExitRoot.globalExitRootMap(computedGlobalExitRoot)).to.not.be.eq(0);

// Remove unmapped sovereign token address, should revert
await expect(
sovereignChainBridgeContract.connect(bridgeManager).removeSovereignTokenAddress(tokenAddress)
).to.be.revertedWithCustomError(sovereignChainBridgeContract, "TokenNotMapped");
// Remove sovereign token address
await expect(
sovereignChainBridgeContract.connect(bridgeManager).removeSovereignTokenAddress(sovereignToken.target)
Expand Down Expand Up @@ -246,21 +250,26 @@ describe("BridgeL2SovereignChain Contract", () => {
.connect(rollupManager)
.setSovereignTokenAddress(networkIDMainnet, tokenAddress, sovereignToken.target, false)
).to.be.revertedWithCustomError(sovereignChainBridgeContract, "OnlyBridgeManager");
// Set rollupManager as bridge manager
await expect(sovereignChainBridgeContract.connect(bridgeManager).setBridgeManager(rollupManager.address))
.to.emit(sovereignChainBridgeContract, "BridgeManagerUpdated")
.withArgs(rollupManager.address);

// invalid token address
await expect(
sovereignChainBridgeContract
.connect(bridgeManager)
.connect(rollupManager)
.setSovereignTokenAddress(networkIDMainnet, ethers.ZeroAddress, sovereignToken.target, false)
).to.be.revertedWithCustomError(sovereignChainBridgeContract, "InvalidZeroAddress");
// Invalid origin network
await expect(
sovereignChainBridgeContract
.connect(bridgeManager)
.connect(rollupManager)
.setSovereignTokenAddress(networkIDRollup2, tokenAddress, sovereignToken.target, false)
).to.be.revertedWithCustomError(sovereignChainBridgeContract, "OriginNetworkInvalid");
await expect(
sovereignChainBridgeContract
.connect(bridgeManager)
.connect(rollupManager)
.setSovereignTokenAddress(networkIDRollup, tokenAddress, sovereignToken.target, false)
)
.to.emit(sovereignChainBridgeContract, "SetSovereignTokenAddress")
Expand Down Expand Up @@ -1095,12 +1104,17 @@ describe("BridgeL2SovereignChain Contract", () => {

const computedGlobalExitRoot2 = calculateGlobalExitRoot(rootJSMainnet, rollupExitRoot);
// Insert global exit root
expect(await sovereignChainGlobalExitRoot.insertGlobalExitRoot(computedGlobalExitRoot))
expect(await sovereignChainGlobalExitRoot.insertGlobalExitRoot(computedGlobalExitRoot2))
.to.emit(sovereignChainGlobalExitRoot, "InsertGlobalExitRoot")
.withArgs(computedGlobalExitRoot);

// Check GER has value in mapping
expect(await sovereignChainGlobalExitRoot.globalExitRootMap(computedGlobalExitRoot)).to.not.be.eq(0);
expect(await sovereignChainGlobalExitRoot.globalExitRootMap(computedGlobalExitRoot2)).to.not.be.eq(0);

// Insert an already inserted GER
await expect(
sovereignChainGlobalExitRoot.insertGlobalExitRoot(computedGlobalExitRoot2)
).to.revertedWithCustomError(sovereignChainGlobalExitRoot, "GlobalExitRootAlreadySet");
});
it("should claim tokens from Rollup to Mainnet, failing deploy wrapped", async () => {
const originNetwork = networkIDRollup;
Expand Down

0 comments on commit f6ee3ba

Please sign in to comment.