Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
42 changes: 37 additions & 5 deletions contracts/PolygonTokenBridger.sol
Original file line number Diff line number Diff line change
Expand Up @@ -40,14 +40,36 @@ contract PolygonTokenBridger is Lockable {
// WETH contract on Ethereum.
WETH9 public immutable l1Weth;

// Chain id for the L1 that this contract is deployed on or communicates with.
// For example: if this contract were meant to facilitate transfers from polygon to mainnet, this value would be
// the mainnet chainId 1.
uint256 public immutable l1ChainId;

// Chain id for the L2 that this contract is deployed on or communicates with.
// For example: if this contract were meant to facilitate transfers from polygon to mainnet, this value would be
// the polygon chainId 137.
uint256 public immutable l2ChainId;

modifier onlyChainId(uint256 chainId) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit instead of using modifier, does calling internal method _requireChainId directly in send and retrieve use less gas? I think its readable either way

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

My understanding is that modifiers are usually inlined, so I expect that it would be the same either way.

_requireChainId(chainId);
_;
}

/**
* @notice Constructs Token Bridger contract.
* @param _destination Where to send tokens to for this network.
* @param _l1Weth Ethereum WETH address.
*/
constructor(address _destination, WETH9 _l1Weth) {
constructor(
address _destination,
WETH9 _l1Weth,
uint256 _l1ChainId,
uint256 _l2ChainId
) {
destination = _destination;
l1Weth = _l1Weth;
l1ChainId = _l1ChainId;
l2ChainId = _l2ChainId;
}

/**
Expand All @@ -60,7 +82,7 @@ contract PolygonTokenBridger is Lockable {
PolygonIERC20 token,
uint256 amount,
bool isWrappedMatic
) public nonReentrant {
) public nonReentrant onlyChainId(l2ChainId) {
token.safeTransferFrom(msg.sender, address(this), amount);

// In the wMatic case, this unwraps. For other ERC20s, this is the burn/send action.
Expand All @@ -74,12 +96,22 @@ contract PolygonTokenBridger is Lockable {
* @notice Called by someone to send tokens to the destination, which should be set to the HubPool.
* @param token Token to send to destination.
*/
function retrieve(IERC20 token) public nonReentrant {
function retrieve(IERC20 token) public nonReentrant onlyChainId(l1ChainId) {
token.safeTransfer(destination, token.balanceOf(address(this)));
}

receive() external payable {
// Note: this should only happen on the mainnet side where ETH is sent to the contract directly by the bridge.
if (functionCallStackOriginatesFromOutsideThisContract()) l1Weth.deposit{ value: address(this).balance }();
if (functionCallStackOriginatesFromOutsideThisContract()) {
// This should only happen on the mainnet side where ETH is sent to the contract directly by the bridge.
_requireChainId(l1ChainId);
l1Weth.deposit{ value: address(this).balance }();
} else {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

more succinct: _requireChainId(functionCallStackOriginatesFromOutsideThisContract() ? l1ChainId : l2ChainId)

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Only reason I don't want to do this is that we still need to do the l1Weth.deposit call in a separate branch, which makes this feel a bit messier since we're doing the same branch twice.

// This should only happen on the l2 side where matic is unwrapped by this contract.
_requireChainId(l2ChainId);
}
}

function _requireChainId(uint256 chainId) internal view {
require(block.chainid == chainId, "Cannot run method on this chain");
}
}
4 changes: 2 additions & 2 deletions deploy/008_deploy_polygon_token_bridger_mainnet.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
import "hardhat-deploy";
import { HardhatRuntimeEnvironment } from "hardhat/types/runtime";

import { L1_ADDRESS_MAP } from "./consts";
import { L1_ADDRESS_MAP, POLYGON_CHAIN_IDS } from "./consts";

const func = async function (hre: HardhatRuntimeEnvironment) {
const { deployments, getNamedAccounts, getChainId } = hre;
Expand All @@ -17,7 +17,7 @@ const func = async function (hre: HardhatRuntimeEnvironment) {
from: deployer,
log: true,
skipIfAlreadyDeployed: true,
args: [hubPool.address, L1_ADDRESS_MAP[chainId].weth],
args: [hubPool.address, L1_ADDRESS_MAP[chainId].weth, chainId, POLYGON_CHAIN_IDS[chainId]],
deterministicDeployment: "0x1234", // Salt for the create2 call.
});
};
Expand Down
5 changes: 3 additions & 2 deletions deploy/010_deploy_polygon_token_bridger_polygon.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,19 +5,20 @@ import { HardhatRuntimeEnvironment } from "hardhat/types/runtime";
import { L1_ADDRESS_MAP } from "./consts";

