From 518bfff51085743dc85d2824d823aaf4bb3e82d8 Mon Sep 17 00:00:00 2001 From: Stanislav Bezkorovainyi Date: Tue, 28 Nov 2023 16:11:26 +0100 Subject: [PATCH] Add the ability to reinitialize erc20 token (#102) --- .github/workflows/ci.yml | 16 ++- ethereum/package.json | 4 +- ethereum/scripts/utils.ts | 48 +++++++ ethereum/src.ts/deploy.ts | 50 +------ ethereum/yarn.lock | 18 +-- zksync/contracts/bridge/L2StandardERC20.sol | 40 +++++- zksync/package.json | 7 +- zksync/src/utils.ts | 9 +- zksync/test/erc20.test.ts | 146 ++++++++++++++++++++ zksync/yarn.lock | 18 +-- 10 files changed, 282 insertions(+), 74 deletions(-) create mode 100644 zksync/test/erc20.test.ts diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 524b178bb..f83017697 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -211,7 +211,7 @@ jobs: run: forge test test-hardhat-l2: - needs: [build-l2, lint-l2] + needs: [build-l1, build-l2, lint-l2] runs-on: ubuntu-latest defaults: @@ -237,6 +237,20 @@ jobs: - name: Install dependencies run: yarn install + - name: Install L1 dependencies + working-directory: ethereum + run: yarn install + + - name: Restore L1 artifacts cache + uses: actions/cache/restore@v3 + with: + fail-on-cache-miss: true + key: artifacts-${{ github.sha }} + path: | + ethereum/artifacts + ethereum/cache + ethereum/typechain + - name: Restore artifacts cache uses: actions/cache/restore@v3 with: diff --git a/ethereum/package.json b/ethereum/package.json index 1f5711401..8b0854e9c 100644 --- a/ethereum/package.json +++ b/ethereum/package.json @@ -9,8 +9,8 @@ "@nomiclabs/hardhat-etherscan": "^3.1.0", "@nomiclabs/hardhat-solpp": "^2.0.0", "@nomiclabs/hardhat-waffle": "^2.0.0", - "@openzeppelin/contracts": "4.8.0", - "@openzeppelin/contracts-upgradeable": "4.8.0", + "@openzeppelin/contracts": "4.9.2", + "@openzeppelin/contracts-upgradeable": "4.9.2", "@typechain/ethers-v5": "^2.0.0", "@types/argparse": "^1.0.36", "@types/chai": "^4.2.21", diff --git a/ethereum/scripts/utils.ts b/ethereum/scripts/utils.ts index 82596007b..b5e9cf372 100644 --- a/ethereum/scripts/utils.ts +++ b/ethereum/scripts/utils.ts @@ -151,3 +151,51 @@ export function getTokens(network: string): L1Token[] { }) ); } + +export interface DeployedAddresses { + ZkSync: { + MailboxFacet: string; + AdminFacet: string; + ExecutorFacet: string; + GettersFacet: string; + Verifier: string; + DiamondInit: string; + DiamondUpgradeInit: string; + DefaultUpgrade: string; + DiamondProxy: string; + }; + Bridges: { + ERC20BridgeImplementation: string; + ERC20BridgeProxy: string; + WethBridgeImplementation: string; + WethBridgeProxy: string; + }; + Governance: string; + ValidatorTimeLock: string; + Create2Factory: string; +} + +export function deployedAddressesFromEnv(): DeployedAddresses { + return { + ZkSync: { + MailboxFacet: getAddressFromEnv("CONTRACTS_MAILBOX_FACET_ADDR"), + AdminFacet: getAddressFromEnv("CONTRACTS_ADMIN_FACET_ADDR"), + ExecutorFacet: getAddressFromEnv("CONTRACTS_EXECUTOR_FACET_ADDR"), + GettersFacet: getAddressFromEnv("CONTRACTS_GETTERS_FACET_ADDR"), + DiamondInit: getAddressFromEnv("CONTRACTS_DIAMOND_INIT_ADDR"), + DiamondUpgradeInit: getAddressFromEnv("CONTRACTS_DIAMOND_UPGRADE_INIT_ADDR"), + DefaultUpgrade: getAddressFromEnv("CONTRACTS_DEFAULT_UPGRADE_ADDR"), + DiamondProxy: getAddressFromEnv("CONTRACTS_DIAMOND_PROXY_ADDR"), + Verifier: getAddressFromEnv("CONTRACTS_VERIFIER_ADDR"), + }, + Bridges: { + ERC20BridgeImplementation: getAddressFromEnv("CONTRACTS_L1_ERC20_BRIDGE_IMPL_ADDR"), + ERC20BridgeProxy: getAddressFromEnv("CONTRACTS_L1_ERC20_BRIDGE_PROXY_ADDR"), + WethBridgeImplementation: getAddressFromEnv("CONTRACTS_L1_WETH_BRIDGE_IMPL_ADDR"), + WethBridgeProxy: getAddressFromEnv("CONTRACTS_L1_WETH_BRIDGE_PROXY_ADDR"), + }, + Create2Factory: getAddressFromEnv("CONTRACTS_CREATE2_FACTORY_ADDR"), + ValidatorTimeLock: getAddressFromEnv("CONTRACTS_VALIDATOR_TIMELOCK_ADDR"), + Governance: getAddressFromEnv("CONTRACTS_GOVERNANCE_ADDR"), + }; +} diff --git a/ethereum/src.ts/deploy.ts b/ethereum/src.ts/deploy.ts index 22416fbe4..e99e92414 100644 --- a/ethereum/src.ts/deploy.ts +++ b/ethereum/src.ts/deploy.ts @@ -11,6 +11,7 @@ import { L1WethBridgeFactory } from "../typechain/L1WethBridgeFactory"; import { ValidatorTimelockFactory } from "../typechain/ValidatorTimelockFactory"; import { SingletonFactoryFactory } from "../typechain/SingletonFactoryFactory"; import { TransparentUpgradeableProxyFactory } from "../typechain/TransparentUpgradeableProxyFactory"; +import type { DeployedAddresses } from "../scripts/utils"; import { readSystemContractsBytecode, hashL2Bytecode, @@ -19,6 +20,7 @@ import { getNumberFromEnv, readBatchBootloaderBytecode, getTokens, + deployedAddressesFromEnv, } from "../scripts/utils"; import { deployViaCreate2 } from "./deploy-utils"; import { IGovernanceFactory } from "../typechain/IGovernanceFactory"; @@ -26,60 +28,12 @@ import { IGovernanceFactory } from "../typechain/IGovernanceFactory"; const L2_BOOTLOADER_BYTECODE_HASH = hexlify(hashL2Bytecode(readBatchBootloaderBytecode())); const L2_DEFAULT_ACCOUNT_BYTECODE_HASH = hexlify(hashL2Bytecode(readSystemContractsBytecode("DefaultAccount"))); -export interface DeployedAddresses { - ZkSync: { - MailboxFacet: string; - AdminFacet: string; - ExecutorFacet: string; - GettersFacet: string; - Verifier: string; - DiamondInit: string; - DiamondUpgradeInit: string; - DefaultUpgrade: string; - DiamondProxy: string; - }; - Bridges: { - ERC20BridgeImplementation: string; - ERC20BridgeProxy: string; - WethBridgeImplementation: string; - WethBridgeProxy: string; - }; - Governance: string; - ValidatorTimeLock: string; - Create2Factory: string; -} - export interface DeployerConfig { deployWallet: Wallet; ownerAddress?: string; verbose?: boolean; } -export function deployedAddressesFromEnv(): DeployedAddresses { - return { - ZkSync: { - MailboxFacet: getAddressFromEnv("CONTRACTS_MAILBOX_FACET_ADDR"), - AdminFacet: getAddressFromEnv("CONTRACTS_ADMIN_FACET_ADDR"), - ExecutorFacet: getAddressFromEnv("CONTRACTS_EXECUTOR_FACET_ADDR"), - GettersFacet: getAddressFromEnv("CONTRACTS_GETTERS_FACET_ADDR"), - DiamondInit: getAddressFromEnv("CONTRACTS_DIAMOND_INIT_ADDR"), - DiamondUpgradeInit: getAddressFromEnv("CONTRACTS_DIAMOND_UPGRADE_INIT_ADDR"), - DefaultUpgrade: getAddressFromEnv("CONTRACTS_DEFAULT_UPGRADE_ADDR"), - DiamondProxy: getAddressFromEnv("CONTRACTS_DIAMOND_PROXY_ADDR"), - Verifier: getAddressFromEnv("CONTRACTS_VERIFIER_ADDR"), - }, - Bridges: { - ERC20BridgeImplementation: getAddressFromEnv("CONTRACTS_L1_ERC20_BRIDGE_IMPL_ADDR"), - ERC20BridgeProxy: getAddressFromEnv("CONTRACTS_L1_ERC20_BRIDGE_PROXY_ADDR"), - WethBridgeImplementation: getAddressFromEnv("CONTRACTS_L1_WETH_BRIDGE_IMPL_ADDR"), - WethBridgeProxy: getAddressFromEnv("CONTRACTS_L1_WETH_BRIDGE_PROXY_ADDR"), - }, - Create2Factory: getAddressFromEnv("CONTRACTS_CREATE2_FACTORY_ADDR"), - ValidatorTimeLock: getAddressFromEnv("CONTRACTS_VALIDATOR_TIMELOCK_ADDR"), - Governance: getAddressFromEnv("CONTRACTS_GOVERNANCE_ADDR"), - }; -} - export class Deployer { public addresses: DeployedAddresses; private deployWallet: Wallet; diff --git a/ethereum/yarn.lock b/ethereum/yarn.lock index cadb8a168..57a0a1e2d 100644 --- a/ethereum/yarn.lock +++ b/ethereum/yarn.lock @@ -866,15 +866,15 @@ "@types/sinon-chai" "^3.2.3" "@types/web3" "1.0.19" -"@openzeppelin/contracts-upgradeable@4.8.0": - version "4.8.0" - resolved "https://registry.yarnpkg.com/@openzeppelin/contracts-upgradeable/-/contracts-upgradeable-4.8.0.tgz#26688982f46969018e3ed3199e72a07c8d114275" - integrity sha512-5GeFgqMiDlqGT8EdORadp1ntGF0qzWZLmEY7Wbp/yVhN7/B3NNzCxujuI77ktlyG81N3CUZP8cZe3ZAQ/cW10w== - -"@openzeppelin/contracts@4.8.0": - version "4.8.0" - resolved "https://registry.yarnpkg.com/@openzeppelin/contracts/-/contracts-4.8.0.tgz#6854c37df205dd2c056bdfa1b853f5d732109109" - integrity sha512-AGuwhRRL+NaKx73WKRNzeCxOCOCxpaqF+kp8TJ89QzAipSwZy/NoflkWaL9bywXFRhIzXt8j38sfF7KBKCPWLw== +"@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@4.9.2": + version "4.9.2" + resolved "https://registry.yarnpkg.com/@openzeppelin/contracts/-/contracts-4.9.2.tgz#1cb2d5e4d3360141a17dbc45094a8cad6aac16c1" + integrity sha512-mO+y6JaqXjWeMh9glYVzVu8HYPGknAAnWyxTRhGeckOruyXQMNnlcW6w/Dx9ftLeIQk6N+ZJFuVmTwF7lEIFrg== "@pkgr/utils@^2.3.1": version "2.4.2" diff --git a/zksync/contracts/bridge/L2StandardERC20.sol b/zksync/contracts/bridge/L2StandardERC20.sol index 28bf8497f..b54ef8585 100644 --- a/zksync/contracts/bridge/L2StandardERC20.sol +++ b/zksync/contracts/bridge/L2StandardERC20.sol @@ -3,12 +3,15 @@ pragma solidity 0.8.20; import "@openzeppelin/contracts-upgradeable/token/ERC20/extensions/draft-ERC20PermitUpgradeable.sol"; +import "@openzeppelin/contracts/proxy/beacon/UpgradeableBeacon.sol"; +import "@openzeppelin/contracts/proxy/ERC1967/ERC1967Upgrade.sol"; + import "./interfaces/IL2StandardToken.sol"; /// @author Matter Labs /// @custom:security-contact security@matterlabs.dev /// @notice The ERC20 token implementation, that is used in the "default" ERC20 bridge -contract L2StandardERC20 is ERC20PermitUpgradeable, IL2StandardToken { +contract L2StandardERC20 is ERC20PermitUpgradeable, IL2StandardToken, ERC1967Upgrade { /// @dev Describes whether there is a specific getter in the token. /// @notice Used to explicitly separate which getters the token has and which it does not. /// @notice Different tokens in L1 can implement or not implement getter function as `name`/`symbol`/`decimals`, @@ -97,11 +100,46 @@ contract L2StandardERC20 is ERC20PermitUpgradeable, IL2StandardToken { emit BridgeInitialize(_l1Address, decodedName, decodedSymbol, decimals_); } + /// @notice A method to be called by the governor to update the token's metadata. + /// @param _availableGetters The getters that the token has. + /// @param _newName The new name of the token. + /// @param _newSymbol The new symbol of the token. + /// @param _newDecimals The new decimals of the token. + /// @param _version The version of the token that will be initialized. + /// @dev The _version must be exactly the version higher by 1 than the current version. This is needed + /// to ensure that the governor can not accidentally disable future reinitialization of the token. + function reinitializeToken( + ERC20Getters calldata _availableGetters, + string memory _newName, + string memory _newSymbol, + uint8 _newDecimals, + uint8 _version + ) external onlyNextVersion(_version) reinitializer(_version) { + // It is expected that this token is deployed as a beacon proxy, so we'll + // allow the governor of the beacon to reinitialize the token. + address beaconAddress = _getBeacon(); + require(msg.sender == UpgradeableBeacon(beaconAddress).owner(), "tt"); + + __ERC20_init_unchained(_newName, _newSymbol); + __ERC20Permit_init(_newName); + decimals_ = _newDecimals; + availableGetters = _availableGetters; + + emit BridgeInitialize(l1Address, _newName, _newSymbol, _newDecimals); + } + modifier onlyBridge() { require(msg.sender == l2Bridge, "xnt"); // Only L2 bridge can call this method _; } + modifier onlyNextVersion(uint8 _version) { + // The version should be incremented by 1. Otherwise, the governor risks disabling + // future reinitialization of the token by providing too large a version. + require(_version == _getInitializedVersion() + 1, "v"); + _; + } + /// @dev Mint tokens to a given account. /// @param _to The account that will receive the created tokens. /// @param _amount The amount that will be created. diff --git a/zksync/package.json b/zksync/package.json index fbd13450e..db329a540 100644 --- a/zksync/package.json +++ b/zksync/package.json @@ -14,8 +14,8 @@ "@nomiclabs/hardhat-ethers": "^2.0.0", "@nomiclabs/hardhat-etherscan": "^3.1.7", "@nomiclabs/hardhat-solpp": "^2.0.0", - "@openzeppelin/contracts": "4.6.0", - "@openzeppelin/contracts-upgradeable": "4.6.0", + "@openzeppelin/contracts": "4.9.2", + "@openzeppelin/contracts-upgradeable": "4.9.2", "@typechain/ethers-v5": "^2.0.0", "@types/chai": "^4.2.21", "@types/chai-as-promised": "^7.1.4", @@ -58,7 +58,8 @@ "deploy-force-deploy-upgrader": "ts-node src/deployForceDeployUpgrader.ts", "publish-bridge-preimages": "ts-node src/publish-bridge-preimages.ts", "deploy-l2-weth": "ts-node src/deployL2Weth.ts", - "upgrade-l2-erc20-contract": "ts-node src/upgradeL2BridgeImpl.ts" + "upgrade-l2-erc20-contract": "ts-node src/upgradeL2BridgeImpl.ts", + "test": "hardhat test --network localhost" }, "dependencies": { "dotenv": "^16.0.3" diff --git a/zksync/src/utils.ts b/zksync/src/utils.ts index bee4d7cd2..a237f6228 100644 --- a/zksync/src/utils.ts +++ b/zksync/src/utils.ts @@ -1,7 +1,7 @@ import { artifacts } from "hardhat"; import { Interface } from "ethers/lib/utils"; -import { deployedAddressesFromEnv } from "../../ethereum/src.ts/deploy"; +import { deployedAddressesFromEnv } from "../../ethereum/scripts/utils"; import { IZkSyncFactory } from "../../ethereum/typechain/IZkSyncFactory"; import type { BytesLike, Wallet } from "ethers"; @@ -21,6 +21,13 @@ export function applyL1ToL2Alias(address: string): string { return ethers.utils.hexlify(ethers.BigNumber.from(address).add(L1_TO_L2_ALIAS_OFFSET).mod(ADDRESS_MODULO)); } +export function unapplyL1ToL2Alias(address: string): string { + // We still add ADDRESS_MODULO to avoid negative numbers + return ethers.utils.hexlify( + ethers.BigNumber.from(address).sub(L1_TO_L2_ALIAS_OFFSET).add(ADDRESS_MODULO).mod(ADDRESS_MODULO) + ); +} + export function hashL2Bytecode(bytecode: ethers.BytesLike): Uint8Array { // For getting the consistent length we first convert the bytecode to UInt8Array const bytecodeAsArray = ethers.utils.arrayify(bytecode); diff --git a/zksync/test/erc20.test.ts b/zksync/test/erc20.test.ts new file mode 100644 index 000000000..a3f92fe64 --- /dev/null +++ b/zksync/test/erc20.test.ts @@ -0,0 +1,146 @@ +import { Deployer } from "@matterlabs/hardhat-zksync-deploy"; +import { expect } from "chai"; +import { ethers } from "ethers"; +import * as hre from "hardhat"; +import { Provider, Wallet } from "zksync-web3"; +import { hashBytecode } from "zksync-web3/build/src/utils"; +import { unapplyL1ToL2Alias } from "../src/utils"; +import { L2ERC20BridgeFactory, L2StandardERC20Factory } from "../typechain"; +import type { L2ERC20Bridge, L2StandardERC20 } from "../typechain"; + +const richAccount = [ + { + address: "0x36615Cf349d7F6344891B1e7CA7C72883F5dc049", + privateKey: "0x7726827caac94a7f9e1b160f7ea819f172f7b6f9d2a97f992c38edeab82d4110", + }, + { + address: "0xa61464658AfeAf65CccaaFD3a512b69A83B77618", + privateKey: "0xac1e735be8536c6534bb4f17f06f6afc73b2b5ba84ac2cfb12f7461b20c0bbe3", + }, + { + address: "0x0D43eB5B8a47bA8900d84AA36656c92024e9772e", + privateKey: "0xd293c684d884d56f8d6abd64fc76757d3664904e309a0645baf8522ab6366d9e", + }, +]; + +describe("ERC20Bridge", function () { + const provider = new Provider(hre.config.networks.localhost.url); + const deployerWallet = new Wallet(richAccount[0].privateKey, provider); + const governorWallet = new Wallet(richAccount[1].privateKey, provider); + + // We need to emulate a L1->L2 transaction from the L1 bridge to L2 counterpart. + // It is a bit easier to use EOA and it is sufficient for the tests. + const l1BridgeWallet = new Wallet(richAccount[2].privateKey, provider); + + // We won't actually deploy an L1 token in these tests, but we need some address for it. + const L1_TOKEN_ADDRESS = "0x1111000000000000000000000000000000001111"; + + let erc20Bridge: L2ERC20Bridge; + let erc20Token: L2StandardERC20; + + before("Deploy token and bridge", async function () { + const deployer = new Deployer(hre, deployerWallet); + + // While we formally don't need to deploy the token and the beacon proxy, it is a neat way to have the bytecode published + const l2TokenImplAddress = await deployer.deploy(await deployer.loadArtifact("L2StandardERC20")); + const l2Erc20TokenBeacon = await deployer.deploy(await deployer.loadArtifact("UpgradeableBeacon"), [ + l2TokenImplAddress.address, + ]); + await deployer.deploy(await deployer.loadArtifact("BeaconProxy"), [l2Erc20TokenBeacon.address, "0x"]); + + const beaconProxyBytecodeHash = hashBytecode((await deployer.loadArtifact("BeaconProxy")).bytecode); + + const erc20BridgeImpl = await deployer.deploy(await deployer.loadArtifact("L2ERC20Bridge")); + const bridgeInitializeData = erc20BridgeImpl.interface.encodeFunctionData("initialize", [ + unapplyL1ToL2Alias(l1BridgeWallet.address), + beaconProxyBytecodeHash, + governorWallet.address, + ]); + + const erc20BridgeProxy = await deployer.deploy(await deployer.loadArtifact("TransparentUpgradeableProxy"), [ + erc20BridgeImpl.address, + governorWallet.address, + bridgeInitializeData, + ]); + + erc20Bridge = L2ERC20BridgeFactory.connect(erc20BridgeProxy.address, deployerWallet); + }); + + it("Should finalize deposit ERC20 deposit", async function () { + const erc20BridgeWithL1Bridge = L2ERC20BridgeFactory.connect(erc20Bridge.address, l1BridgeWallet); + + const l1Depositor = ethers.Wallet.createRandom(); + const l2Receiver = ethers.Wallet.createRandom(); + + const tx = await ( + await erc20BridgeWithL1Bridge.finalizeDeposit( + // Depositor and l2Receiver can be any here + l1Depositor.address, + l2Receiver.address, + L1_TOKEN_ADDRESS, + 100, + encodedTokenData("TestToken", "TT", 18) + ) + ).wait(); + + const l2TokenAddress = tx.events.find((event) => event.event === "FinalizeDeposit").args.l2Token; + + // Checking the correctness of the balance: + erc20Token = L2StandardERC20Factory.connect(l2TokenAddress, deployerWallet); + expect(await erc20Token.balanceOf(l2Receiver.address)).to.equal(100); + expect(await erc20Token.totalSupply()).to.equal(100); + expect(await erc20Token.name()).to.equal("TestToken"); + expect(await erc20Token.symbol()).to.equal("TT"); + expect(await erc20Token.decimals()).to.equal(18); + }); + + it("Governance should be able to reinitialize the token", async () => { + const erc20TokenWithGovernor = L2StandardERC20Factory.connect(erc20Token.address, governorWallet); + + await ( + await erc20TokenWithGovernor.reinitializeToken( + { + ignoreName: false, + ignoreSymbol: false, + ignoreDecimals: false, + }, + "TestTokenNewName", + "TTN", + 12, + 2 + ) + ).wait(); + + expect(await erc20Token.name()).to.equal("TestTokenNewName"); + expect(await erc20Token.symbol()).to.equal("TTN"); + expect(await erc20Token.decimals()).to.equal(12); + }); + + it("Governance should not be able to skip initializer versions", async () => { + const erc20TokenWithGovernor = L2StandardERC20Factory.connect(erc20Token.address, governorWallet); + + await expect( + erc20TokenWithGovernor.reinitializeToken( + { + ignoreName: false, + ignoreSymbol: false, + ignoreDecimals: false, + }, + "TestTokenNewName", + "TTN", + 12, + 20, + { gasLimit: 10000000 } + ) + ).to.be.reverted; + }); +}); + +function encodedTokenData(name: string, symbol: string, decimals: number) { + const abiCoder = ethers.utils.defaultAbiCoder; + const encodedName = abiCoder.encode(["string"], [name]); + const encodedSymbol = abiCoder.encode(["string"], [symbol]); + const encodedDecimals = abiCoder.encode(["uint8"], [decimals]); + + return abiCoder.encode(["bytes", "bytes", "bytes"], [encodedName, encodedSymbol, encodedDecimals]); +} diff --git a/zksync/yarn.lock b/zksync/yarn.lock index 4482a1724..b0dfaae4e 100644 --- a/zksync/yarn.lock +++ b/zksync/yarn.lock @@ -857,15 +857,15 @@ fs-extra "^7.0.1" solpp "^0.11.5" -"@openzeppelin/contracts-upgradeable@4.6.0": - version "4.6.0" - resolved "https://registry.yarnpkg.com/@openzeppelin/contracts-upgradeable/-/contracts-upgradeable-4.6.0.tgz#1bf55f230f008554d4c6fe25eb165b85112108b0" - integrity sha512-5OnVuO4HlkjSCJO165a4i2Pu1zQGzMs//o54LPrwUgxvEO2P3ax1QuaSI0cEHHTveA77guS0PnNugpR2JMsPfA== - -"@openzeppelin/contracts@4.6.0": - version "4.6.0" - resolved "https://registry.yarnpkg.com/@openzeppelin/contracts/-/contracts-4.6.0.tgz#c91cf64bc27f573836dba4122758b4743418c1b3" - integrity sha512-8vi4d50NNya/bQqCmaVzvHNmwHvS0OBKb7HNtuNwEE3scXWrP31fKQoGxNMT+KbzmrNZzatE3QK5p2gFONI/hg== +"@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@4.9.2": + version "4.9.2" + resolved "https://registry.yarnpkg.com/@openzeppelin/contracts/-/contracts-4.9.2.tgz#1cb2d5e4d3360141a17dbc45094a8cad6aac16c1" + integrity sha512-mO+y6JaqXjWeMh9glYVzVu8HYPGknAAnWyxTRhGeckOruyXQMNnlcW6w/Dx9ftLeIQk6N+ZJFuVmTwF7lEIFrg== "@pkgr/utils@^2.3.1": version "2.4.2"