From b9a38f816c53afd3ab45240a0ab3b7f87367c226 Mon Sep 17 00:00:00 2001 From: nicholaspai Date: Wed, 4 Jan 2023 17:45:33 -0500 Subject: [PATCH 01/24] [WIP] feat: Make SpokePool's upgradeable This PR makes SpokePools compatible with OpenZeppelin's upgradeable proxy pattern --- .openzeppelin/unknown-31337.json | 978 ++++++++++++++++++ contracts/Arbitrum_SpokePool.sol | 9 +- contracts/Boba_SpokePool.sol | 16 +- contracts/Ethereum_SpokePool.sol | 15 +- contracts/Optimism_SpokePool.sol | 16 +- contracts/Ovm_SpokePool.sol | 34 +- contracts/Polygon_SpokePool.sol | 12 +- contracts/SpokePool.sol | 37 +- contracts/ZkSync_SpokePool.sol | 9 +- contracts/test/MockSpokePool.sol | 9 +- contracts/upgradeable/LockableUpgradeable.sol | 80 ++ contracts/upgradeable/TestableUpgradeable.sol | 54 + deployments/deployments.json | 4 +- hardhat.config.ts | 1 + package.json | 2 + .../Arbitrum_SpokePool.ts | 10 +- .../Ethereum_SpokePool.ts | 10 +- .../Optimism_SpokePool.ts | 8 +- .../Polygon_SpokePool.ts | 10 +- test/fixtures/HubPool.Fixture.ts | 16 +- test/fixtures/SpokePool.Fixture.ts | 8 +- yarn.lock | 33 + 22 files changed, 1283 insertions(+), 88 deletions(-) create mode 100644 .openzeppelin/unknown-31337.json create mode 100644 contracts/upgradeable/LockableUpgradeable.sol create mode 100644 contracts/upgradeable/TestableUpgradeable.sol diff --git a/.openzeppelin/unknown-31337.json b/.openzeppelin/unknown-31337.json new file mode 100644 index 000000000..8d2f07000 --- /dev/null +++ b/.openzeppelin/unknown-31337.json @@ -0,0 +1,978 @@ +{ + "manifestVersion": "3.2", + "admin": { + "address": "0xE6E340D132b5f46d1e472DebcD681B2aBc16e57E", + "txHash": "0xc341fc9407037da37b6cad0c33a8e8447f32a6b8c2946623c80acc03fe388acd" + }, + "proxies": [ + { + "address": "0x610178dA211FEF7D417bC0e6FeD39F05609AD788", + "txHash": "0x534e8623468ee49a4d9263839ff0577aadf325bc4bfd7397f4ac40d50c78298c", + "kind": "transparent" + }, + { + "address": "0xc3e53F4d16Ae77Db1c982e75a937B9f60FE63690", + "txHash": "0xed8ddd1b8f8e808244b487990c4db11f26204264031b0e22b44b4447377febbc", + "kind": "transparent" + }, + { + "address": "0x1613beB3B2C4f22Ee086B2b38C1476A3cE7f78E8", + "txHash": "0x91463d6bf6d0413585ee11409698c690e5307900cfb032395f7c38f4e6c23aa5", + "kind": "transparent" + }, + { + "address": "0x4826533B4897376654Bb4d4AD88B7faFD0C98528", + "txHash": "0x0078e2fd83b0cecd5e1215678056988a831b7efb067e4eb2fd45771448f02437", + "kind": "transparent" + }, + { + "address": "0x99bbA657f2BbC93c02D617f8bA121cB8Fc104Acf", + "txHash": "0xbc2c64f36e837cda6e4ebebdcd1eb61723f57c96b3ce51a9ae42ee8b7ee2e8c0", + "kind": "transparent" + }, + { + "address": "0x8f86403A4DE0BB5791fa46B8e795C547942fE4Cf", + "txHash": "0xf840b231d39aadf7f571e3b9fd4e2bef9c4f3a8739f65d21d86a7f35defa2b2b", + "kind": "transparent" + }, + { + "address": "0x4EE6eCAD1c2Dae9f525404De8555724e3c35d07B", + "txHash": "0x91ba6ed03f13cac78ae8c95057d9f308ac9274ead22d583650108ffd71827702", + "kind": "transparent" + } + ], + "impls": { + "2f7ef459103dc43c5c66ab95dd1e6fbd6abb8e61452b27e4042c2291cb6439f5": { + "address": "0x67d269191c92Caf3cD7723F116c85e6E9bf55933", + "txHash": "0xf351b900ae39e3e1356756b7d636e8ff6953d895392212dc524d85c20e04287c", + "layout": { + "solcVersion": "0.8.13", + "storage": [ + { + "label": "_initialized", + "offset": 0, + "slot": "0", + "type": "t_uint8", + "contract": "Initializable", + "src": "@openzeppelin/contracts-upgradeable/proxy/utils/Initializable.sol:62", + "retypedFrom": "bool" + }, + { + "label": "_initializing", + "offset": 1, + "slot": "0", + "type": "t_bool", + "contract": "Initializable", + "src": "@openzeppelin/contracts-upgradeable/proxy/utils/Initializable.sol:67" + }, + { + "label": "timerAddress", + "offset": 2, + "slot": "0", + "type": "t_address", + "contract": "TestableUpgradeable", + "src": "contracts/upgradeable/TestableUpgradeable.sol:14" + }, + { + "label": "_notEntered", + "offset": 22, + "slot": "0", + "type": "t_bool", + "contract": "LockableUpgradeable", + "src": "contracts/upgradeable/LockableUpgradeable.sol:13" + }, + { + "label": "__gap", + "offset": 0, + "slot": "1", + "type": "t_array(t_uint256)50_storage", + "contract": "MulticallUpgradeable", + "src": "@openzeppelin/contracts-upgradeable/utils/MulticallUpgradeable.sol:50" + }, + { + "label": "crossDomainAdmin", + "offset": 0, + "slot": "51", + "type": "t_address", + "contract": "SpokePool", + "src": "contracts/SpokePool.sol:31" + }, + { + "label": "hubPool", + "offset": 0, + "slot": "52", + "type": "t_address", + "contract": "SpokePool", + "src": "contracts/SpokePool.sol:35" + }, + { + "label": "wrappedNativeToken", + "offset": 0, + "slot": "53", + "type": "t_contract(WETH9)13949", + "contract": "SpokePool", + "src": "contracts/SpokePool.sol:39" + }, + { + "label": "depositQuoteTimeBuffer", + "offset": 20, + "slot": "53", + "type": "t_uint32", + "contract": "SpokePool", + "src": "contracts/SpokePool.sol:43" + }, + { + "label": "numberOfDeposits", + "offset": 24, + "slot": "53", + "type": "t_uint32", + "contract": "SpokePool", + "src": "contracts/SpokePool.sol:46" + }, + { + "label": "pausedFills", + "offset": 28, + "slot": "53", + "type": "t_bool", + "contract": "SpokePool", + "src": "contracts/SpokePool.sol:49" + }, + { + "label": "pausedDeposits", + "offset": 29, + "slot": "53", + "type": "t_bool", + "contract": "SpokePool", + "src": "contracts/SpokePool.sol:50" + }, + { + "label": "rootBundles", + "offset": 0, + "slot": "54", + "type": "t_array(t_struct(RootBundle)11701_storage)dyn_storage", + "contract": "SpokePool", + "src": "contracts/SpokePool.sol:53" + }, + { + "label": "enabledDepositRoutes", + "offset": 0, + "slot": "55", + "type": "t_mapping(t_address,t_mapping(t_uint256,t_bool))", + "contract": "SpokePool", + "src": "contracts/SpokePool.sol:56" + }, + { + "label": "relayFills", + "offset": 0, + "slot": "56", + "type": "t_mapping(t_bytes32,t_uint256)", + "contract": "SpokePool", + "src": "contracts/SpokePool.sol:61" + }, + { + "label": "chainId_", + "offset": 0, + "slot": "57", + "type": "t_uint256", + "contract": "MockSpokePool", + "src": "contracts/test/MockSpokePool.sol:12" + } + ], + "types": { + "t_address": { + "label": "address", + "numberOfBytes": "20" + }, + "t_array(t_struct(RootBundle)11701_storage)dyn_storage": { + "label": "struct SpokePoolInterface.RootBundle[]", + "numberOfBytes": "32" + }, + "t_array(t_uint256)50_storage": { + "label": "uint256[50]", + "numberOfBytes": "1600" + }, + "t_bool": { + "label": "bool", + "numberOfBytes": "1" + }, + "t_bytes32": { + "label": "bytes32", + "numberOfBytes": "32" + }, + "t_contract(WETH9)13949": { + "label": "contract WETH9", + "numberOfBytes": "20" + }, + "t_mapping(t_address,t_mapping(t_uint256,t_bool))": { + "label": "mapping(address => mapping(uint256 => bool))", + "numberOfBytes": "32" + }, + "t_mapping(t_bytes32,t_uint256)": { + "label": "mapping(bytes32 => uint256)", + "numberOfBytes": "32" + }, + "t_mapping(t_uint256,t_bool)": { + "label": "mapping(uint256 => bool)", + "numberOfBytes": "32" + }, + "t_mapping(t_uint256,t_uint256)": { + "label": "mapping(uint256 => uint256)", + "numberOfBytes": "32" + }, + "t_struct(RootBundle)11701_storage": { + "label": "struct SpokePoolInterface.RootBundle", + "members": [ + { + "label": "slowRelayRoot", + "type": "t_bytes32", + "offset": 0, + "slot": "0" + }, + { + "label": "relayerRefundRoot", + "type": "t_bytes32", + "offset": 0, + "slot": "1" + }, + { + "label": "claimedBitmap", + "type": "t_mapping(t_uint256,t_uint256)", + "offset": 0, + "slot": "2" + } + ], + "numberOfBytes": "96" + }, + "t_uint256": { + "label": "uint256", + "numberOfBytes": "32" + }, + "t_uint32": { + "label": "uint32", + "numberOfBytes": "4" + }, + "t_uint8": { + "label": "uint8", + "numberOfBytes": "1" + } + } + } + }, + "9e79df8f4aae0c6d8aa0ae14b3c0abc4ff9d9d71e662c1e75a91c57a92dc0c99": { + "address": "0x70e0bA845a1A0F2DA3359C97E0285013525FFC49", + "txHash": "0xcd7cd844d7a083141f9bdb7d600f4d533a53522b889f85e44d876bc3135cf228", + "layout": { + "solcVersion": "0.8.13", + "storage": [ + { + "label": "_initialized", + "offset": 0, + "slot": "0", + "type": "t_uint8", + "contract": "Initializable", + "src": "@openzeppelin/contracts-upgradeable/proxy/utils/Initializable.sol:62", + "retypedFrom": "bool" + }, + { + "label": "_initializing", + "offset": 1, + "slot": "0", + "type": "t_bool", + "contract": "Initializable", + "src": "@openzeppelin/contracts-upgradeable/proxy/utils/Initializable.sol:67" + }, + { + "label": "timerAddress", + "offset": 2, + "slot": "0", + "type": "t_address", + "contract": "TestableUpgradeable", + "src": "contracts/upgradeable/TestableUpgradeable.sol:14" + }, + { + "label": "_notEntered", + "offset": 22, + "slot": "0", + "type": "t_bool", + "contract": "LockableUpgradeable", + "src": "contracts/upgradeable/LockableUpgradeable.sol:13" + }, + { + "label": "__gap", + "offset": 0, + "slot": "1", + "type": "t_array(t_uint256)50_storage", + "contract": "MulticallUpgradeable", + "src": "@openzeppelin/contracts-upgradeable/utils/MulticallUpgradeable.sol:50" + }, + { + "label": "crossDomainAdmin", + "offset": 0, + "slot": "51", + "type": "t_address", + "contract": "SpokePool", + "src": "contracts/SpokePool.sol:31" + }, + { + "label": "hubPool", + "offset": 0, + "slot": "52", + "type": "t_address", + "contract": "SpokePool", + "src": "contracts/SpokePool.sol:35" + }, + { + "label": "wrappedNativeToken", + "offset": 0, + "slot": "53", + "type": "t_contract(WETH9)13949", + "contract": "SpokePool", + "src": "contracts/SpokePool.sol:39" + }, + { + "label": "depositQuoteTimeBuffer", + "offset": 20, + "slot": "53", + "type": "t_uint32", + "contract": "SpokePool", + "src": "contracts/SpokePool.sol:43" + }, + { + "label": "numberOfDeposits", + "offset": 24, + "slot": "53", + "type": "t_uint32", + "contract": "SpokePool", + "src": "contracts/SpokePool.sol:46" + }, + { + "label": "pausedFills", + "offset": 28, + "slot": "53", + "type": "t_bool", + "contract": "SpokePool", + "src": "contracts/SpokePool.sol:49" + }, + { + "label": "pausedDeposits", + "offset": 29, + "slot": "53", + "type": "t_bool", + "contract": "SpokePool", + "src": "contracts/SpokePool.sol:50" + }, + { + "label": "rootBundles", + "offset": 0, + "slot": "54", + "type": "t_array(t_struct(RootBundle)11701_storage)dyn_storage", + "contract": "SpokePool", + "src": "contracts/SpokePool.sol:53" + }, + { + "label": "enabledDepositRoutes", + "offset": 0, + "slot": "55", + "type": "t_mapping(t_address,t_mapping(t_uint256,t_bool))", + "contract": "SpokePool", + "src": "contracts/SpokePool.sol:56" + }, + { + "label": "relayFills", + "offset": 0, + "slot": "56", + "type": "t_mapping(t_bytes32,t_uint256)", + "contract": "SpokePool", + "src": "contracts/SpokePool.sol:61" + }, + { + "label": "__gap", + "offset": 0, + "slot": "57", + "type": "t_array(t_uint256)50_storage", + "contract": "ContextUpgradeable", + "src": "@openzeppelin/contracts-upgradeable/utils/ContextUpgradeable.sol:36" + }, + { + "label": "_owner", + "offset": 0, + "slot": "107", + "type": "t_address", + "contract": "OwnableUpgradeable", + "src": "@openzeppelin/contracts-upgradeable/access/OwnableUpgradeable.sol:22" + }, + { + "label": "__gap", + "offset": 0, + "slot": "108", + "type": "t_array(t_uint256)49_storage", + "contract": "OwnableUpgradeable", + "src": "@openzeppelin/contracts-upgradeable/access/OwnableUpgradeable.sol:94" + } + ], + "types": { + "t_address": { + "label": "address", + "numberOfBytes": "20" + }, + "t_array(t_struct(RootBundle)11701_storage)dyn_storage": { + "label": "struct SpokePoolInterface.RootBundle[]", + "numberOfBytes": "32" + }, + "t_array(t_uint256)49_storage": { + "label": "uint256[49]", + "numberOfBytes": "1568" + }, + "t_array(t_uint256)50_storage": { + "label": "uint256[50]", + "numberOfBytes": "1600" + }, + "t_bool": { + "label": "bool", + "numberOfBytes": "1" + }, + "t_bytes32": { + "label": "bytes32", + "numberOfBytes": "32" + }, + "t_contract(WETH9)13949": { + "label": "contract WETH9", + "numberOfBytes": "20" + }, + "t_mapping(t_address,t_mapping(t_uint256,t_bool))": { + "label": "mapping(address => mapping(uint256 => bool))", + "numberOfBytes": "32" + }, + "t_mapping(t_bytes32,t_uint256)": { + "label": "mapping(bytes32 => uint256)", + "numberOfBytes": "32" + }, + "t_mapping(t_uint256,t_bool)": { + "label": "mapping(uint256 => bool)", + "numberOfBytes": "32" + }, + "t_mapping(t_uint256,t_uint256)": { + "label": "mapping(uint256 => uint256)", + "numberOfBytes": "32" + }, + "t_struct(RootBundle)11701_storage": { + "label": "struct SpokePoolInterface.RootBundle", + "members": [ + { + "label": "slowRelayRoot", + "type": "t_bytes32", + "offset": 0, + "slot": "0" + }, + { + "label": "relayerRefundRoot", + "type": "t_bytes32", + "offset": 0, + "slot": "1" + }, + { + "label": "claimedBitmap", + "type": "t_mapping(t_uint256,t_uint256)", + "offset": 0, + "slot": "2" + } + ], + "numberOfBytes": "96" + }, + "t_uint256": { + "label": "uint256", + "numberOfBytes": "32" + }, + "t_uint32": { + "label": "uint32", + "numberOfBytes": "4" + }, + "t_uint8": { + "label": "uint8", + "numberOfBytes": "1" + } + } + } + }, + "76772fa813ad046f1f7b5384a007db6e47c0ee7d15b353fcc30ea0698138be98": { + "address": "0x0E801D84Fa97b50751Dbf25036d067dCf18858bF", + "txHash": "0x7ce7036e1d187889cb28c832816f8d605d6cc674f2872c8f5b8edaf364d90260", + "layout": { + "solcVersion": "0.8.13", + "storage": [ + { + "label": "_initialized", + "offset": 0, + "slot": "0", + "type": "t_uint8", + "contract": "Initializable", + "src": "@openzeppelin/contracts-upgradeable/proxy/utils/Initializable.sol:62", + "retypedFrom": "bool" + }, + { + "label": "_initializing", + "offset": 1, + "slot": "0", + "type": "t_bool", + "contract": "Initializable", + "src": "@openzeppelin/contracts-upgradeable/proxy/utils/Initializable.sol:67" + }, + { + "label": "timerAddress", + "offset": 2, + "slot": "0", + "type": "t_address", + "contract": "TestableUpgradeable", + "src": "contracts/upgradeable/TestableUpgradeable.sol:14" + }, + { + "label": "_notEntered", + "offset": 22, + "slot": "0", + "type": "t_bool", + "contract": "LockableUpgradeable", + "src": "contracts/upgradeable/LockableUpgradeable.sol:13" + }, + { + "label": "__gap", + "offset": 0, + "slot": "1", + "type": "t_array(t_uint256)50_storage", + "contract": "MulticallUpgradeable", + "src": "@openzeppelin/contracts-upgradeable/utils/MulticallUpgradeable.sol:50" + }, + { + "label": "crossDomainAdmin", + "offset": 0, + "slot": "51", + "type": "t_address", + "contract": "SpokePool", + "src": "contracts/SpokePool.sol:31" + }, + { + "label": "hubPool", + "offset": 0, + "slot": "52", + "type": "t_address", + "contract": "SpokePool", + "src": "contracts/SpokePool.sol:35" + }, + { + "label": "wrappedNativeToken", + "offset": 0, + "slot": "53", + "type": "t_contract(WETH9)13949", + "contract": "SpokePool", + "src": "contracts/SpokePool.sol:39" + }, + { + "label": "depositQuoteTimeBuffer", + "offset": 20, + "slot": "53", + "type": "t_uint32", + "contract": "SpokePool", + "src": "contracts/SpokePool.sol:43" + }, + { + "label": "numberOfDeposits", + "offset": 24, + "slot": "53", + "type": "t_uint32", + "contract": "SpokePool", + "src": "contracts/SpokePool.sol:46" + }, + { + "label": "pausedFills", + "offset": 28, + "slot": "53", + "type": "t_bool", + "contract": "SpokePool", + "src": "contracts/SpokePool.sol:49" + }, + { + "label": "pausedDeposits", + "offset": 29, + "slot": "53", + "type": "t_bool", + "contract": "SpokePool", + "src": "contracts/SpokePool.sol:50" + }, + { + "label": "rootBundles", + "offset": 0, + "slot": "54", + "type": "t_array(t_struct(RootBundle)11701_storage)dyn_storage", + "contract": "SpokePool", + "src": "contracts/SpokePool.sol:53" + }, + { + "label": "enabledDepositRoutes", + "offset": 0, + "slot": "55", + "type": "t_mapping(t_address,t_mapping(t_uint256,t_bool))", + "contract": "SpokePool", + "src": "contracts/SpokePool.sol:56" + }, + { + "label": "relayFills", + "offset": 0, + "slot": "56", + "type": "t_mapping(t_bytes32,t_uint256)", + "contract": "SpokePool", + "src": "contracts/SpokePool.sol:61" + }, + { + "label": "fxChild", + "offset": 0, + "slot": "57", + "type": "t_address", + "contract": "Polygon_SpokePool", + "src": "contracts/Polygon_SpokePool.sol:28" + }, + { + "label": "polygonTokenBridger", + "offset": 0, + "slot": "58", + "type": "t_contract(PolygonTokenBridger)9777", + "contract": "Polygon_SpokePool", + "src": "contracts/Polygon_SpokePool.sol:32" + }, + { + "label": "callValidated", + "offset": 20, + "slot": "58", + "type": "t_bool", + "contract": "Polygon_SpokePool", + "src": "contracts/Polygon_SpokePool.sol:36" + } + ], + "types": { + "t_address": { + "label": "address", + "numberOfBytes": "20" + }, + "t_array(t_struct(RootBundle)11701_storage)dyn_storage": { + "label": "struct SpokePoolInterface.RootBundle[]", + "numberOfBytes": "32" + }, + "t_array(t_uint256)50_storage": { + "label": "uint256[50]", + "numberOfBytes": "1600" + }, + "t_bool": { + "label": "bool", + "numberOfBytes": "1" + }, + "t_bytes32": { + "label": "bytes32", + "numberOfBytes": "32" + }, + "t_contract(PolygonTokenBridger)9777": { + "label": "contract PolygonTokenBridger", + "numberOfBytes": "20" + }, + "t_contract(WETH9)13949": { + "label": "contract WETH9", + "numberOfBytes": "20" + }, + "t_mapping(t_address,t_mapping(t_uint256,t_bool))": { + "label": "mapping(address => mapping(uint256 => bool))", + "numberOfBytes": "32" + }, + "t_mapping(t_bytes32,t_uint256)": { + "label": "mapping(bytes32 => uint256)", + "numberOfBytes": "32" + }, + "t_mapping(t_uint256,t_bool)": { + "label": "mapping(uint256 => bool)", + "numberOfBytes": "32" + }, + "t_mapping(t_uint256,t_uint256)": { + "label": "mapping(uint256 => uint256)", + "numberOfBytes": "32" + }, + "t_struct(RootBundle)11701_storage": { + "label": "struct SpokePoolInterface.RootBundle", + "members": [ + { + "label": "slowRelayRoot", + "type": "t_bytes32", + "offset": 0, + "slot": "0" + }, + { + "label": "relayerRefundRoot", + "type": "t_bytes32", + "offset": 0, + "slot": "1" + }, + { + "label": "claimedBitmap", + "type": "t_mapping(t_uint256,t_uint256)", + "offset": 0, + "slot": "2" + } + ], + "numberOfBytes": "96" + }, + "t_uint256": { + "label": "uint256", + "numberOfBytes": "32" + }, + "t_uint32": { + "label": "uint32", + "numberOfBytes": "4" + }, + "t_uint8": { + "label": "uint8", + "numberOfBytes": "1" + } + } + } + }, + "91a5423a1099272c1dfb5d932d736ba7d3658d3cc520630692fce28c3bc95577": { + "address": "0x4826533B4897376654Bb4d4AD88B7faFD0C98528", + "txHash": "0xc437cce67bdb372e2523b924752f35decc36be056165762d15aa92acaeaf8e68", + "layout": { + "solcVersion": "0.8.13", + "storage": [ + { + "label": "_initialized", + "offset": 0, + "slot": "0", + "type": "t_uint8", + "contract": "Initializable", + "src": "@openzeppelin/contracts-upgradeable/proxy/utils/Initializable.sol:62", + "retypedFrom": "bool" + }, + { + "label": "_initializing", + "offset": 1, + "slot": "0", + "type": "t_bool", + "contract": "Initializable", + "src": "@openzeppelin/contracts-upgradeable/proxy/utils/Initializable.sol:67" + }, + { + "label": "timerAddress", + "offset": 2, + "slot": "0", + "type": "t_address", + "contract": "TestableUpgradeable", + "src": "contracts/upgradeable/TestableUpgradeable.sol:14" + }, + { + "label": "_notEntered", + "offset": 22, + "slot": "0", + "type": "t_bool", + "contract": "LockableUpgradeable", + "src": "contracts/upgradeable/LockableUpgradeable.sol:13" + }, + { + "label": "__gap", + "offset": 0, + "slot": "1", + "type": "t_array(t_uint256)50_storage", + "contract": "MulticallUpgradeable", + "src": "@openzeppelin/contracts-upgradeable/utils/MulticallUpgradeable.sol:50" + }, + { + "label": "crossDomainAdmin", + "offset": 0, + "slot": "51", + "type": "t_address", + "contract": "SpokePool", + "src": "contracts/SpokePool.sol:31" + }, + { + "label": "hubPool", + "offset": 0, + "slot": "52", + "type": "t_address", + "contract": "SpokePool", + "src": "contracts/SpokePool.sol:35" + }, + { + "label": "wrappedNativeToken", + "offset": 0, + "slot": "53", + "type": "t_contract(WETH9)13949", + "contract": "SpokePool", + "src": "contracts/SpokePool.sol:39" + }, + { + "label": "depositQuoteTimeBuffer", + "offset": 20, + "slot": "53", + "type": "t_uint32", + "contract": "SpokePool", + "src": "contracts/SpokePool.sol:43" + }, + { + "label": "numberOfDeposits", + "offset": 24, + "slot": "53", + "type": "t_uint32", + "contract": "SpokePool", + "src": "contracts/SpokePool.sol:46" + }, + { + "label": "pausedFills", + "offset": 28, + "slot": "53", + "type": "t_bool", + "contract": "SpokePool", + "src": "contracts/SpokePool.sol:49" + }, + { + "label": "pausedDeposits", + "offset": 29, + "slot": "53", + "type": "t_bool", + "contract": "SpokePool", + "src": "contracts/SpokePool.sol:50" + }, + { + "label": "rootBundles", + "offset": 0, + "slot": "54", + "type": "t_array(t_struct(RootBundle)11701_storage)dyn_storage", + "contract": "SpokePool", + "src": "contracts/SpokePool.sol:53" + }, + { + "label": "enabledDepositRoutes", + "offset": 0, + "slot": "55", + "type": "t_mapping(t_address,t_mapping(t_uint256,t_bool))", + "contract": "SpokePool", + "src": "contracts/SpokePool.sol:56" + }, + { + "label": "relayFills", + "offset": 0, + "slot": "56", + "type": "t_mapping(t_bytes32,t_uint256)", + "contract": "SpokePool", + "src": "contracts/SpokePool.sol:61" + }, + { + "label": "l1Gas", + "offset": 0, + "slot": "57", + "type": "t_uint32", + "contract": "Ovm_SpokePool", + "src": "contracts/Ovm_SpokePool.sol:22" + }, + { + "label": "l2Eth", + "offset": 4, + "slot": "57", + "type": "t_address", + "contract": "Ovm_SpokePool", + "src": "contracts/Ovm_SpokePool.sol:25" + }, + { + "label": "messenger", + "offset": 0, + "slot": "58", + "type": "t_address", + "contract": "Ovm_SpokePool", + "src": "contracts/Ovm_SpokePool.sol:28" + }, + { + "label": "tokenBridges", + "offset": 0, + "slot": "59", + "type": "t_mapping(t_address,t_address)", + "contract": "Ovm_SpokePool", + "src": "contracts/Ovm_SpokePool.sol:32" + } + ], + "types": { + "t_address": { + "label": "address", + "numberOfBytes": "20" + }, + "t_array(t_struct(RootBundle)11701_storage)dyn_storage": { + "label": "struct SpokePoolInterface.RootBundle[]", + "numberOfBytes": "32" + }, + "t_array(t_uint256)50_storage": { + "label": "uint256[50]", + "numberOfBytes": "1600" + }, + "t_bool": { + "label": "bool", + "numberOfBytes": "1" + }, + "t_bytes32": { + "label": "bytes32", + "numberOfBytes": "32" + }, + "t_contract(WETH9)13949": { + "label": "contract WETH9", + "numberOfBytes": "20" + }, + "t_mapping(t_address,t_address)": { + "label": "mapping(address => address)", + "numberOfBytes": "32" + }, + "t_mapping(t_address,t_mapping(t_uint256,t_bool))": { + "label": "mapping(address => mapping(uint256 => bool))", + "numberOfBytes": "32" + }, + "t_mapping(t_bytes32,t_uint256)": { + "label": "mapping(bytes32 => uint256)", + "numberOfBytes": "32" + }, + "t_mapping(t_uint256,t_bool)": { + "label": "mapping(uint256 => bool)", + "numberOfBytes": "32" + }, + "t_mapping(t_uint256,t_uint256)": { + "label": "mapping(uint256 => uint256)", + "numberOfBytes": "32" + }, + "t_struct(RootBundle)11701_storage": { + "label": "struct SpokePoolInterface.RootBundle", + "members": [ + { + "label": "slowRelayRoot", + "type": "t_bytes32", + "offset": 0, + "slot": "0" + }, + { + "label": "relayerRefundRoot", + "type": "t_bytes32", + "offset": 0, + "slot": "1" + }, + { + "label": "claimedBitmap", + "type": "t_mapping(t_uint256,t_uint256)", + "offset": 0, + "slot": "2" + } + ], + "numberOfBytes": "96" + }, + "t_uint256": { + "label": "uint256", + "numberOfBytes": "32" + }, + "t_uint32": { + "label": "uint32", + "numberOfBytes": "4" + }, + "t_uint8": { + "label": "uint8", + "numberOfBytes": "1" + } + } + } + } + } +} diff --git a/contracts/Arbitrum_SpokePool.sol b/contracts/Arbitrum_SpokePool.sol index 64aec54d9..252416535 100644 --- a/contracts/Arbitrum_SpokePool.sol +++ b/contracts/Arbitrum_SpokePool.sol @@ -33,15 +33,16 @@ contract Arbitrum_SpokePool is SpokePool { * @param _crossDomainAdmin Cross domain admin to set. Can be changed by admin. * @param _hubPool Hub pool address to set. Can be changed by admin. * @param _wethAddress Weth address for this network to set. - * @param timerAddress Timer address to set. + * @param _timerAddress Timer address to set. */ - constructor( + function initialize( address _l2GatewayRouter, address _crossDomainAdmin, address _hubPool, address _wethAddress, - address timerAddress - ) SpokePool(_crossDomainAdmin, _hubPool, _wethAddress, timerAddress) { + address _timerAddress + ) public initializer { + __SpokePool_init(_crossDomainAdmin, _hubPool, _wethAddress, _timerAddress); _setL2GatewayRouter(_l2GatewayRouter); } diff --git a/contracts/Boba_SpokePool.sol b/contracts/Boba_SpokePool.sol index adc549387..70bd0d417 100644 --- a/contracts/Boba_SpokePool.sol +++ b/contracts/Boba_SpokePool.sol @@ -11,19 +11,19 @@ contract Boba_SpokePool is Ovm_SpokePool { * @notice Construct the OVM Boba SpokePool. * @param _crossDomainAdmin Cross domain admin to set. Can be changed by admin. * @param _hubPool Hub pool address to set. Can be changed by admin. - * @param timerAddress Timer address to set. + * @param _timerAddress Timer address to set. */ - constructor( + function initialize( address _crossDomainAdmin, address _hubPool, - address timerAddress - ) - Ovm_SpokePool( + address _timerAddress + ) public initializer { + __OvmSpokePool_init( _crossDomainAdmin, _hubPool, 0x4200000000000000000000000000000000000006, 0xDeadDeAddeAddEAddeadDEaDDEAdDeaDDeAD0000, - timerAddress - ) - {} + _timerAddress + ); + } } diff --git a/contracts/Ethereum_SpokePool.sol b/contracts/Ethereum_SpokePool.sol index 9a6a5ddd6..4dec7eab7 100644 --- a/contracts/Ethereum_SpokePool.sol +++ b/contracts/Ethereum_SpokePool.sol @@ -2,27 +2,30 @@ pragma solidity ^0.8.0; import "./SpokePool.sol"; -import "@openzeppelin/contracts/access/Ownable.sol"; import "@openzeppelin/contracts/token/ERC20/IERC20.sol"; import "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol"; +import "@openzeppelin/contracts-upgradeable/access/OwnableUpgradeable.sol"; /** * @notice Ethereum L1 specific SpokePool. Used on Ethereum L1 to facilitate L2->L1 transfers. */ -contract Ethereum_SpokePool is SpokePool, Ownable { +contract Ethereum_SpokePool is SpokePool, OwnableUpgradeable { using SafeERC20 for IERC20; /** * @notice Construct the Ethereum SpokePool. * @param _hubPool Hub pool address to set. Can be changed by admin. * @param _wethAddress Weth address for this network to set. - * @param timerAddress Timer address to set. + * @param _timerAddress Timer address to set. */ - constructor( + function initialize( address _hubPool, address _wethAddress, - address timerAddress - ) SpokePool(msg.sender, _hubPool, _wethAddress, timerAddress) {} + address _timerAddress + ) public initializer { + __Ownable_init(); + __SpokePool_init(msg.sender, _hubPool, _wethAddress, _timerAddress); + } /************************************** * INTERNAL FUNCTIONS * diff --git a/contracts/Optimism_SpokePool.sol b/contracts/Optimism_SpokePool.sol index f877bdb04..48bf5f7bf 100644 --- a/contracts/Optimism_SpokePool.sol +++ b/contracts/Optimism_SpokePool.sol @@ -12,19 +12,19 @@ contract Optimism_SpokePool is Ovm_SpokePool { * @notice Construct the OVM Optimism SpokePool. * @param _crossDomainAdmin Cross domain admin to set. Can be changed by admin. * @param _hubPool Hub pool address to set. Can be changed by admin. - * @param timerAddress Timer address to set. + * @param _timerAddress Timer address to set. */ - constructor( + function initialize( address _crossDomainAdmin, address _hubPool, - address timerAddress - ) - Ovm_SpokePool( + address _timerAddress + ) public initializer { + __OvmSpokePool_init( _crossDomainAdmin, _hubPool, Lib_PredeployAddresses.OVM_ETH, 0x4200000000000000000000000000000000000006, - timerAddress - ) - {} + _timerAddress + ); + } } diff --git a/contracts/Ovm_SpokePool.sol b/contracts/Ovm_SpokePool.sol index c54c47c61..710cd6630 100644 --- a/contracts/Ovm_SpokePool.sol +++ b/contracts/Ovm_SpokePool.sol @@ -4,7 +4,7 @@ pragma solidity ^0.8.0; import "./SpokePool.sol"; import "./interfaces/WETH9.sol"; -import "@eth-optimism/contracts/libraries/bridge/CrossDomainEnabled.sol"; +import "@openzeppelin/contracts-upgradeable/crosschain/optimism/LibOptimismUpgradeable.sol"; import "@eth-optimism/contracts/libraries/constants/Lib_PredeployAddresses.sol"; import "@eth-optimism/contracts/L2/messaging/IL2ERC20Bridge.sol"; @@ -16,13 +16,16 @@ interface SynthetixBridgeToBase { /** * @notice OVM specific SpokePool. Uses OVM cross-domain-enabled logic to implement admin only access to functions. * Optimism and Boba each implement this spoke pool and set their chain specific contract addresses for l2Eth and l2Weth. */ -contract Ovm_SpokePool is CrossDomainEnabled, SpokePool { +contract Ovm_SpokePool is SpokePool { // "l1Gas" parameter used in call to bridge tokens from this contract back to L1 via IL2ERC20Bridge. Currently // unused by bridge but included for future compatibility. - uint32 public l1Gas = 5_000_000; + uint32 public l1Gas; // ETH is an ERC20 on OVM. - address public immutable l2Eth; + address public l2Eth; + + // Address of the Optimism L2 messenger. + address public messenger; // Stores alternative token bridges to use for L2 tokens that don't go over the standard bridge. This is needed // to support non-standard ERC20 tokens on Optimism, such as DIA and SNX which both use custom bridges. @@ -36,18 +39,18 @@ contract Ovm_SpokePool is CrossDomainEnabled, SpokePool { * @notice Construct the OVM SpokePool. * @param _crossDomainAdmin Cross domain admin to set. Can be changed by admin. * @param _hubPool Hub pool address to set. Can be changed by admin. - * @param timerAddress Timer address to set. + * @param _timerAddress Timer address to set. */ - constructor( + function __OvmSpokePool_init( address _crossDomainAdmin, address _hubPool, address _l2Eth, address _wrappedNativeToken, - address timerAddress - ) - CrossDomainEnabled(Lib_PredeployAddresses.L2_CROSS_DOMAIN_MESSENGER) - SpokePool(_crossDomainAdmin, _hubPool, _wrappedNativeToken, timerAddress) - { + address _timerAddress + ) public onlyInitializing { + l1Gas = 5_000_000; + __SpokePool_init(_crossDomainAdmin, _hubPool, _wrappedNativeToken, _timerAddress); + messenger = Lib_PredeployAddresses.L2_CROSS_DOMAIN_MESSENGER; l2Eth = _l2Eth; } @@ -169,5 +172,12 @@ contract Ovm_SpokePool is CrossDomainEnabled, SpokePool { } // Apply OVM-specific transformation to cross domain admin address on L1. - function _requireAdminSender() internal override onlyFromCrossDomainAccount(crossDomainAdmin) {} + function _requireAdminSender() internal view override { + require(LibOptimismUpgradeable.isCrossChain(messenger), "OVM_XCHAIN: messenger contract unauthenticated"); + + require( + LibOptimismUpgradeable.crossChainSender(messenger) == crossDomainAdmin, + "OVM_XCHAIN: wrong sender of cross-domain message" + ); + } } diff --git a/contracts/Polygon_SpokePool.sol b/contracts/Polygon_SpokePool.sol index 9a17950fc..6c4cd869d 100644 --- a/contracts/Polygon_SpokePool.sol +++ b/contracts/Polygon_SpokePool.sol @@ -33,7 +33,7 @@ contract Polygon_SpokePool is IFxMessageProcessor, SpokePool { // Internal variable that only flips temporarily to true upon receiving messages from L1. Used to authenticate that // the caller is the fxChild AND that the fxChild called processMessageFromRoot - bool private callValidated = false; + bool private callValidated; event PolygonTokensBridged(address indexed token, address indexed receiver, uint256 amount); event SetFxChild(address indexed newFxChild); @@ -67,16 +67,18 @@ contract Polygon_SpokePool is IFxMessageProcessor, SpokePool { * @param _hubPool Hub pool address to set. Can be changed by admin. * @param _wmaticAddress Replaces wrappedNativeToken for this network since MATIC is the native currency on polygon. * @param _fxChild FxChild contract, changeable by Admin. - * @param timerAddress Timer address to set. + * @param _timerAddress Timer address to set. */ - constructor( + function initialize( PolygonTokenBridger _polygonTokenBridger, address _crossDomainAdmin, address _hubPool, address _wmaticAddress, // Note: wmatic is used here since it is the token sent via msg.value on polygon. address _fxChild, - address timerAddress - ) SpokePool(_crossDomainAdmin, _hubPool, _wmaticAddress, timerAddress) { + address _timerAddress + ) public initializer { + callValidated = false; + __SpokePool_init(_crossDomainAdmin, _hubPool, _wmaticAddress, _timerAddress); polygonTokenBridger = _polygonTokenBridger; fxChild = _fxChild; } diff --git a/contracts/SpokePool.sol b/contracts/SpokePool.sol index fc6017e06..e257d35f6 100644 --- a/contracts/SpokePool.sol +++ b/contracts/SpokePool.sol @@ -3,16 +3,15 @@ pragma solidity ^0.8.0; import "./MerkleLib.sol"; import "./interfaces/WETH9.sol"; -import "./Lockable.sol"; import "./SpokePoolInterface.sol"; +import "./upgradeable/TestableUpgradeable.sol"; +import "./upgradeable/LockableUpgradeable.sol"; import "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol"; import "@openzeppelin/contracts/token/ERC20/IERC20.sol"; -import "@openzeppelin/contracts/utils/Address.sol"; - import "@openzeppelin/contracts/utils/cryptography/ECDSA.sol"; -import "@uma/core/contracts/common/implementation/Testable.sol"; -import "@uma/core/contracts/common/implementation/MultiCaller.sol"; +import "@openzeppelin/contracts-upgradeable/utils/AddressUpgradeable.sol"; +import "@openzeppelin/contracts-upgradeable/utils/MulticallUpgradeable.sol"; /** * @title SpokePool @@ -23,7 +22,7 @@ import "@uma/core/contracts/common/implementation/MultiCaller.sol"; * Relayers are refunded with destination tokens out of this contract after another off-chain actor, a "data worker", * submits a proof that the relayer correctly submitted a relay on this SpokePool. */ -abstract contract SpokePool is SpokePoolInterface, Testable, Lockable, MultiCaller { +abstract contract SpokePool is SpokePoolInterface, TestableUpgradeable, LockableUpgradeable, MulticallUpgradeable { using SafeERC20 for IERC20; using Address for address; @@ -37,11 +36,11 @@ abstract contract SpokePool is SpokePoolInterface, Testable, Lockable, MultiCall // Address of wrappedNativeToken contract for this network. If an origin token matches this, then the caller can // optionally instruct this contract to wrap native tokens when depositing (ie ETH->WETH or MATIC->WMATIC). - WETH9 public immutable wrappedNativeToken; + WETH9 public wrappedNativeToken; // Any deposit quote times greater than or less than this value to the current contract time is blocked. Forces // caller to use an approximately "current" realized fee. Defaults to 10 minutes. - uint32 public depositQuoteTimeBuffer = 600; + uint32 public depositQuoteTimeBuffer; // Count of deposits is used to construct a unique deposit identifier for this spoke pool. uint32 public numberOfDeposits; @@ -133,14 +132,18 @@ abstract contract SpokePool is SpokePoolInterface, Testable, Lockable, MultiCall * @param _crossDomainAdmin Cross domain admin to set. Can be changed by admin. * @param _hubPool Hub pool address to set. Can be changed by admin. * @param _wrappedNativeTokenAddress wrappedNativeToken address for this network to set. - * @param timerAddress Timer address to set. + * @param _timerAddress Timer address to set. */ - constructor( + function __SpokePool_init( address _crossDomainAdmin, address _hubPool, address _wrappedNativeTokenAddress, - address timerAddress - ) Testable(timerAddress) { + address _timerAddress + ) public onlyInitializing { + __Lockable_init(); + __Multicall_init(); + depositQuoteTimeBuffer = 600; + __Testable_init(_timerAddress); _setCrossDomainAdmin(_crossDomainAdmin); _setHubPool(_hubPool); wrappedNativeToken = WETH9(_wrappedNativeTokenAddress); @@ -882,4 +885,14 @@ abstract contract SpokePool is SpokePoolInterface, Testable, Lockable, MultiCall // Added to enable the this contract to receive native token (ETH). Used when unwrapping wrappedNativeToken. receive() external payable {} + + // TODO: + /** + * @dev This empty reserved space is put in place to allow future versions to add new + * variables without shifting down storage in the inheritance chain. OZ adds this to all of their base contracts + * to allow for variables to be added to them without having to add them to each child contract. They have added + * a __gap such that each contract has the same 50 slots of uint256 slots. + * See https://docs.openzeppelin.com/contracts/4.x/upgradeable#storage_gaps + */ + // uint256[50] private __gap; } diff --git a/contracts/ZkSync_SpokePool.sol b/contracts/ZkSync_SpokePool.sol index 368933409..73dcc7eae 100644 --- a/contracts/ZkSync_SpokePool.sol +++ b/contracts/ZkSync_SpokePool.sol @@ -40,16 +40,17 @@ contract ZkSync_SpokePool is SpokePool { * @param _crossDomainAdmin Cross domain admin to set. Can be changed by admin. * @param _hubPool Hub pool address to set. Can be changed by admin. * @param _wethAddress Weth address for this network to set. - * @param timerAddress Timer address to set. + * @param _timerAddress Timer address to set. */ - constructor( + function initialize( ZkBridgeLike _zkErc20Bridge, ZkBridgeLike _zkEthBridge, address _crossDomainAdmin, address _hubPool, address _wethAddress, - address timerAddress - ) SpokePool(_crossDomainAdmin, _hubPool, _wethAddress, timerAddress) { + address _timerAddress + ) public initializer { + __SpokePool_init(_crossDomainAdmin, _hubPool, _wethAddress, _timerAddress); _setZkBridges(_zkErc20Bridge, _zkEthBridge); } diff --git a/contracts/test/MockSpokePool.sol b/contracts/test/MockSpokePool.sol index 3fe65ffc2..66a87b1e8 100644 --- a/contracts/test/MockSpokePool.sol +++ b/contracts/test/MockSpokePool.sol @@ -11,13 +11,16 @@ import "../SpokePoolInterface.sol"; contract MockSpokePool is SpokePool { uint256 private chainId_; + // Note: function must be renamed because it uses same params as BaseContract.initialize(). // solhint-disable-next-line no-empty-blocks - constructor( + function initialize( address _crossDomainAdmin, address _hubPool, address _wethAddress, - address timerAddress - ) SpokePool(_crossDomainAdmin, _hubPool, _wethAddress, timerAddress) {} // solhint-disable-line no-empty-blocks + address _timerAddress + ) public initializer { + __SpokePool_init(_crossDomainAdmin, _hubPool, _wethAddress, _timerAddress); + } // solhint-disable-next-line no-empty-blocks function _bridgeTokensToHubPool(RelayerRefundLeaf memory relayerRefundLeaf) internal override {} diff --git a/contracts/upgradeable/LockableUpgradeable.sol b/contracts/upgradeable/LockableUpgradeable.sol new file mode 100644 index 000000000..b24ae295d --- /dev/null +++ b/contracts/upgradeable/LockableUpgradeable.sol @@ -0,0 +1,80 @@ +// SPDX-License-Identifier: AGPL-3.0-only +pragma solidity ^0.8.0; + +import "@openzeppelin/contracts-upgradeable/proxy/utils/Initializable.sol"; + +/** + * @title A contract that provides modifiers to prevent reentrancy to state-changing and view-only methods. This contract + * is inspired by https://github.com/OpenZeppelin/openzeppelin-contracts/blob/master/contracts/utils/ReentrancyGuard.sol + * and https://github.com/balancer-labs/balancer-core/blob/master/contracts/BPool.sol. + * @notice This is an upgradeable version of Testable that replaces the constructor with an initializer function. + */ +contract LockableUpgradeable is Initializable { + bool private _notEntered; + + function __Lockable_init() public onlyInitializing { + // Storing an initial non-zero value makes deployment a bit more expensive, but in exchange the refund on every + // call to nonReentrant will be lower in amount. Since refunds are capped to a percentage of the total + // transaction's gas, it is best to keep them low in cases like this one, to increase the likelihood of the full + // refund coming into effect. + _notEntered = true; + } + + /** + * @dev Prevents a contract from calling itself, directly or indirectly. + * Calling a `nonReentrant` function from another `nonReentrant` function is not supported. It is possible to + * prevent this from happening by making the `nonReentrant` function external, and making it call a `private` + * function that does the actual state modification. + */ + modifier nonReentrant() { + _preEntranceCheck(); + _preEntranceSet(); + _; + _postEntranceReset(); + } + + /** + * @dev Designed to prevent a view-only method from being re-entered during a call to a `nonReentrant()` state-changing method. + */ + modifier nonReentrantView() { + _preEntranceCheck(); + _; + } + + // Internal methods are used to avoid copying the require statement's bytecode to every `nonReentrant()` method. + // On entry into a function, `_preEntranceCheck()` should always be called to check if the function is being + // re-entered. Then, if the function modifies state, it should call `_postEntranceSet()`, perform its logic, and + // then call `_postEntranceReset()`. + // View-only methods can simply call `_preEntranceCheck()` to make sure that it is not being re-entered. + function _preEntranceCheck() internal view { + // On the first call to nonReentrant, _notEntered will be true + require(_notEntered, "ReentrancyGuard: reentrant call"); + } + + function _preEntranceSet() internal { + // Any calls to nonReentrant after this point will fail + _notEntered = false; + } + + function _postEntranceReset() internal { + // By storing the original value once again, a refund is triggered (see + // https://eips.ethereum.org/EIPS/eip-2200) + _notEntered = true; + } + + // These functions are intended to be used by child contracts to temporarily disable and re-enable the guard. + // Intended use: + // _startReentrantGuardDisabled(); + // ... + // _endReentrantGuardDisabled(); + // + // IMPORTANT: these should NEVER be used in a method that isn't inside a nonReentrant block. Otherwise, it's + // possible to permanently lock your contract. + function _startReentrantGuardDisabled() internal { + _notEntered = true; + } + + function _endReentrantGuardDisabled() internal { + _notEntered = false; + } +} diff --git a/contracts/upgradeable/TestableUpgradeable.sol b/contracts/upgradeable/TestableUpgradeable.sol new file mode 100644 index 000000000..94c08affe --- /dev/null +++ b/contracts/upgradeable/TestableUpgradeable.sol @@ -0,0 +1,54 @@ +// SPDX-License-Identifier: AGPL-3.0-only +pragma solidity ^0.8.0; + +import "@uma/core/contracts/common/implementation/Timer.sol"; +import "@openzeppelin/contracts-upgradeable/proxy/utils/Initializable.sol"; + +/** + * @title Base class that provides time overrides, but only if being run in test mode. + * @notice This is an upgradeable version of Testable that replaces the constructor with an initializer function. + */ +abstract contract TestableUpgradeable is Initializable { + // If the contract is being run in production, then `timerAddress` will be the 0x0 address. + // Note: this variable should be set on construction and never modified. + address public timerAddress; + + /** + * @notice Constructs the Testable contract. Called by child contracts. + * @param _timerAddress Contract that stores the current time in a testing environment. + * Must be set to 0x0 for production environments that use live time. + */ + function __Testable_init(address _timerAddress) public onlyInitializing { + timerAddress = _timerAddress; + } + + /** + * @notice Reverts if not running in test mode. + */ + modifier onlyIfTest() { + require(timerAddress != address(0x0)); + _; + } + + /** + * @notice Sets the current time. + * @dev Will revert if not running in test mode. + * @param time timestamp to set current Testable time to. + */ + function setCurrentTime(uint256 time) external onlyIfTest { + Timer(timerAddress).setCurrentTime(time); + } + + /** + * @notice Gets the current time. Will return the last time set in `setCurrentTime` if running in test mode. + * Otherwise, it will return the block timestamp. + * @return uint for the current Testable timestamp. + */ + function getCurrentTime() public view virtual returns (uint256) { + if (timerAddress != address(0x0)) { + return Timer(timerAddress).getCurrentTime(); + } else { + return block.timestamp; // solhint-disable-line not-rely-on-time + } + } +} diff --git a/deployments/deployments.json b/deployments/deployments.json index ae7bb7987..823bd3e47 100644 --- a/deployments/deployments.json +++ b/deployments/deployments.json @@ -1,8 +1,8 @@ { "1": { "AcrossConfigStore": { "address": "0x3B03509645713718B78951126E0A6de6f10043f5", "blockNumber": 14717196 }, - "AcrossMerkleDistributor": { "address": "0xE50b2cEAC4f60E840Ae513924033E753e2366487", "blockNumber": 15976846 }, - "Arbitrum_Adapter": { "address": "0x937958992799bF4A9a656E6596FD10d7Da5c2216", "blockNumber": 14704380 }, + "Arbitrum_Adapter": { "address": "0x29528780E29abb8Af95a5e5a125b94766987543F", "blockNumber": 16243583 }, + "Arbitrum_RescueAdapter": { "address": "0xC6fA0a4EBd802c01157d6E7fB1bbd2ae196ae375", "blockNumber": 16233939 }, "Boba_Adapter": { "address": "0x33B0Ec794c15D6Cc705818E70d4CaCe7bCfB5Af3", "blockNumber": 14716798 }, "Ethereum_Adapter": { "address": "0x527E872a5c3f0C7c24Fe33F2593cFB890a285084", "blockNumber": 14704381 }, "SpokePool": { "address": "0x4D9079Bb4165aeb4084c526a32695dCfd2F77381", "blockNumber": 14819486 }, diff --git a/hardhat.config.ts b/hardhat.config.ts index 5adecca9b..146d5e207 100644 --- a/hardhat.config.ts +++ b/hardhat.config.ts @@ -10,6 +10,7 @@ import "@matterlabs/hardhat-zksync-solc"; import "hardhat-gas-reporter"; import "solidity-coverage"; import "hardhat-deploy"; +import "@openzeppelin/hardhat-upgrades"; // Custom tasks to add to HRE. require("./tasks/enableL1TokenAcrossEcosystem"); diff --git a/package.json b/package.json index b6123e3e0..418307893 100644 --- a/package.json +++ b/package.json @@ -35,6 +35,7 @@ "@defi-wonderland/smock": "^2.0.7", "@eth-optimism/contracts": "^0.5.11", "@openzeppelin/contracts": "^4.7.3", + "@openzeppelin/contracts-upgradeable": "^4.8.0", "@uma/common": "^2.29.0", "@uma/contracts-node": "^0.3.18", "@uma/core": "^2.41.0", @@ -47,6 +48,7 @@ "@nomiclabs/hardhat-ethers": "^2.0.5", "@nomiclabs/hardhat-etherscan": "^3.0.3", "@nomiclabs/hardhat-waffle": "^2.0.3", + "@openzeppelin/hardhat-upgrades": "^1.22.0", "@typechain/ethers-v5": "^7.0.1", "@typechain/hardhat": "^2.3.0", "@types/chai": "^4.2.21", diff --git a/test/chain-specific-spokepools/Arbitrum_SpokePool.ts b/test/chain-specific-spokepools/Arbitrum_SpokePool.ts index e07747d1d..e63157d0e 100644 --- a/test/chain-specific-spokepools/Arbitrum_SpokePool.ts +++ b/test/chain-specific-spokepools/Arbitrum_SpokePool.ts @@ -1,6 +1,6 @@ import { mockTreeRoot, amountToReturn, amountHeldByPool, zeroAddress } from "../constants"; import { ethers, expect, Contract, FakeContract, SignerWithAddress, createFake, toWei } from "../utils"; -import { getContractFactory, seedContract, avmL1ToL2Alias, hre, toBN, toBNWei } from "../utils"; +import { getContractFactory, seedContract, avmL1ToL2Alias, hre } from "../utils"; import { hubPoolFixture } from "../fixtures/HubPool.Fixture"; import { constructSingleRelayerRefundTree } from "../MerkleLib.utils"; @@ -23,9 +23,11 @@ describe("Arbitrum Spoke Pool", function () { l2GatewayRouter = await createFake("L2GatewayRouter"); - arbitrumSpokePool = await ( - await getContractFactory("Arbitrum_SpokePool", owner) - ).deploy(l2GatewayRouter.address, owner.address, hubPool.address, l2Weth, timer.address); + arbitrumSpokePool = await hre.upgrades.deployProxy( + await getContractFactory("Arbitrum_SpokePool", owner), + [l2GatewayRouter.address, owner.address, hubPool.address, l2Weth, timer.address], + { unsafeAllow: ["delegatecall"] } + ); await seedContract(arbitrumSpokePool, relayer, [dai], weth, amountHeldByPool); await arbitrumSpokePool.connect(crossDomainAlias).whitelistToken(l2Dai, dai.address); diff --git a/test/chain-specific-spokepools/Ethereum_SpokePool.ts b/test/chain-specific-spokepools/Ethereum_SpokePool.ts index cd194b0e4..e3ca09a66 100644 --- a/test/chain-specific-spokepools/Ethereum_SpokePool.ts +++ b/test/chain-specific-spokepools/Ethereum_SpokePool.ts @@ -1,5 +1,5 @@ import { mockTreeRoot, amountToReturn, amountHeldByPool } from "../constants"; -import { ethers, expect, Contract, SignerWithAddress } from "../utils"; +import { ethers, expect, Contract, SignerWithAddress, hre } from "../utils"; import { getContractFactory, seedContract } from "../utils"; import { hubPoolFixture } from "../fixtures/HubPool.Fixture"; import { constructSingleRelayerRefundTree } from "../MerkleLib.utils"; @@ -13,9 +13,11 @@ describe("Ethereum Spoke Pool", function () { [owner, relayer, rando] = await ethers.getSigners(); ({ weth, dai, hubPool, timer } = await hubPoolFixture()); - spokePool = await ( - await getContractFactory("Ethereum_SpokePool", owner) - ).deploy(hubPool.address, weth.address, timer.address); + spokePool = await hre.upgrades.deployProxy( + await getContractFactory("Ethereum_SpokePool", owner), + [hubPool.address, weth.address, timer.address], + { unsafeAllow: ["delegatecall"] } + ); // Seed spoke pool with tokens that it should transfer to the hub pool // via the _bridgeTokensToHubPool() internal call. diff --git a/test/chain-specific-spokepools/Optimism_SpokePool.ts b/test/chain-specific-spokepools/Optimism_SpokePool.ts index abd4eac2e..0eb3c41f4 100644 --- a/test/chain-specific-spokepools/Optimism_SpokePool.ts +++ b/test/chain-specific-spokepools/Optimism_SpokePool.ts @@ -27,9 +27,11 @@ describe("Optimism Spoke Pool", function () { }); await owner.sendTransaction({ to: crossDomainMessenger.address, value: toWei("1") }); - optimismSpokePool = await ( - await getContractFactory("Optimism_SpokePool", owner) - ).deploy(owner.address, hubPool.address, timer.address); + optimismSpokePool = await hre.upgrades.deployProxy( + await getContractFactory("Optimism_SpokePool", owner), + [owner.address, hubPool.address, timer.address], + { unsafeAllow: ["delegatecall"] } + ); await seedContract(optimismSpokePool, relayer, [dai], weth, amountHeldByPool); }); diff --git a/test/chain-specific-spokepools/Polygon_SpokePool.ts b/test/chain-specific-spokepools/Polygon_SpokePool.ts index c19d8f0ce..c28d49b85 100644 --- a/test/chain-specific-spokepools/Polygon_SpokePool.ts +++ b/test/chain-specific-spokepools/Polygon_SpokePool.ts @@ -1,6 +1,6 @@ import { mockTreeRoot, amountToReturn, amountHeldByPool, zeroAddress, TokenRolesEnum } from "../constants"; import { ethers, expect, Contract, SignerWithAddress, getContractFactory, createFake } from "../utils"; -import { seedContract, toWei, randomBigNumber, seedWallet, FakeContract } from "../utils"; +import { seedContract, toWei, randomBigNumber, seedWallet, FakeContract, hre } from "../utils"; import { hubPoolFixture } from "../fixtures/HubPool.Fixture"; import { constructSingleRelayerRefundTree } from "../MerkleLib.utils"; import { randomBytes } from "crypto"; @@ -31,9 +31,11 @@ describe("Polygon Spoke Pool", function () { dai = await (await getContractFactory("PolygonERC20Test", owner)).deploy(); await dai.addMember(TokenRolesEnum.MINTER, owner.address); - polygonSpokePool = await ( - await getContractFactory("Polygon_SpokePool", owner) - ).deploy(polygonTokenBridger.address, owner.address, hubPool.address, weth.address, fxChild.address, timer.address); + polygonSpokePool = await hre.upgrades.deployProxy( + await getContractFactory("Polygon_SpokePool", owner), + [polygonTokenBridger.address, owner.address, hubPool.address, weth.address, fxChild.address, timer.address], + { unsafeAllow: ["delegatecall"] } + ); await seedContract(polygonSpokePool, relayer, [dai], weth, amountHeldByPool); await seedWallet(owner, [], weth, toWei("1")); diff --git a/test/fixtures/HubPool.Fixture.ts b/test/fixtures/HubPool.Fixture.ts index 3a073b91e..d2807271a 100644 --- a/test/fixtures/HubPool.Fixture.ts +++ b/test/fixtures/HubPool.Fixture.ts @@ -42,18 +42,22 @@ export async function deployHubPool(ethers: any) { // Deploy a mock chain adapter and add it as the chainAdapter for the test chainId. Set the SpokePool to address 0. const mockAdapter = await (await getContractFactory("Mock_Adapter", signer)).deploy(); - const mockSpoke = await ( - await getContractFactory("MockSpokePool", signer) - ).deploy(crossChainAdmin.address, hubPool.address, weth.address, parentFixture.timer.address); + const mockSpoke = await hre.upgrades.deployProxy( + await getContractFactory("MockSpokePool", signer), + [crossChainAdmin.address, hubPool.address, weth.address, parentFixture.timer.address], + { unsafeAllow: ["delegatecall"] } + ); await hubPool.setCrossChainContracts(repaymentChainId, mockAdapter.address, mockSpoke.address); await hubPool.setCrossChainContracts(originChainId, mockAdapter.address, mockSpoke.address); // Deploy a new set of mocks for mainnet. const mainnetChainId = await hre.getChainId(); const mockAdapterMainnet = await (await getContractFactory("Mock_Adapter", signer)).deploy(); - const mockSpokeMainnet = await ( - await getContractFactory("MockSpokePool", signer) - ).deploy(crossChainAdmin.address, hubPool.address, weth.address, parentFixture.timer.address); + const mockSpokeMainnet = await hre.upgrades.deployProxy( + await getContractFactory("MockSpokePool", signer), + [crossChainAdmin.address, hubPool.address, weth.address, parentFixture.timer.address], + { unsafeAllow: ["delegatecall"] } + ); await hubPool.setCrossChainContracts(mainnetChainId, mockAdapterMainnet.address, mockSpokeMainnet.address); // Deploy mock l2 tokens for each token created before and whitelist the routes. diff --git a/test/fixtures/SpokePool.Fixture.ts b/test/fixtures/SpokePool.Fixture.ts index 152a56325..d4917e2f7 100644 --- a/test/fixtures/SpokePool.Fixture.ts +++ b/test/fixtures/SpokePool.Fixture.ts @@ -33,9 +33,11 @@ export async function deploySpokePool(ethers: any): Promise<{ await destErc20.addMember(consts.TokenRolesEnum.MINTER, deployerWallet.address); // Deploy the pool - const spokePool = await ( - await getContractFactory("MockSpokePool", deployerWallet) - ).deploy(crossChainAdmin.address, hubPool.address, weth.address, timer.address); + const spokePool = await hre.upgrades.deployProxy( + await getContractFactory("MockSpokePool", deployerWallet), + [crossChainAdmin.address, hubPool.address, weth.address, timer.address], + { unsafeAllow: ["delegatecall"] } + ); await spokePool.setChainId(consts.destinationChainId); return { timer, weth, erc20, spokePool, unwhitelistedErc20, destErc20 }; diff --git a/yarn.lock b/yarn.lock index 55453c04b..e556040b3 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2084,6 +2084,11 @@ resolved "https://registry.yarnpkg.com/@openzeppelin/contracts-upgradeable/-/contracts-upgradeable-4.7.3.tgz#f1d606e2827d409053f3e908ba4eb8adb1dd6995" integrity sha512-+wuegAMaLcZnLCJIvrVUDzA9z/Wp93f0Dla/4jJvIhijRrPabjQbZe6fWiECLaJyfn5ci9fqf9vTw3xpQOad2A== +"@openzeppelin/contracts-upgradeable@^4.8.0": + version "4.8.0" + resolved "https://registry.yarnpkg.com/@openzeppelin/contracts-upgradeable/-/contracts-upgradeable-4.8.0.tgz#26688982f46969018e3ed3199e72a07c8d114275" + integrity sha512-5GeFgqMiDlqGT8EdORadp1ntGF0qzWZLmEY7Wbp/yVhN7/B3NNzCxujuI77ktlyG81N3CUZP8cZe3ZAQ/cW10w== + "@openzeppelin/contracts@3.4.1-solc-0.7-2": version "3.4.1-solc-0.7-2" resolved "https://registry.yarnpkg.com/@openzeppelin/contracts/-/contracts-3.4.1-solc-0.7-2.tgz#371c67ebffe50f551c3146a9eec5fe6ffe862e92" @@ -2109,6 +2114,29 @@ resolved "https://registry.yarnpkg.com/@openzeppelin/contracts/-/contracts-4.7.3.tgz#939534757a81f8d69cc854c7692805684ff3111e" integrity sha512-dGRS0agJzu8ybo44pCIf3xBaPQN/65AIXNgK8+4gzKd5kbvlqyxryUYVLJv7fK98Seyd2hDZzVEHSWAh0Bt1Yw== +"@openzeppelin/hardhat-upgrades@^1.22.0": + version "1.22.0" + resolved "https://registry.yarnpkg.com/@openzeppelin/hardhat-upgrades/-/hardhat-upgrades-1.22.0.tgz#2a432c72a428a9f277201646bc1a248021538f06" + integrity sha512-1qyZnDaxl0C8tne7ykNRa/fxw3FrNCY2M3fGuCiQW5DDkJoXhLgm3JVsXwl6X7q9mQSrik4vgBbI3ErmxmZTYg== + dependencies: + "@openzeppelin/upgrades-core" "^1.20.0" + chalk "^4.1.0" + debug "^4.1.1" + proper-lockfile "^4.1.1" + +"@openzeppelin/upgrades-core@^1.20.0": + version "1.20.6" + resolved "https://registry.yarnpkg.com/@openzeppelin/upgrades-core/-/upgrades-core-1.20.6.tgz#74f43d600151b8fda6e2d375f46ae0da55922620" + integrity sha512-KWdtlahm+iunlAlzLsdpBueanwEx0LLPfAkDL1p0C4SPjMiUqHHFlyGtmmWwdiqDpJ//605vfwkd5RqfnFrHSg== + dependencies: + cbor "^8.0.0" + chalk "^4.1.0" + compare-versions "^5.0.0" + debug "^4.1.1" + ethereumjs-util "^7.0.3" + proper-lockfile "^4.1.1" + solidity-ast "^0.4.15" + "@openzeppelin/upgrades-core@^1.7.6": version "1.13.0" resolved "https://registry.yarnpkg.com/@openzeppelin/upgrades-core/-/upgrades-core-1.13.0.tgz#fa6ce5e236020e3553115f173528fcdaa67d66e2" @@ -5321,6 +5349,11 @@ compare-versions@^4.0.0: resolved "https://registry.yarnpkg.com/compare-versions/-/compare-versions-4.1.3.tgz#8f7b8966aef7dc4282b45dfa6be98434fc18a1a4" integrity sha512-WQfnbDcrYnGr55UwbxKiQKASnTtNnaAWVi8jZyy8NTpVAXWACSne8lMD1iaIo9AiU6mnuLvSVshCzewVuWxHUg== +compare-versions@^5.0.0: + version "5.0.3" + resolved "https://registry.yarnpkg.com/compare-versions/-/compare-versions-5.0.3.tgz#a9b34fea217472650ef4a2651d905f42c28ebfd7" + integrity sha512-4UZlZP8Z99MGEY+Ovg/uJxJuvoXuN4M6B3hKaiackiHrgzQFEe3diJi1mf1PNHbFujM7FvLrK2bpgIaImbtZ1A== + component-emitter@^1.2.1: version "1.3.0" resolved "https://registry.yarnpkg.com/component-emitter/-/component-emitter-1.3.0.tgz#16e4070fba8ae29b679f2215853ee181ab2eabc0" From d409fcac9a73acc9d771703ffcb00e2cad1ece6b Mon Sep 17 00:00:00 2001 From: nicholaspai Date: Wed, 4 Jan 2023 17:53:37 -0500 Subject: [PATCH 02/24] commit --- .gitignore | 3 +++ test/gas-analytics/HubPool.RootExecution.ts | 18 +++++++++++------- 2 files changed, 14 insertions(+), 7 deletions(-) diff --git a/.gitignore b/.gitignore index 846a473b8..c3ce999de 100644 --- a/.gitignore +++ b/.gitignore @@ -12,5 +12,8 @@ artifacts cache-zk artifacts-zk +# Upgradeability files +.openzeppelin + # IDE .idea diff --git a/test/gas-analytics/HubPool.RootExecution.ts b/test/gas-analytics/HubPool.RootExecution.ts index b44f157a0..3be0451b7 100644 --- a/test/gas-analytics/HubPool.RootExecution.ts +++ b/test/gas-analytics/HubPool.RootExecution.ts @@ -93,16 +93,20 @@ describe("Gas Analytics: HubPool Root Bundle Execution", function () { } const adapter = await (await getContractFactory("Mock_Adapter", owner)).deploy(); - const spoke = await ( - await getContractFactory("MockSpokePool", owner) - ).deploy(randomAddress(), hubPool.address, randomAddress(), consts.zeroAddress); - await hubPool.setCrossChainContracts(hubPoolChainId, adapter.address, spoke.address); + const spokeMainnet = await hre.upgrades.deployProxy( + await getContractFactory("MockSpokePool", owner), + [randomAddress(), hubPool.address, randomAddress(), timer.address], + { unsafeAllow: ["delegatecall"] } + ); + await hubPool.setCrossChainContracts(hubPoolChainId, adapter.address, spokeMainnet.address); for (let i = 0; i < REFUND_CHAIN_COUNT; i++) { const adapter = await (await getContractFactory("Mock_Adapter", owner)).deploy(); - const spoke = await ( - await getContractFactory("MockSpokePool", owner) - ).deploy(randomAddress(), hubPool.address, randomAddress(), consts.zeroAddress); + const spoke = await hre.upgrades.deployProxy( + await getContractFactory("MockSpokePool", owner), + [randomAddress(), hubPool.address, randomAddress(), consts.zeroAddress], + { unsafeAllow: ["delegatecall"] } + ); await hubPool.setCrossChainContracts(i, adapter.address, spoke.address); await Promise.all( l1Tokens.map(async (token) => { From 034e801b2914a331a92f8f832d21bf6a296f9c64 Mon Sep 17 00:00:00 2001 From: nicholaspai <9457025+nicholaspai@users.noreply.github.com> Date: Wed, 4 Jan 2023 17:54:11 -0500 Subject: [PATCH 03/24] Delete unknown-31337.json --- .openzeppelin/unknown-31337.json | 978 ------------------------------- 1 file changed, 978 deletions(-) delete mode 100644 .openzeppelin/unknown-31337.json diff --git a/.openzeppelin/unknown-31337.json b/.openzeppelin/unknown-31337.json deleted file mode 100644 index 8d2f07000..000000000 --- a/.openzeppelin/unknown-31337.json +++ /dev/null @@ -1,978 +0,0 @@ -{ - "manifestVersion": "3.2", - "admin": { - "address": "0xE6E340D132b5f46d1e472DebcD681B2aBc16e57E", - "txHash": "0xc341fc9407037da37b6cad0c33a8e8447f32a6b8c2946623c80acc03fe388acd" - }, - "proxies": [ - { - "address": "0x610178dA211FEF7D417bC0e6FeD39F05609AD788", - "txHash": "0x534e8623468ee49a4d9263839ff0577aadf325bc4bfd7397f4ac40d50c78298c", - "kind": "transparent" - }, - { - "address": "0xc3e53F4d16Ae77Db1c982e75a937B9f60FE63690", - "txHash": "0xed8ddd1b8f8e808244b487990c4db11f26204264031b0e22b44b4447377febbc", - "kind": "transparent" - }, - { - "address": "0x1613beB3B2C4f22Ee086B2b38C1476A3cE7f78E8", - "txHash": "0x91463d6bf6d0413585ee11409698c690e5307900cfb032395f7c38f4e6c23aa5", - "kind": "transparent" - }, - { - "address": "0x4826533B4897376654Bb4d4AD88B7faFD0C98528", - "txHash": "0x0078e2fd83b0cecd5e1215678056988a831b7efb067e4eb2fd45771448f02437", - "kind": "transparent" - }, - { - "address": "0x99bbA657f2BbC93c02D617f8bA121cB8Fc104Acf", - "txHash": "0xbc2c64f36e837cda6e4ebebdcd1eb61723f57c96b3ce51a9ae42ee8b7ee2e8c0", - "kind": "transparent" - }, - { - "address": "0x8f86403A4DE0BB5791fa46B8e795C547942fE4Cf", - "txHash": "0xf840b231d39aadf7f571e3b9fd4e2bef9c4f3a8739f65d21d86a7f35defa2b2b", - "kind": "transparent" - }, - { - "address": "0x4EE6eCAD1c2Dae9f525404De8555724e3c35d07B", - "txHash": "0x91ba6ed03f13cac78ae8c95057d9f308ac9274ead22d583650108ffd71827702", - "kind": "transparent" - } - ], - "impls": { - "2f7ef459103dc43c5c66ab95dd1e6fbd6abb8e61452b27e4042c2291cb6439f5": { - "address": "0x67d269191c92Caf3cD7723F116c85e6E9bf55933", - "txHash": "0xf351b900ae39e3e1356756b7d636e8ff6953d895392212dc524d85c20e04287c", - "layout": { - "solcVersion": "0.8.13", - "storage": [ - { - "label": "_initialized", - "offset": 0, - "slot": "0", - "type": "t_uint8", - "contract": "Initializable", - "src": "@openzeppelin/contracts-upgradeable/proxy/utils/Initializable.sol:62", - "retypedFrom": "bool" - }, - { - "label": "_initializing", - "offset": 1, - "slot": "0", - "type": "t_bool", - "contract": "Initializable", - "src": "@openzeppelin/contracts-upgradeable/proxy/utils/Initializable.sol:67" - }, - { - "label": "timerAddress", - "offset": 2, - "slot": "0", - "type": "t_address", - "contract": "TestableUpgradeable", - "src": "contracts/upgradeable/TestableUpgradeable.sol:14" - }, - { - "label": "_notEntered", - "offset": 22, - "slot": "0", - "type": "t_bool", - "contract": "LockableUpgradeable", - "src": "contracts/upgradeable/LockableUpgradeable.sol:13" - }, - { - "label": "__gap", - "offset": 0, - "slot": "1", - "type": "t_array(t_uint256)50_storage", - "contract": "MulticallUpgradeable", - "src": "@openzeppelin/contracts-upgradeable/utils/MulticallUpgradeable.sol:50" - }, - { - "label": "crossDomainAdmin", - "offset": 0, - "slot": "51", - "type": "t_address", - "contract": "SpokePool", - "src": "contracts/SpokePool.sol:31" - }, - { - "label": "hubPool", - "offset": 0, - "slot": "52", - "type": "t_address", - "contract": "SpokePool", - "src": "contracts/SpokePool.sol:35" - }, - { - "label": "wrappedNativeToken", - "offset": 0, - "slot": "53", - "type": "t_contract(WETH9)13949", - "contract": "SpokePool", - "src": "contracts/SpokePool.sol:39" - }, - { - "label": "depositQuoteTimeBuffer", - "offset": 20, - "slot": "53", - "type": "t_uint32", - "contract": "SpokePool", - "src": "contracts/SpokePool.sol:43" - }, - { - "label": "numberOfDeposits", - "offset": 24, - "slot": "53", - "type": "t_uint32", - "contract": "SpokePool", - "src": "contracts/SpokePool.sol:46" - }, - { - "label": "pausedFills", - "offset": 28, - "slot": "53", - "type": "t_bool", - "contract": "SpokePool", - "src": "contracts/SpokePool.sol:49" - }, - { - "label": "pausedDeposits", - "offset": 29, - "slot": "53", - "type": "t_bool", - "contract": "SpokePool", - "src": "contracts/SpokePool.sol:50" - }, - { - "label": "rootBundles", - "offset": 0, - "slot": "54", - "type": "t_array(t_struct(RootBundle)11701_storage)dyn_storage", - "contract": "SpokePool", - "src": "contracts/SpokePool.sol:53" - }, - { - "label": "enabledDepositRoutes", - "offset": 0, - "slot": "55", - "type": "t_mapping(t_address,t_mapping(t_uint256,t_bool))", - "contract": "SpokePool", - "src": "contracts/SpokePool.sol:56" - }, - { - "label": "relayFills", - "offset": 0, - "slot": "56", - "type": "t_mapping(t_bytes32,t_uint256)", - "contract": "SpokePool", - "src": "contracts/SpokePool.sol:61" - }, - { - "label": "chainId_", - "offset": 0, - "slot": "57", - "type": "t_uint256", - "contract": "MockSpokePool", - "src": "contracts/test/MockSpokePool.sol:12" - } - ], - "types": { - "t_address": { - "label": "address", - "numberOfBytes": "20" - }, - "t_array(t_struct(RootBundle)11701_storage)dyn_storage": { - "label": "struct SpokePoolInterface.RootBundle[]", - "numberOfBytes": "32" - }, - "t_array(t_uint256)50_storage": { - "label": "uint256[50]", - "numberOfBytes": "1600" - }, - "t_bool": { - "label": "bool", - "numberOfBytes": "1" - }, - "t_bytes32": { - "label": "bytes32", - "numberOfBytes": "32" - }, - "t_contract(WETH9)13949": { - "label": "contract WETH9", - "numberOfBytes": "20" - }, - "t_mapping(t_address,t_mapping(t_uint256,t_bool))": { - "label": "mapping(address => mapping(uint256 => bool))", - "numberOfBytes": "32" - }, - "t_mapping(t_bytes32,t_uint256)": { - "label": "mapping(bytes32 => uint256)", - "numberOfBytes": "32" - }, - "t_mapping(t_uint256,t_bool)": { - "label": "mapping(uint256 => bool)", - "numberOfBytes": "32" - }, - "t_mapping(t_uint256,t_uint256)": { - "label": "mapping(uint256 => uint256)", - "numberOfBytes": "32" - }, - "t_struct(RootBundle)11701_storage": { - "label": "struct SpokePoolInterface.RootBundle", - "members": [ - { - "label": "slowRelayRoot", - "type": "t_bytes32", - "offset": 0, - "slot": "0" - }, - { - "label": "relayerRefundRoot", - "type": "t_bytes32", - "offset": 0, - "slot": "1" - }, - { - "label": "claimedBitmap", - "type": "t_mapping(t_uint256,t_uint256)", - "offset": 0, - "slot": "2" - } - ], - "numberOfBytes": "96" - }, - "t_uint256": { - "label": "uint256", - "numberOfBytes": "32" - }, - "t_uint32": { - "label": "uint32", - "numberOfBytes": "4" - }, - "t_uint8": { - "label": "uint8", - "numberOfBytes": "1" - } - } - } - }, - "9e79df8f4aae0c6d8aa0ae14b3c0abc4ff9d9d71e662c1e75a91c57a92dc0c99": { - "address": "0x70e0bA845a1A0F2DA3359C97E0285013525FFC49", - "txHash": "0xcd7cd844d7a083141f9bdb7d600f4d533a53522b889f85e44d876bc3135cf228", - "layout": { - "solcVersion": "0.8.13", - "storage": [ - { - "label": "_initialized", - "offset": 0, - "slot": "0", - "type": "t_uint8", - "contract": "Initializable", - "src": "@openzeppelin/contracts-upgradeable/proxy/utils/Initializable.sol:62", - "retypedFrom": "bool" - }, - { - "label": "_initializing", - "offset": 1, - "slot": "0", - "type": "t_bool", - "contract": "Initializable", - "src": "@openzeppelin/contracts-upgradeable/proxy/utils/Initializable.sol:67" - }, - { - "label": "timerAddress", - "offset": 2, - "slot": "0", - "type": "t_address", - "contract": "TestableUpgradeable", - "src": "contracts/upgradeable/TestableUpgradeable.sol:14" - }, - { - "label": "_notEntered", - "offset": 22, - "slot": "0", - "type": "t_bool", - "contract": "LockableUpgradeable", - "src": "contracts/upgradeable/LockableUpgradeable.sol:13" - }, - { - "label": "__gap", - "offset": 0, - "slot": "1", - "type": "t_array(t_uint256)50_storage", - "contract": "MulticallUpgradeable", - "src": "@openzeppelin/contracts-upgradeable/utils/MulticallUpgradeable.sol:50" - }, - { - "label": "crossDomainAdmin", - "offset": 0, - "slot": "51", - "type": "t_address", - "contract": "SpokePool", - "src": "contracts/SpokePool.sol:31" - }, - { - "label": "hubPool", - "offset": 0, - "slot": "52", - "type": "t_address", - "contract": "SpokePool", - "src": "contracts/SpokePool.sol:35" - }, - { - "label": "wrappedNativeToken", - "offset": 0, - "slot": "53", - "type": "t_contract(WETH9)13949", - "contract": "SpokePool", - "src": "contracts/SpokePool.sol:39" - }, - { - "label": "depositQuoteTimeBuffer", - "offset": 20, - "slot": "53", - "type": "t_uint32", - "contract": "SpokePool", - "src": "contracts/SpokePool.sol:43" - }, - { - "label": "numberOfDeposits", - "offset": 24, - "slot": "53", - "type": "t_uint32", - "contract": "SpokePool", - "src": "contracts/SpokePool.sol:46" - }, - { - "label": "pausedFills", - "offset": 28, - "slot": "53", - "type": "t_bool", - "contract": "SpokePool", - "src": "contracts/SpokePool.sol:49" - }, - { - "label": "pausedDeposits", - "offset": 29, - "slot": "53", - "type": "t_bool", - "contract": "SpokePool", - "src": "contracts/SpokePool.sol:50" - }, - { - "label": "rootBundles", - "offset": 0, - "slot": "54", - "type": "t_array(t_struct(RootBundle)11701_storage)dyn_storage", - "contract": "SpokePool", - "src": "contracts/SpokePool.sol:53" - }, - { - "label": "enabledDepositRoutes", - "offset": 0, - "slot": "55", - "type": "t_mapping(t_address,t_mapping(t_uint256,t_bool))", - "contract": "SpokePool", - "src": "contracts/SpokePool.sol:56" - }, - { - "label": "relayFills", - "offset": 0, - "slot": "56", - "type": "t_mapping(t_bytes32,t_uint256)", - "contract": "SpokePool", - "src": "contracts/SpokePool.sol:61" - }, - { - "label": "__gap", - "offset": 0, - "slot": "57", - "type": "t_array(t_uint256)50_storage", - "contract": "ContextUpgradeable", - "src": "@openzeppelin/contracts-upgradeable/utils/ContextUpgradeable.sol:36" - }, - { - "label": "_owner", - "offset": 0, - "slot": "107", - "type": "t_address", - "contract": "OwnableUpgradeable", - "src": "@openzeppelin/contracts-upgradeable/access/OwnableUpgradeable.sol:22" - }, - { - "label": "__gap", - "offset": 0, - "slot": "108", - "type": "t_array(t_uint256)49_storage", - "contract": "OwnableUpgradeable", - "src": "@openzeppelin/contracts-upgradeable/access/OwnableUpgradeable.sol:94" - } - ], - "types": { - "t_address": { - "label": "address", - "numberOfBytes": "20" - }, - "t_array(t_struct(RootBundle)11701_storage)dyn_storage": { - "label": "struct SpokePoolInterface.RootBundle[]", - "numberOfBytes": "32" - }, - "t_array(t_uint256)49_storage": { - "label": "uint256[49]", - "numberOfBytes": "1568" - }, - "t_array(t_uint256)50_storage": { - "label": "uint256[50]", - "numberOfBytes": "1600" - }, - "t_bool": { - "label": "bool", - "numberOfBytes": "1" - }, - "t_bytes32": { - "label": "bytes32", - "numberOfBytes": "32" - }, - "t_contract(WETH9)13949": { - "label": "contract WETH9", - "numberOfBytes": "20" - }, - "t_mapping(t_address,t_mapping(t_uint256,t_bool))": { - "label": "mapping(address => mapping(uint256 => bool))", - "numberOfBytes": "32" - }, - "t_mapping(t_bytes32,t_uint256)": { - "label": "mapping(bytes32 => uint256)", - "numberOfBytes": "32" - }, - "t_mapping(t_uint256,t_bool)": { - "label": "mapping(uint256 => bool)", - "numberOfBytes": "32" - }, - "t_mapping(t_uint256,t_uint256)": { - "label": "mapping(uint256 => uint256)", - "numberOfBytes": "32" - }, - "t_struct(RootBundle)11701_storage": { - "label": "struct SpokePoolInterface.RootBundle", - "members": [ - { - "label": "slowRelayRoot", - "type": "t_bytes32", - "offset": 0, - "slot": "0" - }, - { - "label": "relayerRefundRoot", - "type": "t_bytes32", - "offset": 0, - "slot": "1" - }, - { - "label": "claimedBitmap", - "type": "t_mapping(t_uint256,t_uint256)", - "offset": 0, - "slot": "2" - } - ], - "numberOfBytes": "96" - }, - "t_uint256": { - "label": "uint256", - "numberOfBytes": "32" - }, - "t_uint32": { - "label": "uint32", - "numberOfBytes": "4" - }, - "t_uint8": { - "label": "uint8", - "numberOfBytes": "1" - } - } - } - }, - "76772fa813ad046f1f7b5384a007db6e47c0ee7d15b353fcc30ea0698138be98": { - "address": "0x0E801D84Fa97b50751Dbf25036d067dCf18858bF", - "txHash": "0x7ce7036e1d187889cb28c832816f8d605d6cc674f2872c8f5b8edaf364d90260", - "layout": { - "solcVersion": "0.8.13", - "storage": [ - { - "label": "_initialized", - "offset": 0, - "slot": "0", - "type": "t_uint8", - "contract": "Initializable", - "src": "@openzeppelin/contracts-upgradeable/proxy/utils/Initializable.sol:62", - "retypedFrom": "bool" - }, - { - "label": "_initializing", - "offset": 1, - "slot": "0", - "type": "t_bool", - "contract": "Initializable", - "src": "@openzeppelin/contracts-upgradeable/proxy/utils/Initializable.sol:67" - }, - { - "label": "timerAddress", - "offset": 2, - "slot": "0", - "type": "t_address", - "contract": "TestableUpgradeable", - "src": "contracts/upgradeable/TestableUpgradeable.sol:14" - }, - { - "label": "_notEntered", - "offset": 22, - "slot": "0", - "type": "t_bool", - "contract": "LockableUpgradeable", - "src": "contracts/upgradeable/LockableUpgradeable.sol:13" - }, - { - "label": "__gap", - "offset": 0, - "slot": "1", - "type": "t_array(t_uint256)50_storage", - "contract": "MulticallUpgradeable", - "src": "@openzeppelin/contracts-upgradeable/utils/MulticallUpgradeable.sol:50" - }, - { - "label": "crossDomainAdmin", - "offset": 0, - "slot": "51", - "type": "t_address", - "contract": "SpokePool", - "src": "contracts/SpokePool.sol:31" - }, - { - "label": "hubPool", - "offset": 0, - "slot": "52", - "type": "t_address", - "contract": "SpokePool", - "src": "contracts/SpokePool.sol:35" - }, - { - "label": "wrappedNativeToken", - "offset": 0, - "slot": "53", - "type": "t_contract(WETH9)13949", - "contract": "SpokePool", - "src": "contracts/SpokePool.sol:39" - }, - { - "label": "depositQuoteTimeBuffer", - "offset": 20, - "slot": "53", - "type": "t_uint32", - "contract": "SpokePool", - "src": "contracts/SpokePool.sol:43" - }, - { - "label": "numberOfDeposits", - "offset": 24, - "slot": "53", - "type": "t_uint32", - "contract": "SpokePool", - "src": "contracts/SpokePool.sol:46" - }, - { - "label": "pausedFills", - "offset": 28, - "slot": "53", - "type": "t_bool", - "contract": "SpokePool", - "src": "contracts/SpokePool.sol:49" - }, - { - "label": "pausedDeposits", - "offset": 29, - "slot": "53", - "type": "t_bool", - "contract": "SpokePool", - "src": "contracts/SpokePool.sol:50" - }, - { - "label": "rootBundles", - "offset": 0, - "slot": "54", - "type": "t_array(t_struct(RootBundle)11701_storage)dyn_storage", - "contract": "SpokePool", - "src": "contracts/SpokePool.sol:53" - }, - { - "label": "enabledDepositRoutes", - "offset": 0, - "slot": "55", - "type": "t_mapping(t_address,t_mapping(t_uint256,t_bool))", - "contract": "SpokePool", - "src": "contracts/SpokePool.sol:56" - }, - { - "label": "relayFills", - "offset": 0, - "slot": "56", - "type": "t_mapping(t_bytes32,t_uint256)", - "contract": "SpokePool", - "src": "contracts/SpokePool.sol:61" - }, - { - "label": "fxChild", - "offset": 0, - "slot": "57", - "type": "t_address", - "contract": "Polygon_SpokePool", - "src": "contracts/Polygon_SpokePool.sol:28" - }, - { - "label": "polygonTokenBridger", - "offset": 0, - "slot": "58", - "type": "t_contract(PolygonTokenBridger)9777", - "contract": "Polygon_SpokePool", - "src": "contracts/Polygon_SpokePool.sol:32" - }, - { - "label": "callValidated", - "offset": 20, - "slot": "58", - "type": "t_bool", - "contract": "Polygon_SpokePool", - "src": "contracts/Polygon_SpokePool.sol:36" - } - ], - "types": { - "t_address": { - "label": "address", - "numberOfBytes": "20" - }, - "t_array(t_struct(RootBundle)11701_storage)dyn_storage": { - "label": "struct SpokePoolInterface.RootBundle[]", - "numberOfBytes": "32" - }, - "t_array(t_uint256)50_storage": { - "label": "uint256[50]", - "numberOfBytes": "1600" - }, - "t_bool": { - "label": "bool", - "numberOfBytes": "1" - }, - "t_bytes32": { - "label": "bytes32", - "numberOfBytes": "32" - }, - "t_contract(PolygonTokenBridger)9777": { - "label": "contract PolygonTokenBridger", - "numberOfBytes": "20" - }, - "t_contract(WETH9)13949": { - "label": "contract WETH9", - "numberOfBytes": "20" - }, - "t_mapping(t_address,t_mapping(t_uint256,t_bool))": { - "label": "mapping(address => mapping(uint256 => bool))", - "numberOfBytes": "32" - }, - "t_mapping(t_bytes32,t_uint256)": { - "label": "mapping(bytes32 => uint256)", - "numberOfBytes": "32" - }, - "t_mapping(t_uint256,t_bool)": { - "label": "mapping(uint256 => bool)", - "numberOfBytes": "32" - }, - "t_mapping(t_uint256,t_uint256)": { - "label": "mapping(uint256 => uint256)", - "numberOfBytes": "32" - }, - "t_struct(RootBundle)11701_storage": { - "label": "struct SpokePoolInterface.RootBundle", - "members": [ - { - "label": "slowRelayRoot", - "type": "t_bytes32", - "offset": 0, - "slot": "0" - }, - { - "label": "relayerRefundRoot", - "type": "t_bytes32", - "offset": 0, - "slot": "1" - }, - { - "label": "claimedBitmap", - "type": "t_mapping(t_uint256,t_uint256)", - "offset": 0, - "slot": "2" - } - ], - "numberOfBytes": "96" - }, - "t_uint256": { - "label": "uint256", - "numberOfBytes": "32" - }, - "t_uint32": { - "label": "uint32", - "numberOfBytes": "4" - }, - "t_uint8": { - "label": "uint8", - "numberOfBytes": "1" - } - } - } - }, - "91a5423a1099272c1dfb5d932d736ba7d3658d3cc520630692fce28c3bc95577": { - "address": "0x4826533B4897376654Bb4d4AD88B7faFD0C98528", - "txHash": "0xc437cce67bdb372e2523b924752f35decc36be056165762d15aa92acaeaf8e68", - "layout": { - "solcVersion": "0.8.13", - "storage": [ - { - "label": "_initialized", - "offset": 0, - "slot": "0", - "type": "t_uint8", - "contract": "Initializable", - "src": "@openzeppelin/contracts-upgradeable/proxy/utils/Initializable.sol:62", - "retypedFrom": "bool" - }, - { - "label": "_initializing", - "offset": 1, - "slot": "0", - "type": "t_bool", - "contract": "Initializable", - "src": "@openzeppelin/contracts-upgradeable/proxy/utils/Initializable.sol:67" - }, - { - "label": "timerAddress", - "offset": 2, - "slot": "0", - "type": "t_address", - "contract": "TestableUpgradeable", - "src": "contracts/upgradeable/TestableUpgradeable.sol:14" - }, - { - "label": "_notEntered", - "offset": 22, - "slot": "0", - "type": "t_bool", - "contract": "LockableUpgradeable", - "src": "contracts/upgradeable/LockableUpgradeable.sol:13" - }, - { - "label": "__gap", - "offset": 0, - "slot": "1", - "type": "t_array(t_uint256)50_storage", - "contract": "MulticallUpgradeable", - "src": "@openzeppelin/contracts-upgradeable/utils/MulticallUpgradeable.sol:50" - }, - { - "label": "crossDomainAdmin", - "offset": 0, - "slot": "51", - "type": "t_address", - "contract": "SpokePool", - "src": "contracts/SpokePool.sol:31" - }, - { - "label": "hubPool", - "offset": 0, - "slot": "52", - "type": "t_address", - "contract": "SpokePool", - "src": "contracts/SpokePool.sol:35" - }, - { - "label": "wrappedNativeToken", - "offset": 0, - "slot": "53", - "type": "t_contract(WETH9)13949", - "contract": "SpokePool", - "src": "contracts/SpokePool.sol:39" - }, - { - "label": "depositQuoteTimeBuffer", - "offset": 20, - "slot": "53", - "type": "t_uint32", - "contract": "SpokePool", - "src": "contracts/SpokePool.sol:43" - }, - { - "label": "numberOfDeposits", - "offset": 24, - "slot": "53", - "type": "t_uint32", - "contract": "SpokePool", - "src": "contracts/SpokePool.sol:46" - }, - { - "label": "pausedFills", - "offset": 28, - "slot": "53", - "type": "t_bool", - "contract": "SpokePool", - "src": "contracts/SpokePool.sol:49" - }, - { - "label": "pausedDeposits", - "offset": 29, - "slot": "53", - "type": "t_bool", - "contract": "SpokePool", - "src": "contracts/SpokePool.sol:50" - }, - { - "label": "rootBundles", - "offset": 0, - "slot": "54", - "type": "t_array(t_struct(RootBundle)11701_storage)dyn_storage", - "contract": "SpokePool", - "src": "contracts/SpokePool.sol:53" - }, - { - "label": "enabledDepositRoutes", - "offset": 0, - "slot": "55", - "type": "t_mapping(t_address,t_mapping(t_uint256,t_bool))", - "contract": "SpokePool", - "src": "contracts/SpokePool.sol:56" - }, - { - "label": "relayFills", - "offset": 0, - "slot": "56", - "type": "t_mapping(t_bytes32,t_uint256)", - "contract": "SpokePool", - "src": "contracts/SpokePool.sol:61" - }, - { - "label": "l1Gas", - "offset": 0, - "slot": "57", - "type": "t_uint32", - "contract": "Ovm_SpokePool", - "src": "contracts/Ovm_SpokePool.sol:22" - }, - { - "label": "l2Eth", - "offset": 4, - "slot": "57", - "type": "t_address", - "contract": "Ovm_SpokePool", - "src": "contracts/Ovm_SpokePool.sol:25" - }, - { - "label": "messenger", - "offset": 0, - "slot": "58", - "type": "t_address", - "contract": "Ovm_SpokePool", - "src": "contracts/Ovm_SpokePool.sol:28" - }, - { - "label": "tokenBridges", - "offset": 0, - "slot": "59", - "type": "t_mapping(t_address,t_address)", - "contract": "Ovm_SpokePool", - "src": "contracts/Ovm_SpokePool.sol:32" - } - ], - "types": { - "t_address": { - "label": "address", - "numberOfBytes": "20" - }, - "t_array(t_struct(RootBundle)11701_storage)dyn_storage": { - "label": "struct SpokePoolInterface.RootBundle[]", - "numberOfBytes": "32" - }, - "t_array(t_uint256)50_storage": { - "label": "uint256[50]", - "numberOfBytes": "1600" - }, - "t_bool": { - "label": "bool", - "numberOfBytes": "1" - }, - "t_bytes32": { - "label": "bytes32", - "numberOfBytes": "32" - }, - "t_contract(WETH9)13949": { - "label": "contract WETH9", - "numberOfBytes": "20" - }, - "t_mapping(t_address,t_address)": { - "label": "mapping(address => address)", - "numberOfBytes": "32" - }, - "t_mapping(t_address,t_mapping(t_uint256,t_bool))": { - "label": "mapping(address => mapping(uint256 => bool))", - "numberOfBytes": "32" - }, - "t_mapping(t_bytes32,t_uint256)": { - "label": "mapping(bytes32 => uint256)", - "numberOfBytes": "32" - }, - "t_mapping(t_uint256,t_bool)": { - "label": "mapping(uint256 => bool)", - "numberOfBytes": "32" - }, - "t_mapping(t_uint256,t_uint256)": { - "label": "mapping(uint256 => uint256)", - "numberOfBytes": "32" - }, - "t_struct(RootBundle)11701_storage": { - "label": "struct SpokePoolInterface.RootBundle", - "members": [ - { - "label": "slowRelayRoot", - "type": "t_bytes32", - "offset": 0, - "slot": "0" - }, - { - "label": "relayerRefundRoot", - "type": "t_bytes32", - "offset": 0, - "slot": "1" - }, - { - "label": "claimedBitmap", - "type": "t_mapping(t_uint256,t_uint256)", - "offset": 0, - "slot": "2" - } - ], - "numberOfBytes": "96" - }, - "t_uint256": { - "label": "uint256", - "numberOfBytes": "32" - }, - "t_uint32": { - "label": "uint32", - "numberOfBytes": "4" - }, - "t_uint8": { - "label": "uint8", - "numberOfBytes": "1" - } - } - } - } - } -} From f4185dfba5326a40d46b47ef337d479bb7a42748 Mon Sep 17 00:00:00 2001 From: nicholaspai <9457025+nicholaspai@users.noreply.github.com> Date: Wed, 4 Jan 2023 17:56:43 -0500 Subject: [PATCH 04/24] Update deployments.json --- deployments/deployments.json | 1 + 1 file changed, 1 insertion(+) diff --git a/deployments/deployments.json b/deployments/deployments.json index 823bd3e47..e3c48c72a 100644 --- a/deployments/deployments.json +++ b/deployments/deployments.json @@ -1,6 +1,7 @@ { "1": { "AcrossConfigStore": { "address": "0x3B03509645713718B78951126E0A6de6f10043f5", "blockNumber": 14717196 }, + "AcrossMerkleDistributor": { "address": "0xE50b2cEAC4f60E840Ae513924033E753e2366487", "blockNumber": 15976846 }, "Arbitrum_Adapter": { "address": "0x29528780E29abb8Af95a5e5a125b94766987543F", "blockNumber": 16243583 }, "Arbitrum_RescueAdapter": { "address": "0xC6fA0a4EBd802c01157d6E7fB1bbd2ae196ae375", "blockNumber": 16233939 }, "Boba_Adapter": { "address": "0x33B0Ec794c15D6Cc705818E70d4CaCe7bCfB5Af3", "blockNumber": 14716798 }, From 6db44cd1188a152629076b26dbba9d1c0f39bfa4 Mon Sep 17 00:00:00 2001 From: nicholaspai Date: Thu, 5 Jan 2023 08:49:11 -0500 Subject: [PATCH 05/24] Update SpokePool.sol --- contracts/SpokePool.sol | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/contracts/SpokePool.sol b/contracts/SpokePool.sol index e257d35f6..2a0c8e926 100644 --- a/contracts/SpokePool.sol +++ b/contracts/SpokePool.sol @@ -24,7 +24,7 @@ import "@openzeppelin/contracts-upgradeable/utils/MulticallUpgradeable.sol"; */ abstract contract SpokePool is SpokePoolInterface, TestableUpgradeable, LockableUpgradeable, MulticallUpgradeable { using SafeERC20 for IERC20; - using Address for address; + using AddressUpgradeable for address; // Address of the L1 contract that acts as the owner of this SpokePool. If this contract is deployed on Ethereum, // then this address should be set to the same owner as the HubPool and the whole system. From 38b3adf753c07d056cafdd1d1875ad49f0279594 Mon Sep 17 00:00:00 2001 From: nicholaspai Date: Thu, 5 Jan 2023 12:42:26 -0500 Subject: [PATCH 06/24] Make implementation UUPS compatible UUPS proxies allow the implementation contract to define the conditions under which an upgrade can be called, compared to a Transparent proxy in which only the owner of the proxy can upgrade. This allows the SpokePool upgrades to be triggered by the cross domain admin via the HubPool --- contracts/SpokePool.sol | 33 +++++++++++--- contracts/test/MockSpokePool.sol | 9 ++-- contracts/test/MockSpokePoolV2.sol | 26 +++++++++++ hardhat.config.ts | 2 + test/SpokePool.Upgrades.ts | 44 +++++++++++++++++++ .../Arbitrum_SpokePool.ts | 14 +++++- .../Ethereum_SpokePool.ts | 14 +++++- .../Optimism_SpokePool.ts | 15 ++++++- .../Polygon_SpokePool.ts | 22 +++++++++- test/fixtures/SpokePool.Fixture.ts | 2 +- 10 files changed, 166 insertions(+), 15 deletions(-) create mode 100644 contracts/test/MockSpokePoolV2.sol create mode 100644 test/SpokePool.Upgrades.ts diff --git a/contracts/SpokePool.sol b/contracts/SpokePool.sol index 2a0c8e926..64b7cad37 100644 --- a/contracts/SpokePool.sol +++ b/contracts/SpokePool.sol @@ -11,7 +11,8 @@ import "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol"; import "@openzeppelin/contracts/token/ERC20/IERC20.sol"; import "@openzeppelin/contracts/utils/cryptography/ECDSA.sol"; import "@openzeppelin/contracts-upgradeable/utils/AddressUpgradeable.sol"; -import "@openzeppelin/contracts-upgradeable/utils/MulticallUpgradeable.sol"; +import "@openzeppelin/contracts-upgradeable/proxy/utils/UUPSUpgradeable.sol"; +import "@uma/core/contracts/common/implementation/MultiCaller.sol"; /** * @title SpokePool @@ -22,7 +23,13 @@ import "@openzeppelin/contracts-upgradeable/utils/MulticallUpgradeable.sol"; * Relayers are refunded with destination tokens out of this contract after another off-chain actor, a "data worker", * submits a proof that the relayer correctly submitted a relay on this SpokePool. */ -abstract contract SpokePool is SpokePoolInterface, TestableUpgradeable, LockableUpgradeable, MulticallUpgradeable { +abstract contract SpokePool is + SpokePoolInterface, + UUPSUpgradeable, + TestableUpgradeable, + LockableUpgradeable, + MultiCaller +{ using SafeERC20 for IERC20; using AddressUpgradeable for address; @@ -127,6 +134,16 @@ abstract contract SpokePool is SpokePoolInterface, TestableUpgradeable, Lockable event PausedDeposits(bool isPaused); event PausedFills(bool isPaused); + /** + * Do not leave an implementation contract uninitialized. An uninitialized implementation contract can be + * taken over by an attacker, which may impact the proxy. To prevent the implementation contract from being + * used, you should invoke the _disableInitializers function in the constructor to automatically lock it when + * it is deployed: */ + /// @custom:oz-upgrades-unsafe-allow constructor + constructor() { + _disableInitializers(); + } + /** * @notice Construct the base SpokePool. * @param _crossDomainAdmin Cross domain admin to set. Can be changed by admin. @@ -140,8 +157,8 @@ abstract contract SpokePool is SpokePoolInterface, TestableUpgradeable, Lockable address _wrappedNativeTokenAddress, address _timerAddress ) public onlyInitializing { + __UUPSUpgradeable_init(); __Lockable_init(); - __Multicall_init(); depositQuoteTimeBuffer = 600; __Testable_init(_timerAddress); _setCrossDomainAdmin(_crossDomainAdmin); @@ -153,8 +170,11 @@ abstract contract SpokePool is SpokePoolInterface, TestableUpgradeable, Lockable * MODIFIERS * ****************************************/ - // Implementing contract needs to override _requireAdminSender() to ensure that admin functions are protected - // appropriately. + /** + * @dev Function that should revert when `msg.sender` is not authorized to upgrade the contract. Called by + * {upgradeTo} and {upgradeToAndCall}. + * @dev This should be set to cross domain admin for specific SpokePool. + */ modifier onlyAdmin() { _requireAdminSender(); _; @@ -174,6 +194,9 @@ abstract contract SpokePool is SpokePoolInterface, TestableUpgradeable, Lockable * ADMIN FUNCTIONS * **************************************/ + // Allows cross domain admin to upgrade UUPS proxy implementation. + function _authorizeUpgrade(address newImplementation) internal override onlyAdmin {} + /** * @notice Pauses deposit and fill functions. This is intended to be used during upgrades or when * something goes awry. diff --git a/contracts/test/MockSpokePool.sol b/contracts/test/MockSpokePool.sol index 66a87b1e8..766fd1eda 100644 --- a/contracts/test/MockSpokePool.sol +++ b/contracts/test/MockSpokePool.sol @@ -2,30 +2,29 @@ pragma solidity ^0.8.0; import "../SpokePool.sol"; -import "../SpokePoolInterface.sol"; +import "@openzeppelin/contracts-upgradeable/access/OwnableUpgradeable.sol"; /** * @title MockSpokePool * @notice Implements abstract contract for testing. */ -contract MockSpokePool is SpokePool { +contract MockSpokePool is SpokePool, OwnableUpgradeable { uint256 private chainId_; - // Note: function must be renamed because it uses same params as BaseContract.initialize(). - // solhint-disable-next-line no-empty-blocks function initialize( address _crossDomainAdmin, address _hubPool, address _wethAddress, address _timerAddress ) public initializer { + __Ownable_init(); __SpokePool_init(_crossDomainAdmin, _hubPool, _wethAddress, _timerAddress); } // solhint-disable-next-line no-empty-blocks function _bridgeTokensToHubPool(RelayerRefundLeaf memory relayerRefundLeaf) internal override {} - function _requireAdminSender() internal override {} // solhint-disable-line no-empty-blocks + function _requireAdminSender() internal override onlyOwner {} // solhint-disable-line no-empty-blocks function chainId() public view override(SpokePool) returns (uint256) { // If chainId_ is set then return it, else do nothing and return the parent chainId(). diff --git a/contracts/test/MockSpokePoolV2.sol b/contracts/test/MockSpokePoolV2.sol new file mode 100644 index 000000000..4d329402a --- /dev/null +++ b/contracts/test/MockSpokePoolV2.sol @@ -0,0 +1,26 @@ +//SPDX-License-Identifier: Unlicense +pragma solidity ^0.8.0; + +import "./MockSpokePool.sol"; +import "@openzeppelin/contracts-upgradeable/token/ERC20/ERC20Upgradeable.sol"; + +/** + * @title MockSpokePoolV2 + * @notice Upgrades MockSpokePool to be an ERC20, for no practical reason other than to demonstrate + * upgradeability options + */ +contract MockSpokePoolV2 is MockSpokePool, ERC20Upgradeable { + event NewEvent(bool value); + + // Demonstrative of how we could reset state variables in a V2 contract conveniently while initializing new + // modules. The `reinitializer` modifier is required to create new Initializable contracts. + function reinitialize(address _hubPool) public reinitializer(2) { + _setHubPool(_hubPool); + __ERC20_init("V2", "V2"); + } + + // Demonstrative new function we could add in a V2 contract. + function emitEvent() external { + emit NewEvent(true); + } +} diff --git a/hardhat.config.ts b/hardhat.config.ts index 146d5e207..66fac7f96 100644 --- a/hardhat.config.ts +++ b/hardhat.config.ts @@ -38,6 +38,8 @@ const config: HardhatUserConfig = { compilers: [{ version: solcVersion, settings: { optimizer: { enabled: true, runs: 1000000 } } }], overrides: { "contracts/HubPool.sol": LARGE_CONTRACT_COMPILER_SETTINGS, + "contracts/Optimism_SpokePool.sol": LARGE_CONTRACT_COMPILER_SETTINGS, + "contracts/test/MockSpokePoolV2.sol": LARGE_CONTRACT_COMPILER_SETTINGS, }, }, zksolc: { diff --git a/test/SpokePool.Upgrades.ts b/test/SpokePool.Upgrades.ts new file mode 100644 index 000000000..0ac89ec94 --- /dev/null +++ b/test/SpokePool.Upgrades.ts @@ -0,0 +1,44 @@ +import { expect, ethers, Contract, SignerWithAddress, hre, randomAddress } from "./utils"; +import { spokePoolFixture } from "./fixtures/SpokePool.Fixture"; + +let spokePool: Contract; +let owner: SignerWithAddress, rando: SignerWithAddress; + +describe("SpokePool Upgrade Functions", async function () { + beforeEach(async function () { + [owner, rando] = await ethers.getSigners(); + ({ spokePool } = await spokePoolFixture()); + }); + it("Can upgrade", async function () { + const spokePoolV2 = await hre.upgrades.deployImplementation(await ethers.getContractFactory("MockSpokePoolV2"), { + unsafeAllow: ["delegatecall"], + kind: "uups", + }); + const spokePoolV2Contract = (await ethers.getContractFactory("MockSpokePoolV2")).attach(spokePoolV2 as string); + + const newHubPool = randomAddress(); + const reinitializeData = await spokePoolV2Contract.populateTransaction.reinitialize(newHubPool); + + // Only owner can upgrade. + await expect(spokePool.connect(rando).upgradeToAndCall(spokePoolV2, reinitializeData.data)).to.be.revertedWith( + "Ownable: caller is not the owner" + ); + await spokePool.connect(owner).upgradeToAndCall(spokePoolV2, reinitializeData.data); + + // Hub pool should be changed. + const spokePoolContract = (await ethers.getContractFactory("MockSpokePoolV2")).attach(spokePool.address); + expect(await spokePoolContract.hubPool()).to.equal(newHubPool); + + // Contract should be an ERC20 now. + expect(await spokePoolContract.totalSupply()).to.equal(0); + + // Can't reinitialize again. + expect(spokePoolContract.reinitialize(newHubPool)).to.be.revertedWith( + "Contract instance has already been initialized" + ); + + // Can call new function. + expect(() => spokePool.emitEvent()).to.throw(/spokePool.emitEvent is not a function/); + await expect(spokePoolContract.emitEvent()).to.emit(spokePoolContract, "NewEvent").withArgs(true); + }); +}); diff --git a/test/chain-specific-spokepools/Arbitrum_SpokePool.ts b/test/chain-specific-spokepools/Arbitrum_SpokePool.ts index e63157d0e..8e4710600 100644 --- a/test/chain-specific-spokepools/Arbitrum_SpokePool.ts +++ b/test/chain-specific-spokepools/Arbitrum_SpokePool.ts @@ -26,13 +26,25 @@ describe("Arbitrum Spoke Pool", function () { arbitrumSpokePool = await hre.upgrades.deployProxy( await getContractFactory("Arbitrum_SpokePool", owner), [l2GatewayRouter.address, owner.address, hubPool.address, l2Weth, timer.address], - { unsafeAllow: ["delegatecall"] } + { unsafeAllow: ["delegatecall"], kind: "uups" } ); await seedContract(arbitrumSpokePool, relayer, [dai], weth, amountHeldByPool); await arbitrumSpokePool.connect(crossDomainAlias).whitelistToken(l2Dai, dai.address); }); + it("Only cross domain owner upgrade logic contract", async function () { + // TODO: Could also use upgrades.prepareUpgrade but I'm unclear of differences + const implementation = await hre.upgrades.deployImplementation( + await getContractFactory("Arbitrum_SpokePool", owner), + { unsafeAllow: ["delegatecall"], kind: "uups" } + ); + + // upgradeTo fails unless called by cross domain admin + await expect(arbitrumSpokePool.upgradeTo(implementation)).to.be.reverted; + await arbitrumSpokePool.connect(crossDomainAlias).upgradeTo(implementation); + }); + it("Only cross domain owner can set L2GatewayRouter", async function () { await expect(arbitrumSpokePool.setL2GatewayRouter(rando.address)).to.be.reverted; await arbitrumSpokePool.connect(crossDomainAlias).setL2GatewayRouter(rando.address); diff --git a/test/chain-specific-spokepools/Ethereum_SpokePool.ts b/test/chain-specific-spokepools/Ethereum_SpokePool.ts index e3ca09a66..5f5d79509 100644 --- a/test/chain-specific-spokepools/Ethereum_SpokePool.ts +++ b/test/chain-specific-spokepools/Ethereum_SpokePool.ts @@ -16,7 +16,7 @@ describe("Ethereum Spoke Pool", function () { spokePool = await hre.upgrades.deployProxy( await getContractFactory("Ethereum_SpokePool", owner), [hubPool.address, weth.address, timer.address], - { unsafeAllow: ["delegatecall"] } + { unsafeAllow: ["delegatecall"], kind: "uups" } ); // Seed spoke pool with tokens that it should transfer to the hub pool @@ -24,6 +24,18 @@ describe("Ethereum Spoke Pool", function () { await seedContract(spokePool, relayer, [dai], weth, amountHeldByPool); }); + it("Only cross domain owner upgrade logic contract", async function () { + // TODO: Could also use upgrades.prepareUpgrade but I'm unclear of differences + const implementation = await hre.upgrades.deployImplementation( + await getContractFactory("Ethereum_SpokePool", owner), + { unsafeAllow: ["delegatecall"], kind: "uups" } + ); + + // upgradeTo fails unless called by cross domain admin + await expect(spokePool.connect(rando).upgradeTo(implementation)).to.be.reverted; + await spokePool.connect(owner).upgradeTo(implementation); + }); + it("Only owner can set the cross domain admin", async function () { await expect(spokePool.connect(rando).setCrossDomainAdmin(rando.address)).to.be.reverted; await spokePool.connect(owner).setCrossDomainAdmin(rando.address); diff --git a/test/chain-specific-spokepools/Optimism_SpokePool.ts b/test/chain-specific-spokepools/Optimism_SpokePool.ts index 0eb3c41f4..631d27a42 100644 --- a/test/chain-specific-spokepools/Optimism_SpokePool.ts +++ b/test/chain-specific-spokepools/Optimism_SpokePool.ts @@ -30,12 +30,25 @@ describe("Optimism Spoke Pool", function () { optimismSpokePool = await hre.upgrades.deployProxy( await getContractFactory("Optimism_SpokePool", owner), [owner.address, hubPool.address, timer.address], - { unsafeAllow: ["delegatecall"] } + { unsafeAllow: ["delegatecall"], kind: "uups" } ); await seedContract(optimismSpokePool, relayer, [dai], weth, amountHeldByPool); }); + it("Only cross domain owner upgrade logic contract", async function () { + // TODO: Could also use upgrades.prepareUpgrade but I'm unclear of differences + const implementation = await hre.upgrades.deployImplementation( + await getContractFactory("Optimism_SpokePool", owner), + { unsafeAllow: ["delegatecall"], kind: "uups" } + ); + + // upgradeTo fails unless called by cross domain admin + await expect(optimismSpokePool.upgradeTo(implementation)).to.be.reverted; + crossDomainMessenger.xDomainMessageSender.returns(owner.address); + await optimismSpokePool.connect(crossDomainMessenger.wallet).upgradeTo(implementation); + }); + it("Only cross domain owner can set l1GasLimit", async function () { await expect(optimismSpokePool.setL1GasLimit(1337)).to.be.reverted; crossDomainMessenger.xDomainMessageSender.returns(owner.address); diff --git a/test/chain-specific-spokepools/Polygon_SpokePool.ts b/test/chain-specific-spokepools/Polygon_SpokePool.ts index c28d49b85..5ae7ac301 100644 --- a/test/chain-specific-spokepools/Polygon_SpokePool.ts +++ b/test/chain-specific-spokepools/Polygon_SpokePool.ts @@ -34,13 +34,33 @@ describe("Polygon Spoke Pool", function () { polygonSpokePool = await hre.upgrades.deployProxy( await getContractFactory("Polygon_SpokePool", owner), [polygonTokenBridger.address, owner.address, hubPool.address, weth.address, fxChild.address, timer.address], - { unsafeAllow: ["delegatecall"] } + { unsafeAllow: ["delegatecall"], kind: "uups" } ); await seedContract(polygonSpokePool, relayer, [dai], weth, amountHeldByPool); await seedWallet(owner, [], weth, toWei("1")); }); + it("Only cross domain owner upgrade logic contract", async function () { + // TODO: Could also use upgrades.prepareUpgrade but I'm unclear of differences + const implementation = await hre.upgrades.deployImplementation( + await getContractFactory("Polygon_SpokePool", owner), + { unsafeAllow: ["delegatecall"], kind: "uups" } + ); + + // upgradeTo fails unless called by cross domain admin + const upgradeData = polygonSpokePool.interface.encodeFunctionData("upgradeTo", [implementation]); + + // Wrong rootMessageSender address. + await expect(polygonSpokePool.connect(fxChild).processMessageFromRoot(0, rando.address, upgradeData)).to.be + .reverted; + + // Wrong calling address. + await expect(polygonSpokePool.connect(rando).processMessageFromRoot(0, owner.address, upgradeData)).to.be.reverted; + + await polygonSpokePool.connect(fxChild).processMessageFromRoot(0, owner.address, upgradeData); + }); + it("Only correct caller can set the cross domain admin", async function () { // Cannot call directly await expect(polygonSpokePool.setCrossDomainAdmin(rando.address)).to.be.reverted; diff --git a/test/fixtures/SpokePool.Fixture.ts b/test/fixtures/SpokePool.Fixture.ts index d4917e2f7..ae715d551 100644 --- a/test/fixtures/SpokePool.Fixture.ts +++ b/test/fixtures/SpokePool.Fixture.ts @@ -36,7 +36,7 @@ export async function deploySpokePool(ethers: any): Promise<{ const spokePool = await hre.upgrades.deployProxy( await getContractFactory("MockSpokePool", deployerWallet), [crossChainAdmin.address, hubPool.address, weth.address, timer.address], - { unsafeAllow: ["delegatecall"] } + { unsafeAllow: ["delegatecall"], kind: "uups" } ); await spokePool.setChainId(consts.destinationChainId); From 54e97c0d99ad181b4eb8a9ec78e50698126cadd6 Mon Sep 17 00:00:00 2001 From: nicholaspai Date: Tue, 10 Jan 2023 12:51:46 -0500 Subject: [PATCH 07/24] Fix test --- test/chain-adapters/Ethereum_Adapter.ts | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/test/chain-adapters/Ethereum_Adapter.ts b/test/chain-adapters/Ethereum_Adapter.ts index a2e9a296b..5ee0a44b2 100644 --- a/test/chain-adapters/Ethereum_Adapter.ts +++ b/test/chain-adapters/Ethereum_Adapter.ts @@ -35,6 +35,10 @@ describe("Ethereum Chain Adapter", function () { await hubPool.setPoolRebalanceRoute(l1ChainId, weth.address, weth.address); await hubPool.setPoolRebalanceRoute(l1ChainId, dai.address, dai.address); + + // HubPool must own MockSpoke to call it via the Ethereum_Adapter who's requireAdminSender has an onlyOwner + // modifier. + await mockSpoke.connect(owner).transferOwnership(hubPool.address); }); it("relayMessage calls spoke pool functions", async function () { From 681a1920bcb34717a35542afd20db1fbeed5a0a2 Mon Sep 17 00:00:00 2001 From: nicholaspai Date: Thu, 19 Jan 2023 09:51:53 -0500 Subject: [PATCH 08/24] Update SpokePool.sol --- contracts/SpokePool.sol | 10 ---------- 1 file changed, 10 deletions(-) diff --git a/contracts/SpokePool.sol b/contracts/SpokePool.sol index fe18c3103..9ddd3a72d 100644 --- a/contracts/SpokePool.sol +++ b/contracts/SpokePool.sol @@ -908,14 +908,4 @@ abstract contract SpokePool is // Added to enable the this contract to receive native token (ETH). Used when unwrapping wrappedNativeToken. receive() external payable {} - - // TODO: - /** - * @dev This empty reserved space is put in place to allow future versions to add new - * variables without shifting down storage in the inheritance chain. OZ adds this to all of their base contracts - * to allow for variables to be added to them without having to add them to each child contract. They have added - * a __gap such that each contract has the same 50 slots of uint256 slots. - * See https://docs.openzeppelin.com/contracts/4.x/upgradeable#storage_gaps - */ - // uint256[50] private __gap; } From 98b272bcdd1614d67fc07a7b8c1e008dd8756ee1 Mon Sep 17 00:00:00 2001 From: nicholaspai Date: Tue, 24 Jan 2023 10:15:51 -0500 Subject: [PATCH 09/24] Update SpokePool.Admin.ts --- test/SpokePool.Admin.ts | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/test/SpokePool.Admin.ts b/test/SpokePool.Admin.ts index 69f543021..b93e17671 100644 --- a/test/SpokePool.Admin.ts +++ b/test/SpokePool.Admin.ts @@ -1,4 +1,4 @@ -import { expect, ethers, Contract, SignerWithAddress, getContractFactory } from "./utils"; +import { expect, ethers, Contract, SignerWithAddress, getContractFactory, hre } from "./utils"; import { spokePoolFixture } from "./fixtures/SpokePool.Fixture"; import { destinationChainId, mockRelayerRefundRoot, mockSlowRelayRoot } from "./constants"; @@ -11,9 +11,11 @@ describe("SpokePool Admin Functions", async function () { ({ spokePool, erc20 } = await spokePoolFixture()); }); it("Can set initial deposit ID", async function () { - const spokePool = await ( - await getContractFactory("MockSpokePool", owner) - ).deploy(1, owner.address, owner.address, owner.address, owner.address); + const spokePool = await hre.upgrades.deployProxy( + await getContractFactory("MockSpokePool", owner), + [1, owner.address, owner.address, owner.address, owner.address], + { unsafeAllow: ["delegatecall"], kind: "uups" } + ); expect(await spokePool.numberOfDeposits()).to.equal(1); }); it("Enable token path", async function () { From dd41c7b1a748e5e2ce3dc7be3b55ee7f11f9091e Mon Sep 17 00:00:00 2001 From: nicholaspai Date: Tue, 24 Jan 2023 15:00:27 -0500 Subject: [PATCH 10/24] No longer require unsafeAllow: delegatecall when deploying proxy because no contracts use delegatecall() anymore --- contracts/Ethereum_SpokePool.sol | 6 ++-- contracts/PolygonTokenBridger.sol | 14 ++++---- contracts/Polygon_SpokePool.sol | 15 ++++---- contracts/SpokePool.sol | 31 +++++++++------- contracts/test/PolygonERC20Test.sol | 2 +- .../upgradeable/MultiCallerUpgradeable.sol | 35 +++++++++++++++++++ hardhat.config.ts | 3 +- test/SpokePool.Admin.ts | 2 +- test/SpokePool.Upgrades.ts | 1 - .../Arbitrum_SpokePool.ts | 4 +-- .../Ethereum_SpokePool.ts | 4 +-- .../Optimism_SpokePool.ts | 4 +-- .../Polygon_SpokePool.ts | 4 +-- test/fixtures/HubPool.Fixture.ts | 4 +-- test/fixtures/SpokePool.Fixture.ts | 2 +- test/gas-analytics/HubPool.RootExecution.ts | 4 +-- 16 files changed, 88 insertions(+), 47 deletions(-) create mode 100644 contracts/upgradeable/MultiCallerUpgradeable.sol diff --git a/contracts/Ethereum_SpokePool.sol b/contracts/Ethereum_SpokePool.sol index 5cc5894b8..ef176e684 100644 --- a/contracts/Ethereum_SpokePool.sol +++ b/contracts/Ethereum_SpokePool.sol @@ -2,15 +2,13 @@ pragma solidity ^0.8.0; import "./SpokePool.sol"; -import "@openzeppelin/contracts/token/ERC20/IERC20.sol"; -import "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol"; import "@openzeppelin/contracts-upgradeable/access/OwnableUpgradeable.sol"; /** * @notice Ethereum L1 specific SpokePool. Used on Ethereum L1 to facilitate L2->L1 transfers. */ contract Ethereum_SpokePool is SpokePool, OwnableUpgradeable { - using SafeERC20 for IERC20; + using SafeERC20Upgradeable for IERC20Upgradeable; /** * @notice Construct the Ethereum SpokePool. @@ -36,7 +34,7 @@ contract Ethereum_SpokePool is SpokePool, OwnableUpgradeable { **************************************/ function _bridgeTokensToHubPool(RelayerRefundLeaf memory relayerRefundLeaf) internal override { - IERC20(relayerRefundLeaf.l2TokenAddress).safeTransfer(hubPool, relayerRefundLeaf.amountToReturn); + IERC20Upgradeable(relayerRefundLeaf.l2TokenAddress).safeTransfer(hubPool, relayerRefundLeaf.amountToReturn); } // The SpokePool deployed to the same network as the HubPool must be owned by the HubPool. diff --git a/contracts/PolygonTokenBridger.sol b/contracts/PolygonTokenBridger.sol index d3481f69d..159473fbd 100644 --- a/contracts/PolygonTokenBridger.sol +++ b/contracts/PolygonTokenBridger.sol @@ -4,8 +4,8 @@ pragma solidity ^0.8.0; import "./Lockable.sol"; import "./interfaces/WETH9.sol"; -import "@openzeppelin/contracts/token/ERC20/IERC20.sol"; -import "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol"; +import "@openzeppelin/contracts-upgradeable/token/ERC20/IERC20Upgradeable.sol"; +import "@openzeppelin/contracts-upgradeable/token/ERC20/utils/SafeERC20Upgradeable.sol"; // Polygon Registry contract that stores their addresses. interface PolygonRegistry { @@ -18,7 +18,7 @@ interface PolygonERC20Predicate { } // ERC20s (on polygon) compatible with polygon's bridge have a withdraw method. -interface PolygonIERC20 is IERC20 { +interface PolygonIERC20Upgradeable is IERC20Upgradeable { function withdraw(uint256 amount) external; } @@ -39,8 +39,8 @@ interface MaticToken { * sender. */ contract PolygonTokenBridger is Lockable { - using SafeERC20 for PolygonIERC20; - using SafeERC20 for IERC20; + using SafeERC20Upgradeable for PolygonIERC20Upgradeable; + using SafeERC20Upgradeable for IERC20Upgradeable; // Gas token for Polygon. MaticToken public constant maticToken = MaticToken(0x0000000000000000000000000000000000001010); @@ -104,7 +104,7 @@ contract PolygonTokenBridger is Lockable { * @param token Token to bridge. * @param amount Amount to bridge. */ - function send(PolygonIERC20 token, uint256 amount) public nonReentrant onlyChainId(l2ChainId) { + function send(PolygonIERC20Upgradeable token, uint256 amount) 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. @@ -119,7 +119,7 @@ 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 onlyChainId(l1ChainId) { + function retrieve(IERC20Upgradeable token) public nonReentrant onlyChainId(l1ChainId) { if (address(token) == address(l1Weth)) { // For WETH, there is a pre-deposit step to ensure any ETH that has been sent to the contract is captured. l1Weth.deposit{ value: address(this).balance }(); diff --git a/contracts/Polygon_SpokePool.sol b/contracts/Polygon_SpokePool.sol index 1a9cf4514..5f222c75d 100644 --- a/contracts/Polygon_SpokePool.sol +++ b/contracts/Polygon_SpokePool.sol @@ -6,9 +6,6 @@ import "./PolygonTokenBridger.sol"; import "./interfaces/WETH9.sol"; import "./SpokePoolInterface.sol"; -import "@openzeppelin/contracts/token/ERC20/IERC20.sol"; -import "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol"; - // IFxMessageProcessor represents interface to process messages. interface IFxMessageProcessor { function processMessageFromRoot( @@ -22,7 +19,7 @@ interface IFxMessageProcessor { * @notice Polygon specific SpokePool. */ contract Polygon_SpokePool is IFxMessageProcessor, SpokePool { - using SafeERC20 for PolygonIERC20; + using SafeERC20Upgradeable for PolygonIERC20Upgradeable; // Address of FxChild which sends and receives messages to and from L1. address public fxChild; @@ -128,6 +125,9 @@ contract Polygon_SpokePool is IFxMessageProcessor, SpokePool { require(rootMessageSender == crossDomainAdmin, "Not from mainnet admin"); // This uses delegatecall to take the information in the message and process it as a function call on this contract. + /// This is a safe delegatecall because its made to address(this) so there is no risk of delegating to a + /// selfdestruct(). + /// @custom:oz-upgrades-unsafe-allow delegatecall (bool success, ) = address(this).delegatecall(data); require(success, "delegatecall failed"); } @@ -211,13 +211,16 @@ contract Polygon_SpokePool is IFxMessageProcessor, SpokePool { **************************************/ function _bridgeTokensToHubPool(RelayerRefundLeaf memory relayerRefundLeaf) internal override { - PolygonIERC20(relayerRefundLeaf.l2TokenAddress).safeIncreaseAllowance( + PolygonIERC20Upgradeable(relayerRefundLeaf.l2TokenAddress).safeIncreaseAllowance( address(polygonTokenBridger), relayerRefundLeaf.amountToReturn ); // Note: WrappedNativeToken is WMATIC on matic, so this tells the tokenbridger that this is an unwrappable native token. - polygonTokenBridger.send(PolygonIERC20(relayerRefundLeaf.l2TokenAddress), relayerRefundLeaf.amountToReturn); + polygonTokenBridger.send( + PolygonIERC20Upgradeable(relayerRefundLeaf.l2TokenAddress), + relayerRefundLeaf.amountToReturn + ); emit PolygonTokensBridged(relayerRefundLeaf.l2TokenAddress, address(this), relayerRefundLeaf.amountToReturn); } diff --git a/contracts/SpokePool.sol b/contracts/SpokePool.sol index 93f006965..eec141724 100644 --- a/contracts/SpokePool.sol +++ b/contracts/SpokePool.sol @@ -6,16 +6,14 @@ import "./interfaces/WETH9.sol"; import "./SpokePoolInterface.sol"; import "./upgradeable/TestableUpgradeable.sol"; import "./upgradeable/LockableUpgradeable.sol"; +import "./upgradeable/MultiCallerUpgradeable.sol"; -import "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol"; -import "@openzeppelin/contracts/token/ERC20/IERC20.sol"; -import "@openzeppelin/contracts/utils/Address.sol"; - +import "@openzeppelin/contracts-upgradeable/token/ERC20/IERC20Upgradeable.sol"; +import "@openzeppelin/contracts-upgradeable/token/ERC20/utils/SafeERC20Upgradeable.sol"; import "@openzeppelin/contracts/utils/cryptography/SignatureChecker.sol"; import "@openzeppelin/contracts/utils/cryptography/ECDSA.sol"; import "@openzeppelin/contracts-upgradeable/utils/AddressUpgradeable.sol"; import "@openzeppelin/contracts-upgradeable/proxy/utils/UUPSUpgradeable.sol"; -import "@uma/core/contracts/common/implementation/MultiCaller.sol"; /** * @title SpokePool @@ -31,9 +29,9 @@ abstract contract SpokePool is UUPSUpgradeable, TestableUpgradeable, LockableUpgradeable, - MultiCaller + MultiCallerUpgradeable { - using SafeERC20 for IERC20; + using SafeERC20Upgradeable for IERC20Upgradeable; using AddressUpgradeable for address; // Address of the L1 contract that acts as the owner of this SpokePool. This should normally be set to the HubPool @@ -338,7 +336,7 @@ abstract contract SpokePool is // Else, it is a normal ERC20. In this case pull the token from the user's wallet as per normal. // Note: this includes the case where the L2 user has WETH (already wrapped ETH) and wants to bridge them. // In this case the msg.value will be set to 0, indicating a "normal" ERC20 bridging action. - } else IERC20(originToken).safeTransferFrom(msg.sender, address(this), amount); + } else IERC20Upgradeable(originToken).safeTransferFrom(msg.sender, address(this), amount); _emitDeposit( amount, @@ -620,7 +618,10 @@ abstract contract SpokePool is for (uint256 i = 0; i < length; ) { uint256 amount = relayerRefundLeaf.refundAmounts[i]; if (amount > 0) - IERC20(relayerRefundLeaf.l2TokenAddress).safeTransfer(relayerRefundLeaf.refundAddresses[i], amount); + IERC20Upgradeable(relayerRefundLeaf.l2TokenAddress).safeTransfer( + relayerRefundLeaf.refundAddresses[i], + amount + ); // OK because we assume refund array length won't be > types(uint256).max. // Based on the stress test results in /test/gas-analytics/SpokePool.RelayerRefundLeaf.ts, the UMIP should @@ -777,7 +778,7 @@ abstract contract SpokePool is // Unwraps ETH and does a transfer to a recipient address. If the recipient is a smart contract then sends wrappedNativeToken. function _unwrapwrappedNativeTokenTo(address payable to, uint256 amount) internal { if (address(to).isContract()) { - IERC20(address(wrappedNativeToken)).safeTransfer(to, amount); + IERC20Upgradeable(address(wrappedNativeToken)).safeTransfer(to, amount); } else { wrappedNativeToken.withdraw(amount); to.transfer(amount); @@ -850,14 +851,18 @@ abstract contract SpokePool is // contract, otherwise we'll need the user to send wrappedNativeToken to this contract. Regardless, we'll // need to unwrap it to native token before sending to the user. if (!useContractFunds) - IERC20(relayData.destinationToken).safeTransferFrom(msg.sender, address(this), amountToSend); + IERC20Upgradeable(relayData.destinationToken).safeTransferFrom(msg.sender, address(this), amountToSend); _unwrapwrappedNativeTokenTo(payable(relayData.recipient), amountToSend); // Else, this is a normal ERC20 token. Send to recipient. } else { // Note: Similar to note above, send token directly from the contract to the user in the slow relay case. if (!useContractFunds) - IERC20(relayData.destinationToken).safeTransferFrom(msg.sender, relayData.recipient, amountToSend); - else IERC20(relayData.destinationToken).safeTransfer(relayData.recipient, amountToSend); + IERC20Upgradeable(relayData.destinationToken).safeTransferFrom( + msg.sender, + relayData.recipient, + amountToSend + ); + else IERC20Upgradeable(relayData.destinationToken).safeTransfer(relayData.recipient, amountToSend); } } diff --git a/contracts/test/PolygonERC20Test.sol b/contracts/test/PolygonERC20Test.sol index 25fb0e934..07b439abe 100644 --- a/contracts/test/PolygonERC20Test.sol +++ b/contracts/test/PolygonERC20Test.sol @@ -7,7 +7,7 @@ import "../PolygonTokenBridger.sol"; /** * @notice Simulated Polygon ERC20 for use in testing PolygonTokenBridger. */ -contract PolygonERC20Test is ExpandedERC20, PolygonIERC20 { +contract PolygonERC20Test is ExpandedERC20 { constructor() ExpandedERC20("Polygon Test", "POLY_TEST", 18) {} // solhint-disable-line no-empty-blocks function withdraw(uint256 amount) public { diff --git a/contracts/upgradeable/MultiCallerUpgradeable.sol b/contracts/upgradeable/MultiCallerUpgradeable.sol new file mode 100644 index 000000000..2a88ca8f8 --- /dev/null +++ b/contracts/upgradeable/MultiCallerUpgradeable.sol @@ -0,0 +1,35 @@ +// SPDX-License-Identifier: AGPL-3.0-only +pragma solidity ^0.8.0; + +/** + * @title MockSpokePool + * @notice Logic is 100% copied from "@uma/core/contracts/common/implementation/MultiCaller.sol" but one + * comment is added to clarify why we allow delegatecall() in this contract, which is typically unsafe for use in + * upgradeable implementation contracts. + * @dev See https://docs.openzeppelin.com/upgrades-plugins/1.x/faq#delegatecall-selfdestruct for more details. + */ +contract MultiCallerUpgradeable { + function multicall(bytes[] calldata data) external returns (bytes[] memory results) { + results = new bytes[](data.length); + for (uint256 i = 0; i < data.length; i++) { + // Typically, implementation contracts used in the upgradeable proxy pattern shouldn't call `delegatecall` + // because it could allow a malicious actor to call this implementation contract directly (rather than + // through a proxy contract) and then selfdestruct() the contract, thereby freezing the upgradeable + // proxy. However, since we're only delegatecall-ing into this contract, then we can consider this + // use of delegatecall() safe. + /// @custom:oz-upgrades-unsafe-allow delegatecall + (bool success, bytes memory result) = address(this).delegatecall(data[i]); + + if (!success) { + // Next 5 lines from https://ethereum.stackexchange.com/a/83577 + if (result.length < 68) revert(); + assembly { + result := add(result, 0x04) + } + revert(abi.decode(result, (string))); + } + + results[i] = result; + } + } +} diff --git a/hardhat.config.ts b/hardhat.config.ts index 66fac7f96..c4c3f3357 100644 --- a/hardhat.config.ts +++ b/hardhat.config.ts @@ -23,7 +23,7 @@ dotenv.config(); // the following config is true. const compileZk = process.env.COMPILE_ZK === "true"; -const solcVersion = "0.8.13"; +const solcVersion = "0.8.17"; const mnemonic = getMnemonic(); // Compilation settings are overridden for large contracts to allow them to compile without going over the bytecode @@ -38,6 +38,7 @@ const config: HardhatUserConfig = { compilers: [{ version: solcVersion, settings: { optimizer: { enabled: true, runs: 1000000 } } }], overrides: { "contracts/HubPool.sol": LARGE_CONTRACT_COMPILER_SETTINGS, + "contracts/Boba_SpokePool.sol": LARGE_CONTRACT_COMPILER_SETTINGS, "contracts/Optimism_SpokePool.sol": LARGE_CONTRACT_COMPILER_SETTINGS, "contracts/test/MockSpokePoolV2.sol": LARGE_CONTRACT_COMPILER_SETTINGS, }, diff --git a/test/SpokePool.Admin.ts b/test/SpokePool.Admin.ts index b93e17671..b3660dc0f 100644 --- a/test/SpokePool.Admin.ts +++ b/test/SpokePool.Admin.ts @@ -14,7 +14,7 @@ describe("SpokePool Admin Functions", async function () { const spokePool = await hre.upgrades.deployProxy( await getContractFactory("MockSpokePool", owner), [1, owner.address, owner.address, owner.address, owner.address], - { unsafeAllow: ["delegatecall"], kind: "uups" } + { kind: "uups" } ); expect(await spokePool.numberOfDeposits()).to.equal(1); }); diff --git a/test/SpokePool.Upgrades.ts b/test/SpokePool.Upgrades.ts index 0ac89ec94..1f6065cb3 100644 --- a/test/SpokePool.Upgrades.ts +++ b/test/SpokePool.Upgrades.ts @@ -11,7 +11,6 @@ describe("SpokePool Upgrade Functions", async function () { }); it("Can upgrade", async function () { const spokePoolV2 = await hre.upgrades.deployImplementation(await ethers.getContractFactory("MockSpokePoolV2"), { - unsafeAllow: ["delegatecall"], kind: "uups", }); const spokePoolV2Contract = (await ethers.getContractFactory("MockSpokePoolV2")).attach(spokePoolV2 as string); diff --git a/test/chain-specific-spokepools/Arbitrum_SpokePool.ts b/test/chain-specific-spokepools/Arbitrum_SpokePool.ts index 577f0cb20..cf4295163 100644 --- a/test/chain-specific-spokepools/Arbitrum_SpokePool.ts +++ b/test/chain-specific-spokepools/Arbitrum_SpokePool.ts @@ -26,7 +26,7 @@ describe("Arbitrum Spoke Pool", function () { arbitrumSpokePool = await hre.upgrades.deployProxy( await getContractFactory("Arbitrum_SpokePool", owner), [0, l2GatewayRouter.address, owner.address, hubPool.address, l2Weth, timer.address], - { unsafeAllow: ["delegatecall"], kind: "uups" } + { kind: "uups" } ); await seedContract(arbitrumSpokePool, relayer, [dai], weth, amountHeldByPool); @@ -37,7 +37,7 @@ describe("Arbitrum Spoke Pool", function () { // TODO: Could also use upgrades.prepareUpgrade but I'm unclear of differences const implementation = await hre.upgrades.deployImplementation( await getContractFactory("Arbitrum_SpokePool", owner), - { unsafeAllow: ["delegatecall"], kind: "uups" } + { kind: "uups" } ); // upgradeTo fails unless called by cross domain admin diff --git a/test/chain-specific-spokepools/Ethereum_SpokePool.ts b/test/chain-specific-spokepools/Ethereum_SpokePool.ts index a193e613d..9f60871a7 100644 --- a/test/chain-specific-spokepools/Ethereum_SpokePool.ts +++ b/test/chain-specific-spokepools/Ethereum_SpokePool.ts @@ -16,7 +16,7 @@ describe("Ethereum Spoke Pool", function () { spokePool = await hre.upgrades.deployProxy( await getContractFactory("Ethereum_SpokePool", owner), [0, hubPool.address, weth.address, timer.address], - { unsafeAllow: ["delegatecall"], kind: "uups" } + { kind: "uups" } ); // Seed spoke pool with tokens that it should transfer to the hub pool @@ -28,7 +28,7 @@ describe("Ethereum Spoke Pool", function () { // TODO: Could also use upgrades.prepareUpgrade but I'm unclear of differences const implementation = await hre.upgrades.deployImplementation( await getContractFactory("Ethereum_SpokePool", owner), - { unsafeAllow: ["delegatecall"], kind: "uups" } + { kind: "uups" } ); // upgradeTo fails unless called by cross domain admin diff --git a/test/chain-specific-spokepools/Optimism_SpokePool.ts b/test/chain-specific-spokepools/Optimism_SpokePool.ts index 925544205..584296a71 100644 --- a/test/chain-specific-spokepools/Optimism_SpokePool.ts +++ b/test/chain-specific-spokepools/Optimism_SpokePool.ts @@ -30,7 +30,7 @@ describe("Optimism Spoke Pool", function () { optimismSpokePool = await hre.upgrades.deployProxy( await getContractFactory("Optimism_SpokePool", owner), [0, owner.address, hubPool.address, timer.address], - { unsafeAllow: ["delegatecall"], kind: "uups" } + { kind: "uups" } ); await seedContract(optimismSpokePool, relayer, [dai], weth, amountHeldByPool); @@ -40,7 +40,7 @@ describe("Optimism Spoke Pool", function () { // TODO: Could also use upgrades.prepareUpgrade but I'm unclear of differences const implementation = await hre.upgrades.deployImplementation( await getContractFactory("Optimism_SpokePool", owner), - { unsafeAllow: ["delegatecall"], kind: "uups" } + { kind: "uups" } ); // upgradeTo fails unless called by cross domain admin diff --git a/test/chain-specific-spokepools/Polygon_SpokePool.ts b/test/chain-specific-spokepools/Polygon_SpokePool.ts index f6c1c9196..35134377e 100644 --- a/test/chain-specific-spokepools/Polygon_SpokePool.ts +++ b/test/chain-specific-spokepools/Polygon_SpokePool.ts @@ -34,7 +34,7 @@ describe("Polygon Spoke Pool", function () { polygonSpokePool = await hre.upgrades.deployProxy( await getContractFactory("Polygon_SpokePool", owner), [0, polygonTokenBridger.address, owner.address, hubPool.address, weth.address, fxChild.address, timer.address], - { unsafeAllow: ["delegatecall"], kind: "uups" } + { kind: "uups" } ); await seedContract(polygonSpokePool, relayer, [dai], weth, amountHeldByPool); @@ -45,7 +45,7 @@ describe("Polygon Spoke Pool", function () { // TODO: Could also use upgrades.prepareUpgrade but I'm unclear of differences const implementation = await hre.upgrades.deployImplementation( await getContractFactory("Polygon_SpokePool", owner), - { unsafeAllow: ["delegatecall"], kind: "uups" } + { kind: "uups" } ); // upgradeTo fails unless called by cross domain admin diff --git a/test/fixtures/HubPool.Fixture.ts b/test/fixtures/HubPool.Fixture.ts index 6b400b569..6a9b0075b 100644 --- a/test/fixtures/HubPool.Fixture.ts +++ b/test/fixtures/HubPool.Fixture.ts @@ -45,7 +45,7 @@ export async function deployHubPool(ethers: any) { const mockSpoke = await hre.upgrades.deployProxy( await getContractFactory("MockSpokePool", signer), [0, crossChainAdmin.address, hubPool.address, weth.address, parentFixture.timer.address], - { unsafeAllow: ["delegatecall"] } + { kind: "uups" } ); await hubPool.setCrossChainContracts(repaymentChainId, mockAdapter.address, mockSpoke.address); await hubPool.setCrossChainContracts(originChainId, mockAdapter.address, mockSpoke.address); @@ -56,7 +56,7 @@ export async function deployHubPool(ethers: any) { const mockSpokeMainnet = await hre.upgrades.deployProxy( await getContractFactory("MockSpokePool", signer), [0, crossChainAdmin.address, hubPool.address, weth.address, parentFixture.timer.address], - { unsafeAllow: ["delegatecall"] } + { kind: "uups" } ); await hubPool.setCrossChainContracts(mainnetChainId, mockAdapterMainnet.address, mockSpokeMainnet.address); diff --git a/test/fixtures/SpokePool.Fixture.ts b/test/fixtures/SpokePool.Fixture.ts index 7f02d33d9..7c026f58d 100644 --- a/test/fixtures/SpokePool.Fixture.ts +++ b/test/fixtures/SpokePool.Fixture.ts @@ -37,7 +37,7 @@ export async function deploySpokePool(ethers: any): Promise<{ const spokePool = await hre.upgrades.deployProxy( await getContractFactory("MockSpokePool", deployerWallet), [0, crossChainAdmin.address, hubPool.address, weth.address, timer.address], - { unsafeAllow: ["delegatecall"], kind: "uups" } + { kind: "uups" } ); await spokePool.setChainId(consts.destinationChainId); diff --git a/test/gas-analytics/HubPool.RootExecution.ts b/test/gas-analytics/HubPool.RootExecution.ts index 089b326db..7f84c8e94 100644 --- a/test/gas-analytics/HubPool.RootExecution.ts +++ b/test/gas-analytics/HubPool.RootExecution.ts @@ -96,7 +96,7 @@ describe("Gas Analytics: HubPool Root Bundle Execution", function () { const spokeMainnet = await hre.upgrades.deployProxy( await getContractFactory("MockSpokePool", owner), [0, randomAddress(), hubPool.address, randomAddress(), timer.address], - { unsafeAllow: ["delegatecall"] } + { kind: "uups" } ); await hubPool.setCrossChainContracts(hubPoolChainId, adapter.address, spokeMainnet.address); @@ -105,7 +105,7 @@ describe("Gas Analytics: HubPool Root Bundle Execution", function () { const spoke = await hre.upgrades.deployProxy( await getContractFactory("MockSpokePool", owner), [0, randomAddress(), hubPool.address, randomAddress(), consts.zeroAddress], - { unsafeAllow: ["delegatecall"] } + { kind: "uups" } ); await hubPool.setCrossChainContracts(i, adapter.address, spoke.address); await Promise.all( From 77b449a21ca70cfc41428aced450c233f4ca1fc4 Mon Sep 17 00:00:00 2001 From: nicholaspai Date: Tue, 24 Jan 2023 15:10:33 -0500 Subject: [PATCH 11/24] Fix gas tests --- .../SpokePool.RelayerRefundLeafExecution.ts | 16 ++++++++-------- .../SpokePool.SlowRelayLeafExecution.ts | 14 +++++++------- 2 files changed, 15 insertions(+), 15 deletions(-) diff --git a/test/gas-analytics/SpokePool.RelayerRefundLeafExecution.ts b/test/gas-analytics/SpokePool.RelayerRefundLeafExecution.ts index da60c8e20..2ddd5e9cd 100644 --- a/test/gas-analytics/SpokePool.RelayerRefundLeafExecution.ts +++ b/test/gas-analytics/SpokePool.RelayerRefundLeafExecution.ts @@ -133,7 +133,7 @@ describe("Gas Analytics: SpokePool Relayer Refund Leaf Execution", function () { ); // Store new tree. - await spokePool.connect(dataWorker).relayRootBundle( + await spokePool.connect(owner).relayRootBundle( initTree.tree.getHexRoot(), // relayer refund root. Generated from the merkle tree constructed before. consts.mockSlowRelayRoot ); @@ -155,14 +155,14 @@ describe("Gas Analytics: SpokePool Relayer Refund Leaf Execution", function () { }); it("Relay proposal", async function () { - const txn = await spokePool.connect(dataWorker).relayRootBundle(tree.getHexRoot(), consts.mockSlowRelayRoot); + const txn = await spokePool.connect(owner).relayRootBundle(tree.getHexRoot(), consts.mockSlowRelayRoot); console.log(`relayRootBundle-gasUsed: ${(await txn.wait()).gasUsed}`); }); it("Executing 1 leaf", async function () { const leafIndexToExecute = 0; - await spokePool.connect(dataWorker).relayRootBundle(tree.getHexRoot(), consts.mockSlowRelayRoot); + await spokePool.connect(owner).relayRootBundle(tree.getHexRoot(), consts.mockSlowRelayRoot); // Execute second root bundle with index 1: const txn = await spokePool @@ -173,7 +173,7 @@ describe("Gas Analytics: SpokePool Relayer Refund Leaf Execution", function () { console.log(`executeRelayerRefundLeaf-gasUsed: ${receipt.gasUsed}`); }); it("Executing all leaves", async function () { - await spokePool.connect(dataWorker).relayRootBundle(tree.getHexRoot(), consts.mockSlowRelayRoot); + await spokePool.connect(owner).relayRootBundle(tree.getHexRoot(), consts.mockSlowRelayRoot); const txns = []; for (let i = 0; i < REFUND_LEAF_COUNT; i++) { @@ -189,7 +189,7 @@ describe("Gas Analytics: SpokePool Relayer Refund Leaf Execution", function () { }); it("Executing all leaves using multicall", async function () { - await spokePool.connect(dataWorker).relayRootBundle(tree.getHexRoot(), consts.mockSlowRelayRoot); + await spokePool.connect(owner).relayRootBundle(tree.getHexRoot(), consts.mockSlowRelayRoot); const multicallData = leaves.map((leaf) => { return spokePool.interface.encodeFunctionData("executeRelayerRefundLeaf", [1, leaf, tree.getHexProof(leaf)]); @@ -211,7 +211,7 @@ describe("Gas Analytics: SpokePool Relayer Refund Leaf Execution", function () { ); // Store new tree. - await spokePool.connect(dataWorker).relayRootBundle( + await spokePool.connect(owner).relayRootBundle( initTree.tree.getHexRoot(), // relayer refund root. Generated from the merkle tree constructed before. consts.mockSlowRelayRoot ); @@ -234,7 +234,7 @@ describe("Gas Analytics: SpokePool Relayer Refund Leaf Execution", function () { it("Executing 1 leaf", async function () { const leafIndexToExecute = 0; - await spokePool.connect(dataWorker).relayRootBundle(tree.getHexRoot(), consts.mockSlowRelayRoot); + await spokePool.connect(owner).relayRootBundle(tree.getHexRoot(), consts.mockSlowRelayRoot); // Execute second root bundle with index 1: const txn = await spokePool @@ -269,7 +269,7 @@ describe("Gas Analytics: SpokePool Relayer Refund Leaf Execution", function () { ); const bigLeafTree = await buildRelayerRefundTree(bigLeaves); - await spokePool.connect(dataWorker).relayRootBundle(bigLeafTree.getHexRoot(), consts.mockSlowRelayRoot); + await spokePool.connect(owner).relayRootBundle(bigLeafTree.getHexRoot(), consts.mockSlowRelayRoot); // Estimate the transaction gas and set it (plus some buffer) explicitly as the transaction's gas limit. This is // done because ethers.js' default gas limit setting doesn't seem to always work and sometimes overestimates diff --git a/test/gas-analytics/SpokePool.SlowRelayLeafExecution.ts b/test/gas-analytics/SpokePool.SlowRelayLeafExecution.ts index 5cc97e7bc..197b98250 100644 --- a/test/gas-analytics/SpokePool.SlowRelayLeafExecution.ts +++ b/test/gas-analytics/SpokePool.SlowRelayLeafExecution.ts @@ -93,7 +93,7 @@ describe("Gas Analytics: SpokePool Slow Relay Root Execution", function () { ); // Store new tree. - await spokePool.connect(dataWorker).relayRootBundle(consts.mockRelayerRefundRoot, initTree.tree.getHexRoot()); + await spokePool.connect(owner).relayRootBundle(consts.mockRelayerRefundRoot, initTree.tree.getHexRoot()); // Execute 1 leaf from initial tree to warm state storage. await spokePool @@ -122,14 +122,14 @@ describe("Gas Analytics: SpokePool Slow Relay Root Execution", function () { }); it("Relay proposal", async function () { - const txn = await spokePool.connect(dataWorker).relayRootBundle(consts.mockRelayerRefundRoot, tree.getHexRoot()); + const txn = await spokePool.connect(owner).relayRootBundle(consts.mockRelayerRefundRoot, tree.getHexRoot()); console.log(`relayRootBundle-gasUsed: ${(await txn.wait()).gasUsed}`); }); it("Executing 1 leaf", async function () { const leafIndexToExecute = 0; - await spokePool.connect(dataWorker).relayRootBundle(consts.mockRelayerRefundRoot, tree.getHexRoot()); + await spokePool.connect(owner).relayRootBundle(consts.mockRelayerRefundRoot, tree.getHexRoot()); // Execute second root bundle with index 1: const txn = await spokePool @@ -150,7 +150,7 @@ describe("Gas Analytics: SpokePool Slow Relay Root Execution", function () { console.log(`executeSlowRelayLeaf-gasUsed: ${receipt.gasUsed}`); }); it("Executing all leaves", async function () { - await spokePool.connect(dataWorker).relayRootBundle(consts.mockRelayerRefundRoot, tree.getHexRoot()); + await spokePool.connect(owner).relayRootBundle(consts.mockRelayerRefundRoot, tree.getHexRoot()); const txns = []; for (let i = 0; i < LEAF_COUNT; i++) { @@ -179,7 +179,7 @@ describe("Gas Analytics: SpokePool Slow Relay Root Execution", function () { }); it("Executing all leaves using multicall", async function () { - await spokePool.connect(dataWorker).relayRootBundle(consts.mockRelayerRefundRoot, tree.getHexRoot()); + await spokePool.connect(owner).relayRootBundle(consts.mockRelayerRefundRoot, tree.getHexRoot()); const multicallData = leaves.map((leaf, i) => { return spokePool.interface.encodeFunctionData("executeSlowRelayLeaf", [ @@ -211,7 +211,7 @@ describe("Gas Analytics: SpokePool Slow Relay Root Execution", function () { ); // Store new tree. - await spokePool.connect(dataWorker).relayRootBundle(consts.mockRelayerRefundRoot, initTree.tree.getHexRoot()); + await spokePool.connect(owner).relayRootBundle(consts.mockRelayerRefundRoot, initTree.tree.getHexRoot()); // Execute 1 leaf from initial tree to warm state storage. await spokePool @@ -242,7 +242,7 @@ describe("Gas Analytics: SpokePool Slow Relay Root Execution", function () { it("Executing 1 leaf", async function () { const leafIndexToExecute = 0; - await spokePool.connect(dataWorker).relayRootBundle(consts.mockRelayerRefundRoot, tree.getHexRoot()); + await spokePool.connect(owner).relayRootBundle(consts.mockRelayerRefundRoot, tree.getHexRoot()); // Execute second root bundle with index 1: const txn = await spokePool From 7e2b7ed24a8f546564347a28766d5d0e34fdb4ae Mon Sep 17 00:00:00 2001 From: nicholaspai Date: Tue, 31 Jan 2023 11:38:54 -0500 Subject: [PATCH 12/24] Use OZ lockable --- contracts/SpokePool.sol | 6 +- contracts/upgradeable/LockableUpgradeable.sol | 80 ------------------- 2 files changed, 3 insertions(+), 83 deletions(-) delete mode 100644 contracts/upgradeable/LockableUpgradeable.sol diff --git a/contracts/SpokePool.sol b/contracts/SpokePool.sol index 4bef1ecbd..7a02a7066 100644 --- a/contracts/SpokePool.sol +++ b/contracts/SpokePool.sol @@ -5,7 +5,6 @@ import "./MerkleLib.sol"; import "./interfaces/WETH9.sol"; import "./SpokePoolInterface.sol"; import "./upgradeable/TestableUpgradeable.sol"; -import "./upgradeable/LockableUpgradeable.sol"; import "./upgradeable/MultiCallerUpgradeable.sol"; import "@openzeppelin/contracts-upgradeable/token/ERC20/IERC20Upgradeable.sol"; @@ -14,6 +13,7 @@ import "@openzeppelin/contracts/utils/cryptography/SignatureChecker.sol"; import "@openzeppelin/contracts/utils/cryptography/ECDSA.sol"; import "@openzeppelin/contracts-upgradeable/utils/AddressUpgradeable.sol"; import "@openzeppelin/contracts-upgradeable/proxy/utils/UUPSUpgradeable.sol"; +import "@openzeppelin/contracts-upgradeable/security/ReentrancyGuardUpgradeable.sol"; /** * @title SpokePool @@ -28,7 +28,7 @@ abstract contract SpokePool is SpokePoolInterface, UUPSUpgradeable, TestableUpgradeable, - LockableUpgradeable, + ReentrancyGuardUpgradeable, MultiCallerUpgradeable { using SafeERC20Upgradeable for IERC20Upgradeable; @@ -163,7 +163,7 @@ abstract contract SpokePool is ) public onlyInitializing { numberOfDeposits = _initialDepositId; __UUPSUpgradeable_init(); - __Lockable_init(); + __ReentrancyGuard_init(); depositQuoteTimeBuffer = 3600; __Testable_init(_timerAddress); _setCrossDomainAdmin(_crossDomainAdmin); diff --git a/contracts/upgradeable/LockableUpgradeable.sol b/contracts/upgradeable/LockableUpgradeable.sol deleted file mode 100644 index b24ae295d..000000000 --- a/contracts/upgradeable/LockableUpgradeable.sol +++ /dev/null @@ -1,80 +0,0 @@ -// SPDX-License-Identifier: AGPL-3.0-only -pragma solidity ^0.8.0; - -import "@openzeppelin/contracts-upgradeable/proxy/utils/Initializable.sol"; - -/** - * @title A contract that provides modifiers to prevent reentrancy to state-changing and view-only methods. This contract - * is inspired by https://github.com/OpenZeppelin/openzeppelin-contracts/blob/master/contracts/utils/ReentrancyGuard.sol - * and https://github.com/balancer-labs/balancer-core/blob/master/contracts/BPool.sol. - * @notice This is an upgradeable version of Testable that replaces the constructor with an initializer function. - */ -contract LockableUpgradeable is Initializable { - bool private _notEntered; - - function __Lockable_init() public onlyInitializing { - // Storing an initial non-zero value makes deployment a bit more expensive, but in exchange the refund on every - // call to nonReentrant will be lower in amount. Since refunds are capped to a percentage of the total - // transaction's gas, it is best to keep them low in cases like this one, to increase the likelihood of the full - // refund coming into effect. - _notEntered = true; - } - - /** - * @dev Prevents a contract from calling itself, directly or indirectly. - * Calling a `nonReentrant` function from another `nonReentrant` function is not supported. It is possible to - * prevent this from happening by making the `nonReentrant` function external, and making it call a `private` - * function that does the actual state modification. - */ - modifier nonReentrant() { - _preEntranceCheck(); - _preEntranceSet(); - _; - _postEntranceReset(); - } - - /** - * @dev Designed to prevent a view-only method from being re-entered during a call to a `nonReentrant()` state-changing method. - */ - modifier nonReentrantView() { - _preEntranceCheck(); - _; - } - - // Internal methods are used to avoid copying the require statement's bytecode to every `nonReentrant()` method. - // On entry into a function, `_preEntranceCheck()` should always be called to check if the function is being - // re-entered. Then, if the function modifies state, it should call `_postEntranceSet()`, perform its logic, and - // then call `_postEntranceReset()`. - // View-only methods can simply call `_preEntranceCheck()` to make sure that it is not being re-entered. - function _preEntranceCheck() internal view { - // On the first call to nonReentrant, _notEntered will be true - require(_notEntered, "ReentrancyGuard: reentrant call"); - } - - function _preEntranceSet() internal { - // Any calls to nonReentrant after this point will fail - _notEntered = false; - } - - function _postEntranceReset() internal { - // By storing the original value once again, a refund is triggered (see - // https://eips.ethereum.org/EIPS/eip-2200) - _notEntered = true; - } - - // These functions are intended to be used by child contracts to temporarily disable and re-enable the guard. - // Intended use: - // _startReentrantGuardDisabled(); - // ... - // _endReentrantGuardDisabled(); - // - // IMPORTANT: these should NEVER be used in a method that isn't inside a nonReentrant block. Otherwise, it's - // possible to permanently lock your contract. - function _startReentrantGuardDisabled() internal { - _notEntered = true; - } - - function _endReentrantGuardDisabled() internal { - _notEntered = false; - } -} From ba9e88f18f3290ca3255854c79bd4c952a6af527 Mon Sep 17 00:00:00 2001 From: nicholaspai Date: Tue, 31 Jan 2023 11:55:20 -0500 Subject: [PATCH 13/24] specify revert reasons Signed-off-by: nicholaspai --- contracts/Ovm_SpokePool.sol | 2 -- test/chain-specific-spokepools/Arbitrum_SpokePool.ts | 2 +- test/chain-specific-spokepools/Ethereum_SpokePool.ts | 4 +++- test/chain-specific-spokepools/Optimism_SpokePool.ts | 7 +++++-- test/chain-specific-spokepools/Polygon_SpokePool.ts | 9 ++++++--- 5 files changed, 15 insertions(+), 9 deletions(-) diff --git a/contracts/Ovm_SpokePool.sol b/contracts/Ovm_SpokePool.sol index 43024418d..b37fe7e65 100644 --- a/contracts/Ovm_SpokePool.sol +++ b/contracts/Ovm_SpokePool.sol @@ -176,8 +176,6 @@ contract Ovm_SpokePool is SpokePool { // Apply OVM-specific transformation to cross domain admin address on L1. function _requireAdminSender() internal view override { - require(LibOptimismUpgradeable.isCrossChain(messenger), "OVM_XCHAIN: messenger contract unauthenticated"); - require( LibOptimismUpgradeable.crossChainSender(messenger) == crossDomainAdmin, "OVM_XCHAIN: wrong sender of cross-domain message" diff --git a/test/chain-specific-spokepools/Arbitrum_SpokePool.ts b/test/chain-specific-spokepools/Arbitrum_SpokePool.ts index cf4295163..0010e2a28 100644 --- a/test/chain-specific-spokepools/Arbitrum_SpokePool.ts +++ b/test/chain-specific-spokepools/Arbitrum_SpokePool.ts @@ -41,7 +41,7 @@ describe("Arbitrum Spoke Pool", function () { ); // upgradeTo fails unless called by cross domain admin - await expect(arbitrumSpokePool.upgradeTo(implementation)).to.be.reverted; + await expect(arbitrumSpokePool.upgradeTo(implementation)).to.be.revertedWith("ONLY_COUNTERPART_GATEWAY"); await arbitrumSpokePool.connect(crossDomainAlias).upgradeTo(implementation); }); diff --git a/test/chain-specific-spokepools/Ethereum_SpokePool.ts b/test/chain-specific-spokepools/Ethereum_SpokePool.ts index 9f60871a7..92bc2f54e 100644 --- a/test/chain-specific-spokepools/Ethereum_SpokePool.ts +++ b/test/chain-specific-spokepools/Ethereum_SpokePool.ts @@ -32,7 +32,9 @@ describe("Ethereum Spoke Pool", function () { ); // upgradeTo fails unless called by cross domain admin - await expect(spokePool.connect(rando).upgradeTo(implementation)).to.be.reverted; + await expect(spokePool.connect(rando).upgradeTo(implementation)).to.be.revertedWith( + "Ownable: caller is not the owner" + ); await spokePool.connect(owner).upgradeTo(implementation); }); diff --git a/test/chain-specific-spokepools/Optimism_SpokePool.ts b/test/chain-specific-spokepools/Optimism_SpokePool.ts index 584296a71..af5966ce3 100644 --- a/test/chain-specific-spokepools/Optimism_SpokePool.ts +++ b/test/chain-specific-spokepools/Optimism_SpokePool.ts @@ -43,8 +43,11 @@ describe("Optimism Spoke Pool", function () { { kind: "uups" } ); - // upgradeTo fails unless called by cross domain admin - await expect(optimismSpokePool.upgradeTo(implementation)).to.be.reverted; + // upgradeTo fails unless called by cross domain admin via messenger contract + await expect(optimismSpokePool.connect(rando).upgradeTo(implementation)).to.be.revertedWith("NotCrossChainCall"); + await expect(optimismSpokePool.connect(crossDomainMessenger.wallet).upgradeTo(implementation)).to.be.revertedWith( + "OVM_XCHAIN: wrong sender of cross-domain message" + ); crossDomainMessenger.xDomainMessageSender.returns(owner.address); await optimismSpokePool.connect(crossDomainMessenger.wallet).upgradeTo(implementation); }); diff --git a/test/chain-specific-spokepools/Polygon_SpokePool.ts b/test/chain-specific-spokepools/Polygon_SpokePool.ts index 35134377e..fdf460f14 100644 --- a/test/chain-specific-spokepools/Polygon_SpokePool.ts +++ b/test/chain-specific-spokepools/Polygon_SpokePool.ts @@ -52,11 +52,14 @@ describe("Polygon Spoke Pool", function () { const upgradeData = polygonSpokePool.interface.encodeFunctionData("upgradeTo", [implementation]); // Wrong rootMessageSender address. - await expect(polygonSpokePool.connect(fxChild).processMessageFromRoot(0, rando.address, upgradeData)).to.be - .reverted; + await expect( + polygonSpokePool.connect(fxChild).processMessageFromRoot(0, rando.address, upgradeData) + ).to.be.revertedWith("Not from mainnet admin"); // Wrong calling address. - await expect(polygonSpokePool.connect(rando).processMessageFromRoot(0, owner.address, upgradeData)).to.be.reverted; + await expect( + polygonSpokePool.connect(rando).processMessageFromRoot(0, owner.address, upgradeData) + ).to.be.revertedWith("Not from fxChild"); await polygonSpokePool.connect(fxChild).processMessageFromRoot(0, owner.address, upgradeData); }); From 8adde773263f1a66615d7a9f4a45d2aa538bec5f Mon Sep 17 00:00:00 2001 From: nicholaspai <9457025+nicholaspai@users.noreply.github.com> Date: Tue, 31 Jan 2023 11:55:50 -0500 Subject: [PATCH 14/24] Update contracts/SpokePool.sol Co-authored-by: Paul <108695806+pxrl@users.noreply.github.com> --- contracts/SpokePool.sol | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/contracts/SpokePool.sol b/contracts/SpokePool.sol index 7a02a7066..eb9005fd2 100644 --- a/contracts/SpokePool.sol +++ b/contracts/SpokePool.sol @@ -139,7 +139,8 @@ abstract contract SpokePool is * Do not leave an implementation contract uninitialized. An uninitialized implementation contract can be * taken over by an attacker, which may impact the proxy. To prevent the implementation contract from being * used, you should invoke the _disableInitializers function in the constructor to automatically lock it when - * it is deployed: */ + * it is deployed: + */ /// @custom:oz-upgrades-unsafe-allow constructor constructor() { _disableInitializers(); From 6a9f5093193229800f4e46aaa52dd1d54e3fe667 Mon Sep 17 00:00:00 2001 From: nicholaspai Date: Tue, 31 Jan 2023 12:07:47 -0500 Subject: [PATCH 15/24] add 50 32byte slots to base SpokePool Signed-off-by: nicholaspai --- contracts/SpokePool.sol | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/contracts/SpokePool.sol b/contracts/SpokePool.sol index eb9005fd2..b0e2076c6 100644 --- a/contracts/SpokePool.sol +++ b/contracts/SpokePool.sol @@ -60,6 +60,11 @@ abstract contract SpokePool is // This contract can store as many root bundles as the HubPool chooses to publish here. RootBundle[] public rootBundles; + // Reserve storage slots for future versions of this base contract to add state variables without + // affecting the storage layout of child contracts. Decrement the size of __gap whenever state variables + // are added. + uint256[50] __gap; + // Origin token to destination token routings can be turned on or off, which can enable or disable deposits. mapping(address => mapping(uint256 => bool)) public enabledDepositRoutes; From 29329462e5df83ffa32f20a19f3fb5482574414b Mon Sep 17 00:00:00 2001 From: nicholaspai Date: Thu, 2 Feb 2023 16:30:07 -0500 Subject: [PATCH 16/24] add __gap to ovm-spokepool Signed-off-by: nicholaspai --- contracts/Ovm_SpokePool.sol | 5 +++++ contracts/SpokePool.sol | 2 +- 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/contracts/Ovm_SpokePool.sol b/contracts/Ovm_SpokePool.sol index b37fe7e65..bafdae86c 100644 --- a/contracts/Ovm_SpokePool.sol +++ b/contracts/Ovm_SpokePool.sol @@ -27,6 +27,11 @@ contract Ovm_SpokePool is SpokePool { // Address of the Optimism L2 messenger. address public messenger; + // Reserve storage slots for future versions of this base contract to add state variables without + // affecting the storage layout of child contracts. Decrement the size of __gap whenever state variables + // are added. + uint256[100] __gap; + // Stores alternative token bridges to use for L2 tokens that don't go over the standard bridge. This is needed // to support non-standard ERC20 tokens on Optimism, such as DIA and SNX which both use custom bridges. mapping(address => address) public tokenBridges; diff --git a/contracts/SpokePool.sol b/contracts/SpokePool.sol index b0e2076c6..eea0a8057 100644 --- a/contracts/SpokePool.sol +++ b/contracts/SpokePool.sol @@ -63,7 +63,7 @@ abstract contract SpokePool is // Reserve storage slots for future versions of this base contract to add state variables without // affecting the storage layout of child contracts. Decrement the size of __gap whenever state variables // are added. - uint256[50] __gap; + uint256[1000] __gap; // Origin token to destination token routings can be turned on or off, which can enable or disable deposits. mapping(address => mapping(uint256 => bool)) public enabledDepositRoutes; From c689fa29a77441df92190a0e39518685a738cc84 Mon Sep 17 00:00:00 2001 From: nicholaspai Date: Thu, 2 Feb 2023 16:39:28 -0500 Subject: [PATCH 17/24] __gap private Signed-off-by: nicholaspai --- contracts/Ovm_SpokePool.sol | 2 +- contracts/SpokePool.sol | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/contracts/Ovm_SpokePool.sol b/contracts/Ovm_SpokePool.sol index bafdae86c..8a3371302 100644 --- a/contracts/Ovm_SpokePool.sol +++ b/contracts/Ovm_SpokePool.sol @@ -30,7 +30,7 @@ contract Ovm_SpokePool is SpokePool { // Reserve storage slots for future versions of this base contract to add state variables without // affecting the storage layout of child contracts. Decrement the size of __gap whenever state variables // are added. - uint256[100] __gap; + uint256[100] private __gap; // Stores alternative token bridges to use for L2 tokens that don't go over the standard bridge. This is needed // to support non-standard ERC20 tokens on Optimism, such as DIA and SNX which both use custom bridges. diff --git a/contracts/SpokePool.sol b/contracts/SpokePool.sol index eea0a8057..2da285aba 100644 --- a/contracts/SpokePool.sol +++ b/contracts/SpokePool.sol @@ -63,7 +63,7 @@ abstract contract SpokePool is // Reserve storage slots for future versions of this base contract to add state variables without // affecting the storage layout of child contracts. Decrement the size of __gap whenever state variables // are added. - uint256[1000] __gap; + uint256[1000] private __gap; // Origin token to destination token routings can be turned on or off, which can enable or disable deposits. mapping(address => mapping(uint256 => bool)) public enabledDepositRoutes; From c0bb1b124ddc97353561ce811c8eb57dbd6e7cf3 Mon Sep 17 00:00:00 2001 From: nicholaspai Date: Thu, 2 Feb 2023 16:42:47 -0500 Subject: [PATCH 18/24] Update SpokePool.sol --- contracts/SpokePool.sol | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/contracts/SpokePool.sol b/contracts/SpokePool.sol index a724015be..83f3f62eb 100644 --- a/contracts/SpokePool.sol +++ b/contracts/SpokePool.sol @@ -44,7 +44,7 @@ abstract contract SpokePool is // Address of wrappedNativeToken contract for this network. If an origin token matches this, then the caller can // optionally instruct this contract to wrap native tokens when depositing (ie ETH->WETH or MATIC->WMATIC). - WETH9Interface public immutable wrappedNativeToken; + WETH9Interface public wrappedNativeToken; // Any deposit quote times greater than or less than this value to the current contract time is blocked. Forces // caller to use an approximately "current" realized fee. Defaults to 1 hour. From b8fb099e0a4c798ed55333d1e310c4b958e74cce Mon Sep 17 00:00:00 2001 From: nicholaspai Date: Thu, 2 Feb 2023 18:26:44 -0500 Subject: [PATCH 19/24] Update Ovm_SpokePool.sol --- contracts/Ovm_SpokePool.sol | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/contracts/Ovm_SpokePool.sol b/contracts/Ovm_SpokePool.sol index 02ac5004e..5ef878f24 100644 --- a/contracts/Ovm_SpokePool.sol +++ b/contracts/Ovm_SpokePool.sol @@ -30,7 +30,7 @@ contract Ovm_SpokePool is SpokePool { // Reserve storage slots for future versions of this base contract to add state variables without // affecting the storage layout of child contracts. Decrement the size of __gap whenever state variables // are added. - uint256[100] private __gap; + uint256[1000] private __gap; // Stores alternative token bridges to use for L2 tokens that don't go over the standard bridge. This is needed // to support non-standard ERC20 tokens on Optimism, such as DIA and SNX which both use custom bridges. From cb54ea797b262272d6c3c6f3c2f6c1e548a47ed5 Mon Sep 17 00:00:00 2001 From: nicholaspai Date: Sat, 4 Feb 2023 13:11:18 -0500 Subject: [PATCH 20/24] improve: Make changes after running Slither on SpokePool contracts Signed-off-by: nicholaspai --- README.md | 17 ++++++++++++++++ contracts/Arbitrum_SpokePool.sol | 1 + contracts/Ovm_SpokePool.sol | 2 ++ contracts/PolygonTokenBridger.sol | 7 +++++-- contracts/Polygon_SpokePool.sol | 5 +++++ contracts/SpokePool.sol | 20 ++++++++++++------- contracts/interfaces/WETH9Interface.sol | 2 +- .../upgradeable/MultiCallerUpgradeable.sol | 6 ++++++ contracts/upgradeable/TestableUpgradeable.sol | 1 + 9 files changed, 51 insertions(+), 10 deletions(-) diff --git a/README.md b/README.md index 063bc6561..f91eb6fdf 100644 --- a/README.md +++ b/README.md @@ -41,6 +41,23 @@ NODE_URL_1=https://mainnet.infura.com/xxx yarn hardhat deploy --tags HubPool --n ETHERSCAN_API_KEY=XXX yarn hardhat etherscan-verify --network mainnet --license AGPL-3.0 --force-license --solc-input ``` +## Slither + +[Slither](https://github.com/crytic/slither) is a Solidity static analysis framework written in Python 3. It runs a +suite of vulnerability detectors, prints visual information about contract details, and provides an API to easily write +custom analyses. Slither enables developers to find vulnerabilities, enhance their code comprehension, and quickly +prototype custom analyses. + +Spire-Contracts has been analyzed using `Slither@0.9.2` and no major bugs was found. To rerun the analytics, run: + +```sh +slither contracts/SpokePool.sol +\ --solc-remaps @=node_modules/@ +\ --solc-args "--optimize --optimize-runs 1000000" +\ --filter-paths "node_modules" +\ --exclude naming-convention +``` + ## ZK Sync Adapter These are special instructions for compiling and deploying contracts on `zksync`. The compile command will create `artifacts-zk` and `cache-zk` directories. diff --git a/contracts/Arbitrum_SpokePool.sol b/contracts/Arbitrum_SpokePool.sol index cc6522026..74744a2e2 100644 --- a/contracts/Arbitrum_SpokePool.sol +++ b/contracts/Arbitrum_SpokePool.sol @@ -83,6 +83,7 @@ contract Arbitrum_SpokePool is SpokePool { // Check that the Ethereum counterpart of the L2 token is stored on this contract. address ethereumTokenToBridge = whitelistedTokens[relayerRefundLeaf.l2TokenAddress]; require(ethereumTokenToBridge != address(0), "Uninitialized mainnet token"); + //slither-disable-next-line unused-return StandardBridgeLike(l2GatewayRouter).outboundTransfer( ethereumTokenToBridge, // _l1Token. Address of the L1 token to bridge over. hubPool, // _to. Withdraw, over the bridge, to the l1 hub pool contract. diff --git a/contracts/Ovm_SpokePool.sol b/contracts/Ovm_SpokePool.sol index 5ef878f24..035821edf 100644 --- a/contracts/Ovm_SpokePool.sol +++ b/contracts/Ovm_SpokePool.sol @@ -59,6 +59,7 @@ contract Ovm_SpokePool is SpokePool { l1Gas = 5_000_000; __SpokePool_init(_initialDepositId, _crossDomainAdmin, _hubPool, _wrappedNativeToken, _timerAddress); messenger = Lib_PredeployAddresses.L2_CROSS_DOMAIN_MESSENGER; + require(_l2Eth != address(0), "Invalid L2 ETH address"); l2Eth = _l2Eth; } @@ -147,6 +148,7 @@ contract Ovm_SpokePool is SpokePool { // this logic inside a fallback method that executes when this contract receives ETH because ETH is an ERC20 // on the OVM. function _depositEthToWeth() internal { + //slither-disable-next-line arbitrary-send-eth if (address(this).balance > 0) wrappedNativeToken.deposit{ value: address(this).balance }(); } diff --git a/contracts/PolygonTokenBridger.sol b/contracts/PolygonTokenBridger.sol index 5494b0cc1..e5b7f44b0 100644 --- a/contracts/PolygonTokenBridger.sol +++ b/contracts/PolygonTokenBridger.sol @@ -1,11 +1,11 @@ // SPDX-License-Identifier: AGPL-3.0-only pragma solidity ^0.8.0; -import "./Lockable.sol"; import "./interfaces/WETH9Interface.sol"; import "@openzeppelin/contracts-upgradeable/token/ERC20/IERC20Upgradeable.sol"; import "@openzeppelin/contracts-upgradeable/token/ERC20/utils/SafeERC20Upgradeable.sol"; +import "@openzeppelin/contracts/security/ReentrancyGuard.sol"; // Polygon Registry contract that stores their addresses. interface PolygonRegistry { @@ -38,7 +38,7 @@ interface MaticToken { * This ultimately allows create2 to generate deterministic addresses that don't depend on the transaction count of the * sender. */ -contract PolygonTokenBridger is Lockable { +contract PolygonTokenBridger is ReentrancyGuard { using SafeERC20Upgradeable for PolygonIERC20Upgradeable; using SafeERC20Upgradeable for IERC20Upgradeable; @@ -90,6 +90,8 @@ contract PolygonTokenBridger is Lockable { uint256 _l1ChainId, uint256 _l2ChainId ) { + require(_destination != address(0), "invalid dest address"); + require(_l2WrappedMatic != address(0), "invalid wmatic address"); destination = _destination; l1PolygonRegistry = _l1PolygonRegistry; l1Weth = _l1Weth; @@ -122,6 +124,7 @@ contract PolygonTokenBridger is Lockable { function retrieve(IERC20Upgradeable token) public nonReentrant onlyChainId(l1ChainId) { if (address(token) == address(l1Weth)) { // For WETH, there is a pre-deposit step to ensure any ETH that has been sent to the contract is captured. + //slither-disable-next-line arbitrary-send-eth l1Weth.deposit{ value: address(this).balance }(); } token.safeTransfer(destination, token.balanceOf(address(this))); diff --git a/contracts/Polygon_SpokePool.sol b/contracts/Polygon_SpokePool.sol index 7bfb51532..56e3e64ad 100644 --- a/contracts/Polygon_SpokePool.sol +++ b/contracts/Polygon_SpokePool.sol @@ -77,6 +77,7 @@ contract Polygon_SpokePool is IFxMessageProcessor, SpokePool { address _fxChild, address _timerAddress ) public initializer { + require(_fxChild != address(0), "invalid fChild address"); callValidated = false; __SpokePool_init(_initialDepositId, _crossDomainAdmin, _hubPool, _wmaticAddress, _timerAddress); polygonTokenBridger = _polygonTokenBridger; @@ -92,6 +93,7 @@ contract Polygon_SpokePool is IFxMessageProcessor, SpokePool { * @param newFxChild New FxChild. */ function setFxChild(address newFxChild) public onlyAdmin nonReentrant { + require(newFxChild != address(0), "invalid fChild address"); fxChild = newFxChild; emit SetFxChild(newFxChild); } @@ -127,8 +129,10 @@ contract Polygon_SpokePool is IFxMessageProcessor, SpokePool { // This uses delegatecall to take the information in the message and process it as a function call on this contract. /// This is a safe delegatecall because its made to address(this) so there is no risk of delegating to a /// selfdestruct(). + //slither-disable-start low-level-calls /// @custom:oz-upgrades-unsafe-allow delegatecall (bool success, ) = address(this).delegatecall(data); + //slither-disable-end low-level-calls require(success, "delegatecall failed"); } @@ -227,6 +231,7 @@ contract Polygon_SpokePool is IFxMessageProcessor, SpokePool { function _wrap() internal { uint256 balance = address(this).balance; + //slither-disable-next-line arbitrary-send-eth if (balance > 0) wrappedNativeToken.deposit{ value: balance }(); } diff --git a/contracts/SpokePool.sol b/contracts/SpokePool.sol index 020408ae5..04a6ca60d 100644 --- a/contracts/SpokePool.sol +++ b/contracts/SpokePool.sol @@ -290,6 +290,11 @@ abstract contract SpokePool is * to ensure that a small input range doesn't limit which indices this method is able to reach. */ function emergencyDeleteRootBundle(uint256 rootBundleId) public override onlyAdmin nonReentrant { + // Deleting a struct containing a mapping does not delete the mapping in Solidity, therefore the bitmap's + // data will still remain potentially leading to vulnerabilities down the line. The way around this would + // be to iterate through every key in the mapping and resetting the value to 0, but this seems expensive and + // would require a new list in storage to keep track of keys. + //slither-disable-next-line mapping-deletion delete rootBundles[rootBundleId]; emit EmergencyDeleteRootBundle(rootBundleId); } @@ -332,11 +337,17 @@ abstract contract SpokePool is // buffer of this contract's block time to allow for this variance. // Note also that quoteTimestamp cannot be less than the buffer otherwise the following arithmetic can result // in underflow. This isn't a problem as the deposit will revert, but the error might be unexpected for clients. + + //slither-disable-next-line timestamp require( getCurrentTime() >= quoteTimestamp - depositQuoteTimeBuffer && getCurrentTime() <= quoteTimestamp + depositQuoteTimeBuffer, "invalid quote time" ); + + // Increment count of deposits so that deposit ID for this spoke pool is unique. + uint32 newDepositId = numberOfDeposits++; + // If the address of the origin token is a wrappedNativeToken contract and there is a msg.value with the // transaction then the user is sending ETH. In this case, the ETH should be deposited to wrappedNativeToken. if (originToken == address(wrappedNativeToken) && msg.value > 0) { @@ -352,18 +363,12 @@ abstract contract SpokePool is chainId(), destinationChainId, relayerFeePct, - numberOfDeposits, + newDepositId, quoteTimestamp, originToken, recipient, msg.sender ); - - // Increment count of deposits so that deposit ID for this spoke pool is unique. - // @dev: Use pre-increment to save gas: - // i++ --> Load, Store, Add, Store - // ++i --> Load, Add, Store - ++numberOfDeposits; } /** @@ -798,6 +803,7 @@ abstract contract SpokePool is IERC20Upgradeable(address(wrappedNativeToken)).safeTransfer(to, amount); } else { wrappedNativeToken.withdraw(amount); + //slither-disable-next-line arbitrary-send-eth to.transfer(amount); } } diff --git a/contracts/interfaces/WETH9Interface.sol b/contracts/interfaces/WETH9Interface.sol index d95fab21a..b13caf788 100644 --- a/contracts/interfaces/WETH9Interface.sol +++ b/contracts/interfaces/WETH9Interface.sol @@ -8,5 +8,5 @@ interface WETH9Interface { function balanceOf(address guy) external view returns (uint256 wad); - function transfer(address guy, uint256 wad) external; + function transfer(address guy, uint256 wad) external returns (bool); } diff --git a/contracts/upgradeable/MultiCallerUpgradeable.sol b/contracts/upgradeable/MultiCallerUpgradeable.sol index eefca8bfc..5b3f81f82 100644 --- a/contracts/upgradeable/MultiCallerUpgradeable.sol +++ b/contracts/upgradeable/MultiCallerUpgradeable.sol @@ -11,18 +11,23 @@ pragma solidity ^0.8.0; contract MultiCallerUpgradeable { function multicall(bytes[] calldata data) external returns (bytes[] memory results) { results = new bytes[](data.length); + //slither-disable-start calls-loop for (uint256 i = 0; i < data.length; i++) { // Typically, implementation contracts used in the upgradeable proxy pattern shouldn't call `delegatecall` // because it could allow a malicious actor to call this implementation contract directly (rather than // through a proxy contract) and then selfdestruct() the contract, thereby freezing the upgradeable // proxy. However, since we're only delegatecall-ing into this contract, then we can consider this // use of delegatecall() safe. + + //slither-disable-start low-level-calls /// @custom:oz-upgrades-unsafe-allow delegatecall (bool success, bytes memory result) = address(this).delegatecall(data[i]); + //slither-disable-end low-level-calls if (!success) { // Next 5 lines from https://ethereum.stackexchange.com/a/83577 if (result.length < 68) revert(); + //slither-disable-next-line assembly assembly { result := add(result, 0x04) } @@ -31,6 +36,7 @@ contract MultiCallerUpgradeable { results[i] = result; } + //slither-disable-end calls-loop } /** diff --git a/contracts/upgradeable/TestableUpgradeable.sol b/contracts/upgradeable/TestableUpgradeable.sol index be2d7d0e2..ef8767ace 100644 --- a/contracts/upgradeable/TestableUpgradeable.sol +++ b/contracts/upgradeable/TestableUpgradeable.sol @@ -19,6 +19,7 @@ abstract contract TestableUpgradeable is Initializable { * Must be set to 0x0 for production environments that use live time. */ function __Testable_init(address _timerAddress) public onlyInitializing { + //slither-disable-next-line missing-zero-check timerAddress = _timerAddress; } From 5d13900c9ba7fa7b56e348b5f0ba7fb90858185f Mon Sep 17 00:00:00 2001 From: nicholaspai Date: Sat, 4 Feb 2023 13:17:23 -0500 Subject: [PATCH 21/24] Update README.md --- README.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/README.md b/README.md index f91eb6fdf..c5193ba28 100644 --- a/README.md +++ b/README.md @@ -58,6 +58,8 @@ slither contracts/SpokePool.sol \ --exclude naming-convention ``` +You can replace `SpokePool.sol` with the specific contract you want to analyze. + ## ZK Sync Adapter These are special instructions for compiling and deploying contracts on `zksync`. The compile command will create `artifacts-zk` and `cache-zk` directories. From 2ab13f6ec021e3a54e42e35ccc65ac4fc41e67d2 Mon Sep 17 00:00:00 2001 From: nicholaspai Date: Wed, 8 Feb 2023 09:18:36 -0700 Subject: [PATCH 22/24] remove zero checks --- contracts/Ovm_SpokePool.sol | 2 +- contracts/PolygonTokenBridger.sol | 4 ++-- contracts/Polygon_SpokePool.sol | 4 ++-- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/contracts/Ovm_SpokePool.sol b/contracts/Ovm_SpokePool.sol index 035821edf..e0818e38a 100644 --- a/contracts/Ovm_SpokePool.sol +++ b/contracts/Ovm_SpokePool.sol @@ -59,7 +59,7 @@ contract Ovm_SpokePool is SpokePool { l1Gas = 5_000_000; __SpokePool_init(_initialDepositId, _crossDomainAdmin, _hubPool, _wrappedNativeToken, _timerAddress); messenger = Lib_PredeployAddresses.L2_CROSS_DOMAIN_MESSENGER; - require(_l2Eth != address(0), "Invalid L2 ETH address"); + //slither-disable-next-line missing-zero-check l2Eth = _l2Eth; } diff --git a/contracts/PolygonTokenBridger.sol b/contracts/PolygonTokenBridger.sol index e5b7f44b0..6c2b7e5c2 100644 --- a/contracts/PolygonTokenBridger.sol +++ b/contracts/PolygonTokenBridger.sol @@ -90,11 +90,11 @@ contract PolygonTokenBridger is ReentrancyGuard { uint256 _l1ChainId, uint256 _l2ChainId ) { - require(_destination != address(0), "invalid dest address"); - require(_l2WrappedMatic != address(0), "invalid wmatic address"); + //slither-disable-next-line missing-zero-check destination = _destination; l1PolygonRegistry = _l1PolygonRegistry; l1Weth = _l1Weth; + //slither-disable-next-line missing-zero-check l2WrappedMatic = _l2WrappedMatic; l1ChainId = _l1ChainId; l2ChainId = _l2ChainId; diff --git a/contracts/Polygon_SpokePool.sol b/contracts/Polygon_SpokePool.sol index 56e3e64ad..e5f4ca6ea 100644 --- a/contracts/Polygon_SpokePool.sol +++ b/contracts/Polygon_SpokePool.sol @@ -77,10 +77,10 @@ contract Polygon_SpokePool is IFxMessageProcessor, SpokePool { address _fxChild, address _timerAddress ) public initializer { - require(_fxChild != address(0), "invalid fChild address"); callValidated = false; __SpokePool_init(_initialDepositId, _crossDomainAdmin, _hubPool, _wmaticAddress, _timerAddress); polygonTokenBridger = _polygonTokenBridger; + //slither-disable-next-line missing-zero-check fxChild = _fxChild; } @@ -93,7 +93,7 @@ contract Polygon_SpokePool is IFxMessageProcessor, SpokePool { * @param newFxChild New FxChild. */ function setFxChild(address newFxChild) public onlyAdmin nonReentrant { - require(newFxChild != address(0), "invalid fChild address"); + //slither-disable-next-line missing-zero-check fxChild = newFxChild; emit SetFxChild(newFxChild); } From 57855d1983bb45b43a9afcc7f39f620f48388ceb Mon Sep 17 00:00:00 2001 From: nicholaspai Date: Mon, 13 Feb 2023 10:10:21 -0500 Subject: [PATCH 23/24] Update EIP712CrossChainUpgradeable.sol --- contracts/upgradeable/EIP712CrossChainUpgradeable.sol | 7 ------- 1 file changed, 7 deletions(-) diff --git a/contracts/upgradeable/EIP712CrossChainUpgradeable.sol b/contracts/upgradeable/EIP712CrossChainUpgradeable.sol index ed0000ad8..b7e4e17a2 100644 --- a/contracts/upgradeable/EIP712CrossChainUpgradeable.sol +++ b/contracts/upgradeable/EIP712CrossChainUpgradeable.sol @@ -84,11 +84,4 @@ abstract contract EIP712CrossChainUpgradeable is Initializable { function _hashTypedDataV4(bytes32 structHash, uint256 originChainId) internal view virtual returns (bytes32) { return ECDSAUpgradeable.toTypedDataHash(_domainSeparatorV4(originChainId), structHash); } - - /** - * @dev This empty reserved space is put in place to allow future versions to add new - * variables without shifting down storage in the inheritance chain. - * See https://docs.openzeppelin.com/contracts/4.x/upgradeable#storage_gaps - */ - uint256[1000] private __gap; } From bc58c17fba202f2256336be70eed9a4c61a82d2f Mon Sep 17 00:00:00 2001 From: nicholaspai Date: Thu, 16 Feb 2023 10:49:29 -0500 Subject: [PATCH 24/24] Update PolygonTokenBridger.sol --- contracts/PolygonTokenBridger.sol | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/contracts/PolygonTokenBridger.sol b/contracts/PolygonTokenBridger.sol index 6c2b7e5c2..883cf95d7 100644 --- a/contracts/PolygonTokenBridger.sol +++ b/contracts/PolygonTokenBridger.sol @@ -1,11 +1,11 @@ // SPDX-License-Identifier: AGPL-3.0-only pragma solidity ^0.8.0; +import "./Lockable.sol"; import "./interfaces/WETH9Interface.sol"; import "@openzeppelin/contracts-upgradeable/token/ERC20/IERC20Upgradeable.sol"; import "@openzeppelin/contracts-upgradeable/token/ERC20/utils/SafeERC20Upgradeable.sol"; -import "@openzeppelin/contracts/security/ReentrancyGuard.sol"; // Polygon Registry contract that stores their addresses. interface PolygonRegistry { @@ -38,7 +38,7 @@ interface MaticToken { * This ultimately allows create2 to generate deterministic addresses that don't depend on the transaction count of the * sender. */ -contract PolygonTokenBridger is ReentrancyGuard { +contract PolygonTokenBridger is Lockable { using SafeERC20Upgradeable for PolygonIERC20Upgradeable; using SafeERC20Upgradeable for IERC20Upgradeable;