const func = async function (hre: HardhatRuntimeEnvironment) {
const { deployments, getNamedAccounts } = hre;
const { deployments, getNamedAccounts, getChainId } = hre;
const { deploy } = deployments;

const { deployer } = await getNamedAccounts();

const chainId = parseInt(await getChainId());
const l1ChainId = parseInt(await hre.companionNetworks.l1.getChainId());
const l1HubPool = await hre.companionNetworks.l1.deployments.get("HubPool");

await deploy("PolygonTokenBridger", {
from: deployer,
log: true,
skipIfAlreadyDeployed: true,
args: [l1HubPool.address, L1_ADDRESS_MAP[l1ChainId].weth],
args: [l1HubPool.address, L1_ADDRESS_MAP[l1ChainId].weth, l1ChainId, chainId],
deterministicDeployment: "0x1234", // Salt for the create2 call.
});
};
Expand Down
5 changes: 5 additions & 0 deletions deploy/consts.ts
Original file line number Diff line number Diff line change
Expand Up @@ -52,3 +52,8 @@ export const L2_ADDRESS_MAP: { [key: number]: { [contractName: string]: string }
fxChild: "0xCf73231F28B7331BBe3124B907840A94851f9f11",
},
};

export const POLYGON_CHAIN_IDS: { [l1ChainId: number]: number } = {
1: 137,
5: 80001,
};
64 changes: 61 additions & 3 deletions test/chain-specific-spokepools/Polygon_SpokePool.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,16 @@
import { TokenRolesEnum, ZERO_ADDRESS } from "@uma/common";
import { mockTreeRoot, amountToReturn, amountHeldByPool } from "../constants";
import { ethers, expect, Contract, SignerWithAddress, getContractFactory, seedContract, toWei } from "../utils";
import {
ethers,
expect,
Contract,
SignerWithAddress,
getContractFactory,
seedContract,
toWei,
randomBigNumber,
seedWallet,
} from "../utils";
import { hubPoolFixture } from "../fixtures/HubPool.Fixture";
import { constructSingleRelayerRefundTree } from "../MerkleLib.utils";

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

// The spoke pool exists on l2, so add a random chainId for L1 to ensure that the L2's block.chainid will not match.
const l1ChainId = randomBigNumber();
const l2ChainId = await owner.getChainId();

const polygonTokenBridger = await (
await getContractFactory("PolygonTokenBridger", owner)
).deploy(hubPool.address, weth.address);
).deploy(hubPool.address, weth.address, l1ChainId, l2ChainId);

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

await seedContract(polygonSpokePool, relayer, [dai], weth, amountHeldByPool);
await seedWallet(owner, [], weth, toWei("1"));
});

it("Only correct caller can set the cross domain admin", async function () {
Expand Down Expand Up @@ -163,9 +178,13 @@ describe("Polygon Spoke Pool", function () {
});

it("PolygonTokenBridger retrieves and unwraps tokens correctly", async function () {
const l1ChainId = await owner.getChainId();

// Retrieve can only be performed on L1, so seed the L2 chainId with a non matching value.
const l2ChainId = randomBigNumber();
const polygonTokenBridger = await (
await getContractFactory("PolygonTokenBridger", owner)
).deploy(hubPool.address, weth.address);
).deploy(hubPool.address, weth.address, l1ChainId, l2ChainId);

await expect(() =>
owner.sendTransaction({ to: polygonTokenBridger.address, value: toWei("1") })
Expand All @@ -177,4 +196,43 @@ describe("Polygon Spoke Pool", function () {
[toWei("1").mul(-1), toWei("1")]
);
});

it("PolygonTokenBridger doesn't allow L1 actions on L2", async function () {
// Make sure the L1 chain is different from the chainId where this is deployed.
const l1ChainId = randomBigNumber();
const l2ChainId = await owner.getChainId();

const polygonTokenBridger = await (
await getContractFactory("PolygonTokenBridger", owner)
).deploy(hubPool.address, weth.address, l1ChainId, l2ChainId);

// Cannot send ETH directly into the contract on L2.
await expect(owner.sendTransaction({ to: polygonTokenBridger.address, value: toWei("1") })).to.be.revertedWith(
"Cannot run method on this chain"
);

// Cannot call retrieve on the contract on L2.
await weth.connect(owner).transfer(polygonTokenBridger.address, toWei("1"));
await expect(polygonTokenBridger.connect(owner).retrieve(weth.address)).to.be.revertedWith(
"Cannot run method on this chain"
);
});

it("PolygonTokenBridger doesn't allow L2 actions on L1", async function () {
const l1ChainId = await owner.getChainId();

// Make sure the L1 chain is different from the chainId where this is deployed.
const l2ChainId = randomBigNumber();

const polygonTokenBridger = await (
await getContractFactory("PolygonTokenBridger", owner)
).deploy(hubPool.address, weth.address, l1ChainId, l2ChainId);

await weth.connect(owner).approve(polygonTokenBridger.address, toWei("1"));

// Cannot call send on the contract on L1.
await expect(polygonTokenBridger.connect(owner).send(weth.address, toWei("1"), false)).to.be.revertedWith(
"Cannot run method on this chain"
);
});
});