diff --git a/contracts/utils/MultiSendETH.sol b/contracts/utils/MultiSendETH.sol new file mode 100644 index 00000000..6c4adf04 --- /dev/null +++ b/contracts/utils/MultiSendETH.sol @@ -0,0 +1,40 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.24; + +/** + * @title MultiSendETH + * @notice A utility contract for sending ETH to multiple recipients in a single transaction + */ +contract MultiSendETH { + /// @notice Maximum number of recipients allowed in a single batch + uint8 public constant ARRAY_LIMIT = 200; + + /// @notice Emitted when a batch of ETH transfers is completed + /// @param total The total amount of ETH sent + event Multisended(uint256 total); + + /** + * @notice Send ETH to multiple recipients in a single transaction + * @param _recipients Array of recipient addresses + * @param _amounts Array of amounts to send to each recipient (in wei) + * @dev The sum of all amounts must equal msg.value + * @dev Arrays must have matching lengths and not exceed ARRAY_LIMIT + */ + function multisendETH(address[] calldata _recipients, uint256[] calldata _amounts) external payable { + require(_recipients.length == _amounts.length, "Mismatched arrays"); + require(_recipients.length <= ARRAY_LIMIT, "Array length exceeds limit"); + + uint256 total = 0; + + // Execute transfers + for (uint8 i = 0; i < _recipients.length; i++) { + total += _amounts[i]; + (bool success, ) = payable(_recipients[i]).call{value: _amounts[i]}(""); + require(success, "Transfer failed"); + } + + require(total == msg.value, "Incorrect ETH amount sent"); + + emit Multisended(total); + } +} diff --git a/hardhat.config.ts b/hardhat.config.ts index f882782e..d7f4f555 100644 --- a/hardhat.config.ts +++ b/hardhat.config.ts @@ -3,10 +3,9 @@ import "hardhat-storage-layout"; import "@typechain/hardhat"; import "@nomiclabs/hardhat-ethers"; import "@nomicfoundation/hardhat-viem"; -import "@nomiclabs/hardhat-etherscan"; +import "@nomicfoundation/hardhat-verify"; import "@nomiclabs/hardhat-waffle"; import "hardhat-gas-reporter"; -import "@nomiclabs/hardhat-etherscan"; import dotenv from "dotenv"; dotenv.config(); @@ -30,7 +29,7 @@ const config: HardhatUserConfig = { settings: { optimizer: { enabled: true, - runs: 4294967295, + runs: 200, }, outputSelection: { "*": { @@ -52,17 +51,20 @@ const config: HardhatUserConfig = { chainId: 8453, gasMultiplier: 1.5, }, + celo: { + url: process.env.CELO_RPC_URL || "https://forno.celo.org", + accounts: deployer, + chainId: 42220, + gasMultiplier: 1.5, + }, }, gasReporter: { currency: "ETH", showMethodSig: true, }, etherscan: { - // Your API keys for Etherscan - apiKey: { - base: process.env.BASE_API_KEY || "", - baseSepolia: process.env.BASE_API_KEY || "", - }, + // Your API keys for Etherscan - using v2 format + apiKey: process.env.BASE_API_KEY || "", // Custom chains that are not supported by default customChains: [ { @@ -81,8 +83,19 @@ const config: HardhatUserConfig = { browserURL: "https://basescan.org", }, }, + { + network: "celo", + chainId: 42220, + urls: { + apiURL: "https://api.celoscan.io/api", + browserURL: "https://celoscan.io", + }, + }, ], }, + sourcify: { + enabled: true, + }, }; export default config; diff --git a/package.json b/package.json index 4515805b..b992f46f 100644 --- a/package.json +++ b/package.json @@ -21,7 +21,6 @@ "@typechain/hardhat": "^6.1.6", "dayjs": "^1.10.7", "ethers": "^5.7.0", - "hardhat": "^2.22.4", "hardhat-gas-reporter": "^1.0.9", "openzeppelin-solidity": "^4.2.0", "permissionless": "^0.1.4", @@ -36,6 +35,7 @@ "devDependencies": { "@nomicfoundation/hardhat-chai-matchers": "^1.0.6", "@nomicfoundation/hardhat-network-helpers": "^1.0.10", + "@nomicfoundation/hardhat-verify": "^2.0.14", "@nomiclabs/hardhat-etherscan": "^3.1.7", "@types/chai": "^4.3.4", "@types/chai-as-promised": "^7.1.4", @@ -49,6 +49,7 @@ "dotenv": "^16.0.3", "eslint": "^8.19.0", "ethereum-waffle": "3.4.0", + "hardhat": "^2.24.3", "hardhat-storage-layout": "^0.1.7", "prettier": "^2.4.1", "prettier-plugin-solidity": "^1.0.0-beta.18", diff --git a/scripts/utils/deployMultiSendETH.ts b/scripts/utils/deployMultiSendETH.ts new file mode 100644 index 00000000..b06cd940 --- /dev/null +++ b/scripts/utils/deployMultiSendETH.ts @@ -0,0 +1,59 @@ +import { ethers, network } from "hardhat"; +import hre from "hardhat"; + +async function main() { + console.log(`Deploying MultiSendETH at ${network.name}`); + + const [admin] = await ethers.getSigners(); + + console.log(`Admin will be ${admin.address}`); + console.log("Account balance:", (await admin.getBalance()).toString()); + + // Deploy the MultiSendETH contract + const MultiSendETH = await ethers.getContractFactory("MultiSendETH"); + const multiSendETH = await MultiSendETH.deploy(); + + await multiSendETH.deployed(); + + console.log(`MultiSendETH Address: ${multiSendETH.address}`); + console.log(`Transaction hash: ${multiSendETH.deployTransaction.hash}`); + + // Verify deployment + const arrayLimit = await multiSendETH.ARRAY_LIMIT(); + console.log(`Array limit: ${arrayLimit.toString()}`); + + // Verify contract on Sourcify (only for non-local networks) + if (network.name !== "hardhat" && network.name !== "localhost") { + console.log("\nWaiting for block confirmations..."); + await multiSendETH.deployTransaction.wait(5); + + console.log("Verifying contract on Sourcify..."); + try { + await hre.run("verify:sourcify", { + address: multiSendETH.address, + }); + console.log("Contract verified successfully on Sourcify!"); + } catch (error) { + console.log("Sourcify verification failed:", error instanceof Error ? error.message : String(error)); + } + + console.log("Verifying contract on Etherscan..."); + try { + await hre.run("verify:verify", { + address: multiSendETH.address, + }); + console.log("Contract verified successfully on Etherscan!"); + } catch (error) { + console.log("Etherscan verification failed:", error instanceof Error ? error.message : String(error)); + } + } + + console.log("Done"); +} + +main() + .then(() => process.exit(0)) + .catch((error) => { + console.error(error); + process.exit(1); + }); diff --git a/test/contracts/utils/MultiSendETH.ts b/test/contracts/utils/MultiSendETH.ts new file mode 100644 index 00000000..f41688ae --- /dev/null +++ b/test/contracts/utils/MultiSendETH.ts @@ -0,0 +1,194 @@ +import chai from "chai"; +import { ethers, waffle } from "hardhat"; +import { solidity } from "ethereum-waffle"; +import { BigNumber } from "ethers"; + +import type { SignerWithAddress } from "@nomiclabs/hardhat-ethers/signers"; + +import { MultiSendETH } from "../../../typechain-types"; +import { Artifacts } from "../../shared"; + +import { findEvent } from "../../shared/utils"; + +chai.use(solidity); + +const { expect } = chai; +const { deployContract } = waffle; + +describe("MultiSendETH", () => { + let deployer: SignerWithAddress; + let sender: SignerWithAddress; + let recipient1: SignerWithAddress; + let recipient2: SignerWithAddress; + let recipient3: SignerWithAddress; + + let multiSendETH: MultiSendETH; + + beforeEach(async () => { + [deployer, sender, recipient1, recipient2, recipient3] = await ethers.getSigners(); + }); + + async function deployMultiSendETH() { + return deployContract(deployer, Artifacts.MultiSendETH, []); + } + + describe("Contract deployment", () => { + beforeEach(async () => { + multiSendETH = (await deployMultiSendETH()) as MultiSendETH; + }); + + it("should deploy with correct constants", async () => { + expect(await multiSendETH.ARRAY_LIMIT()).to.eq(200); + }); + }); + + describe("multisendETH function", () => { + beforeEach(async () => { + multiSendETH = (await deployMultiSendETH()) as MultiSendETH; + }); + + describe("Successful transfers", () => { + it("should send ETH to single recipient", async () => { + const recipients = [recipient1.address]; + const amounts = [ethers.utils.parseEther("1")]; + const totalAmount = ethers.utils.parseEther("1"); + + const initialBalance = await recipient1.getBalance(); + + const tx = await multiSendETH.connect(sender).multisendETH(recipients, amounts, { value: totalAmount }); + + const finalBalance = await recipient1.getBalance(); + expect(finalBalance.sub(initialBalance)).to.eq(ethers.utils.parseEther("1")); + + // Check events + const multisentEvent = await findEvent(tx, "Multisended"); + expect(multisentEvent?.args?.total).to.eq(totalAmount); + }); + + it("should send ETH to multiple recipients with different amounts", async () => { + const recipients = [recipient1.address, recipient2.address, recipient3.address]; + const amounts = [ethers.utils.parseEther("1"), ethers.utils.parseEther("2"), ethers.utils.parseEther("0.5")]; + const totalAmount = ethers.utils.parseEther("3.5"); + + const initialBalances = [ + await recipient1.getBalance(), + await recipient2.getBalance(), + await recipient3.getBalance(), + ]; + + const tx = await multiSendETH.connect(sender).multisendETH(recipients, amounts, { value: totalAmount }); + + const finalBalances = [ + await recipient1.getBalance(), + await recipient2.getBalance(), + await recipient3.getBalance(), + ]; + + expect(finalBalances[0].sub(initialBalances[0])).to.eq(ethers.utils.parseEther("1")); + expect(finalBalances[1].sub(initialBalances[1])).to.eq(ethers.utils.parseEther("2")); + expect(finalBalances[2].sub(initialBalances[2])).to.eq(ethers.utils.parseEther("0.5")); + + // Check events + const multisentEvent = await findEvent(tx, "Multisended"); + expect(multisentEvent?.args?.total).to.eq(totalAmount); + }); + + it("should handle maximum array length", async () => { + const arrayLength = 200; // ARRAY_LIMIT + const recipients: string[] = []; + const amounts: BigNumber[] = []; + const amountPerRecipient = ethers.utils.parseEther("0.01"); + + // Create arrays with max length + for (let i = 0; i < arrayLength; i++) { + recipients.push(ethers.Wallet.createRandom().address); + amounts.push(amountPerRecipient); + } + + const totalAmount = amountPerRecipient.mul(arrayLength); + + const tx = await multiSendETH.connect(sender).multisendETH(recipients, amounts, { value: totalAmount }); + + const multisentEvent = await findEvent(tx, "Multisended"); + expect(multisentEvent?.args?.total).to.eq(totalAmount); + }); + }); + + describe("Validation failures", () => { + it("should revert with mismatched arrays", async () => { + const recipients = [recipient1.address, recipient2.address]; + const amounts = [ethers.utils.parseEther("1")]; // Only one amount for two recipients + + await expect( + multiSendETH.connect(sender).multisendETH(recipients, amounts, { value: ethers.utils.parseEther("1") }) + ).to.be.revertedWith("Mismatched arrays"); + }); + + it("should revert when array length exceeds limit", async () => { + const arrayLength = 201; // Exceeds ARRAY_LIMIT + const recipients: string[] = []; + const amounts = []; + + for (let i = 0; i < arrayLength; i++) { + recipients.push(ethers.Wallet.createRandom().address); + amounts.push(ethers.utils.parseEther("0.01")); + } + + await expect( + multiSendETH.connect(sender).multisendETH(recipients, amounts, { value: ethers.utils.parseEther("2.01") }) + ).to.be.revertedWith("Array length exceeds limit"); + }); + + it("should revert when total doesn't match msg.value (too little sent)", async () => { + const recipients = [recipient1.address, recipient2.address]; + const amounts = [ethers.utils.parseEther("1"), ethers.utils.parseEther("1")]; + const insufficientValue = ethers.utils.parseEther("1.5"); // Less than total (2 ETH) + + await expect( + multiSendETH.connect(sender).multisendETH(recipients, amounts, { value: insufficientValue }) + ).to.be.revertedWith("Transfer failed"); + }); + + it("should revert when total doesn't match msg.value (too much sent)", async () => { + const recipients = [recipient1.address]; + const amounts = [ethers.utils.parseEther("1")]; + const excessiveValue = ethers.utils.parseEther("2"); // More than total (1 ETH) + + await expect( + multiSendETH.connect(sender).multisendETH(recipients, amounts, { value: excessiveValue }) + ).to.be.revertedWith("Incorrect ETH amount sent"); + }); + }); + }); + + describe("Gas optimization tests", () => { + beforeEach(async () => { + multiSendETH = (await deployMultiSendETH()) as MultiSendETH; + }); + + it("should have reasonable gas consumption for different array sizes", async () => { + const testSizes = [1, 5, 10, 50, 100]; + + for (const size of testSizes) { + const recipients: string[] = []; + const amounts = []; + const amountPerRecipient = ethers.utils.parseEther("0.01"); + + for (let i = 0; i < size; i++) { + recipients.push(ethers.Wallet.createRandom().address); + amounts.push(amountPerRecipient); + } + + const totalAmount = amountPerRecipient.mul(size); + + const tx = await multiSendETH.connect(sender).multisendETH(recipients, amounts, { value: totalAmount }); + + const receipt = await tx.wait(); + console.log(`Gas used for ${size} recipients: ${receipt.gasUsed.toString()}`); + + // Ensure transaction succeeded + expect(receipt.status).to.eq(1); + } + }); + }); +}); diff --git a/test/shared/artifacts.ts b/test/shared/artifacts.ts index 308172cf..66ee9bf9 100644 --- a/test/shared/artifacts.ts +++ b/test/shared/artifacts.ts @@ -13,6 +13,7 @@ import TalentTGEUnlockTimestamp from "../../artifacts/contracts/talent/TalentTGE import TalentVault from "../../artifacts/contracts/talent/TalentVault.sol/TalentVault.json"; import TalentVaultV2 from "../../artifacts/contracts/talent/TalentVaultV2.sol/TalentVaultV2.json"; import BaseAPY from "../../artifacts/contracts/talent/vault-options/BaseAPY.sol/BaseAPY.json"; +import MultiSendETH from "../../artifacts/contracts/utils/MultiSendETH.sol/MultiSendETH.json"; export { PassportRegistry, @@ -30,4 +31,5 @@ export { TalentVault, TalentVaultV2, BaseAPY, + MultiSendETH, }; diff --git a/yarn.lock b/yarn.lock index 39eff526..c034b8c3 100644 --- a/yarn.lock +++ b/yarn.lock @@ -150,6 +150,11 @@ resolved "https://registry.yarnpkg.com/@ethereumjs/rlp/-/rlp-4.0.1.tgz#626fabfd9081baab3d0a3074b0c7ecaf674aaa41" integrity sha512-tqsQiBQDQdmPWE1xkkBq4rlSW5QZpLOUJ5RJh2/9fug+q9tnUhuZoVLk7s0scUIKTOzEtR72DFBXI4WiZcMpvw== +"@ethereumjs/rlp@^5.0.2": + version "5.0.2" + resolved "https://registry.yarnpkg.com/@ethereumjs/rlp/-/rlp-5.0.2.tgz#c89bd82f2f3bec248ab2d517ae25f5bbc4aac842" + integrity sha512-DziebCdg4JpGlEqEdGgXmjqcFoJi+JGulUXwEjsZGAscAQ7MyD/7LE/GVCP29vEQxKc7AAwjT3A2ywHp2xfoCA== + "@ethereumjs/util@^8.1.0": version "8.1.0" resolved "https://registry.yarnpkg.com/@ethereumjs/util/-/util-8.1.0.tgz#299df97fb6b034e0577ce9f94c7d9d1004409ed4" @@ -159,6 +164,14 @@ ethereum-cryptography "^2.0.0" micro-ftch "^0.3.1" +"@ethereumjs/util@^9.1.0": + version "9.1.0" + resolved "https://registry.yarnpkg.com/@ethereumjs/util/-/util-9.1.0.tgz#75e3898a3116d21c135fa9e29886565609129bce" + integrity sha512-XBEKsYqLGXLah9PNJbgdkigthkG7TAGvlD/sH12beMXEyHDyigfcbdvHhmLyDWgDyOJn4QwiQUaF7yeuhnjdog== + dependencies: + "@ethereumjs/rlp" "^5.0.2" + ethereum-cryptography "^2.2.1" + "@ethersproject/abi@5.0.0-beta.153": version "5.0.0-beta.153" resolved "https://registry.yarnpkg.com/@ethersproject/abi/-/abi-5.0.0-beta.153.tgz#43a37172b33794e4562999f6e2d555b7599a8eee" @@ -583,6 +596,20 @@ dependencies: "@noble/hashes" "1.3.3" +"@noble/curves@1.4.2", "@noble/curves@~1.4.0": + version "1.4.2" + resolved "https://registry.yarnpkg.com/@noble/curves/-/curves-1.4.2.tgz#40309198c76ed71bc6dbf7ba24e81ceb4d0d1fe9" + integrity sha512-TavHr8qycMChk8UwMld0ZDRvatedkzWfH8IiaeGCfymOP5i0hSCozz9vHOL0nkwk7HRMlFnAiKpS2jrUmSybcw== + dependencies: + "@noble/hashes" "1.4.0" + +"@noble/curves@~1.8.1": + version "1.8.2" + resolved "https://registry.yarnpkg.com/@noble/curves/-/curves-1.8.2.tgz#8f24c037795e22b90ae29e222a856294c1d9ffc7" + integrity sha512-vnI7V6lFNe0tLAuJMu+2sX+FcL14TaCWy1qiczg1VwRmPrpQCdq5ESXQMqUc2tluRNf6irBXrWbl1mGN8uaU/g== + dependencies: + "@noble/hashes" "1.7.2" + "@noble/hashes@1.2.0", "@noble/hashes@~1.2.0": version "1.2.0" resolved "https://registry.yarnpkg.com/@noble/hashes/-/hashes-1.2.0.tgz#a3150eeb09cc7ab207ebf6d7b9ad311a9bdbed12" @@ -598,11 +625,16 @@ resolved "https://registry.yarnpkg.com/@noble/hashes/-/hashes-1.3.3.tgz#39908da56a4adc270147bb07968bf3b16cfe1699" integrity sha512-V7/fPHgl+jsVPXqqeOzT8egNj2iBIVt+ECeMMG8TdcnTikP3oaBtUVqpT/gYCR68aEBJSF+XbYUxStjbFMqIIA== -"@noble/hashes@^1.4.0": +"@noble/hashes@1.4.0", "@noble/hashes@^1.4.0", "@noble/hashes@~1.4.0": version "1.4.0" resolved "https://registry.yarnpkg.com/@noble/hashes/-/hashes-1.4.0.tgz#45814aa329f30e4fe0ba49426f49dfccdd066426" integrity sha512-V1JJ1WTRUqHHrOSh597hURcMqVKVGL/ea3kv0gSnEdsEZ0/+VyPghM1lMNGc00z7CIQorSvbKpuJkxvuHbvdbg== +"@noble/hashes@1.7.2", "@noble/hashes@~1.7.1": + version "1.7.2" + resolved "https://registry.yarnpkg.com/@noble/hashes/-/hashes-1.7.2.tgz#d53c65a21658fb02f3303e7ee3ba89d6754c64b4" + integrity sha512-biZ0NUSxyjLLqo6KxEJ1b+C2NAx0wtDoFvCaXHGgUkeHzf3Xc1xKumFKREuT7f7DARNZ/slvYUwFG6B0f2b6hQ== + "@noble/secp256k1@1.7.1", "@noble/secp256k1@~1.7.0": version "1.7.1" resolved "https://registry.yarnpkg.com/@noble/secp256k1/-/secp256k1-1.7.1.tgz#b251c70f824ce3ca7f8dc3df08d58f005cc0507c" @@ -629,41 +661,89 @@ "@nodelib/fs.scandir" "2.1.5" fastq "^1.6.0" +"@nomicfoundation/edr-darwin-arm64@0.11.1": + version "0.11.1" + resolved "https://registry.yarnpkg.com/@nomicfoundation/edr-darwin-arm64/-/edr-darwin-arm64-0.11.1.tgz#70b9187fbd046e8c0911f1e5a97b7ad13701a686" + integrity sha512-vjca7gkl1o0yYqMjwxQpMEtdsb20nWHBnnxDO8ZBCTD5IwfYT5LiMxFaJo8NUJ7ODIRkF/zuEtAF3W7+ZlC5RA== + "@nomicfoundation/edr-darwin-arm64@0.3.8": version "0.3.8" resolved "https://registry.yarnpkg.com/@nomicfoundation/edr-darwin-arm64/-/edr-darwin-arm64-0.3.8.tgz#09de1f03c0336670fce959f376f0fe9137545836" integrity sha512-eB0leCexS8sQEmfyD72cdvLj9djkBzQGP4wSQw6SNf2I4Sw4Cnzb3d45caG2FqFFjbvfqL0t+badUUIceqQuMw== +"@nomicfoundation/edr-darwin-x64@0.11.1": + version "0.11.1" + resolved "https://registry.yarnpkg.com/@nomicfoundation/edr-darwin-x64/-/edr-darwin-x64-0.11.1.tgz#e59298fa126057eab6f34cdf9c8bcc4839b81873" + integrity sha512-0aGStHq9XePXX9UqdU1w60HGO9AfYCgkNEir5sBpntU5E0TvZEK6jwyYr667+s90n2mihdeP97QSA0O/6PT6PA== + "@nomicfoundation/edr-darwin-x64@0.3.8": version "0.3.8" resolved "https://registry.yarnpkg.com/@nomicfoundation/edr-darwin-x64/-/edr-darwin-x64-0.3.8.tgz#c3ca237c74ed3b6fb800fd7f1de7174f4ad24f72" integrity sha512-JksVCS1N5ClwVF14EvO25HCQ+Laljh/KRfHERMVAC9ZwPbTuAd/9BtKvToCBi29uCHWqsXMI4lxCApYQv2nznw== +"@nomicfoundation/edr-linux-arm64-gnu@0.11.1": + version "0.11.1" + resolved "https://registry.yarnpkg.com/@nomicfoundation/edr-linux-arm64-gnu/-/edr-linux-arm64-gnu-0.11.1.tgz#bd2a7c835a2cbc25fe149f5d4e0f3c6832dfdf7b" + integrity sha512-OWhCETc03PVdtzatW/c2tpOPx+GxlBfBaLmMuGRD1soAr1nMOmg2WZAlo4i6Up9fkQYl+paiYMMFVat1meaMvQ== + "@nomicfoundation/edr-linux-arm64-gnu@0.3.8": version "0.3.8" resolved "https://registry.yarnpkg.com/@nomicfoundation/edr-linux-arm64-gnu/-/edr-linux-arm64-gnu-0.3.8.tgz#08bd367789e745f4e78a8a87368fc470eea8a7de" integrity sha512-raCE+fOeNXhVBLUo87cgsHSGvYYRB6arih4eG6B9KGACWK5Veebtm9xtKeiD8YCsdUlUfat6F7ibpeNm91fpsA== +"@nomicfoundation/edr-linux-arm64-musl@0.11.1": + version "0.11.1" + resolved "https://registry.yarnpkg.com/@nomicfoundation/edr-linux-arm64-musl/-/edr-linux-arm64-musl-0.11.1.tgz#e3ecc8ea5862e594e43bdea1cc54c5e68c272567" + integrity sha512-p0qvtIvDA2eZ8pQ5XUKnWdW1IrwFzSrjyrO88oYx6Lkw8nYwf2JEeETo5o5W84DDfimfoBGP7RWPTPcTBKCaLQ== + "@nomicfoundation/edr-linux-arm64-musl@0.3.8": version "0.3.8" resolved "https://registry.yarnpkg.com/@nomicfoundation/edr-linux-arm64-musl/-/edr-linux-arm64-musl-0.3.8.tgz#9cab5cbec0052cb5812c6c66c463d28a756cd916" integrity sha512-PwiDp4wBZWMCIy29eKkv8moTKRrpiSDlrc+GQMSZLhOAm8T33JKKXPwD/2EbplbhCygJDGXZdtEKl9x9PaH66A== +"@nomicfoundation/edr-linux-x64-gnu@0.11.1": + version "0.11.1" + resolved "https://registry.yarnpkg.com/@nomicfoundation/edr-linux-x64-gnu/-/edr-linux-x64-gnu-0.11.1.tgz#e900dc3ae97368ee5dd1a7918a3098b56399b450" + integrity sha512-V4Us7Q0E8kng3O/czd5GRcxmZxWX+USgqz9yQ3o7DVq7FP96idaKvtcbMQp64tjHf2zNtX2y77sGzgbVau7Bww== + "@nomicfoundation/edr-linux-x64-gnu@0.3.8": version "0.3.8" resolved "https://registry.yarnpkg.com/@nomicfoundation/edr-linux-x64-gnu/-/edr-linux-x64-gnu-0.3.8.tgz#d4a11b6ebcd1b29d7431d185c6df3e65a2cd4bde" integrity sha512-6AcvA/XKoipGap5jJmQ9Y6yT7Uf39D9lu2hBcDCXnXbMcXaDGw4mn1/L4R63D+9VGZyu1PqlcJixCUZlGGIWlg== +"@nomicfoundation/edr-linux-x64-musl@0.11.1": + version "0.11.1" + resolved "https://registry.yarnpkg.com/@nomicfoundation/edr-linux-x64-musl/-/edr-linux-x64-musl-0.11.1.tgz#52374d0d4389b7edad871b6a35ac301c963e4a8d" + integrity sha512-lCSXsF10Kjjvs5duGbM6pi1WciWHXFNWkMgDAY4pg6ZRIy4gh+uGC6CONMfP4BDZwfrALo2p6+LwyotrJEqpyg== + "@nomicfoundation/edr-linux-x64-musl@0.3.8": version "0.3.8" resolved "https://registry.yarnpkg.com/@nomicfoundation/edr-linux-x64-musl/-/edr-linux-x64-musl-0.3.8.tgz#b8eef960d06380a365866ddd1e97ecb7fbf6bd70" integrity sha512-cxb0sEmZjlwhYWO28sPsV64VDx31ekskhC1IsDXU1p9ntjHSJRmW4KEIqJ2O3QwJap/kLKfMS6TckvY10gjc6w== +"@nomicfoundation/edr-win32-x64-msvc@0.11.1": + version "0.11.1" + resolved "https://registry.yarnpkg.com/@nomicfoundation/edr-win32-x64-msvc/-/edr-win32-x64-msvc-0.11.1.tgz#7d3b7d541d902ea5385a1bb4385522625007863b" + integrity sha512-sNSmmRTURAd1sdKuyO5tqrFiJvHHVPZLM4HB53F21makGoyInFGhejdo3qZrkoinM8k0ewEJDbUp0YuMEgMOhQ== + "@nomicfoundation/edr-win32-x64-msvc@0.3.8": version "0.3.8" resolved "https://registry.yarnpkg.com/@nomicfoundation/edr-win32-x64-msvc/-/edr-win32-x64-msvc-0.3.8.tgz#ac7061aeb07cc847c429513080b76bb05297a869" integrity sha512-yVuVPqRRNLZk7TbBMkKw7lzCvI8XO8fNTPTYxymGadjr9rEGRuNTU1yBXjfJ59I1jJU/X2TSkRk1OFX0P5tpZQ== +"@nomicfoundation/edr@^0.11.1": + version "0.11.1" + resolved "https://registry.yarnpkg.com/@nomicfoundation/edr/-/edr-0.11.1.tgz#c645a8984a02cd1797f772298c46ee12bfbe9ebc" + integrity sha512-P97XwcD9DdMMZm9aqw89+mzqzlKmqzSPM3feBES2WVRm5/LOiBYorhpeAX+ANj0X8532SKgxoZK/CN5OWv9vZA== + dependencies: + "@nomicfoundation/edr-darwin-arm64" "0.11.1" + "@nomicfoundation/edr-darwin-x64" "0.11.1" + "@nomicfoundation/edr-linux-arm64-gnu" "0.11.1" + "@nomicfoundation/edr-linux-arm64-musl" "0.11.1" + "@nomicfoundation/edr-linux-x64-gnu" "0.11.1" + "@nomicfoundation/edr-linux-x64-musl" "0.11.1" + "@nomicfoundation/edr-win32-x64-msvc" "0.11.1" + "@nomicfoundation/edr@^0.3.7": version "0.3.8" resolved "https://registry.yarnpkg.com/@nomicfoundation/edr/-/edr-0.3.8.tgz#28fe7ae4f462ae74a16cd1a714ff7b1cd9c22b4c" @@ -725,6 +805,21 @@ dependencies: ethereumjs-util "^7.1.4" +"@nomicfoundation/hardhat-verify@^2.0.14": + version "2.0.14" + resolved "https://registry.yarnpkg.com/@nomicfoundation/hardhat-verify/-/hardhat-verify-2.0.14.tgz#ba80918fac840f1165825f2a422a694486f82f6f" + integrity sha512-z3iVF1WYZHzcdMMUuureFpSAfcnlfJbJx3faOnGrOYg6PRTki1Ut9JAuRccnFzMHf1AmTEoSUpWcyvBCoxL5Rg== + dependencies: + "@ethersproject/abi" "^5.1.2" + "@ethersproject/address" "^5.0.2" + cbor "^8.1.0" + debug "^4.1.1" + lodash.clonedeep "^4.5.0" + picocolors "^1.1.0" + semver "^6.3.0" + table "^6.8.0" + undici "^5.14.0" + "@nomicfoundation/hardhat-viem@^2.0.0": version "2.0.2" resolved "https://registry.yarnpkg.com/@nomicfoundation/hardhat-viem/-/hardhat-viem-2.0.2.tgz#91d7a6d3e66c93bc39b2322420a1bad82360df7d" @@ -1030,6 +1125,16 @@ resolved "https://registry.yarnpkg.com/@scure/base/-/base-1.1.6.tgz#8ce5d304b436e4c84f896e0550c83e4d88cb917d" integrity sha512-ok9AWwhcgYuGG3Zfhyqg+zwl+Wn5uE+dwC0NV/2qQkx4dABbb/bx96vWu8NSj+BNjjSjno+JRYRjle1jV08k3g== +"@scure/base@~1.1.6": + version "1.1.9" + resolved "https://registry.yarnpkg.com/@scure/base/-/base-1.1.9.tgz#e5e142fbbfe251091f9c5f1dd4c834ac04c3dbd1" + integrity sha512-8YKhl8GHiNI/pU2VMaofa2Tor7PJRAjwQLBBuilkJ9L5+13yVbC7JO/wS7piioAvPSwR3JKM1IJ/u4xQzbcXKg== + +"@scure/base@~1.2.5": + version "1.2.6" + resolved "https://registry.yarnpkg.com/@scure/base/-/base-1.2.6.tgz#ca917184b8231394dd8847509c67a0be522e59f6" + integrity sha512-g/nm5FgUa//MCj1gV09zTJTaM6KBAHqLN907YVQqf7zC49+DcO4B1so4ZX07Ef10Twr6nuqYEH9GEggFXA4Fmg== + "@scure/bip32@1.1.5": version "1.1.5" resolved "https://registry.yarnpkg.com/@scure/bip32/-/bip32-1.1.5.tgz#d2ccae16dcc2e75bc1d75f5ef3c66a338d1ba300" @@ -1057,6 +1162,15 @@ "@noble/hashes" "~1.3.2" "@scure/base" "~1.1.4" +"@scure/bip32@1.4.0": + version "1.4.0" + resolved "https://registry.yarnpkg.com/@scure/bip32/-/bip32-1.4.0.tgz#4e1f1e196abedcef395b33b9674a042524e20d67" + integrity sha512-sVUpc0Vq3tXCkDGYVWGIZTRfnvu8LoTDaev7vbwh0omSvVORONr960MQWdKqJDCReIEmTj3PAr73O3aoxz7OPg== + dependencies: + "@noble/curves" "~1.4.0" + "@noble/hashes" "~1.4.0" + "@scure/base" "~1.1.6" + "@scure/bip39@1.1.1": version "1.1.1" resolved "https://registry.yarnpkg.com/@scure/bip39/-/bip39-1.1.1.tgz#b54557b2e86214319405db819c4b6a370cf340c5" @@ -1081,6 +1195,14 @@ "@noble/hashes" "~1.3.2" "@scure/base" "~1.1.4" +"@scure/bip39@1.3.0": + version "1.3.0" + resolved "https://registry.yarnpkg.com/@scure/bip39/-/bip39-1.3.0.tgz#0f258c16823ddd00739461ac31398b4e7d6a18c3" + integrity sha512-disdg7gHuTDZtY+ZdkmLpPCk7fxZSu3gBiEGuoC1XYxv9cGx3Z6cpTggCgW6odSOOIXCiDjuGejW+aJKCY/pIQ== + dependencies: + "@noble/hashes" "~1.4.0" + "@scure/base" "~1.1.6" + "@sentry/core@5.30.0": version "5.30.0" resolved "https://registry.yarnpkg.com/@sentry/core/-/core-5.30.0.tgz#6b203664f69e75106ee8b5a2fe1d717379b331f3" @@ -3139,6 +3261,13 @@ chokidar@3.5.3, chokidar@^3.4.0: optionalDependencies: fsevents "~2.3.2" +chokidar@^4.0.0: + version "4.0.3" + resolved "https://registry.yarnpkg.com/chokidar/-/chokidar-4.0.3.tgz#7be37a4c03c9aee1ecfe862a4a23b2c70c205d30" + integrity sha512-Qgzu8kfBvo+cA4962jnP1KkS6Dop5NS6g7R5LFYJr4b8Ub94PPQXUksCw9PvXoeXPRRddRNC5C1JQUR2SMGtnA== + dependencies: + readdirp "^4.0.1" + chownr@^1.1.4: version "1.1.4" resolved "https://registry.yarnpkg.com/chownr/-/chownr-1.1.4.tgz#6fc9d7b42d32a583596337666e7d08084da2cc6b" @@ -4448,6 +4577,16 @@ ethereum-cryptography@^2.0.0, ethereum-cryptography@^2.1.2: "@scure/bip32" "1.3.3" "@scure/bip39" "1.2.2" +ethereum-cryptography@^2.2.1: + version "2.2.1" + resolved "https://registry.yarnpkg.com/ethereum-cryptography/-/ethereum-cryptography-2.2.1.tgz#58f2810f8e020aecb97de8c8c76147600b0b8ccf" + integrity sha512-r/W8lkHSiTLxUxW8Rf3u4HGB0xQweG2RyETjywylKZSzLWoWAijRz8WCuOtJ6wah+avllXBqZuk29HCCvhEIRg== + dependencies: + "@noble/curves" "1.4.2" + "@noble/hashes" "1.4.0" + "@scure/bip32" "1.4.0" + "@scure/bip39" "1.3.0" + ethereum-waffle@3.4.0: version "3.4.0" resolved "https://registry.yarnpkg.com/ethereum-waffle/-/ethereum-waffle-3.4.0.tgz#990b3c6c26db9c2dd943bf26750a496f60c04720" @@ -4881,6 +5020,11 @@ fastq@^1.6.0: dependencies: reusify "^1.0.4" +fdir@^6.4.4: + version "6.4.6" + resolved "https://registry.yarnpkg.com/fdir/-/fdir-6.4.6.tgz#2b268c0232697063111bbf3f64810a2a741ba281" + integrity sha512-hiFoqpyZcfNm1yc4u8oWCf9A2c4D3QjCrks3zmoVKVxpQRzmPNar1hUJcBG2RQHvEVGDN+Jm81ZheVLAQMK6+w== + fetch-ponyfill@^4.0.0: version "4.1.0" resolved "https://registry.yarnpkg.com/fetch-ponyfill/-/fetch-ponyfill-4.1.0.tgz#ae3ce5f732c645eab87e4ae8793414709b239893" @@ -5551,7 +5695,7 @@ hardhat-storage-layout@^0.1.7: dependencies: console-table-printer "^2.9.0" -hardhat@2.22.4, hardhat@^2.22.4: +hardhat@2.22.4: version "2.22.4" resolved "https://registry.yarnpkg.com/hardhat/-/hardhat-2.22.4.tgz#766227b6cefca5dbf4fd15ab5b5a68138fa13baf" integrity sha512-09qcXJFBHQUaraJkYNr7XlmwjOj27xBB0SL2rYS024hTj9tPMbp26AFjlf5quBMO9SR4AJFg+4qWahcYcvXBuQ== @@ -5600,6 +5744,53 @@ hardhat@2.22.4, hardhat@^2.22.4: uuid "^8.3.2" ws "^7.4.6" +hardhat@^2.24.3: + version "2.24.3" + resolved "https://registry.yarnpkg.com/hardhat/-/hardhat-2.24.3.tgz#be190a2b3580b0011b7de811871c073992ecedf6" + integrity sha512-2dhniQ1wW8/Wh3mP91kKcEnVva93mWYRaYLkV+a0ATkUEKrByGF2P5hCrlNHbqYP//D7L0CGYLtDjPQY6ILaVA== + dependencies: + "@ethereumjs/util" "^9.1.0" + "@ethersproject/abi" "^5.1.2" + "@nomicfoundation/edr" "^0.11.1" + "@nomicfoundation/solidity-analyzer" "^0.1.0" + "@sentry/node" "^5.18.1" + "@types/bn.js" "^5.1.0" + "@types/lru-cache" "^5.1.0" + adm-zip "^0.4.16" + aggregate-error "^3.0.0" + ansi-escapes "^4.3.0" + boxen "^5.1.2" + chokidar "^4.0.0" + ci-info "^2.0.0" + debug "^4.1.1" + enquirer "^2.3.0" + env-paths "^2.2.0" + ethereum-cryptography "^1.0.3" + find-up "^5.0.0" + fp-ts "1.19.3" + fs-extra "^7.0.1" + immutable "^4.0.0-rc.12" + io-ts "1.10.4" + json-stream-stringify "^3.1.4" + keccak "^3.0.2" + lodash "^4.17.11" + micro-eth-signer "^0.14.0" + mnemonist "^0.38.0" + mocha "^10.0.0" + p-map "^4.0.0" + picocolors "^1.1.0" + raw-body "^2.4.1" + resolve "1.17.0" + semver "^6.3.0" + solc "0.8.26" + source-map-support "^0.5.13" + stacktrace-parser "^0.1.10" + tinyglobby "^0.2.6" + tsort "0.0.1" + undici "^5.14.0" + uuid "^8.3.2" + ws "^7.4.6" + has-ansi@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/has-ansi/-/has-ansi-2.0.0.tgz#34f5049ce1ecdf2b0649af3ef24e45ed35416d91" @@ -6420,6 +6611,11 @@ json-stable-stringify@^1.0.1: dependencies: jsonify "^0.0.1" +json-stream-stringify@^3.1.4: + version "3.1.6" + resolved "https://registry.yarnpkg.com/json-stream-stringify/-/json-stream-stringify-3.1.6.tgz#ebe32193876fb99d4ec9f612389a8d8e2b5d54d4" + integrity sha512-x7fpwxOkbhFCaJDJ8vb1fBY3DdSa4AlITaz+HHILQJzdPMnHEFjxPwVUi1ALIbcIxDE0PNe/0i7frnY8QnBQog== + json-stringify-safe@~5.0.1: version "5.0.1" resolved "https://registry.yarnpkg.com/json-stringify-safe/-/json-stringify-safe-5.0.1.tgz#1296a2d58fd45f19a0f6ce01d65701e2c735b6eb" @@ -6767,6 +6963,11 @@ lodash.camelcase@^4.3.0: resolved "https://registry.yarnpkg.com/lodash.camelcase/-/lodash.camelcase-4.3.0.tgz#b28aa6288a2b9fc651035c7711f65ab6190331a6" integrity sha512-TwuEnCnxbc3rAvhf/LbG7tJUDzhqXyFnv3dtzLOPgCG/hODL7WFnsbwktkD7yUV0RrreP/l1PALq/YSg6VvjlA== +lodash.clonedeep@^4.5.0: + version "4.5.0" + resolved "https://registry.yarnpkg.com/lodash.clonedeep/-/lodash.clonedeep-4.5.0.tgz#e23f3f9c4f8fbdde872529c1071857a086e5ccef" + integrity sha512-H5ZhCF25riFd9uB5UCkVKo61m3S/xZk1x4wA6yp/L3RFP6Z/eHH1ymQcGLo7J3GMPfm0V/7m1tryHuGVxpqEBQ== + lodash.memoize@^4.1.2: version "4.1.2" resolved "https://registry.yarnpkg.com/lodash.memoize/-/lodash.memoize-4.1.2.tgz#bcc6c49a42a2840ed997f323eada5ecd182e0bfe" @@ -6977,11 +7178,27 @@ methods@~1.1.2: resolved "https://registry.yarnpkg.com/methods/-/methods-1.1.2.tgz#5529a4d67654134edcc5266656835b0f851afcee" integrity sha512-iclAHeNqNm68zFtnZ0e+1L2yUIdvzNoauKU4WBA3VvH/vPFieF7qfRlwUZU+DA9P9bPXIS90ulxoUoCH23sV2w== +micro-eth-signer@^0.14.0: + version "0.14.0" + resolved "https://registry.yarnpkg.com/micro-eth-signer/-/micro-eth-signer-0.14.0.tgz#8aa1fe997d98d6bdf42f2071cef7eb01a66ecb22" + integrity sha512-5PLLzHiVYPWClEvZIXXFu5yutzpadb73rnQCpUqIHu3No3coFuWQNfE5tkBQJ7djuLYl6aRLaS0MgWJYGoqiBw== + dependencies: + "@noble/curves" "~1.8.1" + "@noble/hashes" "~1.7.1" + micro-packed "~0.7.2" + micro-ftch@^0.3.1: version "0.3.1" resolved "https://registry.yarnpkg.com/micro-ftch/-/micro-ftch-0.3.1.tgz#6cb83388de4c1f279a034fb0cf96dfc050853c5f" integrity sha512-/0LLxhzP0tfiR5hcQebtudP56gUurs2CLkGarnCiB/OqEyUFQ6U3paQi/tgLv0hBJYt2rnr9MNpxz4fiiugstg== +micro-packed@~0.7.2: + version "0.7.3" + resolved "https://registry.yarnpkg.com/micro-packed/-/micro-packed-0.7.3.tgz#59e96b139dffeda22705c7a041476f24cabb12b6" + integrity sha512-2Milxs+WNC00TRlem41oRswvw31146GiSaoCT7s3Xi2gMUglW5QBeqlQaZeHr5tJx9nm3i57LNXPqxOOaWtTYg== + dependencies: + "@scure/base" "~1.2.5" + micromatch@^3.1.4: version "3.1.10" resolved "https://registry.yarnpkg.com/micromatch/-/micromatch-3.1.10.tgz#70859bc95c9840952f359a068a3fc49f9ecfac23" @@ -7824,11 +8041,21 @@ picocolors@^1.0.0: resolved "https://registry.yarnpkg.com/picocolors/-/picocolors-1.0.1.tgz#a8ad579b571952f0e5d25892de5445bcfe25aaa1" integrity sha512-anP1Z8qwhkbmu7MFP5iTt+wQKXgwzf7zTyGlcdzabySa9vd0Xt392U0rVmz9poOaBj0uHJKyyo9/upk0HrEQew== +picocolors@^1.1.0: + version "1.1.1" + resolved "https://registry.yarnpkg.com/picocolors/-/picocolors-1.1.1.tgz#3d321af3eab939b083c8f929a1d12cda81c26b6b" + integrity sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA== + picomatch@^2.0.4, picomatch@^2.2.1, picomatch@^2.3.1: version "2.3.1" resolved "https://registry.yarnpkg.com/picomatch/-/picomatch-2.3.1.tgz#3ba3833733646d9d3e4995946c1365a67fb07a42" integrity sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA== +picomatch@^4.0.2: + version "4.0.2" + resolved "https://registry.yarnpkg.com/picomatch/-/picomatch-4.0.2.tgz#77c742931e8f3b8820946c76cd0c1f13730d1dab" + integrity sha512-M7BAV6Rlcy5u+m6oPhAPFgJTzAioX/6B0DxyvDlo9l8+T3nLKbrczg2WLUyzd45L8RqfUMyGPzekbMvX2Ldkwg== + pify@^2.0.0, pify@^2.3.0: version "2.3.0" resolved "https://registry.yarnpkg.com/pify/-/pify-2.3.0.tgz#ed141a6ac043a849ea588498e7dca8b15330e90c" @@ -8202,6 +8429,11 @@ readable-stream@~1.0.15: isarray "0.0.1" string_decoder "~0.10.x" +readdirp@^4.0.1: + version "4.1.2" + resolved "https://registry.yarnpkg.com/readdirp/-/readdirp-4.1.2.tgz#eb85801435fbf2a7ee58f19e0921b068fc69948d" + integrity sha512-GDhwkLfywWL2s6vEjyhri+eXmfH6j1L7JE27WhqLeYzoh/A3DBaYGEj2H/HFZCn/kMfim73FXxEJTw06WtxQwg== + readdirp@~3.6.0: version "3.6.0" resolved "https://registry.yarnpkg.com/readdirp/-/readdirp-3.6.0.tgz#74a370bd857116e245b29cc97340cd431a02a6c7" @@ -8888,6 +9120,19 @@ solc@0.7.3: semver "^5.5.0" tmp "0.0.33" +solc@0.8.26: + version "0.8.26" + resolved "https://registry.yarnpkg.com/solc/-/solc-0.8.26.tgz#afc78078953f6ab3e727c338a2fefcd80dd5b01a" + integrity sha512-yiPQNVf5rBFHwN6SIf3TUUvVAFKcQqmSUFeq+fb6pNRCo0ZCgpYOZDi3BVoezCPIAcKrVYd/qXlBLUP9wVrZ9g== + dependencies: + command-exists "^1.2.8" + commander "^8.1.0" + follow-redirects "^1.12.1" + js-sha3 "0.8.0" + memorystream "^0.3.1" + semver "^5.5.0" + tmp "0.0.33" + solc@^0.4.20: version "0.4.26" resolved "https://registry.yarnpkg.com/solc/-/solc-0.4.26.tgz#5390a62a99f40806b86258c737c1cf653cc35cb5" @@ -9462,6 +9707,14 @@ timed-out@^4.0.1: resolved "https://registry.yarnpkg.com/timed-out/-/timed-out-4.0.1.tgz#f32eacac5a175bea25d7fab565ab3ed8741ef56f" integrity sha512-G7r3AhovYtr5YKOWQkta8RKAPb+J9IsO4uVmzjl8AZwfhs8UcUwTiD6gcJYSgOtzyjvQKrKYn41syHbUWMkafA== +tinyglobby@^0.2.6: + version "0.2.14" + resolved "https://registry.yarnpkg.com/tinyglobby/-/tinyglobby-0.2.14.tgz#5280b0cf3f972b050e74ae88406c0a6a58f4079d" + integrity sha512-tX5e7OM1HnYr2+a2C/4V0htOcSQcoSTH9KgJnVvNm5zm/cyEWKJ7j7YutsH9CxMdtOkkLFy2AHrMci9IM8IPZQ== + dependencies: + fdir "^6.4.4" + picomatch "^4.0.2" + tmp@0.0.33, tmp@^0.0.33: version "0.0.33" resolved "https://registry.yarnpkg.com/tmp/-/tmp-0.0.33.tgz#6d34335889768d21b2bcda0aa277ced3b1bfadf9"