diff --git a/contracts/SpokePool.sol b/contracts/SpokePool.sol index 58e9f8d9..1d32f29e 100644 --- a/contracts/SpokePool.sol +++ b/contracts/SpokePool.sol @@ -6,11 +6,11 @@ import "./external/interfaces/WETH9Interface.sol"; import "./interfaces/SpokePoolInterface.sol"; 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/contracts-upgradeable/utils/AddressUpgradeable.sol"; import "@openzeppelin/contracts-upgradeable/proxy/utils/UUPSUpgradeable.sol"; import "@openzeppelin/contracts-upgradeable/security/ReentrancyGuardUpgradeable.sol"; import "@openzeppelin/contracts/utils/math/SignedMath.sol"; @@ -43,7 +43,7 @@ abstract contract SpokePool is EIP712CrossChainUpgradeable { using SafeERC20Upgradeable for IERC20Upgradeable; - using AddressUpgradeable for address; + 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 // address. The crossDomainAdmin address is unused when the SpokePool is deployed to the same chain as the HubPool. @@ -1100,8 +1100,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); + AddressLibUpgradeable.sendValue(to, amount); } } diff --git a/contracts/upgradeable/AddressLibUpgradeable.sol b/contracts/upgradeable/AddressLibUpgradeable.sol new file mode 100644 index 00000000..6a3d0cb0 --- /dev/null +++ b/contracts/upgradeable/AddressLibUpgradeable.sol @@ -0,0 +1,254 @@ +// SPDX-License-Identifier: MIT +// OpenZeppelin Contracts (last updated v4.9.0) (utils/Address.sol) + +pragma solidity ^0.8.1; + +/** + * @title AddressUpgradeable + * @dev Collection of functions related to the address type + * @notice Logic is 100% copied from "@openzeppelin/contracts-upgradeable/contracts/utils/AddressUpgradeable.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. + */ +library AddressLibUpgradeable { + /** + * @dev Returns true if `account` is a contract. + * + * [IMPORTANT] + * ==== + * It is unsafe to assume that an address for which this function returns + * false is an externally-owned account (EOA) and not a contract. + * + * Among others, `isContract` will return false for the following + * types of addresses: + * + * - an externally-owned account + * - a contract in construction + * - an address where a contract will be created + * - an address where a contract lived, but was destroyed + * + * Furthermore, `isContract` will also return true if the target contract within + * the same transaction is already scheduled for destruction by `SELFDESTRUCT`, + * which only has an effect at the end of a transaction. + * ==== + * + * [IMPORTANT] + * ==== + * You shouldn't rely on `isContract` to protect against flash loan attacks! + * + * Preventing calls from contracts is highly discouraged. It breaks composability, breaks support for smart wallets + * like Gnosis Safe, and does not provide security since it can be circumvented by calling from a contract + * constructor. + * ==== + */ + function isContract(address account) internal view returns (bool) { + // This method relies on extcodesize/address.code.length, which returns 0 + // for contracts in construction, since the code is only stored at the end + // of the constructor execution. + + return account.code.length > 0; + } + + /** + * @dev Replacement for Solidity's `transfer`: sends `amount` wei to + * `recipient`, forwarding all available gas and reverting on errors. + * + * https://eips.ethereum.org/EIPS/eip-1884[EIP1884] increases the gas cost + * of certain opcodes, possibly making contracts go over the 2300 gas limit + * imposed by `transfer`, making them unable to receive funds via + * `transfer`. {sendValue} removes this limitation. + * + * https://consensys.net/diligence/blog/2019/09/stop-using-soliditys-transfer-now/[Learn more]. + * + * IMPORTANT: because control is transferred to `recipient`, care must be + * taken to not create reentrancy vulnerabilities. Consider using + * {ReentrancyGuard} or the + * https://solidity.readthedocs.io/en/v0.8.0/security-considerations.html#use-the-checks-effects-interactions-pattern[checks-effects-interactions pattern]. + */ + function sendValue(address payable recipient, uint256 amount) internal { + require(address(this).balance >= amount, "Address: insufficient balance"); + + (bool success, ) = recipient.call{ value: amount }(""); + require(success, "Address: unable to send value, recipient may have reverted"); + } + + /** + * @dev Performs a Solidity function call using a low level `call`. A + * plain `call` is an unsafe replacement for a function call: use this + * function instead. + * + * If `target` reverts with a revert reason, it is bubbled up by this + * function (like regular Solidity function calls). + * + * Returns the raw returned data. To convert to the expected return value, + * use https://solidity.readthedocs.io/en/latest/units-and-global-variables.html?highlight=abi.decode#abi-encoding-and-decoding-functions[`abi.decode`]. + * + * Requirements: + * + * - `target` must be a contract. + * - calling `target` with `data` must not revert. + * + * _Available since v3.1._ + */ + function functionCall(address target, bytes memory data) internal returns (bytes memory) { + return functionCallWithValue(target, data, 0, "Address: low-level call failed"); + } + + /** + * @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`], but with + * `errorMessage` as a fallback revert reason when `target` reverts. + * + * _Available since v3.1._ + */ + function functionCall( + address target, + bytes memory data, + string memory errorMessage + ) internal returns (bytes memory) { + return functionCallWithValue(target, data, 0, errorMessage); + } + + /** + * @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`], + * but also transferring `value` wei to `target`. + * + * Requirements: + * + * - the calling contract must have an ETH balance of at least `value`. + * - the called Solidity function must be `payable`. + * + * _Available since v3.1._ + */ + function functionCallWithValue( + address target, + bytes memory data, + uint256 value + ) internal returns (bytes memory) { + return functionCallWithValue(target, data, value, "Address: low-level call with value failed"); + } + + /** + * @dev Same as {xref-Address-functionCallWithValue-address-bytes-uint256-}[`functionCallWithValue`], but + * with `errorMessage` as a fallback revert reason when `target` reverts. + * + * _Available since v3.1._ + */ + function functionCallWithValue( + address target, + bytes memory data, + uint256 value, + string memory errorMessage + ) internal returns (bytes memory) { + require(address(this).balance >= value, "Address: insufficient balance for call"); + (bool success, bytes memory returndata) = target.call{ value: value }(data); + return verifyCallResultFromTarget(target, success, returndata, errorMessage); + } + + /** + * @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`], + * but performing a static call. + * + * _Available since v3.3._ + */ + function functionStaticCall(address target, bytes memory data) internal view returns (bytes memory) { + return functionStaticCall(target, data, "Address: low-level static call failed"); + } + + /** + * @dev Same as {xref-Address-functionCall-address-bytes-string-}[`functionCall`], + * but performing a static call. + * + * _Available since v3.3._ + */ + function functionStaticCall( + address target, + bytes memory data, + string memory errorMessage + ) internal view returns (bytes memory) { + (bool success, bytes memory returndata) = target.staticcall(data); + return verifyCallResultFromTarget(target, success, returndata, errorMessage); + } + + /** + * @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`], + * but performing a delegate call. + * + * _Available since v3.4._ + */ + function functionDelegateCall(address target, bytes memory data) internal returns (bytes memory) { + return functionDelegateCall(target, data, "Address: low-level delegate call failed"); + } + + /** + * @dev Same as {xref-Address-functionCall-address-bytes-string-}[`functionCall`], + * but performing a delegate call. + * + * _Available since v3.4._ + */ + function functionDelegateCall( + address target, + bytes memory data, + string memory errorMessage + ) internal returns (bytes memory) { + /// @custom:oz-upgrades-unsafe-allow delegatecall + (bool success, bytes memory returndata) = target.delegatecall(data); + return verifyCallResultFromTarget(target, success, returndata, errorMessage); + } + + /** + * @dev Tool to verify that a low level call to smart-contract was successful, and revert (either by bubbling + * the revert reason or using the provided one) in case of unsuccessful call or if target was not a contract. + * + * _Available since v4.8._ + */ + function verifyCallResultFromTarget( + address target, + bool success, + bytes memory returndata, + string memory errorMessage + ) internal view returns (bytes memory) { + if (success) { + if (returndata.length == 0) { + // only check isContract if the call was successful and the return data is empty + // otherwise we already know that it was a contract + require(isContract(target), "Address: call to non-contract"); + } + return returndata; + } else { + _revert(returndata, errorMessage); + } + } + + /** + * @dev Tool to verify that a low level call was successful, and revert if it wasn't, either by bubbling the + * revert reason or using the provided one. + * + * _Available since v4.3._ + */ + function verifyCallResult( + bool success, + bytes memory returndata, + string memory errorMessage + ) internal pure returns (bytes memory) { + if (success) { + return returndata; + } else { + _revert(returndata, errorMessage); + } + } + + function _revert(bytes memory returndata, string memory errorMessage) private pure { + // Look for revert reason and bubble it up if present + if (returndata.length > 0) { + // The easiest way to bubble the revert reason is using memory via assembly + /// @solidity memory-safe-assembly + assembly { + let returndata_size := mload(returndata) + revert(add(32, returndata), returndata_size) + } + } else { + revert(errorMessage); + } + } +} diff --git a/package.json b/package.json index 30f39ba8..dfd01d6a 100644 --- a/package.json +++ b/package.json @@ -38,7 +38,7 @@ "@ethersproject/abstract-signer": "5.7.0", "@ethersproject/bignumber": "5.7.0", "@openzeppelin/contracts": "4.9.2", - "@openzeppelin/contracts-upgradeable": "4.8.3", + "@openzeppelin/contracts-upgradeable": "4.9.2", "@uma/common": "^2.29.0", "@uma/contracts-node": "^0.3.18", "@uma/core": "^2.41.0", diff --git a/test/SpokePool.Admin.ts b/test/SpokePool.Admin.ts index fc9e4b91..388f28af 100644 --- a/test/SpokePool.Admin.ts +++ b/test/SpokePool.Admin.ts @@ -15,7 +15,7 @@ describe("SpokePool Admin Functions", async function () { const spokePool = await hre.upgrades.deployProxy( await getContractFactory("MockSpokePool", owner), [1, owner.address, owner.address, owner.address], - { kind: "uups" } + { kind: "uups", unsafeAllow: ["delegatecall"] } ); expect(await spokePool.numberOfDeposits()).to.equal(1); }); diff --git a/test/SpokePool.Upgrades.ts b/test/SpokePool.Upgrades.ts index 38b2b207..aade93be 100644 --- a/test/SpokePool.Upgrades.ts +++ b/test/SpokePool.Upgrades.ts @@ -13,6 +13,7 @@ describe("SpokePool Upgrade Functions", async function () { it("Can upgrade", async function () { const spokePoolV2 = await hre.upgrades.deployImplementation(await ethers.getContractFactory("MockSpokePoolV2"), { kind: "uups", + unsafeAllow: ["delegatecall"], }); 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 087490d3..7af34818 100644 --- a/test/chain-specific-spokepools/Arbitrum_SpokePool.ts +++ b/test/chain-specific-spokepools/Arbitrum_SpokePool.ts @@ -15,7 +15,7 @@ import { hre } from "../../utils/utils.hre"; import { hubPoolFixture } from "../fixtures/HubPool.Fixture"; import { constructSingleRelayerRefundTree } from "../MerkleLib.utils"; -let hubPool: Contract, arbitrumSpokePool: Contract, timer: Contract, dai: Contract, weth: Contract; +let hubPool: Contract, arbitrumSpokePool: Contract, dai: Contract, weth: Contract; let l2Weth: string, l2Dai: string, crossDomainAliasAddress; let owner: SignerWithAddress, relayer: SignerWithAddress, rando: SignerWithAddress, crossDomainAlias: SignerWithAddress; @@ -24,7 +24,7 @@ let l2GatewayRouter: FakeContract; describe("Arbitrum Spoke Pool", function () { beforeEach(async function () { [owner, relayer, rando] = await ethers.getSigners(); - ({ weth, l2Weth, dai, l2Dai, hubPool, timer } = await hubPoolFixture()); + ({ weth, l2Weth, dai, l2Dai, hubPool } = await hubPoolFixture()); // Create an alias for the Owner. Impersonate the account. Crate a signer for it and send it ETH. crossDomainAliasAddress = avmL1ToL2Alias(owner.address); @@ -37,7 +37,7 @@ describe("Arbitrum Spoke Pool", function () { arbitrumSpokePool = await hre.upgrades.deployProxy( await getContractFactory("Arbitrum_SpokePool", owner), [0, l2GatewayRouter.address, owner.address, hubPool.address, l2Weth], - { kind: "uups" } + { kind: "uups", unsafeAllow: ["delegatecall"] } ); await seedContract(arbitrumSpokePool, relayer, [dai], weth, amountHeldByPool); @@ -48,7 +48,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), - { kind: "uups" } + { kind: "uups", unsafeAllow: ["delegatecall"] } ); // 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 46fed70e..09704f2b 100644 --- a/test/chain-specific-spokepools/Ethereum_SpokePool.ts +++ b/test/chain-specific-spokepools/Ethereum_SpokePool.ts @@ -4,19 +4,19 @@ import { hre } from "../../utils/utils.hre"; import { hubPoolFixture } from "../fixtures/HubPool.Fixture"; import { constructSingleRelayerRefundTree } from "../MerkleLib.utils"; -let hubPool: Contract, spokePool: Contract, timer: Contract, dai: Contract, weth: Contract; +let hubPool: Contract, spokePool: Contract, dai: Contract, weth: Contract; let owner: SignerWithAddress, relayer: SignerWithAddress, rando: SignerWithAddress; describe("Ethereum Spoke Pool", function () { beforeEach(async function () { [owner, relayer, rando] = await ethers.getSigners(); - ({ weth, dai, hubPool, timer } = await hubPoolFixture()); + ({ weth, dai, hubPool } = await hubPoolFixture()); spokePool = await hre.upgrades.deployProxy( await getContractFactory("Ethereum_SpokePool", owner), [0, hubPool.address, weth.address], - { kind: "uups" } + { kind: "uups", unsafeAllow: ["delegatecall"] } ); // 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), - { kind: "uups" } + { kind: "uups", unsafeAllow: ["delegatecall"] } ); // 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 f9e36065..97da613a 100644 --- a/test/chain-specific-spokepools/Optimism_SpokePool.ts +++ b/test/chain-specific-spokepools/Optimism_SpokePool.ts @@ -40,7 +40,7 @@ describe("Optimism Spoke Pool", function () { optimismSpokePool = await hre.upgrades.deployProxy( await getContractFactory("MockOptimism_SpokePool", owner), [weth.address, l2Eth, 0, owner.address, hubPool.address], - { kind: "uups" } + { kind: "uups", unsafeAllow: ["delegatecall"] } ); await seedContract(optimismSpokePool, relayer, [dai], weth, amountHeldByPool); @@ -50,7 +50,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), - { kind: "uups" } + { kind: "uups", unsafeAllow: ["delegatecall"] } ); // upgradeTo fails unless called by cross domain admin via messenger contract diff --git a/test/chain-specific-spokepools/Polygon_SpokePool.ts b/test/chain-specific-spokepools/Polygon_SpokePool.ts index 8ad4c3ea..32407b79 100644 --- a/test/chain-specific-spokepools/Polygon_SpokePool.ts +++ b/test/chain-specific-spokepools/Polygon_SpokePool.ts @@ -17,7 +17,7 @@ import { hubPoolFixture } from "../fixtures/HubPool.Fixture"; import { constructSingleRelayerRefundTree } from "../MerkleLib.utils"; import { randomBytes } from "crypto"; -let hubPool: Contract, polygonSpokePool: Contract, timer: Contract, dai: Contract, weth: Contract, l2Dai: string; +let hubPool: Contract, polygonSpokePool: Contract, dai: Contract, weth: Contract, l2Dai: string; let polygonRegistry: FakeContract, erc20Predicate: FakeContract; let owner: SignerWithAddress, relayer: SignerWithAddress, rando: SignerWithAddress, fxChild: SignerWithAddress; @@ -25,7 +25,7 @@ let owner: SignerWithAddress, relayer: SignerWithAddress, rando: SignerWithAddre describe("Polygon Spoke Pool", function () { beforeEach(async function () { [owner, relayer, fxChild, rando] = await ethers.getSigners(); - ({ weth, hubPool, timer, l2Dai } = await hubPoolFixture()); + ({ weth, hubPool, l2Dai } = await hubPoolFixture()); // The spoke pool exists on l2, so add a random chainId for L1 to ensure that the L2's block.chainid will not match. const l1ChainId = randomBigNumber(); @@ -46,7 +46,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], - { kind: "uups" } + { kind: "uups", unsafeAllow: ["delegatecall"] } ); await seedContract(polygonSpokePool, relayer, [dai], weth, amountHeldByPool); @@ -57,7 +57,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), - { kind: "uups" } + { kind: "uups", unsafeAllow: ["delegatecall"] } ); // upgradeTo fails unless called by cross domain admin diff --git a/test/chain-specific-spokepools/Succinct_SpokePool.ts b/test/chain-specific-spokepools/Succinct_SpokePool.ts index 28e94b16..5fb158fd 100644 --- a/test/chain-specific-spokepools/Succinct_SpokePool.ts +++ b/test/chain-specific-spokepools/Succinct_SpokePool.ts @@ -2,7 +2,7 @@ import { ethers, expect, Contract, SignerWithAddress, getContractFactory } from import { hre } from "../../utils/utils.hre"; import { hubPoolFixture } from "../fixtures/HubPool.Fixture"; -let succinctSpokePool: Contract, timer: Contract, weth: Contract; +let succinctSpokePool: Contract, weth: Contract; let owner: SignerWithAddress, succinctTargetAmb: SignerWithAddress, @@ -13,12 +13,12 @@ const l1ChainId = 45; describe("Succinct Spoke Pool", function () { beforeEach(async function () { [hubPool, succinctTargetAmb, rando] = await ethers.getSigners(); - ({ timer, weth } = await hubPoolFixture()); + ({ weth } = await hubPoolFixture()); succinctSpokePool = await hre.upgrades.deployProxy( await getContractFactory("Succinct_SpokePool", owner), [l1ChainId, succinctTargetAmb.address, 0, hubPool.address, hubPool.address, weth.address], - { kind: "uups" } + { kind: "uups", unsafeAllow: ["delegatecall"] } ); }); diff --git a/test/fixtures/HubPool.Fixture.ts b/test/fixtures/HubPool.Fixture.ts index 065e47e4..978eb56e 100644 --- a/test/fixtures/HubPool.Fixture.ts +++ b/test/fixtures/HubPool.Fixture.ts @@ -46,7 +46,7 @@ export async function deployHubPool(ethers: any) { const mockSpoke = await hre.upgrades.deployProxy( await getContractFactory("MockSpokePool", signer), [0, crossChainAdmin.address, hubPool.address, weth.address], - { kind: "uups" } + { kind: "uups", unsafeAllow: ["delegatecall"] } ); await hubPool.setCrossChainContracts(repaymentChainId, mockAdapter.address, mockSpoke.address); await hubPool.setCrossChainContracts(originChainId, mockAdapter.address, mockSpoke.address); @@ -57,7 +57,7 @@ export async function deployHubPool(ethers: any) { const mockSpokeMainnet = await hre.upgrades.deployProxy( await getContractFactory("MockSpokePool", signer), [0, crossChainAdmin.address, hubPool.address, weth.address], - { kind: "uups" } + { kind: "uups", unsafeAllow: ["delegatecall"] } ); await hubPool.setCrossChainContracts(mainnetChainId, mockAdapterMainnet.address, mockSpokeMainnet.address); diff --git a/test/fixtures/SpokePool.Fixture.ts b/test/fixtures/SpokePool.Fixture.ts index 35ca2e31..bb30939c 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), [0, crossChainAdmin.address, hubPool.address, weth.address], - { kind: "uups" } + { kind: "uups", unsafeAllow: ["delegatecall"] } ); await spokePool.setChainId(consts.destinationChainId); diff --git a/test/gas-analytics/HubPool.RootExecution.ts b/test/gas-analytics/HubPool.RootExecution.ts index 05a4216a..b293d631 100644 --- a/test/gas-analytics/HubPool.RootExecution.ts +++ b/test/gas-analytics/HubPool.RootExecution.ts @@ -108,7 +108,7 @@ describe("Gas Analytics: HubPool Root Bundle Execution", function () { const spokeMainnet = await hre.upgrades.deployProxy( await getContractFactory("MockSpokePool", owner), [0, randomAddress(), hubPool.address, randomAddress()], - { kind: "uups" } + { kind: "uups", unsafeAllow: ["delegatecall"] } ); await hubPool.setCrossChainContracts(hubPoolChainId, adapter.address, spokeMainnet.address); @@ -117,7 +117,7 @@ describe("Gas Analytics: HubPool Root Bundle Execution", function () { const spoke = await hre.upgrades.deployProxy( await getContractFactory("MockSpokePool", owner), [0, randomAddress(), hubPool.address, randomAddress()], - { kind: "uups" } + { kind: "uups", unsafeAllow: ["delegatecall"] } ); await hubPool.setCrossChainContracts(i, adapter.address, spoke.address); await Promise.all( diff --git a/utils/utils.hre.ts b/utils/utils.hre.ts index b7c5a1ba..e9eacfd6 100644 --- a/utils/utils.hre.ts +++ b/utils/utils.hre.ts @@ -4,7 +4,9 @@ import hre from "hardhat"; export async function deployNewProxy(name: string, args: (number | string)[]): Promise { const { run, upgrades } = hre; - const proxy = await upgrades.deployProxy(await getContractFactory(name, {}), args, { kind: "uups" }); + const proxy = await upgrades.deployProxy(await getContractFactory(name, {}), args, { + kind: "uups", + }); const instance = await proxy.deployed(); console.log(`New ${name} proxy deployed @ ${instance.address}`); const implementationAddress = await upgrades.erc1967.getImplementationAddress(instance.address); diff --git a/yarn.lock b/yarn.lock index 00db66c7..78326431 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2522,7 +2522,12 @@ resolved "https://registry.yarnpkg.com/@openzeppelin/contracts-upgradeable/-/contracts-upgradeable-3.4.2.tgz#2c2a1b0fa748235a1f495b6489349776365c51b3" integrity sha512-mDlBS17ymb2wpaLcrqRYdnBAmP1EwqhOXMvqWk2c5Q1N1pm5TkiCtXM9Xzznh4bYsQBq0aIWEkFFE2+iLSN1Tw== -"@openzeppelin/contracts-upgradeable@4.8.3", "@openzeppelin/contracts-upgradeable@^4.2.0": +"@openzeppelin/contracts-upgradeable@4.9.2": + version "4.9.2" + resolved "https://registry.yarnpkg.com/@openzeppelin/contracts-upgradeable/-/contracts-upgradeable-4.9.2.tgz#a817c75688f8daede420052fbcb34e52482e769e" + integrity sha512-siviV3PZV/fHfPaoIC51rf1Jb6iElkYWnNYZ0leO23/ukXuvOyoC/ahy8jqiV7g+++9Nuo3n/rk5ajSN/+d/Sg== + +"@openzeppelin/contracts-upgradeable@^4.2.0": version "4.8.3" resolved "https://registry.yarnpkg.com/@openzeppelin/contracts-upgradeable/-/contracts-upgradeable-4.8.3.tgz#6b076a7b751811b90fe3a172a7faeaa603e13a3f" integrity sha512-SXDRl7HKpl2WDoJpn7CK/M9U4Z8gNXDHHChAKh0Iz+Wew3wu6CmFYBeie3je8V0GSXZAIYYwUktSrnW/kwVPtg==