Skip to content

Commit 72b796e

Browse files
authored
fix: polygon testnet fixes (#128)
1 parent 3676377 commit 72b796e

17 files changed

+1212
-145
lines changed

contracts/PolygonTokenBridger.sol

Lines changed: 40 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,16 @@ import "./interfaces/WETH9.sol";
77
import "@openzeppelin/contracts/token/ERC20/IERC20.sol";
88
import "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol";
99

10+
// Polygon Registry contract that stores their addresses.
11+
interface PolygonRegistry {
12+
function erc20Predicate() external returns (address);
13+
}
14+
15+
// Polygon ERC20Predicate contract that handles Plasma exits (only used for Matic).
16+
interface PolygonERC20Predicate {
17+
function startExitWithBurntTokens(bytes calldata data) external;
18+
}
19+
1020
// ERC20s (on polygon) compatible with polygon's bridge have a withdraw method.
1121
interface PolygonIERC20 is IERC20 {
1222
function withdraw(uint256 amount) external;
@@ -38,6 +48,9 @@ contract PolygonTokenBridger is Lockable {
3848
// Should be set to HubPool on Ethereum, or unused on Polygon.
3949
address public immutable destination;
4050

51+
// Registry that stores L1 polygon addresses.
52+
PolygonRegistry public immutable l1PolygonRegistry;
53+
4154
// WETH contract on Ethereum.
4255
WETH9 public immutable l1Weth;
4356

@@ -59,15 +72,21 @@ contract PolygonTokenBridger is Lockable {
5972
/**
6073
* @notice Constructs Token Bridger contract.
6174
* @param _destination Where to send tokens to for this network.
62-
* @param _l1Weth Ethereum WETH address.
75+
* @param _l1PolygonRegistry L1 registry that stores updated addresses of polygon contracts. This should always be
76+
* set to the L1 registry regardless if whether it's deployed on L2 or L1.
77+
* @param _l1Weth L1 WETH address.
78+
* @param _l1ChainId the chain id for the L1 in this environment.
79+
* @param _l2ChainId the chain id for the L2 in this environment.
6380
*/
6481
constructor(
6582
address _destination,
83+
PolygonRegistry _l1PolygonRegistry,
6684
WETH9 _l1Weth,
6785
uint256 _l1ChainId,
6886
uint256 _l2ChainId
6987
) {
7088
destination = _destination;
89+
l1PolygonRegistry = _l1PolygonRegistry;
7190
l1Weth = _l1Weth;
7291
l1ChainId = _l1ChainId;
7392
l2ChainId = _l2ChainId;
@@ -99,18 +118,30 @@ contract PolygonTokenBridger is Lockable {
99118
* @param token Token to send to destination.
100119
*/
101120
function retrieve(IERC20 token) public nonReentrant onlyChainId(l1ChainId) {
121+
if (address(token) == address(l1Weth)) {
122+
// For WETH, there is a pre-deposit step to ensure any ETH that has been sent to the contract is captured.
123+
l1Weth.deposit{ value: address(this).balance }();
124+
}
102125
token.safeTransfer(destination, token.balanceOf(address(this)));
103126
}
104127

128+
/**
129+
* @notice Called to initiate an l1 exit (withdrawal) of matic tokens that have been sent over the plasma bridge.
130+
* @param data the proof data to trigger the exit. Can be generated using the maticjs-plasma package.
131+
*/
132+
function callExit(bytes memory data) public nonReentrant onlyChainId(l1ChainId) {
133+
PolygonERC20Predicate erc20Predicate = PolygonERC20Predicate(l1PolygonRegistry.erc20Predicate());
134+
erc20Predicate.startExitWithBurntTokens(data);
135+
}
136+
105137
receive() external payable {
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-
}
138+
// This method is empty to avoid any gas expendatures that might cause transfers to fail.
139+
// Note: the fact that there is _no_ code in this function means that matic can be erroneously transferred in
140+
// to the contract on the polygon side. These tokens would be locked indefinitely since the receive function
141+
// cannot be called on the polygon side. While this does have some downsides, the lack of any functionality
142+
// in this function means that it has no chance of running out of gas on transfers, which is a much more
143+
// important benefit. This just makes the matic token risk similar to that of ERC20s that are erroneously
144+
// sent to the contract.
114145
}
115146

116147
function _requireChainId(uint256 chainId) internal view {

contracts/Polygon_SpokePool.sol

Lines changed: 79 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -127,6 +127,80 @@ contract Polygon_SpokePool is IFxMessageProcessor, SpokePool {
127127
require(success, "delegatecall failed");
128128
}
129129

130+
/**
131+
* @notice Allows the caller to trigger the wrapping of any unwrapped matic tokens.
132+
* @dev Matic sends via L1 -> L2 bridging actions don't call into the contract receiving the tokens, so wrapping
133+
* must be done via a separate transaction.
134+
*/
135+
function wrap() public nonReentrant {
136+
_wrap();
137+
}
138+
139+
/**
140+
* @notice Executes a relayer refund leaf stored as part of a root bundle. Will send the relayer the amount they
141+
* sent to the recipient plus a relayer fee.
142+
* @dev this is only overridden to wrap any matic the contract holds before running.
143+
* @param rootBundleId Unique ID of root bundle containing relayer refund root that this leaf is contained in.
144+
* @param relayerRefundLeaf Contains all data necessary to reconstruct leaf contained in root bundle and to
145+
* refund relayer. This data structure is explained in detail in the SpokePoolInterface.
146+
* @param proof Inclusion proof for this leaf in relayer refund root in root bundle.
147+
*/
148+
function executeRelayerRefundLeaf(
149+
uint32 rootBundleId,
150+
SpokePoolInterface.RelayerRefundLeaf memory relayerRefundLeaf,
151+
bytes32[] memory proof
152+
) public override nonReentrant {
153+
_wrap();
154+
_executeRelayerRefundLeaf(rootBundleId, relayerRefundLeaf, proof);
155+
}
156+
157+
/**
158+
* @notice Executes a slow relay leaf stored as part of a root bundle. Will send the full amount remaining in the
159+
* relay to the recipient, less fees.
160+
* @dev This function assumes that the relay's destination chain ID is the current chain ID, which prevents
161+
* the caller from executing a slow relay intended for another chain on this chain. This is only overridden to call
162+
* wrap before running the function.
163+
* @param depositor Depositor on origin chain who set this chain as the destination chain.
164+
* @param recipient Specified recipient on this chain.
165+
* @param destinationToken Token to send to recipient. Should be mapped to the origin token, origin chain ID
166+
* and this chain ID via a mapping on the HubPool.
167+
* @param amount Full size of the deposit.
168+
* @param originChainId Chain of SpokePool where deposit originated.
169+
* @param realizedLpFeePct Fee % based on L1 HubPool utilization at deposit quote time. Deterministic based on
170+
* quote time.
171+
* @param relayerFeePct Original fee % to keep as relayer set by depositor.
172+
* @param depositId Unique deposit ID on origin spoke pool.
173+
* @param rootBundleId Unique ID of root bundle containing slow relay root that this leaf is contained in.
174+
* @param proof Inclusion proof for this leaf in slow relay root in root bundle.
175+
*/
176+
function executeSlowRelayLeaf(
177+
address depositor,
178+
address recipient,
179+
address destinationToken,
180+
uint256 amount,
181+
uint256 originChainId,
182+
uint64 realizedLpFeePct,
183+
uint64 relayerFeePct,
184+
uint32 depositId,
185+
uint32 rootBundleId,
186+
bytes32[] memory proof
187+
) public virtual override nonReentrant {
188+
_wrap();
189+
_executeSlowRelayLeaf(
190+
depositor,
191+
recipient,
192+
destinationToken,
193+
amount,
194+
originChainId,
195+
chainId(),
196+
realizedLpFeePct,
197+
relayerFeePct,
198+
depositId,
199+
rootBundleId,
200+
proof
201+
);
202+
}
203+
130204
/**************************************
131205
* INTERNAL FUNCTIONS *
132206
**************************************/
@@ -147,6 +221,11 @@ contract Polygon_SpokePool is IFxMessageProcessor, SpokePool {
147221
emit PolygonTokensBridged(relayerRefundLeaf.l2TokenAddress, address(this), relayerRefundLeaf.amountToReturn);
148222
}
149223

224+
function _wrap() internal {
225+
uint256 balance = address(this).balance;
226+
if (balance > 0) wrappedNativeToken.deposit{ value: balance }();
227+
}
228+
150229
// @dev: This contract will trigger admin functions internally via the `processMessageFromRoot`, which is why
151230
// the `callValidated` check is made below and why we use the `validateInternalCalls` modifier on
152231
// `processMessageFromRoot`. This prevents calling the admin functions from any other method besides

contracts/chain-adapters/Polygon_Adapter.sol

Lines changed: 26 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,14 @@ interface IFxStateSender {
2121
function sendMessageToChild(address _receiver, bytes calldata _data) external;
2222
}
2323

24+
interface DepositManager {
25+
function depositERC20ForUser(
26+
address token,
27+
address user,
28+
uint256 amount
29+
) external;
30+
}
31+
2432
/**
2533
* @notice Sends cross chain messages Polygon L2 network.
2634
* @dev Public functions calling external contracts do not guard against reentrancy because they are expected to be
@@ -34,21 +42,33 @@ contract Polygon_Adapter is AdapterInterface {
3442
using SafeERC20 for IERC20;
3543
IRootChainManager public immutable rootChainManager;
3644
IFxStateSender public immutable fxStateSender;
45+
DepositManager public immutable depositManager;
46+
address public immutable erc20Predicate;
47+
address public immutable l1Matic;
3748
WETH9 public immutable l1Weth;
3849

3950
/**
4051
* @notice Constructs new Adapter.
41-
* @param _rootChainManager RootChainManager Polygon system helper contract.
42-
* @param _fxStateSender FxStateSender Polygon system helper contract.
52+
* @param _rootChainManager RootChainManager Polygon system contract to deposit tokens over the PoS bridge.
53+
* @param _fxStateSender FxStateSender Polygon system contract to send arbitrary messages to L2.
54+
* @param _depositManager DepositManager Polygon system contract to deposit tokens over the Plasma bridge (Matic).
55+
* @param _erc20Predicate ERC20Predicate Polygon system contract to approve when depositing to the PoS bridge.
56+
* @param _l1Matic matic address on l1.
4357
* @param _l1Weth WETH address on L1.
4458
*/
4559
constructor(
4660
IRootChainManager _rootChainManager,
4761
IFxStateSender _fxStateSender,
62+
DepositManager _depositManager,
63+
address _erc20Predicate,
64+
address _l1Matic,
4865
WETH9 _l1Weth
4966
) {
5067
rootChainManager = _rootChainManager;
5168
fxStateSender = _fxStateSender;
69+
depositManager = _depositManager;
70+
erc20Predicate = _erc20Predicate;
71+
l1Matic = _l1Matic;
5272
l1Weth = _l1Weth;
5373
}
5474

@@ -80,8 +100,11 @@ contract Polygon_Adapter is AdapterInterface {
80100
if (l1Token == address(l1Weth)) {
81101
l1Weth.withdraw(amount);
82102
rootChainManager.depositEtherFor{ value: amount }(to);
103+
} else if (l1Token == l1Matic) {
104+
IERC20(l1Token).safeIncreaseAllowance(address(depositManager), amount);
105+
depositManager.depositERC20ForUser(l1Token, to, amount);
83106
} else {
84-
IERC20(l1Token).safeIncreaseAllowance(address(rootChainManager), amount);
107+
IERC20(l1Token).safeIncreaseAllowance(erc20Predicate, amount);
85108
rootChainManager.depositFor(to, l1Token, abi.encode(amount));
86109
}
87110
emit TokensRelayed(l1Token, l2Token, amount, to);

contracts/test/PolygonMocks.sol

Lines changed: 29 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,10 @@
11
// SPDX-License-Identifier: AGPL-3.0-only
22
pragma solidity ^0.8.0;
33

4+
import "@openzeppelin/contracts/token/ERC20/ERC20.sol";
5+
46
contract RootChainManagerMock {
5-
function depositEtherFor(address user) external payable {} // solhint-disable-line no-empty-blocks
7+
function depositEtherFor(address user) external payable {}
68

79
function depositFor(
810
address user,
@@ -15,3 +17,29 @@ contract FxStateSenderMock {
1517
// solhint-disable-next-line no-empty-blocks
1618
function sendMessageToChild(address _receiver, bytes calldata _data) external {}
1719
}
20+
21+
contract DepositManagerMock {
22+
function depositERC20ForUser(
23+
address token,
24+
address user,
25+
uint256 amount // solhint-disable-next-line no-empty-blocks
26+
) external {} // solhint-disable-line no-empty-blocks
27+
}
28+
29+
contract PolygonRegistryMock {
30+
// solhint-disable-next-line no-empty-blocks
31+
function erc20Predicate() external returns (address predicate) {}
32+
}
33+
34+
contract PolygonERC20PredicateMock {
35+
// solhint-disable-next-line no-empty-blocks
36+
function startExitWithBurntTokens(bytes calldata data) external {}
37+
}
38+
39+
contract PolygonERC20Mock is ERC20 {
40+
// solhint-disable-next-line no-empty-blocks
41+
constructor() ERC20("Test ERC20", "TEST") {}
42+
43+
// solhint-disable-next-line no-empty-blocks
44+
function withdraw(uint256 amount) external {}
45+
}

deploy/008_deploy_polygon_token_bridger_mainnet.ts

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,13 @@ const func = async function (hre: HardhatRuntimeEnvironment) {
1717
from: deployer,
1818
log: true,
1919
skipIfAlreadyDeployed: true,
20-
args: [hubPool.address, L1_ADDRESS_MAP[chainId].weth, chainId, POLYGON_CHAIN_IDS[chainId]],
20+
args: [
21+
hubPool.address,
22+
L1_ADDRESS_MAP[chainId].polygonRegistry,
23+
L1_ADDRESS_MAP[chainId].weth,
24+
chainId,
25+
POLYGON_CHAIN_IDS[chainId],
26+
],
2127
deterministicDeployment: "0x1234", // Salt for the create2 call.
2228
});
2329
};

deploy/009_deploy_polygon_adapter.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,9 @@ const func = async function (hre: HardhatRuntimeEnvironment) {
1919
args: [
2020
L1_ADDRESS_MAP[chainId].polygonRootChainManager,
2121
L1_ADDRESS_MAP[chainId].polygonFxRoot,
22+
L1_ADDRESS_MAP[chainId].polygonDepositManager,
23+
L1_ADDRESS_MAP[chainId].polygonERC20Predicate,
24+
L1_ADDRESS_MAP[chainId].matic,
2225
L1_ADDRESS_MAP[chainId].weth,
2326
],
2427
});

deploy/010_deploy_polygon_token_bridger_polygon.ts

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,13 @@ const func = async function (hre: HardhatRuntimeEnvironment) {
1818
from: deployer,
1919
log: true,
2020
skipIfAlreadyDeployed: true,
21-
args: [l1HubPool.address, L1_ADDRESS_MAP[l1ChainId].weth, l1ChainId, chainId],
21+
args: [
22+
l1HubPool.address,
23+
L1_ADDRESS_MAP[l1ChainId].polygonRegistry,
24+
L1_ADDRESS_MAP[l1ChainId].weth,
25+
l1ChainId,
26+
chainId,
27+
],
2228
deterministicDeployment: "0x1234", // Salt for the create2 call.
2329
});
2430
};

deploy/consts.ts

Lines changed: 21 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -9,26 +9,36 @@ export const L1_ADDRESS_MAP: { [key: number]: { [contractName: string]: string }
99
finder: "0x40f941E48A552bF496B154Af6bf55725f18D77c3",
1010
polygonRootChainManager: "0xA0c68C638235ee32657e8f720a23ceC1bFc77C77",
1111
polygonFxRoot: "0xfe5e5D361b2ad62c541bAb87C45a0B9B018389a2",
12+
polygonERC20Predicate: "0x40ec5B33f54e0E8A33A975908C5BA1c14e5BbbDf",
13+
polygonRegistry: "0x33a02E6cC863D393d6Bf231B697b82F6e499cA71",
14+
polygonDepositManager: "0x401F6c983eA34274ec46f84D70b31C151321188b",
15+
matic: "0x7D1AfA7B718fb893dB30A3aBc0Cfc608AaCfeBB0",
1216
},
1317
4: {
1418
weth: "0xc778417E063141139Fce010982780140Aa0cD5Ab",
1519
finder: "0xbb6206fb01fAad31e8aaFc3AD303cEA89D8c8157",
1620
l1ArbitrumInbox: "0x578BAde599406A8fE3d24Fd7f7211c0911F5B29e", // Should be listed as "DelayedInbox" here: https://developer.offchainlabs.com/docs/useful_addresses
1721
l1ERC20GatewayRouter: "0x70C143928eCfFaf9F5b406f7f4fC28Dc43d68380", // Should be listed as "L1 ERC20 Gateway Router" here: https://developer.offchainlabs.com/docs/useful_addresses
18-
optimismCrossDomainMessenger: "0x25ace71c97B33Cc4729CF772ae268934F7ab5fA1", // dummy: Optimism's testnet is kovan
19-
optimismStandardBridge: "0x99C9fc46f92E8a1c0deC1b1747d010903E884bE1", // dummy: Optimism's testnet is kovan
20-
polygonRootChainManager: "0xBbD7cBFA79faee899Eaf900F13C9065bF03B1A74", // dummy: Polygon's testnet is goerli
21-
polygonFxRoot: "0x3d1d3E34f7fB6D26245E6640E1c50710eFFf15bA", // dummy: Polygon's testnet is goerli
22+
polygonRootChainManager: "0xBbD7cBFA79faee899Eaf900F13C9065bF03B1A74", // Dummy: Polygon's testnet is goerli
23+
polygonFxRoot: "0x3d1d3E34f7fB6D26245E6640E1c50710eFFf15bA", // Dummy: Polygon's testnet is goerli
24+
polygonERC20Predicate: "0xdD6596F2029e6233DEFfaCa316e6A95217d4Dc34", // Dummy: Polygon's testnet is goerli
25+
polygonRegistry: "0xeE11713Fe713b2BfF2942452517483654078154D", // Dummy: Polygon's testnet is goerli
26+
polygonDepositManager: "0x7850ec290A2e2F40B82Ed962eaf30591bb5f5C96", // Dummy: Polygon's testnet is goerli
27+
matic: "0x499d11E0b6eAC7c0593d8Fb292DCBbF815Fb29Ae", // Dummy: Polygon's testnet is goerli
2228
},
2329
5: {
2430
optimismCrossDomainMessenger: "0x25ace71c97B33Cc4729CF772ae268934F7ab5fA1", // dummy: Optimism's testnet is kovan
31+
weth: "0x60D4dB9b534EF9260a88b0BED6c486fe13E604Fc",
2532
optimismStandardBridge: "0x99C9fc46f92E8a1c0deC1b1747d010903E884bE1", // dummy: Optimism's testnet is kovan
2633
l1ArbitrumInbox: "0x578BAde599406A8fE3d24Fd7f7211c0911F5B29e", // dummy: Arbitrum's testnet is rinkeby
2734
l1ERC20GatewayRouter: "0x70C143928eCfFaf9F5b406f7f4fC28Dc43d68380", // dummy: Arbitrum's testnet is rinkeby
2835
finder: "0xDC6b80D38004F495861E081e249213836a2F3217",
2936
polygonRootChainManager: "0xBbD7cBFA79faee899Eaf900F13C9065bF03B1A74",
3037
polygonFxRoot: "0x3d1d3E34f7fB6D26245E6640E1c50710eFFf15bA",
31-
weth: "0x60D4dB9b534EF9260a88b0BED6c486fe13E604Fc",
38+
polygonERC20Predicate: "0xdD6596F2029e6233DEFfaCa316e6A95217d4Dc34",
39+
polygonRegistry: "0xeE11713Fe713b2BfF2942452517483654078154D",
40+
polygonDepositManager: "0x7850ec290A2e2F40B82Ed962eaf30591bb5f5C96",
41+
matic: "0x499d11E0b6eAC7c0593d8Fb292DCBbF815Fb29Ae",
3242
},
3343
42: {
3444
l1ArbitrumInbox: "0x578BAde599406A8fE3d24Fd7f7211c0911F5B29e", // dummy: Arbitrum's testnet is rinkeby
@@ -37,8 +47,12 @@ export const L1_ADDRESS_MAP: { [key: number]: { [contractName: string]: string }
3747
weth: "0xd0A1E359811322d97991E03f863a0C30C2cF029C",
3848
optimismStandardBridge: "0x22F24361D548e5FaAfb36d1437839f080363982B",
3949
finder: "0xeD0169a88d267063184b0853BaAAAe66c3c154B2",
40-
polygonRootChainManager: "0xBbD7cBFA79faee899Eaf900F13C9065bF03B1A74", // dummy: Polygon's testnet is goerli
41-
polygonFxRoot: "0x3d1d3E34f7fB6D26245E6640E1c50710eFFf15bA", // dummy: Polygon's testnet is goerli
50+
polygonRootChainManager: "0xBbD7cBFA79faee899Eaf900F13C9065bF03B1A74", // Dummy: Polygon's testnet is goerli
51+
polygonFxRoot: "0x3d1d3E34f7fB6D26245E6640E1c50710eFFf15bA", // Dummy: Polygon's testnet is goerli
52+
polygonERC20Predicate: "0xdD6596F2029e6233DEFfaCa316e6A95217d4Dc34", // Dummy: Polygon's testnet is goerli
53+
polygonRegistry: "0xeE11713Fe713b2BfF2942452517483654078154D", // Dummy: Polygon's testnet is goerli
54+
polygonDepositManager: "0x7850ec290A2e2F40B82Ed962eaf30591bb5f5C96", // Dummy: Polygon's testnet is goerli
55+
matic: "0x499d11E0b6eAC7c0593d8Fb292DCBbF815Fb29Ae", // Dummy: Polygon's testnet is goerli
4256
},
4357
};
4458

0 commit comments

Comments
 (0)