Skip to content

Commit 6576663

Browse files
mrice32nicholaspai
authored andcommitted
fix[L04] Enforce chainId requirements in PolygonTokenBridger (#115)
1 parent 980defa commit 6576663

File tree

5 files changed

+108
-12
lines changed

5 files changed

+108
-12
lines changed

contracts/PolygonTokenBridger.sol

Lines changed: 37 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -41,14 +41,36 @@ contract PolygonTokenBridger is Lockable {
4141
// WETH contract on Ethereum.
4242
WETH9 public immutable l1Weth;
4343

44+
// Chain id for the L1 that this contract is deployed on or communicates with.
45+
// For example: if this contract were meant to facilitate transfers from polygon to mainnet, this value would be
46+
// the mainnet chainId 1.
47+
uint256 public immutable l1ChainId;
48+
49+
// Chain id for the L2 that this contract is deployed on or communicates with.
50+
// For example: if this contract were meant to facilitate transfers from polygon to mainnet, this value would be
51+
// the polygon chainId 137.
52+
uint256 public immutable l2ChainId;
53+
54+
modifier onlyChainId(uint256 chainId) {
55+
_requireChainId(chainId);
56+
_;
57+
}
58+
4459
/**
4560
* @notice Constructs Token Bridger contract.
4661
* @param _destination Where to send tokens to for this network.
4762
* @param _l1Weth Ethereum WETH address.
4863
*/
49-
constructor(address _destination, WETH9 _l1Weth) {
64+
constructor(
65+
address _destination,
66+
WETH9 _l1Weth,
67+
uint256 _l1ChainId,
68+
uint256 _l2ChainId
69+
) {
5070
destination = _destination;
5171
l1Weth = _l1Weth;
72+
l1ChainId = _l1ChainId;
73+
l2ChainId = _l2ChainId;
5274
}
5375

5476
/**
@@ -62,7 +84,7 @@ contract PolygonTokenBridger is Lockable {
6284
PolygonIERC20 token,
6385
uint256 amount,
6486
bool isWrappedMatic
65-
) public nonReentrant {
87+
) public nonReentrant onlyChainId(l2ChainId) {
6688
token.safeTransferFrom(msg.sender, address(this), amount);
6789

6890
// In the wMatic case, this unwraps. For other ERC20s, this is the burn/send action.
@@ -76,12 +98,22 @@ contract PolygonTokenBridger is Lockable {
7698
* @notice Called by someone to send tokens to the destination, which should be set to the HubPool.
7799
* @param token Token to send to destination.
78100
*/
79-
function retrieve(IERC20 token) public nonReentrant {
101+
function retrieve(IERC20 token) public nonReentrant onlyChainId(l1ChainId) {
80102
token.safeTransfer(destination, token.balanceOf(address(this)));
81103
}
82104

83105
receive() external payable {
84-
// Note: this should only happen on the mainnet side where ETH is sent to the contract directly by the bridge.
85-
if (functionCallStackOriginatesFromOutsideThisContract()) l1Weth.deposit{ value: address(this).balance }();
106+
if (functionCallStackOriginatesFromOutsideThisContract()) {
107+
// This should only happen on the mainnet side where ETH is sent to the contract directly by the bridge.
108+
_requireChainId(l1ChainId);
109+
l1Weth.deposit{ value: address(this).balance }();
110+
} else {
111+
// This should only happen on the l2 side where matic is unwrapped by this contract.
112+
_requireChainId(l2ChainId);
113+
}
114+
}
115+
116+
function _requireChainId(uint256 chainId) internal view {
117+
require(block.chainid == chainId, "Cannot run method on this chain");
86118
}
87119
}

deploy/008_deploy_polygon_token_bridger_mainnet.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
import "hardhat-deploy";
33
import { HardhatRuntimeEnvironment } from "hardhat/types/runtime";
44

5-
import { L1_ADDRESS_MAP } from "./consts";
5+
import { L1_ADDRESS_MAP, POLYGON_CHAIN_IDS } from "./consts";
66

77
const func = async function (hre: HardhatRuntimeEnvironment) {
88
const { deployments, getNamedAccounts, getChainId } = hre;
@@ -17,7 +17,7 @@ const func = async function (hre: HardhatRuntimeEnvironment) {
1717
from: deployer,
1818
log: true,
1919
skipIfAlreadyDeployed: true,
20-
args: [hubPool.address, L1_ADDRESS_MAP[chainId].weth],
20+
args: [hubPool.address, L1_ADDRESS_MAP[chainId].weth, chainId, POLYGON_CHAIN_IDS[chainId]],
2121
deterministicDeployment: "0x1234", // Salt for the create2 call.
2222
});
2323
};

deploy/010_deploy_polygon_token_bridger_polygon.ts

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,19 +5,20 @@ import { HardhatRuntimeEnvironment } from "hardhat/types/runtime";
55
import { L1_ADDRESS_MAP } from "./consts";
66

77
const func = async function (hre: HardhatRuntimeEnvironment) {
8-
const { deployments, getNamedAccounts } = hre;
8+
const { deployments, getNamedAccounts, getChainId } = hre;
99
const { deploy } = deployments;
1010

1111
const { deployer } = await getNamedAccounts();
1212

13+
const chainId = parseInt(await getChainId());
1314
const l1ChainId = parseInt(await hre.companionNetworks.l1.getChainId());
1415
const l1HubPool = await hre.companionNetworks.l1.deployments.get("HubPool");
1516

1617
await deploy("PolygonTokenBridger", {
1718
from: deployer,
1819
log: true,
1920
skipIfAlreadyDeployed: true,
20-
args: [l1HubPool.address, L1_ADDRESS_MAP[l1ChainId].weth],
21+
args: [l1HubPool.address, L1_ADDRESS_MAP[l1ChainId].weth, l1ChainId, chainId],
2122
deterministicDeployment: "0x1234", // Salt for the create2 call.
2223
});
2324
};

deploy/consts.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -60,3 +60,8 @@ export const L2_ADDRESS_MAP: { [key: number]: { [contractName: string]: string }
6060
fxChild: "0xCf73231F28B7331BBe3124B907840A94851f9f11",
6161
},
6262
};
63+
64+
export const POLYGON_CHAIN_IDS: { [l1ChainId: number]: number } = {
65+
1: 137,
66+
5: 80001,
67+
};

test/chain-specific-spokepools/Polygon_SpokePool.ts

Lines changed: 61 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,16 @@
11
import { TokenRolesEnum, ZERO_ADDRESS } from "@uma/common";
22
import { mockTreeRoot, amountToReturn, amountHeldByPool } from "../constants";
3-
import { ethers, expect, Contract, SignerWithAddress, getContractFactory, seedContract, toWei } from "../utils";
3+
import {
4+
ethers,
5+
expect,
6+
Contract,
7+
SignerWithAddress,
8+
getContractFactory,
9+
seedContract,
10+
toWei,
11+
randomBigNumber,
12+
seedWallet,
13+
} from "../utils";
414
import { hubPoolFixture } from "../fixtures/HubPool.Fixture";
515
import { constructSingleRelayerRefundTree } from "../MerkleLib.utils";
616

@@ -13,9 +23,13 @@ describe("Polygon Spoke Pool", function () {
1323
[owner, relayer, fxChild, rando] = await ethers.getSigners();
1424
({ weth, hubPool, timer, l2Dai } = await hubPoolFixture());
1525

26+
// The spoke pool exists on l2, so add a random chainId for L1 to ensure that the L2's block.chainid will not match.
27+
const l1ChainId = randomBigNumber();
28+
const l2ChainId = await owner.getChainId();
29+
1630
const polygonTokenBridger = await (
1731
await getContractFactory("PolygonTokenBridger", owner)
18-
).deploy(hubPool.address, weth.address);
32+
).deploy(hubPool.address, weth.address, l1ChainId, l2ChainId);
1933

2034
dai = await (await getContractFactory("PolygonERC20Test", owner)).deploy();
2135
await dai.addMember(TokenRolesEnum.MINTER, owner.address);
@@ -25,6 +39,7 @@ describe("Polygon Spoke Pool", function () {
2539
).deploy(polygonTokenBridger.address, owner.address, hubPool.address, weth.address, fxChild.address, timer.address);
2640

2741
await seedContract(polygonSpokePool, relayer, [dai], weth, amountHeldByPool);
42+
await seedWallet(owner, [], weth, toWei("1"));
2843
});
2944

3045
it("Only correct caller can set the cross domain admin", async function () {
@@ -196,9 +211,13 @@ describe("Polygon Spoke Pool", function () {
196211
});
197212

198213
it("PolygonTokenBridger retrieves and unwraps tokens correctly", async function () {
214+
const l1ChainId = await owner.getChainId();
215+
216+
// Retrieve can only be performed on L1, so seed the L2 chainId with a non matching value.
217+
const l2ChainId = randomBigNumber();
199218
const polygonTokenBridger = await (
200219
await getContractFactory("PolygonTokenBridger", owner)
201-
).deploy(hubPool.address, weth.address);
220+
).deploy(hubPool.address, weth.address, l1ChainId, l2ChainId);
202221

203222
await expect(() =>
204223
owner.sendTransaction({ to: polygonTokenBridger.address, value: toWei("1") })
@@ -210,4 +229,43 @@ describe("Polygon Spoke Pool", function () {
210229
[toWei("1").mul(-1), toWei("1")]
211230
);
212231
});
232+
233+
it("PolygonTokenBridger doesn't allow L1 actions on L2", async function () {
234+
// Make sure the L1 chain is different from the chainId where this is deployed.
235+
const l1ChainId = randomBigNumber();
236+
const l2ChainId = await owner.getChainId();
237+
238+
const polygonTokenBridger = await (
239+
await getContractFactory("PolygonTokenBridger", owner)
240+
).deploy(hubPool.address, weth.address, l1ChainId, l2ChainId);
241+
242+
// Cannot send ETH directly into the contract on L2.
243+
await expect(owner.sendTransaction({ to: polygonTokenBridger.address, value: toWei("1") })).to.be.revertedWith(
244+
"Cannot run method on this chain"
245+
);
246+
247+
// Cannot call retrieve on the contract on L2.
248+
await weth.connect(owner).transfer(polygonTokenBridger.address, toWei("1"));
249+
await expect(polygonTokenBridger.connect(owner).retrieve(weth.address)).to.be.revertedWith(
250+
"Cannot run method on this chain"
251+
);
252+
});
253+
254+
it("PolygonTokenBridger doesn't allow L2 actions on L1", async function () {
255+
const l1ChainId = await owner.getChainId();
256+
257+
// Make sure the L1 chain is different from the chainId where this is deployed.
258+
const l2ChainId = randomBigNumber();
259+
260+
const polygonTokenBridger = await (
261+
await getContractFactory("PolygonTokenBridger", owner)
262+
).deploy(hubPool.address, weth.address, l1ChainId, l2ChainId);
263+
264+
await weth.connect(owner).approve(polygonTokenBridger.address, toWei("1"));
265+
266+
// Cannot call send on the contract on L1.
267+
await expect(polygonTokenBridger.connect(owner).send(weth.address, toWei("1"), false)).to.be.revertedWith(
268+
"Cannot run method on this chain"
269+
);
270+
});
213271
});

0 commit comments

Comments
 (0)