diff --git a/contracts/Blast_DaiRetriever.sol b/contracts/Blast_DaiRetriever.sol index e472f8a7c..0d12c30a8 100644 --- a/contracts/Blast_DaiRetriever.sol +++ b/contracts/Blast_DaiRetriever.sol @@ -3,8 +3,8 @@ pragma solidity ^0.8.0; import "./Lockable.sol"; -import "@openzeppelin/contracts-upgradeable/token/ERC20/IERC20Upgradeable.sol"; -import "@openzeppelin/contracts-upgradeable/token/ERC20/utils/SafeERC20Upgradeable.sol"; +import "@openzeppelin/contracts5/token/ERC20/IERC20.sol"; +import "@openzeppelin/contracts5/token/ERC20/utils/SafeERC20.sol"; import "@uma/core/contracts/common/implementation/MultiCaller.sol"; interface USDYieldManager { @@ -19,7 +19,7 @@ interface USDYieldManager { * and then an EOA can call this contract to retrieve the DAI. */ contract Blast_DaiRetriever is Lockable, MultiCaller { - using SafeERC20Upgradeable for IERC20Upgradeable; + using SafeERC20 for IERC20; // Should be set to HubPool on Ethereum address public immutable hubPool; @@ -28,7 +28,7 @@ contract Blast_DaiRetriever is Lockable, MultiCaller { USDYieldManager public immutable usdYieldManager; // Token to be retrieved. - IERC20Upgradeable public immutable dai; + IERC20 public immutable dai; /** * @notice Constructs USDB Retriever @@ -39,7 +39,7 @@ contract Blast_DaiRetriever is Lockable, MultiCaller { constructor( address _hubPool, USDYieldManager _usdYieldManager, - IERC20Upgradeable _dai + IERC20 _dai ) { //slither-disable-next-line missing-zero-check hubPool = _hubPool; diff --git a/contracts/Ethereum_SpokePool.sol b/contracts/Ethereum_SpokePool.sol index 1882693e0..1c57a28e0 100644 --- a/contracts/Ethereum_SpokePool.sol +++ b/contracts/Ethereum_SpokePool.sol @@ -9,7 +9,7 @@ import "@openzeppelin/contracts-upgradeable/access/OwnableUpgradeable.sol"; * @custom:security-contact bugs@across.to */ contract Ethereum_SpokePool is SpokePool, OwnableUpgradeable { - using SafeERC20Upgradeable for IERC20Upgradeable; + using SafeERC20 for IERC20; /// @custom:oz-upgrades-unsafe-allow constructor constructor( @@ -35,7 +35,7 @@ contract Ethereum_SpokePool is SpokePool, OwnableUpgradeable { **************************************/ function _bridgeTokensToHubPool(uint256 amountToReturn, address l2TokenAddress) internal override { - IERC20Upgradeable(l2TokenAddress).safeTransfer(hubPool, amountToReturn); + IERC20(l2TokenAddress).safeTransfer(hubPool, amountToReturn); } // The SpokePool deployed to the same network as the HubPool must be owned by the HubPool. diff --git a/contracts/Linea_SpokePool.sol b/contracts/Linea_SpokePool.sol index af1c473c7..704b0d6e8 100644 --- a/contracts/Linea_SpokePool.sol +++ b/contracts/Linea_SpokePool.sol @@ -6,8 +6,8 @@ pragma solidity ^0.8.19; import "./SpokePool.sol"; import { IMessageService, ITokenBridge, IUSDCBridge } from "./external/interfaces/LineaInterfaces.sol"; -import "@openzeppelin/contracts/token/ERC20/IERC20.sol"; -import "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol"; +import "@openzeppelin/contracts5/token/ERC20/IERC20.sol"; +import "@openzeppelin/contracts5/token/ERC20/utils/SafeERC20.sol"; /** * @notice Linea specific SpokePool. @@ -154,12 +154,12 @@ contract Linea_SpokePool is SpokePool { } // If the l1Token is USDC, then we need sent it via the USDC Bridge. else if (l2TokenAddress == l2UsdcBridge.usdc()) { - IERC20(l2TokenAddress).safeIncreaseAllowance(address(l2UsdcBridge), amountToReturn); + IERC20(l2TokenAddress).forceApprove(address(l2UsdcBridge), amountToReturn); l2UsdcBridge.depositTo{ value: msg.value }(amountToReturn, hubPool); } // For other tokens, we can use the Canonical Token Bridge. else { - IERC20(l2TokenAddress).safeIncreaseAllowance(address(l2TokenBridge), amountToReturn); + IERC20(l2TokenAddress).forceApprove(address(l2TokenBridge), amountToReturn); l2TokenBridge.bridgeToken{ value: msg.value }(l2TokenAddress, amountToReturn, hubPool); } } diff --git a/contracts/Ovm_SpokePool.sol b/contracts/Ovm_SpokePool.sol index 830d57727..528ccd8ae 100644 --- a/contracts/Ovm_SpokePool.sol +++ b/contracts/Ovm_SpokePool.sol @@ -177,7 +177,7 @@ contract Ovm_SpokePool is SpokePool, CircleCCTPAdapter { if (remoteL1Tokens[l2TokenAddress] != address(0)) { // If there is a mapping for this L2 token to an L1 token, then use the L1 token address and // call bridgeERC20To. - IERC20(l2TokenAddress).safeIncreaseAllowance(address(tokenBridge), amountToReturn); + IERC20(l2TokenAddress).forceApprove(address(tokenBridge), amountToReturn); address remoteL1Token = remoteL1Tokens[l2TokenAddress]; tokenBridge.bridgeERC20To( l2TokenAddress, // _l2Token. Address of the L2 token to bridge over. diff --git a/contracts/PermissionSplitterProxy.sol b/contracts/PermissionSplitterProxy.sol index eebbd07e0..674fac0e1 100644 --- a/contracts/PermissionSplitterProxy.sol +++ b/contracts/PermissionSplitterProxy.sol @@ -2,7 +2,7 @@ pragma solidity ^0.8.0; import "@uma/core/contracts/common/implementation/MultiCaller.sol"; -import "@openzeppelin/contracts/access/AccessControl.sol"; +import "@openzeppelin/contracts5/access/AccessControl.sol"; /** * @notice This contract is designed to own an Ownable "target" contract and gate access to specific diff --git a/contracts/PolygonTokenBridger.sol b/contracts/PolygonTokenBridger.sol index ab5dac349..de934b6fd 100644 --- a/contracts/PolygonTokenBridger.sol +++ b/contracts/PolygonTokenBridger.sol @@ -4,8 +4,8 @@ pragma solidity ^0.8.0; import "./Lockable.sol"; import "./external/interfaces/WETH9Interface.sol"; -import "@openzeppelin/contracts-upgradeable/token/ERC20/IERC20Upgradeable.sol"; -import "@openzeppelin/contracts-upgradeable/token/ERC20/utils/SafeERC20Upgradeable.sol"; +import "@openzeppelin/contracts5/token/ERC20/IERC20.sol"; +import "@openzeppelin/contracts5/token/ERC20/utils/SafeERC20.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 PolygonIERC20Upgradeable is IERC20Upgradeable { +interface PolygonIERC20Upgradeable is IERC20 { function withdraw(uint256 amount) external; } @@ -40,8 +40,8 @@ interface MaticToken { * @custom:security-contact bugs@across.to */ contract PolygonTokenBridger is Lockable { - using SafeERC20Upgradeable for PolygonIERC20Upgradeable; - using SafeERC20Upgradeable for IERC20Upgradeable; + using SafeERC20 for PolygonIERC20Upgradeable; + using SafeERC20 for IERC20; // Gas token for Polygon. MaticToken public constant MATIC = MaticToken(0x0000000000000000000000000000000000001010); @@ -121,7 +121,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(IERC20Upgradeable token) public nonReentrant onlyChainId(l1ChainId) { + function retrieve(IERC20 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 diff --git a/contracts/PolygonZkEVM_SpokePool.sol b/contracts/PolygonZkEVM_SpokePool.sol index 8de3867b4..74003dd3b 100644 --- a/contracts/PolygonZkEVM_SpokePool.sol +++ b/contracts/PolygonZkEVM_SpokePool.sol @@ -4,8 +4,8 @@ pragma solidity ^0.8.0; import "./SpokePool.sol"; import "./external/interfaces/IPolygonZkEVMBridge.sol"; -import "@openzeppelin/contracts/token/ERC20/IERC20.sol"; -import "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol"; +import "@openzeppelin/contracts5/token/ERC20/IERC20.sol"; +import "@openzeppelin/contracts5/token/ERC20/utils/SafeERC20.sol"; /** * @notice Define interface for PolygonZkEVM Bridge message receiver @@ -184,7 +184,7 @@ contract PolygonZkEVM_SpokePool is SpokePool, IBridgeMessageReceiver { "" ); } else { - IERC20(l2TokenAddress).safeIncreaseAllowance(address(l2PolygonZkEVMBridge), amountToReturn); + IERC20(l2TokenAddress).forceApprove(address(l2PolygonZkEVMBridge), amountToReturn); l2PolygonZkEVMBridge.bridgeAsset( POLYGON_ZKEVM_L1_NETWORK_ID, hubPool, diff --git a/contracts/Polygon_SpokePool.sol b/contracts/Polygon_SpokePool.sol index 413a18abd..4af13a299 100644 --- a/contracts/Polygon_SpokePool.sol +++ b/contracts/Polygon_SpokePool.sol @@ -33,7 +33,7 @@ interface IFxMessageProcessor { * @custom:security-contact bugs@across.to */ contract Polygon_SpokePool is IFxMessageProcessor, SpokePool, CircleCCTPAdapter { - using SafeERC20Upgradeable for PolygonIERC20Upgradeable; + using SafeERC20 for PolygonIERC20Upgradeable; // Address of FxChild which sends and receives messages to and from L1. address public fxChild; @@ -241,10 +241,7 @@ contract Polygon_SpokePool is IFxMessageProcessor, SpokePool, CircleCCTPAdapter if (_isCCTPEnabled() && l2TokenAddress == address(usdcToken)) { _transferUsdc(hubPool, amountToReturn); } else { - PolygonIERC20Upgradeable(l2TokenAddress).safeIncreaseAllowance( - address(polygonTokenBridger), - amountToReturn - ); + PolygonIERC20Upgradeable(l2TokenAddress).forceApprove(address(polygonTokenBridger), amountToReturn); // Note: WrappedNativeToken is WMATIC on matic, so this tells the tokenbridger that this is an unwrappable native token. polygonTokenBridger.send(PolygonIERC20Upgradeable(l2TokenAddress), amountToReturn); } diff --git a/contracts/Scroll_SpokePool.sol b/contracts/Scroll_SpokePool.sol index d9ba1d987..98ce4fff7 100644 --- a/contracts/Scroll_SpokePool.sol +++ b/contracts/Scroll_SpokePool.sol @@ -18,7 +18,7 @@ interface IL2GatewayRouterExtended is IL2GatewayRouter { * @custom:security-contact bugs@across.to */ contract Scroll_SpokePool is SpokePool { - using SafeERC20Upgradeable for IERC20Upgradeable; + using SafeERC20 for IERC20; /** * @notice The address of the official l2GatewayRouter contract for Scroll for bridging tokens from L2 -> L1 @@ -99,7 +99,7 @@ contract Scroll_SpokePool is SpokePool { // Tokens with a custom ERC20 gateway require an approval in order to withdraw. address erc20Gateway = l2GatewayRouter.getERC20Gateway(l2TokenAddress); if (erc20Gateway != l2GatewayRouter.defaultERC20Gateway()) { - IERC20Upgradeable(l2TokenAddress).safeIncreaseAllowance(erc20Gateway, amountToReturn); + IERC20(l2TokenAddress).forceApprove(erc20Gateway, amountToReturn); } // The scroll bridge handles arbitrary ERC20 tokens and is mindful of the official WETH address on-chain. diff --git a/contracts/SpokePool.sol b/contracts/SpokePool.sol index d4d1a3599..5a40b87c2 100644 --- a/contracts/SpokePool.sol +++ b/contracts/SpokePool.sol @@ -1,7 +1,7 @@ // SPDX-License-Identifier: BUSL-1.1 pragma solidity ^0.8.0; -import "./MerkleLib.sol"; +import { MerkleLib } from "./MerkleLib.sol"; import "./external/interfaces/WETH9Interface.sol"; import "./interfaces/SpokePoolMessageHandler.sol"; import "./interfaces/SpokePoolInterface.sol"; @@ -10,12 +10,17 @@ import "./upgradeable/MultiCallerUpgradeable.sol"; import "./upgradeable/EIP712CrossChainUpgradeable.sol"; import "./upgradeable/AddressLibUpgradeable.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/contracts5/token/ERC20/IERC20.sol"; +import "@openzeppelin/contracts5/token/ERC20/utils/SafeERC20.sol"; +import "@openzeppelin/contracts5/utils/cryptography/SignatureChecker.sol"; +import "@openzeppelin/contracts5/utils/math/SignedMath.sol"; + +// Use 4.x version of OpenZeppelin proxy contract which match those deployed in production and we don't plan +// to upgrade. import "@openzeppelin/contracts-upgradeable/proxy/utils/UUPSUpgradeable.sol"; +// We can't use the 4.x version of ReentrancyGuardUpgradeable because it imports Initializable +// which UUPSUpgradeable also imports so these two versions need to match. import "@openzeppelin/contracts-upgradeable/security/ReentrancyGuardUpgradeable.sol"; -import "@openzeppelin/contracts/utils/math/SignedMath.sol"; /** * @title SpokePool @@ -35,7 +40,7 @@ abstract contract SpokePool is MultiCallerUpgradeable, EIP712CrossChainUpgradeable { - using SafeERC20Upgradeable for IERC20Upgradeable; + using SafeERC20 for IERC20; using AddressLibUpgradeable for address; // Address of the L1 contract that acts as the owner of this SpokePool. This should normally be set to the HubPool @@ -579,7 +584,7 @@ abstract contract SpokePool is } else { // msg.value should be 0 if input token isn't the wrapped native token. if (msg.value != 0) revert MsgValueDoesNotMatchInputAmount(); - IERC20Upgradeable(inputToken).safeTransferFrom(msg.sender, address(this), inputAmount); + IERC20(inputToken).safeTransferFrom(msg.sender, address(this), inputAmount); } emit V3FundsDeposited( @@ -1103,7 +1108,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 IERC20Upgradeable(originToken).safeTransferFrom(msg.sender, address(this), amount); + } else IERC20(originToken).safeTransferFrom(msg.sender, address(this), amount); emit V3FundsDeposited( originToken, // inputToken @@ -1147,7 +1152,7 @@ abstract contract SpokePool is uint256 length = refundAmounts.length; for (uint256 i = 0; i < length; ++i) { uint256 amount = refundAmounts[i]; - if (amount > 0) IERC20Upgradeable(l2TokenAddress).safeTransfer(refundAddresses[i], amount); + if (amount > 0) IERC20(l2TokenAddress).safeTransfer(refundAddresses[i], amount); } // If leaf's amountToReturn is positive, then send L2 --> L1 message to bridge tokens back via @@ -1269,7 +1274,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()) { - IERC20Upgradeable(address(wrappedNativeToken)).safeTransfer(to, amount); + IERC20(address(wrappedNativeToken)).safeTransfer(to, amount); } else { wrappedNativeToken.withdraw(amount); AddressLibUpgradeable.sendValue(to, amount); @@ -1357,13 +1362,13 @@ abstract contract SpokePool is // recipient wants wrappedNativeToken, then we can assume that wrappedNativeToken is already in the // 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 (!isSlowFill) IERC20Upgradeable(outputToken).safeTransferFrom(msg.sender, address(this), amountToSend); + if (!isSlowFill) IERC20(outputToken).safeTransferFrom(msg.sender, address(this), amountToSend); _unwrapwrappedNativeTokenTo(payable(recipientToSend), 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 (!isSlowFill) IERC20Upgradeable(outputToken).safeTransferFrom(msg.sender, recipientToSend, amountToSend); - else IERC20Upgradeable(outputToken).safeTransfer(recipientToSend, amountToSend); + if (!isSlowFill) IERC20(outputToken).safeTransferFrom(msg.sender, recipientToSend, amountToSend); + else IERC20(outputToken).safeTransfer(recipientToSend, amountToSend); } bytes memory updatedMessage = relayExecution.updatedMessage; diff --git a/contracts/SpokePoolVerifier.sol b/contracts/SpokePoolVerifier.sol index 5dc333684..b5684e4c0 100644 --- a/contracts/SpokePoolVerifier.sol +++ b/contracts/SpokePoolVerifier.sol @@ -1,7 +1,8 @@ // SPDX-License-Identifier: BUSL-1.1 -pragma solidity ^0.8.0; +// NOTE: Linea and Arbitrum only support 0.8.19, so keep this contract at 19 so create2 addressses for all chains are +// the same. +pragma solidity ^0.8.19; -import "@openzeppelin/contracts/utils/Address.sol"; import "./interfaces/V3SpokePoolInterface.sol"; /** @@ -13,8 +14,6 @@ import "./interfaces/V3SpokePoolInterface.sol"; * @custom:security-contact bugs@across.to */ contract SpokePoolVerifier { - using Address for address; - error InvalidMsgValue(); error InvalidSpokePool(); @@ -54,7 +53,7 @@ contract SpokePoolVerifier { bytes memory message ) external payable { if (msg.value != inputAmount) revert InvalidMsgValue(); - if (!address(spokePool).isContract()) revert InvalidSpokePool(); + if (address(spokePool).code.length == 0) revert InvalidSpokePool(); // Set msg.sender as the depositor so that msg.sender can speed up the deposit. spokePool.depositV3{ value: msg.value }( msg.sender, diff --git a/contracts/SwapAndBridge.sol b/contracts/SwapAndBridge.sol index 787d4bef0..14175f91f 100644 --- a/contracts/SwapAndBridge.sol +++ b/contracts/SwapAndBridge.sol @@ -2,8 +2,8 @@ pragma solidity ^0.8.0; import "./interfaces/V3SpokePoolInterface.sol"; -import "@openzeppelin/contracts/token/ERC20/IERC20.sol"; -import "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol"; +import "@openzeppelin/contracts5/token/ERC20/IERC20.sol"; +import "@openzeppelin/contracts5/token/ERC20/utils/SafeERC20.sol"; import "./Lockable.sol"; import "@uma/core/contracts/common/implementation/MultiCaller.sol"; @@ -109,7 +109,7 @@ abstract contract SwapAndBridgeBase is Lockable, MultiCaller { uint256 srcBalanceBefore = _swapToken.balanceOf(address(this)); uint256 dstBalanceBefore = _acrossInputToken.balanceOf(address(this)); - _swapToken.safeIncreaseAllowance(EXCHANGE, swapTokenAmount); + _swapToken.forceApprove(EXCHANGE, swapTokenAmount); // solhint-disable-next-line avoid-low-level-calls (bool success, bytes memory result) = EXCHANGE.call(routerCalldata); require(success, string(result)); @@ -159,7 +159,7 @@ abstract contract SwapAndBridgeBase is Lockable, MultiCaller { depositData.outputAmount ); // Deposit the swapped tokens into Across and bridge them using remainder of input params. - _acrossInputToken.safeIncreaseAllowance(address(SPOKE_POOL), returnAmount); + _acrossInputToken.forceApprove(address(SPOKE_POOL), returnAmount); SPOKE_POOL.depositV3( depositData.depositor, depositData.recipient, diff --git a/contracts/chain-adapters/Arbitrum_Adapter.sol b/contracts/chain-adapters/Arbitrum_Adapter.sol index 36a78ee33..bdb73085a 100644 --- a/contracts/chain-adapters/Arbitrum_Adapter.sol +++ b/contracts/chain-adapters/Arbitrum_Adapter.sol @@ -3,8 +3,8 @@ pragma solidity ^0.8.0; import "./interfaces/AdapterInterface.sol"; -import "@openzeppelin/contracts/token/ERC20/IERC20.sol"; -import "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol"; +import "@openzeppelin/contracts5/token/ERC20/IERC20.sol"; +import "@openzeppelin/contracts5/token/ERC20/utils/SafeERC20.sol"; import "../external/interfaces/CCTPInterfaces.sol"; import "../libraries/CircleCCTPAdapter.sol"; @@ -238,7 +238,7 @@ contract Arbitrum_Adapter is AdapterInterface, CircleCCTPAdapter { // Approve the gateway, not the router, to spend the hub pool's balance. The gateway, which is different // per L1 token, will temporarily escrow the tokens to be bridged and pull them from this contract. address erc20Gateway = L1_ERC20_GATEWAY_ROUTER.getGateway(l1Token); - IERC20(l1Token).safeIncreaseAllowance(erc20Gateway, amount); + IERC20(l1Token).forceApprove(erc20Gateway, amount); // `outboundTransfer` expects that the caller includes a bytes message as the last param that includes the // maxSubmissionCost to use when creating an L2 retryable ticket: https://github.com/OffchainLabs/arbitrum/blob/e98d14873dd77513b569771f47b5e05b72402c5e/packages/arb-bridge-peripherals/contracts/tokenbridge/ethereum/gateway/L1GatewayRouter.sol#L232 diff --git a/contracts/chain-adapters/Arbitrum_CustomGasToken_Adapter.sol b/contracts/chain-adapters/Arbitrum_CustomGasToken_Adapter.sol index bf4243006..ff7a089e6 100644 --- a/contracts/chain-adapters/Arbitrum_CustomGasToken_Adapter.sol +++ b/contracts/chain-adapters/Arbitrum_CustomGasToken_Adapter.sol @@ -3,8 +3,8 @@ pragma solidity ^0.8.0; import "./interfaces/AdapterInterface.sol"; -import "@openzeppelin/contracts/token/ERC20/IERC20.sol"; -import "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol"; +import "@openzeppelin/contracts5/token/ERC20/IERC20.sol"; +import "@openzeppelin/contracts5/token/ERC20/utils/SafeERC20.sol"; import "../external/interfaces/CCTPInterfaces.sol"; import "../libraries/CircleCCTPAdapter.sol"; @@ -194,7 +194,7 @@ contract Arbitrum_CustomGasToken_Adapter is AdapterInterface, CircleCCTPAdapter */ function relayMessage(address target, bytes memory message) external payable override { uint256 requiredL1TokenTotalFeeAmount = _pullCustomGas(RELAY_MESSAGE_L2_GAS_LIMIT); - CUSTOM_GAS_TOKEN.safeIncreaseAllowance(address(L1_INBOX), requiredL1TokenTotalFeeAmount); + CUSTOM_GAS_TOKEN.forceApprove(address(L1_INBOX), requiredL1TokenTotalFeeAmount); L1_INBOX.createRetryableTicket( target, // destAddr destination L2 contract address L2_CALL_VALUE, // l2CallValue call value for retryable L2 message @@ -239,7 +239,7 @@ contract Arbitrum_CustomGasToken_Adapter is AdapterInterface, CircleCCTPAdapter // Source: https://github.com/OffchainLabs/token-bridge-contracts/blob/5bdf33259d2d9ae52ddc69bc5a9cbc558c4c40c7/contracts/tokenbridge/ethereum/gateway/L1OrbitERC20Gateway.sol#L33 if (l1Token == address(CUSTOM_GAS_TOKEN)) { uint256 amountToBridge = amount + requiredL1TokenTotalFeeAmount; - CUSTOM_GAS_TOKEN.safeIncreaseAllowance(address(L1_INBOX), amountToBridge); + CUSTOM_GAS_TOKEN.forceApprove(address(L1_INBOX), amountToBridge); L1_INBOX.createRetryableTicket( to, // destAddr destination L2 contract address L2_CALL_VALUE, // l2CallValue call value for retryable L2 message @@ -252,8 +252,8 @@ contract Arbitrum_CustomGasToken_Adapter is AdapterInterface, CircleCCTPAdapter "0x" // data ABI encoded data of L2 message ); } else { - IERC20(l1Token).safeIncreaseAllowance(erc20Gateway, amount); - CUSTOM_GAS_TOKEN.safeIncreaseAllowance(erc20Gateway, requiredL1TokenTotalFeeAmount); + IERC20(l1Token).forceApprove(erc20Gateway, amount); + CUSTOM_GAS_TOKEN.forceApprove(erc20Gateway, requiredL1TokenTotalFeeAmount); // To pay for gateway outbound transfer with custom gas token, encode the tokenTotalFeeAmount in the data field: // The data format should be (uint256 maxSubmissionCost, bytes extraData, uint256 tokenTotalFeeAmount). diff --git a/contracts/chain-adapters/Arbitrum_CustomGasToken_Funder.sol b/contracts/chain-adapters/Arbitrum_CustomGasToken_Funder.sol index b473393a4..cb0adf0fa 100644 --- a/contracts/chain-adapters/Arbitrum_CustomGasToken_Funder.sol +++ b/contracts/chain-adapters/Arbitrum_CustomGasToken_Funder.sol @@ -1,13 +1,15 @@ // SPDX-License-Identifier: MIT pragma solidity ^0.8.0; -import "@openzeppelin/contracts/access/Ownable.sol"; -import "@openzeppelin/contracts/token/ERC20/IERC20.sol"; -import "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol"; +import "@openzeppelin/contracts5/access/Ownable.sol"; +import "@openzeppelin/contracts5/token/ERC20/IERC20.sol"; +import "@openzeppelin/contracts5/token/ERC20/utils/SafeERC20.sol"; contract Arbitrum_CustomGasToken_Funder is Ownable { using SafeERC20 for IERC20; + constructor(address owner) Ownable(owner) {} + /** * @notice Withdraw tokens from the contract. * @param token Token to withdraw. diff --git a/contracts/chain-adapters/Arbitrum_RescueAdapter.sol b/contracts/chain-adapters/Arbitrum_RescueAdapter.sol index 1a66e5133..70977af91 100644 --- a/contracts/chain-adapters/Arbitrum_RescueAdapter.sol +++ b/contracts/chain-adapters/Arbitrum_RescueAdapter.sol @@ -4,8 +4,8 @@ pragma solidity ^0.8.0; import "./interfaces/AdapterInterface.sol"; import "./Arbitrum_Adapter.sol"; // Used to import `ArbitrumL1ERC20GatewayLike` and `ArbitrumL1InboxLike` -import "@openzeppelin/contracts/token/ERC20/IERC20.sol"; -import "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol"; +import "@openzeppelin/contracts5/token/ERC20/IERC20.sol"; +import "@openzeppelin/contracts5/token/ERC20/utils/SafeERC20.sol"; /** * @notice Meant to copy the Arbitrum_Adapter exactly in how it sends L1 --> L2 messages but is designed only to be diff --git a/contracts/chain-adapters/Arbitrum_SendTokensAdapter.sol b/contracts/chain-adapters/Arbitrum_SendTokensAdapter.sol index 344684363..0656afe97 100644 --- a/contracts/chain-adapters/Arbitrum_SendTokensAdapter.sol +++ b/contracts/chain-adapters/Arbitrum_SendTokensAdapter.sol @@ -4,8 +4,8 @@ pragma solidity ^0.8.0; import "./interfaces/AdapterInterface.sol"; import { ArbitrumL1ERC20GatewayLike } from "./Arbitrum_Adapter.sol"; -import "@openzeppelin/contracts/token/ERC20/IERC20.sol"; -import "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol"; +import "@openzeppelin/contracts5/token/ERC20/IERC20.sol"; +import "@openzeppelin/contracts5/token/ERC20/utils/SafeERC20.sol"; /** * @notice This adapter is built for emergencies to send funds from the Hub to a Spoke in the event that a spoke pool @@ -47,7 +47,7 @@ contract Arbitrum_SendTokensAdapter is AdapterInterface { // Approve the gateway, not the router, to spend the hub pool's balance. The gateway, which is different // per L1 token, will temporarily escrow the tokens to be bridged and pull them from this contract. address erc20Gateway = l1ERC20GatewayRouter.getGateway(l1Token); - IERC20(l1Token).safeIncreaseAllowance(erc20Gateway, amount); + IERC20(l1Token).forceApprove(erc20Gateway, amount); // `outboundTransfer` expects that the caller includes a bytes message as the last param that includes the // maxSubmissionCost to use when creating an L2 retryable ticket: https://github.com/OffchainLabs/arbitrum/blob/e98d14873dd77513b569771f47b5e05b72402c5e/packages/arb-bridge-peripherals/contracts/tokenbridge/ethereum/gateway/L1GatewayRouter.sol#L232 diff --git a/contracts/chain-adapters/Base_Adapter.sol b/contracts/chain-adapters/Base_Adapter.sol index f79f1b7e1..64adff7ad 100644 --- a/contracts/chain-adapters/Base_Adapter.sol +++ b/contracts/chain-adapters/Base_Adapter.sol @@ -9,8 +9,8 @@ import "../external/interfaces/WETH9Interface.sol"; import "./CrossDomainEnabled.sol"; import "@eth-optimism/contracts/L1/messaging/IL1StandardBridge.sol"; -import "@openzeppelin/contracts/token/ERC20/IERC20.sol"; -import "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol"; +import "@openzeppelin/contracts5/token/ERC20/IERC20.sol"; +import "@openzeppelin/contracts5/token/ERC20/utils/SafeERC20.sol"; import "../libraries/CircleCCTPAdapter.sol"; import "../external/interfaces/CCTPInterfaces.sol"; @@ -87,7 +87,7 @@ contract Base_Adapter is CrossDomainEnabled, AdapterInterface, CircleCCTPAdapter } else { IL1StandardBridge _l1StandardBridge = L1_STANDARD_BRIDGE; - IERC20(l1Token).safeIncreaseAllowance(address(_l1StandardBridge), amount); + IERC20(l1Token).forceApprove(address(_l1StandardBridge), amount); _l1StandardBridge.depositERC20To(l1Token, l2Token, to, amount, L2_GAS_LIMIT, ""); } emit TokensRelayed(l1Token, l2Token, amount, to); diff --git a/contracts/chain-adapters/Blast_Adapter.sol b/contracts/chain-adapters/Blast_Adapter.sol index 83fa8053c..763170980 100644 --- a/contracts/chain-adapters/Blast_Adapter.sol +++ b/contracts/chain-adapters/Blast_Adapter.sol @@ -9,8 +9,8 @@ import "../external/interfaces/WETH9Interface.sol"; import "./CrossDomainEnabled.sol"; import { IL1StandardBridge } from "@eth-optimism/contracts/L1/messaging/IL1StandardBridge.sol"; -import "@openzeppelin/contracts/token/ERC20/IERC20.sol"; -import "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol"; +import "@openzeppelin/contracts5/token/ERC20/IERC20.sol"; +import "@openzeppelin/contracts5/token/ERC20/utils/SafeERC20.sol"; import "../libraries/CircleCCTPAdapter.sol"; import "../external/interfaces/CCTPInterfaces.sol"; @@ -119,12 +119,12 @@ contract Blast_Adapter is CrossDomainEnabled, AdapterInterface, CircleCCTPAdapte } // Check if this token is DAI, then use the L1 Blast Bridge else if (l1Token == L1_DAI) { - IERC20(l1Token).safeIncreaseAllowance(address(L1_BLAST_BRIDGE), amount); + IERC20(l1Token).forceApprove(address(L1_BLAST_BRIDGE), amount); L1_BLAST_BRIDGE.bridgeERC20To(l1Token, l2Token, to, amount, L2_GAS_LIMIT, ""); } else { IL1StandardBridge _l1StandardBridge = L1_STANDARD_BRIDGE; - IERC20(l1Token).safeIncreaseAllowance(address(_l1StandardBridge), amount); + IERC20(l1Token).forceApprove(address(_l1StandardBridge), amount); _l1StandardBridge.depositERC20To(l1Token, l2Token, to, amount, L2_GAS_LIMIT, ""); } emit TokensRelayed(l1Token, l2Token, amount, to); diff --git a/contracts/chain-adapters/Blast_RescueAdapter.sol b/contracts/chain-adapters/Blast_RescueAdapter.sol index 5b4140983..45358fe28 100644 --- a/contracts/chain-adapters/Blast_RescueAdapter.sol +++ b/contracts/chain-adapters/Blast_RescueAdapter.sol @@ -4,8 +4,8 @@ pragma solidity ^0.8.0; import "./interfaces/AdapterInterface.sol"; import { USDYieldManager } from "../Blast_DaiRetriever.sol"; -import "@openzeppelin/contracts/token/ERC20/IERC20.sol"; -import "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol"; +import "@openzeppelin/contracts5/token/ERC20/IERC20.sol"; +import "@openzeppelin/contracts5/token/ERC20/utils/SafeERC20.sol"; /** * @notice This adapter is built to to retrieve Blast USDB from the USDBYieldManager contract on Ethereum that was diff --git a/contracts/chain-adapters/Boba_Adapter.sol b/contracts/chain-adapters/Boba_Adapter.sol index 76e23af07..b9404bc15 100644 --- a/contracts/chain-adapters/Boba_Adapter.sol +++ b/contracts/chain-adapters/Boba_Adapter.sol @@ -9,8 +9,8 @@ import "../external/interfaces/WETH9Interface.sol"; import "./CrossDomainEnabled.sol"; import "@eth-optimism/contracts/L1/messaging/IL1StandardBridge.sol"; -import "@openzeppelin/contracts/token/ERC20/IERC20.sol"; -import "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol"; +import "@openzeppelin/contracts5/token/ERC20/IERC20.sol"; +import "@openzeppelin/contracts5/token/ERC20/utils/SafeERC20.sol"; /** * @notice Contract containing logic to send messages from L1 to Boba. This is a modified version of the Optimism adapter @@ -75,7 +75,7 @@ contract Boba_Adapter is CrossDomainEnabled, AdapterInterface { } else { IL1StandardBridge _l1StandardBridge = L1_STANDARD_BRIDGE; - IERC20(l1Token).safeIncreaseAllowance(address(_l1StandardBridge), amount); + IERC20(l1Token).forceApprove(address(_l1StandardBridge), amount); _l1StandardBridge.depositERC20To(l1Token, l2Token, to, amount, L2_GAS_LIMIT, ""); } emit TokensRelayed(l1Token, l2Token, amount, to); diff --git a/contracts/chain-adapters/Ethereum_Adapter.sol b/contracts/chain-adapters/Ethereum_Adapter.sol index ceef12ab3..678484892 100644 --- a/contracts/chain-adapters/Ethereum_Adapter.sol +++ b/contracts/chain-adapters/Ethereum_Adapter.sol @@ -3,8 +3,8 @@ pragma solidity ^0.8.0; import "./interfaces/AdapterInterface.sol"; -import "@openzeppelin/contracts/token/ERC20/IERC20.sol"; -import "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol"; +import "@openzeppelin/contracts5/token/ERC20/IERC20.sol"; +import "@openzeppelin/contracts5/token/ERC20/utils/SafeERC20.sol"; /** * @custom:security-contact bugs@across.to diff --git a/contracts/chain-adapters/Ethereum_RescueAdapter.sol b/contracts/chain-adapters/Ethereum_RescueAdapter.sol index c8fc200ac..65cbbf0b2 100644 --- a/contracts/chain-adapters/Ethereum_RescueAdapter.sol +++ b/contracts/chain-adapters/Ethereum_RescueAdapter.sol @@ -3,8 +3,8 @@ pragma solidity ^0.8.0; import "./interfaces/AdapterInterface.sol"; -import "@openzeppelin/contracts/token/ERC20/IERC20.sol"; -import "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol"; +import "@openzeppelin/contracts5/token/ERC20/IERC20.sol"; +import "@openzeppelin/contracts5/token/ERC20/utils/SafeERC20.sol"; /** * @notice This adapter is built for emergencies to rescue funds from a Hub in the event of a misconfiguration or diff --git a/contracts/chain-adapters/Linea_Adapter.sol b/contracts/chain-adapters/Linea_Adapter.sol index 7ede0c142..1a61c31d1 100644 --- a/contracts/chain-adapters/Linea_Adapter.sol +++ b/contracts/chain-adapters/Linea_Adapter.sol @@ -6,8 +6,8 @@ import "../external/interfaces/WETH9Interface.sol"; import { IMessageService, ITokenBridge, IUSDCBridge } from "../external/interfaces/LineaInterfaces.sol"; -import "@openzeppelin/contracts/token/ERC20/IERC20.sol"; -import "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol"; +import "@openzeppelin/contracts5/token/ERC20/IERC20.sol"; +import "@openzeppelin/contracts5/token/ERC20/utils/SafeERC20.sol"; /** * @notice Supports sending messages and tokens from L1 to Linea. @@ -75,12 +75,12 @@ contract Linea_Adapter is AdapterInterface { } // If the l1Token is USDC, then we need sent it via the USDC Bridge. else if (l1Token == L1_USDC_BRIDGE.usdc()) { - IERC20(l1Token).safeIncreaseAllowance(address(L1_USDC_BRIDGE), amount); + IERC20(l1Token).forceApprove(address(L1_USDC_BRIDGE), amount); L1_USDC_BRIDGE.depositTo(amount, to); } // For other tokens, we can use the Canonical Token Bridge. else { - IERC20(l1Token).safeIncreaseAllowance(address(L1_TOKEN_BRIDGE), amount); + IERC20(l1Token).forceApprove(address(L1_TOKEN_BRIDGE), amount); L1_TOKEN_BRIDGE.bridgeToken(l1Token, amount, to); } diff --git a/contracts/chain-adapters/Lisk_Adapter.sol b/contracts/chain-adapters/Lisk_Adapter.sol index 7b0e749eb..a83dad1c6 100644 --- a/contracts/chain-adapters/Lisk_Adapter.sol +++ b/contracts/chain-adapters/Lisk_Adapter.sol @@ -9,8 +9,8 @@ import "../external/interfaces/WETH9Interface.sol"; import "./CrossDomainEnabled.sol"; import "@eth-optimism/contracts/L1/messaging/IL1StandardBridge.sol"; -import "@openzeppelin/contracts/token/ERC20/IERC20.sol"; -import "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol"; +import "@openzeppelin/contracts5/token/ERC20/IERC20.sol"; +import "@openzeppelin/contracts5/token/ERC20/utils/SafeERC20.sol"; import "../libraries/CircleCCTPAdapter.sol"; import "../external/interfaces/CCTPInterfaces.sol"; @@ -92,7 +92,7 @@ contract Lisk_Adapter is CrossDomainEnabled, AdapterInterface, CircleCCTPAdapter } else { IL1StandardBridge _l1StandardBridge = L1_STANDARD_BRIDGE; - IERC20(l1Token).safeIncreaseAllowance(address(_l1StandardBridge), amount); + IERC20(l1Token).forceApprove(address(_l1StandardBridge), amount); _l1StandardBridge.depositERC20To(l1Token, l2Token, to, amount, L2_GAS_LIMIT, ""); } emit TokensRelayed(l1Token, l2Token, amount, to); diff --git a/contracts/chain-adapters/Mock_Adapter.sol b/contracts/chain-adapters/Mock_Adapter.sol index 3095ab138..f4a21f7b5 100644 --- a/contracts/chain-adapters/Mock_Adapter.sol +++ b/contracts/chain-adapters/Mock_Adapter.sol @@ -2,7 +2,7 @@ pragma solidity ^0.8.0; import "./interfaces/AdapterInterface.sol"; -import "@openzeppelin/contracts/token/ERC20/IERC20.sol"; +import "@openzeppelin/contracts5/token/ERC20/IERC20.sol"; /** * @notice Contract used for testing communication between HubPool and Adapter. diff --git a/contracts/chain-adapters/Mode_Adapter.sol b/contracts/chain-adapters/Mode_Adapter.sol index 389dd6848..81031f939 100644 --- a/contracts/chain-adapters/Mode_Adapter.sol +++ b/contracts/chain-adapters/Mode_Adapter.sol @@ -9,8 +9,8 @@ import "../external/interfaces/WETH9Interface.sol"; import "./CrossDomainEnabled.sol"; import "@eth-optimism/contracts/L1/messaging/IL1StandardBridge.sol"; -import "@openzeppelin/contracts/token/ERC20/IERC20.sol"; -import "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol"; +import "@openzeppelin/contracts5/token/ERC20/IERC20.sol"; +import "@openzeppelin/contracts5/token/ERC20/utils/SafeERC20.sol"; import "../libraries/CircleCCTPAdapter.sol"; import "../external/interfaces/CCTPInterfaces.sol"; @@ -92,7 +92,7 @@ contract Mode_Adapter is CrossDomainEnabled, AdapterInterface, CircleCCTPAdapter } else { IL1StandardBridge _l1StandardBridge = L1_STANDARD_BRIDGE; - IERC20(l1Token).safeIncreaseAllowance(address(_l1StandardBridge), amount); + IERC20(l1Token).forceApprove(address(_l1StandardBridge), amount); _l1StandardBridge.depositERC20To(l1Token, l2Token, to, amount, L2_GAS_LIMIT, ""); } emit TokensRelayed(l1Token, l2Token, amount, to); diff --git a/contracts/chain-adapters/Optimism_Adapter.sol b/contracts/chain-adapters/Optimism_Adapter.sol index 22eadabec..5b1f9c8e1 100644 --- a/contracts/chain-adapters/Optimism_Adapter.sol +++ b/contracts/chain-adapters/Optimism_Adapter.sol @@ -11,8 +11,8 @@ import "../external/interfaces/CCTPInterfaces.sol"; import "./CrossDomainEnabled.sol"; import "@eth-optimism/contracts/L1/messaging/IL1StandardBridge.sol"; -import "@openzeppelin/contracts/token/ERC20/IERC20.sol"; -import "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol"; +import "@openzeppelin/contracts5/token/ERC20/IERC20.sol"; +import "@openzeppelin/contracts5/token/ERC20/utils/SafeERC20.sol"; /** * @notice Interface for Synthetix custom bridge to Optimism. @@ -114,7 +114,7 @@ contract Optimism_Adapter is CrossDomainEnabled, AdapterInterface, CircleCCTPAda if (l1Token == DAI) bridgeToUse = DAI_OPTIMISM_BRIDGE; // 1. DAI if (l1Token == SNX) bridgeToUse = SNX_OPTIMISM_BRIDGE; // 2. SNX - IERC20(l1Token).safeIncreaseAllowance(bridgeToUse, amount); + IERC20(l1Token).forceApprove(bridgeToUse, amount); if (l1Token == SNX) SynthetixBridgeToOptimism(bridgeToUse).depositTo(to, amount); else IL1StandardBridge(bridgeToUse).depositERC20To(l1Token, l2Token, to, amount, L2_GAS_LIMIT, ""); } diff --git a/contracts/chain-adapters/PolygonZkEVM_Adapter.sol b/contracts/chain-adapters/PolygonZkEVM_Adapter.sol index 318e87195..e54b038b1 100644 --- a/contracts/chain-adapters/PolygonZkEVM_Adapter.sol +++ b/contracts/chain-adapters/PolygonZkEVM_Adapter.sol @@ -5,8 +5,8 @@ import "./interfaces/AdapterInterface.sol"; import "../external/interfaces/WETH9Interface.sol"; import "../external/interfaces/IPolygonZkEVMBridge.sol"; -import "@openzeppelin/contracts/token/ERC20/IERC20.sol"; -import "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol"; +import "@openzeppelin/contracts5/token/ERC20/IERC20.sol"; +import "@openzeppelin/contracts5/token/ERC20/utils/SafeERC20.sol"; /** * @notice Supports sending messages and tokens from L1 to PolygonZkEVM. @@ -71,7 +71,7 @@ contract PolygonZkEVM_Adapter is AdapterInterface { "" ); } else { - IERC20(l1Token).safeIncreaseAllowance(address(L1_POLYGON_ZKEVM_BRIDGE), amount); + IERC20(l1Token).forceApprove(address(L1_POLYGON_ZKEVM_BRIDGE), amount); L1_POLYGON_ZKEVM_BRIDGE.bridgeAsset(POLYGON_ZKEVM_L2_NETWORK_ID, to, amount, l1Token, true, ""); } diff --git a/contracts/chain-adapters/Polygon_Adapter.sol b/contracts/chain-adapters/Polygon_Adapter.sol index 09d65829d..3c695084e 100644 --- a/contracts/chain-adapters/Polygon_Adapter.sol +++ b/contracts/chain-adapters/Polygon_Adapter.sol @@ -4,8 +4,8 @@ pragma solidity ^0.8.0; import "./interfaces/AdapterInterface.sol"; import "../external/interfaces/WETH9Interface.sol"; -import "@openzeppelin/contracts/token/ERC20/IERC20.sol"; -import "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol"; +import "@openzeppelin/contracts5/token/ERC20/IERC20.sol"; +import "@openzeppelin/contracts5/token/ERC20/utils/SafeERC20.sol"; import "../libraries/CircleCCTPAdapter.sol"; import "../external/interfaces/CCTPInterfaces.sol"; @@ -143,10 +143,10 @@ contract Polygon_Adapter is AdapterInterface, CircleCCTPAdapter { else if (_isCCTPEnabled() && l1Token == address(usdcToken)) { _transferUsdc(to, amount); } else if (l1Token == L1_MATIC) { - IERC20(l1Token).safeIncreaseAllowance(address(DEPOSIT_MANAGER), amount); + IERC20(l1Token).forceApprove(address(DEPOSIT_MANAGER), amount); DEPOSIT_MANAGER.depositERC20ForUser(l1Token, to, amount); } else { - IERC20(l1Token).safeIncreaseAllowance(ERC20_PREDICATE, amount); + IERC20(l1Token).forceApprove(ERC20_PREDICATE, amount); ROOT_CHAIN_MANAGER.depositFor(to, l1Token, abi.encode(amount)); } emit TokensRelayed(l1Token, l2Token, amount, to); diff --git a/contracts/chain-adapters/Redstone_Adapter.sol b/contracts/chain-adapters/Redstone_Adapter.sol index 401c33be8..165110fed 100644 --- a/contracts/chain-adapters/Redstone_Adapter.sol +++ b/contracts/chain-adapters/Redstone_Adapter.sol @@ -9,8 +9,8 @@ import "../external/interfaces/WETH9Interface.sol"; import "./CrossDomainEnabled.sol"; import "@eth-optimism/contracts/L1/messaging/IL1StandardBridge.sol"; -import "@openzeppelin/contracts/token/ERC20/IERC20.sol"; -import "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol"; +import "@openzeppelin/contracts5/token/ERC20/IERC20.sol"; +import "@openzeppelin/contracts5/token/ERC20/utils/SafeERC20.sol"; import "../libraries/CircleCCTPAdapter.sol"; import "../external/interfaces/CCTPInterfaces.sol"; @@ -92,7 +92,7 @@ contract Redstone_Adapter is CrossDomainEnabled, AdapterInterface, CircleCCTPAda } else { IL1StandardBridge _l1StandardBridge = L1_STANDARD_BRIDGE; - IERC20(l1Token).safeIncreaseAllowance(address(_l1StandardBridge), amount); + IERC20(l1Token).forceApprove(address(_l1StandardBridge), amount); _l1StandardBridge.depositERC20To(l1Token, l2Token, to, amount, L2_GAS_LIMIT, ""); } emit TokensRelayed(l1Token, l2Token, amount, to); diff --git a/contracts/chain-adapters/Scroll_Adapter.sol b/contracts/chain-adapters/Scroll_Adapter.sol index aa7ec7509..688546025 100644 --- a/contracts/chain-adapters/Scroll_Adapter.sol +++ b/contracts/chain-adapters/Scroll_Adapter.sol @@ -1,8 +1,8 @@ // SPDX-License-Identifier: BUSL-1.1 pragma solidity ^0.8.0; -import "@openzeppelin/contracts/token/ERC20/IERC20.sol"; -import "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol"; +import "@openzeppelin/contracts5/token/ERC20/IERC20.sol"; +import "@openzeppelin/contracts5/token/ERC20/utils/SafeERC20.sol"; import "@scroll-tech/contracts/L1/gateways/IL1GatewayRouter.sol"; import "@scroll-tech/contracts/L1/rollup/IL2GasPriceOracle.sol"; import "@scroll-tech/contracts/L1/IL1ScrollMessenger.sol"; @@ -116,7 +116,7 @@ contract Scroll_Adapter is AdapterInterface { address _l2Token = _l1GatewayRouter.getL2ERC20Address(l1Token); require(_l2Token == l2Token, "l2Token Mismatch"); - IERC20(l1Token).safeIncreaseAllowance(address(_l1GatewayRouter), amount); + IERC20(l1Token).forceApprove(address(_l1GatewayRouter), amount); // The scroll bridge handles arbitrary ERC20 tokens and is mindful of // the official WETH address on-chain. We don't need to do anything specific diff --git a/contracts/chain-adapters/ZkSync_Adapter.sol b/contracts/chain-adapters/ZkSync_Adapter.sol index be6f5e0b2..e1035915d 100644 --- a/contracts/chain-adapters/ZkSync_Adapter.sol +++ b/contracts/chain-adapters/ZkSync_Adapter.sol @@ -4,8 +4,8 @@ pragma solidity ^0.8.0; import "./interfaces/AdapterInterface.sol"; import "../external/interfaces/WETH9Interface.sol"; -import "@openzeppelin/contracts/token/ERC20/IERC20.sol"; -import "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol"; +import "@openzeppelin/contracts5/token/ERC20/IERC20.sol"; +import "@openzeppelin/contracts5/token/ERC20/utils/SafeERC20.sol"; interface ZkSyncInterface { // _contractL2: L2 address of the contract to be called. @@ -97,7 +97,7 @@ contract LimitBypassProxy is ZkSyncInterface, ZkBridgeLike { uint256 _l2TxGasPerPubdataByte, address _refundRecipient ) external payable returns (bytes32 txHash) { - IERC20(_l1Token).safeIncreaseAllowance(address(zkErc20Bridge), _amount); + IERC20(_l1Token).forceApprove(address(zkErc20Bridge), _amount); return zkErc20Bridge.deposit{ value: msg.value }( _l2Receiver, @@ -238,7 +238,7 @@ contract ZkSync_Adapter is AdapterInterface { l2RefundAddress ); } else { - IERC20(l1Token).safeIncreaseAllowance(address(zkErc20Bridge), amount); + IERC20(l1Token).forceApprove(address(zkErc20Bridge), amount); txHash = zkErc20Bridge.deposit{ value: txBaseCost }( to, l1Token, diff --git a/contracts/chain-adapters/Zora_Adapter.sol b/contracts/chain-adapters/Zora_Adapter.sol index c36288fd5..75d98ef6a 100644 --- a/contracts/chain-adapters/Zora_Adapter.sol +++ b/contracts/chain-adapters/Zora_Adapter.sol @@ -9,8 +9,8 @@ import "../external/interfaces/WETH9Interface.sol"; import "./CrossDomainEnabled.sol"; import "@eth-optimism/contracts/L1/messaging/IL1StandardBridge.sol"; -import "@openzeppelin/contracts/token/ERC20/IERC20.sol"; -import "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol"; +import "@openzeppelin/contracts5/token/ERC20/IERC20.sol"; +import "@openzeppelin/contracts5/token/ERC20/utils/SafeERC20.sol"; import "../libraries/CircleCCTPAdapter.sol"; import "../external/interfaces/CCTPInterfaces.sol"; @@ -92,7 +92,7 @@ contract Zora_Adapter is CrossDomainEnabled, AdapterInterface, CircleCCTPAdapter } else { IL1StandardBridge _l1StandardBridge = L1_STANDARD_BRIDGE; - IERC20(l1Token).safeIncreaseAllowance(address(_l1StandardBridge), amount); + IERC20(l1Token).forceApprove(address(_l1StandardBridge), amount); _l1StandardBridge.depositERC20To(l1Token, l2Token, to, amount, L2_GAS_LIMIT, ""); } emit TokensRelayed(l1Token, l2Token, amount, to); diff --git a/contracts/erc1155/MintableERC1155.sol b/contracts/erc1155/MintableERC1155.sol index 7603c9b4d..30f8dfd70 100644 --- a/contracts/erc1155/MintableERC1155.sol +++ b/contracts/erc1155/MintableERC1155.sol @@ -1,8 +1,8 @@ // SPDX-License-Identifier: BUSL-1.1 pragma solidity ^0.8.0; -import "@openzeppelin/contracts/access/Ownable.sol"; -import "@openzeppelin/contracts/token/ERC1155/ERC1155.sol"; +import "@openzeppelin/contracts5/access/Ownable.sol"; +import "@openzeppelin/contracts5/token/ERC1155/ERC1155.sol"; /** * @title MintableERC1155 @@ -17,7 +17,7 @@ contract MintableERC1155 is ERC1155, Ownable { // We are passing an empty string as the `baseURI` because we use `_tokenURIs` instead // to allow for IPFS URIs. // solhint-disable-next-line - constructor() ERC1155("") {} + constructor() ERC1155("") Ownable(msg.sender) {} /** * @notice Creates `amount` new tokens for `recipients` of token type `tokenId`. diff --git a/contracts/erc7683/ERC7683OrderDepositor.sol b/contracts/erc7683/ERC7683OrderDepositor.sol index 315e28356..830e3ca9d 100644 --- a/contracts/erc7683/ERC7683OrderDepositor.sol +++ b/contracts/erc7683/ERC7683OrderDepositor.sol @@ -2,8 +2,8 @@ pragma solidity ^0.8.0; import "../external/interfaces/IPermit2.sol"; -import "@openzeppelin/contracts/utils/math/SafeCast.sol"; -import "@openzeppelin/contracts/token/ERC20/IERC20.sol"; +import "@openzeppelin/contracts5/utils/math/SafeCast.sol"; +import "@openzeppelin/contracts5/token/ERC20/IERC20.sol"; import { Input, Output, CrossChainOrder, ResolvedCrossChainOrder, ISettlementContract } from "./ERC7683.sol"; import { AcrossOrderData, AcrossFillerData, ERC7683Permit2Lib } from "./ERC7683Across.sol"; diff --git a/contracts/erc7683/ERC7683OrderDepositorExternal.sol b/contracts/erc7683/ERC7683OrderDepositorExternal.sol index c6260da55..e4962ac62 100644 --- a/contracts/erc7683/ERC7683OrderDepositorExternal.sol +++ b/contracts/erc7683/ERC7683OrderDepositorExternal.sol @@ -1,7 +1,7 @@ // SPDX-License-Identifier: UNLICENSED pragma solidity ^0.8.0; -import "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol"; +import "@openzeppelin/contracts5/token/ERC20/utils/SafeERC20.sol"; import { ERC7683OrderDepositor } from "./ERC7683OrderDepositor.sol"; import "../interfaces/V3SpokePoolInterface.sol"; import "../external/interfaces/IPermit2.sol"; @@ -37,7 +37,7 @@ contract ERC7683OrderDepositorExternal is ERC7683OrderDepositor { uint32 exclusivityDeadline, bytes memory message ) internal override { - IERC20(inputToken).safeIncreaseAllowance(address(SPOKE_POOL), inputAmount); + IERC20(inputToken).forceApprove(address(SPOKE_POOL), inputAmount); SPOKE_POOL.depositV3( depositor, diff --git a/contracts/handlers/MulticallHandler.sol b/contracts/handlers/MulticallHandler.sol index 0f6c72503..4eebfad2d 100644 --- a/contracts/handlers/MulticallHandler.sol +++ b/contracts/handlers/MulticallHandler.sol @@ -2,10 +2,10 @@ pragma solidity ^0.8.0; import "../interfaces/SpokePoolMessageHandler.sol"; -import "@openzeppelin/contracts/token/ERC20/IERC20.sol"; -import "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol"; -import "@openzeppelin/contracts/utils/Address.sol"; -import "@openzeppelin/contracts/security/ReentrancyGuard.sol"; +import "@openzeppelin/contracts5/token/ERC20/IERC20.sol"; +import "@openzeppelin/contracts5/token/ERC20/utils/SafeERC20.sol"; +import "@openzeppelin/contracts5/utils/Address.sol"; +import "@openzeppelin/contracts5/utils/ReentrancyGuard.sol"; /** * @title Across Multicall contract that allows a user to specify a series of calls that should be made by the handler diff --git a/contracts/libraries/CircleCCTPAdapter.sol b/contracts/libraries/CircleCCTPAdapter.sol index 043c1a820..920643461 100644 --- a/contracts/libraries/CircleCCTPAdapter.sol +++ b/contracts/libraries/CircleCCTPAdapter.sol @@ -1,8 +1,8 @@ // SPDX-License-Identifier: BUSL-1.1 pragma solidity ^0.8.0; -import "@openzeppelin/contracts/token/ERC20/IERC20.sol"; -import "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol"; +import "@openzeppelin/contracts5/token/ERC20/IERC20.sol"; +import "@openzeppelin/contracts5/token/ERC20/utils/SafeERC20.sol"; import "../external/interfaces/CCTPInterfaces.sol"; library CircleDomainIds { @@ -88,7 +88,7 @@ abstract contract CircleCCTPAdapter { */ function _transferUsdc(address to, uint256 amount) internal { // Only approve the exact amount to be transferred - usdcToken.safeIncreaseAllowance(address(cctpTokenMessenger), amount); + usdcToken.forceApprove(address(cctpTokenMessenger), amount); // Submit the amount to be transferred to bridged via the TokenMessenger. // If the amount to send exceeds the burn limit per message, then split the message into smaller parts. ITokenMinter cctpMinter = cctpTokenMessenger.localMinter(); diff --git a/contracts/merkle-distributor/AcrossMerkleDistributor.sol b/contracts/merkle-distributor/AcrossMerkleDistributor.sol index 3fde38ece..8a6aec308 100644 --- a/contracts/merkle-distributor/AcrossMerkleDistributor.sol +++ b/contracts/merkle-distributor/AcrossMerkleDistributor.sol @@ -1,9 +1,9 @@ // SPDX-License-Identifier: BUSL-1.1 pragma solidity ^0.8.0; -import "@uma/core/contracts/merkle-distributor/implementation/MerkleDistributor.sol"; -import "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol"; -import "@openzeppelin/contracts/token/ERC20/IERC20.sol"; +import "./MerkleDistributor.sol"; +import "@openzeppelin/contracts5/token/ERC20/utils/SafeERC20.sol"; +import "@openzeppelin/contracts5/token/ERC20/IERC20.sol"; /** * @title Extended MerkleDistributor contract. diff --git a/contracts/merkle-distributor/MerkleDistributor.sol b/contracts/merkle-distributor/MerkleDistributor.sol new file mode 100644 index 000000000..12c0e26fb --- /dev/null +++ b/contracts/merkle-distributor/MerkleDistributor.sol @@ -0,0 +1,243 @@ +// SPDX-License-Identifier: AGPL-3.0-only +pragma solidity ^0.8.0; + +import "@openzeppelin/contracts5/token/ERC20/utils/SafeERC20.sol"; +import "@openzeppelin/contracts5/utils/cryptography/MerkleProof.sol"; +import "@openzeppelin/contracts5/access/Ownable.sol"; +import "@openzeppelin/contracts5/token/ERC20/IERC20.sol"; +import "./MerkleDistributorInterface.sol"; + +/** + * Inspired by: + * - https://github.com/pie-dao/vested-token-migration-app + * - https://github.com/Uniswap/merkle-distributor + * - https://github.com/balancer-labs/erc20-redeemable + * + * @title MerkleDistributor contract. + * @notice Allows an owner to distribute any reward ERC20 to claimants according to Merkle roots. The owner can specify + * multiple Merkle roots distributions with customized reward currencies. + * @dev The Merkle trees are not validated in any way, so the system assumes the contract owner behaves honestly. + */ +contract MerkleDistributor is MerkleDistributorInterface, Ownable { + using SafeERC20 for IERC20; + + // Windows are mapped to arbitrary indices. + mapping(uint256 => Window) public merkleWindows; + + // Index of next created Merkle root. + uint256 public nextCreatedIndex; + + // Track which accounts have claimed for each window index. + // Note: uses a packed array of bools for gas optimization on tracking certain claims. Copied from Uniswap's contract. + mapping(uint256 => mapping(uint256 => uint256)) private claimedBitMap; + + constructor() Ownable(msg.sender) {} + + /**************************************** + * EVENTS + ****************************************/ + event Claimed( + address indexed caller, + uint256 windowIndex, + address indexed account, + uint256 accountIndex, + uint256 amount, + address indexed rewardToken + ); + event CreatedWindow( + uint256 indexed windowIndex, + uint256 rewardsDeposited, + address indexed rewardToken, + address owner + ); + event WithdrawRewards(address indexed owner, uint256 amount, address indexed currency); + event DeleteWindow(uint256 indexed windowIndex, address owner); + + /**************************** + * ADMIN FUNCTIONS + ****************************/ + + /** + * @notice Set merkle root for the next available window index and seed allocations. + * @notice Callable only by owner of this contract. Caller must have approved this contract to transfer + * `rewardsToDeposit` amount of `rewardToken` or this call will fail. Importantly, we assume that the + * owner of this contract correctly chooses an amount `rewardsToDeposit` that is sufficient to cover all + * claims within the `merkleRoot`. + * @param rewardsToDeposit amount of rewards to deposit to seed this allocation. + * @param rewardToken ERC20 reward token. + * @param merkleRoot merkle root describing allocation. + * @param ipfsHash hash of IPFS object, conveniently stored for clients + */ + function setWindow( + uint256 rewardsToDeposit, + address rewardToken, + bytes32 merkleRoot, + string calldata ipfsHash + ) external onlyOwner { + uint256 indexToSet = nextCreatedIndex; + nextCreatedIndex = indexToSet + 1; + + _setWindow(indexToSet, rewardsToDeposit, rewardToken, merkleRoot, ipfsHash); + } + + /** + * @notice Delete merkle root at window index. + * @dev Callable only by owner. Likely to be followed by a withdrawRewards call to clear contract state. + * @param windowIndex merkle root index to delete. + */ + function deleteWindow(uint256 windowIndex) external onlyOwner { + delete merkleWindows[windowIndex]; + emit DeleteWindow(windowIndex, msg.sender); + } + + /** + * @notice Emergency method that transfers rewards out of the contract if the contract was configured improperly. + * @dev Callable only by owner. + * @param rewardCurrency rewards to withdraw from contract. + * @param amount amount of rewards to withdraw. + */ + function withdrawRewards(IERC20 rewardCurrency, uint256 amount) external onlyOwner { + rewardCurrency.safeTransfer(msg.sender, amount); + emit WithdrawRewards(msg.sender, amount, address(rewardCurrency)); + } + + /**************************** + * NON-ADMIN FUNCTIONS + ****************************/ + + /** + * @notice Batch claims to reduce gas versus individual submitting all claims. Method will fail + * if any individual claims within the batch would fail. + * @dev Optimistically tries to batch together consecutive claims for the same account and same + * reward token to reduce gas. Therefore, the most gas-cost-optimal way to use this method + * is to pass in an array of claims sorted by account and reward currency. It also reverts + * when any of individual `_claim`'s `amount` exceeds `remainingAmount` for its window. + * @param claims array of claims to claim. + */ + function claimMulti(Claim[] memory claims) public virtual override { + uint256 batchedAmount; + uint256 claimCount = claims.length; + for (uint256 i = 0; i < claimCount; i++) { + Claim memory _claim = claims[i]; + _verifyAndMarkClaimed(_claim); + batchedAmount += _claim.amount; + + // If the next claim is NOT the same account or the same token (or this claim is the last one), + // then disburse the `batchedAmount` to the current claim's account for the current claim's reward token. + uint256 nextI = i + 1; + IERC20 currentRewardToken = merkleWindows[_claim.windowIndex].rewardToken; + if ( + nextI == claimCount || + // This claim is last claim. + claims[nextI].account != _claim.account || + // Next claim account is different than current one. + merkleWindows[claims[nextI].windowIndex].rewardToken != currentRewardToken + // Next claim reward token is different than current one. + ) { + currentRewardToken.safeTransfer(_claim.account, batchedAmount); + batchedAmount = 0; + } + } + } + + /** + * @notice Claim amount of reward tokens for account, as described by Claim input object. + * @dev If the `_claim`'s `amount`, `accountIndex`, and `account` do not exactly match the + * values stored in the merkle root for the `_claim`'s `windowIndex` this method + * will revert. It also reverts when `_claim`'s `amount` exceeds `remainingAmount` for the window. + * @param _claim claim object describing amount, accountIndex, account, window index, and merkle proof. + */ + function claim(Claim memory _claim) public virtual override { + _verifyAndMarkClaimed(_claim); + merkleWindows[_claim.windowIndex].rewardToken.safeTransfer(_claim.account, _claim.amount); + } + + /** + * @notice Returns True if the claim for `accountIndex` has already been completed for the Merkle root at + * `windowIndex`. + * @dev This method will only work as intended if all `accountIndex`'s are unique for a given `windowIndex`. + * The onus is on the Owner of this contract to submit only valid Merkle roots. + * @param windowIndex merkle root to check. + * @param accountIndex account index to check within window index. + * @return True if claim has been executed already, False otherwise. + */ + function isClaimed(uint256 windowIndex, uint256 accountIndex) public view returns (bool) { + uint256 claimedWordIndex = accountIndex / 256; + uint256 claimedBitIndex = accountIndex % 256; + uint256 claimedWord = claimedBitMap[windowIndex][claimedWordIndex]; + uint256 mask = (1 << claimedBitIndex); + return claimedWord & mask == mask; + } + + /** + * @notice Returns rewardToken set by admin for windowIndex. + * @param windowIndex merkle root to check. + * @return address Reward token address + */ + function getRewardTokenForWindow(uint256 windowIndex) public view override returns (address) { + return address(merkleWindows[windowIndex].rewardToken); + } + + /** + * @notice Returns True if leaf described by {account, amount, accountIndex} is stored in Merkle root at given + * window index. + * @param _claim claim object describing amount, accountIndex, account, window index, and merkle proof. + * @return valid True if leaf exists. + */ + function verifyClaim(Claim memory _claim) public view returns (bool valid) { + bytes32 leaf = keccak256(abi.encodePacked(_claim.account, _claim.amount, _claim.accountIndex)); + return MerkleProof.verify(_claim.merkleProof, merkleWindows[_claim.windowIndex].merkleRoot, leaf); + } + + /**************************** + * PRIVATE FUNCTIONS + ****************************/ + + // Mark claim as completed for `accountIndex` for Merkle root at `windowIndex`. + function _setClaimed(uint256 windowIndex, uint256 accountIndex) private { + uint256 claimedWordIndex = accountIndex / 256; + uint256 claimedBitIndex = accountIndex % 256; + claimedBitMap[windowIndex][claimedWordIndex] = + claimedBitMap[windowIndex][claimedWordIndex] | + (1 << claimedBitIndex); + } + + // Store new Merkle root at `windowindex`. Pull `rewardsDeposited` from caller to seed distribution for this root. + function _setWindow( + uint256 windowIndex, + uint256 rewardsDeposited, + address rewardToken, + bytes32 merkleRoot, + string memory ipfsHash + ) private { + Window storage window = merkleWindows[windowIndex]; + window.merkleRoot = merkleRoot; + window.remainingAmount = rewardsDeposited; + window.rewardToken = IERC20(rewardToken); + window.ipfsHash = ipfsHash; + + emit CreatedWindow(windowIndex, rewardsDeposited, rewardToken, msg.sender); + + window.rewardToken.safeTransferFrom(msg.sender, address(this), rewardsDeposited); + } + + // Verify claim is valid and mark it as completed in this contract. + function _verifyAndMarkClaimed(Claim memory _claim) internal { + // Check claimed proof against merkle window at given index. + require(verifyClaim(_claim), "Incorrect merkle proof"); + // Check the account has not yet claimed for this window. + require(!isClaimed(_claim.windowIndex, _claim.accountIndex), "Account has already claimed for this window"); + + // Proof is correct and claim has not occurred yet, mark claimed complete. + _setClaimed(_claim.windowIndex, _claim.accountIndex); + merkleWindows[_claim.windowIndex].remainingAmount -= _claim.amount; + emit Claimed( + msg.sender, + _claim.windowIndex, + _claim.account, + _claim.accountIndex, + _claim.amount, + address(merkleWindows[_claim.windowIndex].rewardToken) + ); + } +} diff --git a/contracts/merkle-distributor/MerkleDistributorInterface.sol b/contracts/merkle-distributor/MerkleDistributorInterface.sol new file mode 100644 index 000000000..654db3097 --- /dev/null +++ b/contracts/merkle-distributor/MerkleDistributorInterface.sol @@ -0,0 +1,41 @@ +// SPDX-License-Identifier: GPL-3.0-only +pragma solidity ^0.8.0; + +import "@openzeppelin/contracts5/token/ERC20/IERC20.sol"; + +/** + * @notice Concise list of functions in MerkleDistributor implementation that would be called by + * a consuming external contract (such as the Across Protocol's AcceleratingDistributor). + */ +interface MerkleDistributorInterface { + // A Window maps a Merkle root to a reward token address. + struct Window { + // Merkle root describing the distribution. + bytes32 merkleRoot; + // Remaining amount of deposited rewards that have not yet been claimed. + uint256 remainingAmount; + // Currency in which reward is processed. + IERC20 rewardToken; + // IPFS hash of the merkle tree. Can be used to independently fetch recipient proofs and tree. Note that the canonical + // data type for storing an IPFS hash is a multihash which is the concatenation of + // . We opted to store this in a string type to make it easier + // for users to query the ipfs data without needing to reconstruct the multihash. to view the IPFS data simply + // go to https://cloudflare-ipfs.com/ipfs/. + string ipfsHash; + } + + // Represents an account's claim for `amount` within the Merkle root located at the `windowIndex`. + struct Claim { + uint256 windowIndex; + uint256 amount; + uint256 accountIndex; // Used only for bitmap. Assumed to be unique for each claim. + address account; + bytes32[] merkleProof; + } + + function claim(Claim memory _claim) external; + + function claimMulti(Claim[] memory claims) external; + + function getRewardTokenForWindow(uint256 windowIndex) external view returns (address); +} diff --git a/contracts/permit2-order/Permit2Depositor.sol b/contracts/permit2-order/Permit2Depositor.sol index 8a72318c9..bebd3f766 100644 --- a/contracts/permit2-order/Permit2Depositor.sol +++ b/contracts/permit2-order/Permit2Depositor.sol @@ -5,9 +5,9 @@ import "./Permit2OrderLib.sol"; import "../external/interfaces/IPermit2.sol"; import "../interfaces/V3SpokePoolInterface.sol"; -import "@openzeppelin/contracts/utils/math/SafeCast.sol"; -import "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol"; -import "@openzeppelin/contracts/token/ERC20/IERC20.sol"; +import "@openzeppelin/contracts5/utils/math/SafeCast.sol"; +import "@openzeppelin/contracts5/token/ERC20/utils/SafeERC20.sol"; +import "@openzeppelin/contracts5/token/ERC20/IERC20.sol"; /** * @notice Permit2Depositor processes an external order type and translates it into an AcrossV3 deposit. @@ -56,7 +56,7 @@ contract Permit2Depositor { // If the user is not filled or filled by someone else, the filler loses their collateral. uint256 amountToDeposit = order.input.amount + order.fillerCollateral.amount; - IERC20(order.input.token).safeIncreaseAllowance(address(SPOKE_POOL), amountToDeposit); + IERC20(order.input.token).forceApprove(address(SPOKE_POOL), amountToDeposit); SPOKE_POOL.depositV3( order.info.offerer, // Note: Permit2OrderLib checks that order only has a single output. diff --git a/contracts/test/MockERC1271.sol b/contracts/test/MockERC1271.sol index 3da5f2440..eadca2192 100644 --- a/contracts/test/MockERC1271.sol +++ b/contracts/test/MockERC1271.sol @@ -1,19 +1,16 @@ //SPDX-License-Identifier: Unlicense pragma solidity ^0.8.0; -import "@openzeppelin/contracts/interfaces/IERC1271.sol"; - -import "@openzeppelin/contracts/utils/cryptography/ECDSA.sol"; -import "@openzeppelin/contracts/access/Ownable.sol"; +import "@openzeppelin/contracts5/interfaces/IERC1271.sol"; +import "@openzeppelin/contracts5/utils/cryptography/ECDSA.sol"; +import "@openzeppelin/contracts5/access/Ownable.sol"; /** * @title MockERC1271 * @notice Implements mocked ERC1271 contract for testing. */ contract MockERC1271 is IERC1271, Ownable { - constructor(address originalOwner) { - transferOwnership(originalOwner); - } + constructor(address originalOwner) Ownable(originalOwner) {} function isValidSignature(bytes32 hash, bytes memory signature) public view override returns (bytes4 magicValue) { return ECDSA.recover(hash, signature) == owner() ? this.isValidSignature.selector : bytes4(0); diff --git a/contracts/test/MockSpokePool.sol b/contracts/test/MockSpokePool.sol index 6c7ddbe90..667059c4c 100644 --- a/contracts/test/MockSpokePool.sol +++ b/contracts/test/MockSpokePool.sol @@ -11,7 +11,7 @@ import "./V2MerkleLib.sol"; * @notice Implements abstract contract for testing. */ contract MockSpokePool is SpokePool, MockV2SpokePoolInterface, OwnableUpgradeable { - using SafeERC20Upgradeable for IERC20Upgradeable; + using SafeERC20 for IERC20; uint256 private chainId_; uint256 private currentTime; @@ -161,7 +161,7 @@ contract MockSpokePool is SpokePool, MockV2SpokePoolInterface, OwnableUpgradeabl if (originToken == address(wrappedNativeToken) && msg.value > 0) { require(msg.value == amount); wrappedNativeToken.deposit{ value: msg.value }(); - } else IERC20Upgradeable(originToken).safeTransferFrom(msg.sender, address(this), amount); + } else IERC20(originToken).safeTransferFrom(msg.sender, address(this), amount); emit FundsDeposited( amount, @@ -439,20 +439,16 @@ contract MockSpokePool is SpokePool, MockV2SpokePoolInterface, OwnableUpgradeabl if (relayData.destinationToken == address(wrappedNativeToken)) { if (!relayExecution.slowFill) - IERC20Upgradeable(relayData.destinationToken).safeTransferFrom(msg.sender, address(this), amountToSend); + IERC20(relayData.destinationToken).safeTransferFrom(msg.sender, address(this), amountToSend); _unwrapwrappedNativeTokenTo(payable(relayExecution.updatedRecipient), amountToSend); } else { if (!relayExecution.slowFill) - IERC20Upgradeable(relayData.destinationToken).safeTransferFrom( + IERC20(relayData.destinationToken).safeTransferFrom( msg.sender, relayExecution.updatedRecipient, amountToSend ); - else - IERC20Upgradeable(relayData.destinationToken).safeTransfer( - relayExecution.updatedRecipient, - amountToSend - ); + else IERC20(relayData.destinationToken).safeTransfer(relayExecution.updatedRecipient, amountToSend); } } diff --git a/contracts/test/PolygonERC20Test.sol b/contracts/test/PolygonERC20Test.sol index a0b3d8b18..f9dd47381 100644 --- a/contracts/test/PolygonERC20Test.sol +++ b/contracts/test/PolygonERC20Test.sol @@ -2,7 +2,6 @@ pragma solidity ^0.8.0; import "@uma/core/contracts/common/implementation/ExpandedERC20.sol"; -import "../PolygonTokenBridger.sol"; /** * @notice Simulated Polygon ERC20 for use in testing PolygonTokenBridger. diff --git a/contracts/test/PolygonMocks.sol b/contracts/test/PolygonMocks.sol index 69fbf567e..1a748ac56 100644 --- a/contracts/test/PolygonMocks.sol +++ b/contracts/test/PolygonMocks.sol @@ -1,7 +1,7 @@ // SPDX-License-Identifier: BUSL-1.1 pragma solidity ^0.8.0; -import "@openzeppelin/contracts/token/ERC20/ERC20.sol"; +import "@openzeppelin/contracts5/token/ERC20/ERC20.sol"; contract RootChainManagerMock { function depositEtherFor(address user) external payable {} // solhint-disable-line no-empty-blocks diff --git a/contracts/upgradeable/EIP712CrossChainUpgradeable.sol b/contracts/upgradeable/EIP712CrossChainUpgradeable.sol index 4c13f779d..9b1deca6e 100644 --- a/contracts/upgradeable/EIP712CrossChainUpgradeable.sol +++ b/contracts/upgradeable/EIP712CrossChainUpgradeable.sol @@ -1,8 +1,8 @@ // SPDX-License-Identifier: BUSL-1.1 pragma solidity ^0.8.0; -import "@openzeppelin/contracts-upgradeable/utils/cryptography/ECDSAUpgradeable.sol"; import "@openzeppelin/contracts-upgradeable/proxy/utils/Initializable.sol"; +import "@openzeppelin/contracts5/utils/cryptography/MessageHashUtils.sol"; /** * @dev https://eips.ethereum.org/EIPS/eip-712[EIP 712] is a standard for hashing and signing of typed structured data. @@ -76,7 +76,7 @@ abstract contract EIP712CrossChainUpgradeable is Initializable { * @return bytes32 Hash digest that is recoverable via `EDCSA.recover`. */ function _hashTypedDataV4(bytes32 structHash, uint256 originChainId) internal view virtual returns (bytes32) { - return ECDSAUpgradeable.toTypedDataHash(_domainSeparatorV4(originChainId), structHash); + return MessageHashUtils.toTypedDataHash(_domainSeparatorV4(originChainId), structHash); } // Reserve storage slots for future versions of this base contract to add state variables without diff --git a/hardhat.config.ts b/hardhat.config.ts index 67a02e863..87214ffb4 100644 --- a/hardhat.config.ts +++ b/hardhat.config.ts @@ -61,25 +61,6 @@ const config: HardhatUserConfig = { compilers: [DEFAULT_CONTRACT_COMPILER_SETTINGS], overrides: { "contracts/HubPool.sol": LARGE_CONTRACT_COMPILER_SETTINGS, - "contracts/Arbitrum_SpokePool.sol": { - ...DEFAULT_CONTRACT_COMPILER_SETTINGS, - // NOTE: Arbitrum, only supports 0.8.19. - // See https://docs.arbitrum.io/for-devs/concepts/differences-between-arbitrum-ethereum/solidity-support#differences-from-solidity-on-ethereum - version: "0.8.19", - }, - // "contracts/Polygon_SpokePool.sol": MEDIUM_CONTRACT_COMPILER_SETTINGS, - "contracts/Linea_SpokePool.sol": { - ...DEFAULT_CONTRACT_COMPILER_SETTINGS, - // NOTE: Linea only supports 0.8.19. - // See https://docs.linea.build/build-on-linea/ethereum-differences#evm-opcodes - version: "0.8.19", - }, - "contracts/SpokePoolVerifier.sol": { - ...DEFAULT_CONTRACT_COMPILER_SETTINGS, - // NOTE: Linea only supports 0.8.19. - // See https://docs.linea.build/build-on-linea/ethereum-differences#evm-opcodes - version: "0.8.19", - }, "contracts/Blast_SpokePool.sol": LARGE_CONTRACT_COMPILER_SETTINGS, "contracts/Lisk_SpokePool.sol": LARGE_CONTRACT_COMPILER_SETTINGS, "contracts/Redstone_SpokePool.sol": LARGE_CONTRACT_COMPILER_SETTINGS, diff --git a/package.json b/package.json index dbf429421..2fa014aee 100644 --- a/package.json +++ b/package.json @@ -40,6 +40,7 @@ "@ethersproject/bignumber": "5.7.0", "@openzeppelin/contracts": "4.9.6", "@openzeppelin/contracts-upgradeable": "4.9.6", + "@openzeppelin/contracts5": "npm:@openzeppelin/contracts@5.0.2", "@scroll-tech/contracts": "^0.1.0", "@uma/common": "^2.34.0", "@uma/contracts-node": "^0.4.17", diff --git a/test/erc1155/MintableERC1155.ts b/test/erc1155/MintableERC1155.ts index 7b434eb30..721404fb2 100644 --- a/test/erc1155/MintableERC1155.ts +++ b/test/erc1155/MintableERC1155.ts @@ -21,7 +21,7 @@ describe("MintableERC1155", () => { it("revert if not called by owner", async () => { await expect(mintableErc1155.connect(otherAccount1).airdrop(tokenId, recipients, 1)).to.be.revertedWith( - "Ownable: caller is not the owner" + `OwnableUnauthorizedAccount("${otherAccount1.address}")` ); }); @@ -38,7 +38,7 @@ describe("MintableERC1155", () => { describe("#setTokenURI() + #uri()", () => { it("revert if not called by owner", async () => { await expect(mintableErc1155.connect(otherAccount1).setTokenURI(0, "uri")).to.be.revertedWith( - "Ownable: caller is not the owner" + `OwnableUnauthorizedAccount("${otherAccount1.address}")` ); }); diff --git a/test/foundry/local/Blast_DaiRetriever.t.sol b/test/foundry/local/Blast_DaiRetriever.t.sol index 2aa737bb4..05cc1a43e 100644 --- a/test/foundry/local/Blast_DaiRetriever.t.sol +++ b/test/foundry/local/Blast_DaiRetriever.t.sol @@ -3,8 +3,9 @@ pragma solidity ^0.8.0; import { Test } from "forge-std/Test.sol"; import { MockERC20 } from "forge-std/mocks/MockERC20.sol"; -import { ERC20 } from "@openzeppelin/contracts/token/ERC20/ERC20.sol"; -import "@openzeppelin/contracts-upgradeable/token/ERC20/IERC20Upgradeable.sol"; +import { ERC20 } from "@openzeppelin/contracts5/token/ERC20/ERC20.sol"; +import "@openzeppelin/contracts5/token/ERC20/IERC20.sol"; +import "@openzeppelin/contracts5/token/ERC20/utils/SafeERC20.sol"; import { Blast_DaiRetriever } from "../../../contracts/Blast_DaiRetriever.sol"; import { MockBlastUsdYieldManager } from "../../../contracts/test/MockBlastUsdYieldManager.sol"; @@ -37,7 +38,7 @@ contract BlastDaiRetrieverTest is Test { hubPool = vm.addr(2); usdYieldManager = new MockBlastUsdYieldManager(); - daiRetriever = new Blast_DaiRetriever(hubPool, usdYieldManager, IERC20Upgradeable(address(dai))); + daiRetriever = new Blast_DaiRetriever(hubPool, usdYieldManager, IERC20(address(dai))); } function testRetrieveSuccess() public { diff --git a/test/foundry/local/MultiCallerUpgradeable.t.sol b/test/foundry/local/MultiCallerUpgradeable.t.sol index cb7dcf279..e8a63b593 100644 --- a/test/foundry/local/MultiCallerUpgradeable.t.sol +++ b/test/foundry/local/MultiCallerUpgradeable.t.sol @@ -6,8 +6,8 @@ import "forge-std/console.sol"; import { SpokePool } from "../../../contracts/SpokePool.sol"; import { Ethereum_SpokePool } from "../../../contracts/Ethereum_SpokePool.sol"; -import { ERC20 } from "@openzeppelin/contracts/token/ERC20/ERC20.sol"; -import { IERC20 } from "@openzeppelin/contracts/token/ERC20/IERC20.sol"; +import { ERC20 } from "@openzeppelin/contracts5/token/ERC20/ERC20.sol"; +import { IERC20 } from "@openzeppelin/contracts5/token/ERC20/IERC20.sol"; // This test does not require a mainnet fork (since it is testing contracts before deployment). contract MultiCallerUpgradeableTest is Test { diff --git a/test/foundry/local/MulticallHandler.t.sol b/test/foundry/local/MulticallHandler.t.sol index 6150f69a5..7192bd91e 100644 --- a/test/foundry/local/MulticallHandler.t.sol +++ b/test/foundry/local/MulticallHandler.t.sol @@ -3,7 +3,7 @@ pragma solidity ^0.8.0; import { Test } from "forge-std/Test.sol"; import { MulticallHandler } from "../../../contracts/handlers/MulticallHandler.sol"; -import { IERC20 } from "@openzeppelin/contracts/token/ERC20/IERC20.sol"; +import { IERC20 } from "@openzeppelin/contracts5/token/ERC20/IERC20.sol"; // Run this test to verify PermissionSplitter behavior when changing ownership of the HubPool // to it. Therefore this test should be run as a fork test via: diff --git a/test/foundry/local/SpokePoolVerifier.t.sol b/test/foundry/local/SpokePoolVerifier.t.sol index 6b770d056..891202764 100644 --- a/test/foundry/local/SpokePoolVerifier.t.sol +++ b/test/foundry/local/SpokePoolVerifier.t.sol @@ -7,7 +7,9 @@ import { SpokePoolVerifier } from "../../../contracts/SpokePoolVerifier.sol"; import { Ethereum_SpokePool } from "../../../contracts/Ethereum_SpokePool.sol"; import { V3SpokePoolInterface } from "../../../contracts/interfaces/V3SpokePoolInterface.sol"; import { WETH9 } from "../../../contracts/external/WETH9.sol"; -import { ERC20 } from "@openzeppelin/contracts/token/ERC20/ERC20.sol"; +import { ERC20 } from "@openzeppelin/contracts5/token/ERC20/ERC20.sol"; + +// Use 4.x. version of proxy contracts to replicate production. import { ERC1967Proxy } from "@openzeppelin/contracts/proxy/ERC1967/ERC1967Proxy.sol"; contract SpokePoolVerifierTest is Test { diff --git a/yarn.lock b/yarn.lock index e4343b733..8b58f416e 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2131,6 +2131,11 @@ resolved "https://registry.yarnpkg.com/@openzeppelin/contracts-upgradeable/-/contracts-upgradeable-4.9.6.tgz#38b21708a719da647de4bb0e4802ee235a0d24df" integrity sha512-m4iHazOsOCv1DgM7eD7GupTJ+NFVujRZt1wzddDPSVGpWdKq1SKkla5htKG7+IS4d2XOCtzkUNwRZ7Vq5aEUMA== +"@openzeppelin/contracts5@npm:@openzeppelin/contracts@5.0.2": + version "5.0.2" + resolved "https://registry.yarnpkg.com/@openzeppelin/contracts/-/contracts-5.0.2.tgz#b1d03075e49290d06570b2fd42154d76c2a5d210" + integrity sha512-ytPc6eLGcHHnapAZ9S+5qsdomhjo6QBHTDRRBFfTxXIpsicMhVPouPgmUPebZZZGX7vt9USA+Z+0M0dSVtSUEA== + "@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"