diff --git a/packages/contracts/Justfile b/packages/contracts/Justfile index 7455e69b..61cdd484 100644 --- a/packages/contracts/Justfile +++ b/packages/contracts/Justfile @@ -47,7 +47,7 @@ eth: # just test sleep 0.2 && just eth-deploy & just wagmi & - anvil --host 0.0.0.0 + anvil --code-size-limit 100000 --host 0.0.0.0 build-contracts: forge build @@ -79,6 +79,7 @@ forge-script script: forge script \ $1 \ --fork-url http://localhost:8545 \ + --code-size-limit 100000 \ --broadcast \ --mnemonics "{{ mnemonic }}" \ --sender "{{ sender }}" \ diff --git a/packages/contracts/contracts/discovery/Batch.sol b/packages/contracts/contracts/discovery/Batch.sol index c70671c1..444959ac 100644 --- a/packages/contracts/contracts/discovery/Batch.sol +++ b/packages/contracts/contracts/discovery/Batch.sol @@ -161,13 +161,13 @@ contract Batch is IBatch, ICommon, ProjectVoting { investmentEnd = votingPeriod.end + extraInvestmentDuration; } - function vote(address projectAddress) + function vote(address projectAddress, bytes32[] calldata _merkleProof) external votingPeriodIsSet inVotingPeriod { require( - IController(controller).canVote(msg.sender), + IController(controller).canVote(msg.sender, _merkleProof), "not allowed to vote" ); diff --git a/packages/contracts/contracts/discovery/Controller.sol b/packages/contracts/contracts/discovery/Controller.sol index cdb1e1e7..f14884c1 100644 --- a/packages/contracts/contracts/discovery/Controller.sol +++ b/packages/contracts/contracts/discovery/Controller.sol @@ -5,6 +5,7 @@ import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol"; import {ERC165} from "@openzeppelin/contracts/utils/introspection/ERC165.sol"; import {AccessControl} from "@openzeppelin/contracts/access/AccessControl.sol"; import {SafeERC20} from "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol"; +import {MerkleProof} from "@openzeppelin/contracts/utils/cryptography/MerkleProof.sol"; import {IController} from "./interfaces/IController.sol"; import {IProject} from "./interfaces/IProject.sol"; @@ -12,7 +13,6 @@ import {IBatch} from "./interfaces/IBatch.sol"; import {IStaking} from "./interfaces/IStaking.sol"; import {Project} from "./Project.sol"; import {Batch} from "./Batch.sol"; -import {FractalRegistry} from "../fractal_registry/FractalRegistry.sol"; contract Controller is IController, ERC165, AccessControl { using SafeERC20 for IERC20; @@ -47,21 +47,17 @@ contract Controller is IController, ERC165, AccessControl { // CTND staking contract address public staking; - // Fractal Registry contract - address public registry; - // CTND token contract address public token; + // Merkle root to pass to the projects bytes32 public merkleRoot; constructor( - address _registry, address _staking, address _token, bytes32 _merkleRoot ) { - registry = _registry; staking = _staking; token = _token; merkleRoot = _merkleRoot; @@ -133,40 +129,37 @@ contract Controller is IController, ERC165, AccessControl { } /// @inheritdoc IController - function canInvestInStakersPool(address _user) - external - view - override(IController) - returns (bool) - { + function canInvestInStakersPool( + address _user, + bytes32[] calldata _merkleProof + ) external view override(IController) returns (bool) { return - _hasKYC(_user) && + _hasKYC(_user, _merkleProof) && _belongsToDAO(_user) && IStaking(staking).hasStaked(_user); } /// @inheritdoc IController - function canInvestInPeoplesPool(address _project, address _user) - external - view - override(IController) - returns (bool) - { + function canInvestInPeoplesPool( + address _project, + address _user, + bytes32[] calldata _merkleProof + ) external view override(IController) returns (bool) { Batch batch = Batch(projectsToBatches[_project]); return - _hasKYC(_user) && + _hasKYC(_user, _merkleProof) && _belongsToDAO(_user) && batch.userHasVotedForProject(_project, _user); } - function canVote(address _user) + function canVote(address _user, bytes32[] calldata _merkleProof) external view override(IController) returns (bool) { - return _hasKYC(_user) && _belongsToDAO(_user); + return _hasKYC(_user, _merkleProof) && _belongsToDAO(_user); } /// @inheritdoc IController @@ -179,9 +172,15 @@ contract Controller is IController, ERC165, AccessControl { Batch(batch).setVotingPeriod(start, end, extraInvestmentDuration); } - function _hasKYC(address _user) internal view returns (bool) { - bytes32 fractalId = FractalRegistry(registry).getFractalId(_user); - return fractalId != 0; + function _hasKYC(address _user, bytes32[] calldata _merkleProof) + internal + view + returns (bool) + { + bytes32 leaf = keccak256(abi.encodePacked(_user)); + bool isValid = MerkleProof.verify(_merkleProof, merkleRoot, leaf); + + return isValid; } function _belongsToDAO(address _user) internal view returns (bool) { diff --git a/packages/contracts/contracts/discovery/interfaces/IBatch.sol b/packages/contracts/contracts/discovery/interfaces/IBatch.sol index 05ea5305..a753c9ac 100644 --- a/packages/contracts/contracts/discovery/interfaces/IBatch.sol +++ b/packages/contracts/contracts/discovery/interfaces/IBatch.sol @@ -2,5 +2,6 @@ pragma solidity ^0.8.20; interface IBatch { - function vote(address projectAddress) external; + function vote(address projectAddress, bytes32[] calldata _merkleProof) + external; } diff --git a/packages/contracts/contracts/discovery/interfaces/IController.sol b/packages/contracts/contracts/discovery/interfaces/IController.sol index 61bda966..b8d74bd9 100644 --- a/packages/contracts/contracts/discovery/interfaces/IController.sol +++ b/packages/contracts/contracts/discovery/interfaces/IController.sol @@ -51,17 +51,24 @@ interface IController { returns (bool); /// Checks if a user can invest in the staker's pool of a project - function canInvestInStakersPool(address _user) external view returns (bool); + function canInvestInStakersPool( + address _user, + bytes32[] calldata _merkleProof + ) external view returns (bool); /// Checks if a user can invest in the people's pool of a project - function canInvestInPeoplesPool(address _project, address _user) + function canInvestInPeoplesPool( + address _project, + address _user, + bytes32[] calldata _merkleProof + ) external view returns (bool); + + /// Checks if a user can vote + function canVote(address _user, bytes32[] calldata _merkleProof) external view returns (bool); - /// Checks if a user can vote - function canVote(address _user) external view returns (bool); - /// Sets the voting period for a Batch function setBatchVotingPeriod( address batch, diff --git a/packages/contracts/deploy/controller.ts b/packages/contracts/deploy/controller.ts index 8238d395..f9d9254d 100644 --- a/packages/contracts/deploy/controller.ts +++ b/packages/contracts/deploy/controller.ts @@ -6,7 +6,6 @@ const func: DeployFunction = async function (hre) { const { deployer } = await hre.getNamedAccounts(); const { get } = hre.deployments; - const registry = await get("FractalRegistry"); const citizend = await get("Citizend"); const staking = await get("Staking"); const merkleRoot = @@ -14,13 +13,13 @@ const func: DeployFunction = async function (hre) { await acalaDeploy(hre, "Controller", { from: deployer, - args: [registry.address, staking.address, citizend.address, merkleRoot], + args: [staking.address, citizend.address, merkleRoot], log: true, }); }; func.id = "controller"; func.tags = ["controller"]; -func.dependencies = ["ctnd.token", "staking", "fractal-registry"]; +func.dependencies = ["ctnd.token", "staking"]; export default func; diff --git a/packages/contracts/script/DevDeploy.s.sol b/packages/contracts/script/DevDeploy.s.sol index 06180d84..7f8d47f8 100644 --- a/packages/contracts/script/DevDeploy.s.sol +++ b/packages/contracts/script/DevDeploy.s.sol @@ -1,9 +1,10 @@ // SPDX-License-Identifier: UNLICENSED -pragma solidity ^0.8.12; +pragma solidity ^0.8.20; import {Script} from "lib/forge-std/src/Script.sol"; import {Citizend} from "contracts/token/Citizend.sol"; +import {Controller} from "contracts/discovery/Controller.sol"; import {Staking} from "contracts/discovery/Staking.sol"; import {Project} from "contracts/discovery/Project.sol"; @@ -24,10 +25,13 @@ contract DevDeployScript is Script { function run() public { vm.startBroadcast(); + bytes32 merkleRoot = 0xa5c09e2a9128afef7246a5900cfe02c4bd2cfcac8ac4286f0159a699c8455a49; + Citizend citizend = new Citizend(alice); Staking staking = new Staking(address(citizend)); - bytes32 merkleRoot = 0xa5c09e2a9128afef7246a5900cfe02c4bd2cfcac8ac4286f0159a699c8455a49; + Controller controller = new Controller(address(staking), address(citizend), merkleRoot); + Project project = new Project("token sale project", address(citizend), 1000, 1, address(0), merkleRoot); for (uint256 i; i < testAccounts.length; i++) { diff --git a/packages/contracts/test/contracts/discovery/Batch.ts b/packages/contracts/test/contracts/discovery/Batch.ts index f52f59b0..98d328ce 100644 --- a/packages/contracts/test/contracts/discovery/Batch.ts +++ b/packages/contracts/test/contracts/discovery/Batch.ts @@ -21,6 +21,7 @@ import { import { goToTime, currentTimestamp } from "../../timeHelpers"; import { registerProject, makeProjectReady, setUpBatch } from "./helpers"; +import { BytesLike } from "ethers"; const { parseUnits, formatBytes32String } = ethers.utils; @@ -32,7 +33,6 @@ describe("Batch", () => { let batch: Batch; let controller: Controller; - let registry: FractalRegistry; let citizend: Citizend; let staking: Staking; let projectToken: MockERC20; @@ -42,6 +42,9 @@ describe("Batch", () => { let oneDay: number; let votingStart: number; let votingEnd: number; + let merkleRoot: BytesLike; + let aliceMerkleProof: BytesLike[]; + let bobMerkleProof: BytesLike[]; beforeEach(async () => { [owner, alice, bob, carol] = await ethers.getSigners(); @@ -53,15 +56,23 @@ describe("Batch", () => { "ProjectToken", 18 ); + merkleRoot = + "0xa5c09e2a9128afef7246a5900cfe02c4bd2cfcac8ac4286f0159a699c8455a49"; + aliceMerkleProof = [ + "0xe9707d0e6171f728f7473c24cc0432a9b07eaaf1efed6a137a4a8c12c79552d9", + "0x347dce04eb339ca70588960730ef0cada966bb1d5e10a9b9489a3e0ba47dc1b6", + ]; + bobMerkleProof = [ + "0x8a3552d60a98e0ade765adddad0a2e420ca9b1eef5f326ba7ab860bb4ea72c94", + "0x070e8db97b197cc0e4a1790c5e6c3667bab32d733db7f815fbe84f5824c7168d", + ]; - registry = await new FractalRegistry__factory(owner).deploy(owner.address); citizend = await new Citizend__factory(owner).deploy(owner.address); staking = await new Staking__factory(owner).deploy(citizend.address); controller = await new Controller__factory(owner).deploy( - registry.address, staking.address, citizend.address, - "0xa5c09e2a9128afef7246a5900cfe02c4bd2cfcac8ac4286f0159a699c8455a49" + merkleRoot ); aUSD = await new MockERC20__factory(owner).deploy("aUSD", "aUSD", 12); @@ -81,8 +92,6 @@ describe("Batch", () => { owner ); - await registry.addUserAddress(alice.address, formatBytes32String("id1")); - await registry.addUserAddress(bob.address, formatBytes32String("id2")); await citizend.transfer(alice.address, 1000); await citizend.transfer(bob.address, 1000); @@ -181,7 +190,7 @@ describe("Batch", () => { describe("vote", () => { it("allows a user to vote", async () => { - await batch.connect(alice).vote(fakeProject.address); + await batch.connect(alice).vote(fakeProject.address, aliceMerkleProof); expect(await batch.userVoteCount(alice.address)).to.eq(1); expect(await batch.projectVoteCount(fakeProject.address)).to.eq(1); @@ -191,10 +200,10 @@ describe("Batch", () => { }); it("does not allow a user to vote twice in the same project", async () => { - await batch.connect(alice).vote(fakeProject.address); + await batch.connect(alice).vote(fakeProject.address, aliceMerkleProof); await expect( - batch.connect(alice).vote(fakeProject.address) + batch.connect(alice).vote(fakeProject.address, aliceMerkleProof) ).to.be.revertedWith("already voted in this project"); }); @@ -227,10 +236,10 @@ describe("Batch", () => { ); await goToTime(votingStart + oneDay); - await batch.connect(alice).vote(fakeProject.address); + await batch.connect(alice).vote(fakeProject.address, aliceMerkleProof); await expect( - batch.connect(alice).vote(anotherFakeProject.address) + batch.connect(alice).vote(anotherFakeProject.address, aliceMerkleProof) ).to.be.revertedWith("vote limit reached"); }); @@ -249,7 +258,7 @@ describe("Batch", () => { ); await expect( - batch.connect(alice).vote(newProject.address) + batch.connect(alice).vote(newProject.address, aliceMerkleProof) ).to.be.revertedWith("voting period not set"); }); @@ -257,15 +266,13 @@ describe("Batch", () => { await citizend.transfer(carol.address, 1000); await expect( - batch.connect(carol).vote(fakeProject.address) + batch.connect(carol).vote(fakeProject.address, aliceMerkleProof) ).to.be.revertedWith("not allowed to vote"); }); it("does not allow a user not belonging to the DAO to vote", async () => { - await registry.addUserAddress(carol.address, formatBytes32String("id3")); - await expect( - batch.connect(carol).vote(fakeProject.address) + batch.connect(carol).vote(fakeProject.address, aliceMerkleProof) ).to.be.revertedWith("not allowed to vote"); }); }); @@ -278,9 +285,11 @@ describe("Batch", () => { }); it("takes the votes into account", async () => { - await batch.connect(alice).vote(fakeProject.address); - await batch.connect(bob).vote(fakeProject.address); - await batch.connect(alice).vote(anotherFakeProject.address); + await batch.connect(alice).vote(fakeProject.address, aliceMerkleProof); + await batch.connect(bob).vote(fakeProject.address, bobMerkleProof); + await batch + .connect(alice) + .vote(anotherFakeProject.address, aliceMerkleProof); await goToTime(votingStart + 5 * oneDay); const status = await batch.getProjectStatus(fakeProject.address); @@ -299,7 +308,7 @@ describe("Batch", () => { describe("getCurrentWinners", () => { it("calculates the winner if one exists", async () => { - await batch.connect(alice).vote(fakeProject.address); + await batch.connect(alice).vote(fakeProject.address, aliceMerkleProof); await goToTime(votingStart + 5 * oneDay); const winners = await batch.getCurrentWinners(); @@ -308,9 +317,11 @@ describe("Batch", () => { }); it("calculates the winners of multiple slots", async () => { - await batch.connect(alice).vote(fakeProject.address); - await batch.connect(bob).vote(fakeProject.address); - await batch.connect(alice).vote(anotherFakeProject.address); + await batch.connect(alice).vote(fakeProject.address, aliceMerkleProof); + await batch.connect(bob).vote(fakeProject.address, bobMerkleProof); + await batch + .connect(alice) + .vote(anotherFakeProject.address, aliceMerkleProof); await goToTime(votingStart + 5 * oneDay); let winners = await batch.getCurrentWinners(); @@ -328,14 +339,16 @@ describe("Batch", () => { }); it("calculates the winners of multiple slots with votes in multiple slots", async () => { - await batch.connect(alice).vote(fakeProject.address); - await batch.connect(bob).vote(fakeProject.address); - await batch.connect(alice).vote(anotherFakeProject.address); + await batch.connect(alice).vote(fakeProject.address, aliceMerkleProof); + await batch.connect(bob).vote(fakeProject.address, bobMerkleProof); + await batch + .connect(alice) + .vote(anotherFakeProject.address, aliceMerkleProof); await goToTime(votingStart + 5 * oneDay); let winners = await batch.getCurrentWinners(); - await batch.connect(bob).vote(anotherFakeProject.address); + await batch.connect(bob).vote(anotherFakeProject.address, bobMerkleProof); expect(winners.length).to.eq(1); expect(winners[0]).to.eq(fakeProject.address); @@ -349,9 +362,11 @@ describe("Batch", () => { }); it("works even when it doesn't actually have to calculate anything", async () => { - await batch.connect(alice).vote(fakeProject.address); + await batch.connect(alice).vote(fakeProject.address, aliceMerkleProof); await goToTime(votingStart + 5 * oneDay); - await batch.connect(alice).vote(anotherFakeProject.address); + await batch + .connect(alice) + .vote(anotherFakeProject.address, aliceMerkleProof); let winners = await batch.getCurrentWinners(); @@ -370,8 +385,8 @@ describe("Batch", () => { describe("projectVoteCount", () => { it("counts 1 vote per project, linearly", async () => { - await batch.connect(alice).vote(fakeProject.address); - await batch.connect(bob).vote(fakeProject.address); + await batch.connect(alice).vote(fakeProject.address, aliceMerkleProof); + await batch.connect(bob).vote(fakeProject.address, bobMerkleProof); expect(await batch.projectVoteCount(fakeProject.address)).to.eq(2); }); @@ -380,14 +395,14 @@ describe("Batch", () => { describe("weightedProjectVoteCount", () => { it("gives more weight to early votes, following a linear curve", async () => { await goToTime(votingStart + oneDay); - await batch.connect(alice).vote(fakeProject.address); + await batch.connect(alice).vote(fakeProject.address, aliceMerkleProof); expect( await batch.weightedProjectVoteCount(fakeProject.address) ).to.be.closeTo(parseUnits("0.045"), parseUnits("0.0001")); await goToTime(votingStart + 4 * oneDay); - await batch.connect(bob).vote(fakeProject.address); + await batch.connect(bob).vote(fakeProject.address, bobMerkleProof); expect( await batch.weightedProjectVoteCount(fakeProject.address) diff --git a/packages/contracts/test/contracts/discovery/Controller.ts b/packages/contracts/test/contracts/discovery/Controller.ts index fcf5f50e..4bd92262 100644 --- a/packages/contracts/test/contracts/discovery/Controller.ts +++ b/packages/contracts/test/contracts/discovery/Controller.ts @@ -11,8 +11,6 @@ import { Project__factory, MockERC20, MockERC20__factory, - FractalRegistry, - FractalRegistry__factory, Citizend, Citizend__factory, Staking, @@ -20,6 +18,7 @@ import { } from "../../../src/types"; import { registerProject, makeProjectReady, setUpBatch } from "./helpers"; +import { BytesLike } from "ethers"; const { parseUnits, formatBytes32String } = ethers.utils; const { MaxUint256 } = ethers.constants; @@ -33,14 +32,14 @@ describe("Controller", () => { let project: Project; let projectToken: MockERC20; let aUSD: MockERC20; - let registry: FractalRegistry; let citizend: Citizend; let staking: Staking; + let merkleRoot: BytesLike; + let aliceMerkleProof: BytesLike[]; beforeEach(async () => { [owner, alice, bob] = await ethers.getSigners(); - registry = await new FractalRegistry__factory(owner).deploy(owner.address); citizend = await new Citizend__factory(owner).deploy(owner.address); staking = await new Staking__factory(owner).deploy(citizend.address); aUSD = await new MockERC20__factory(owner).deploy("aUSD", "aUSD", 12); @@ -49,18 +48,22 @@ describe("Controller", () => { "ProjectToken", 18 ); + merkleRoot = + "0xa5c09e2a9128afef7246a5900cfe02c4bd2cfcac8ac4286f0159a699c8455a49"; + aliceMerkleProof = [ + "0xe9707d0e6171f728f7473c24cc0432a9b07eaaf1efed6a137a4a8c12c79552d9", + "0x347dce04eb339ca70588960730ef0cada966bb1d5e10a9b9489a3e0ba47dc1b6", + ]; controller = await new Controller__factory(owner).deploy( - registry.address, staking.address, citizend.address, - "0xa5c09e2a9128afef7246a5900cfe02c4bd2cfcac8ac4286f0159a699c8455a49" + merkleRoot ); }); describe("constructor", () => { it("sets the correct params", async () => { - expect(await controller.registry()).to.eq(registry.address); expect(await controller.staking()).to.eq(staking.address); expect(await controller.token()).to.eq(citizend.address); expect( @@ -69,18 +72,20 @@ describe("Controller", () => { owner.address ) ).to.be.true; + expect(await controller.merkleRoot()).to.eq(merkleRoot); }); }); describe("canInvestInStakersPool", () => { it("is true if the user meets all the requirements", async () => { - await registry.addUserAddress(alice.address, formatBytes32String("id1")); await citizend.transfer(alice.address, 1000); await citizend.connect(alice).approve(staking.address, MaxUint256); await staking.connect(alice).stake(100); project = await registerProject(owner, projectToken, controller, aUSD); - expect(await controller.canInvestInStakersPool(alice.address)).to.be.true; + expect( + await controller.canInvestInStakersPool(alice.address, aliceMerkleProof) + ).to.be.true; }); it("is false if the user does not have the KYC", async () => { @@ -89,39 +94,42 @@ describe("Controller", () => { await staking.connect(alice).stake(100); project = await registerProject(owner, projectToken, controller, aUSD); - expect(await controller.canInvestInStakersPool(alice.address)).to.be + expect(await controller.canInvestInStakersPool(alice.address, [])).to.be .false; }); it("is false if the user does not belong to the DAO", async () => { - await registry.addUserAddress(alice.address, formatBytes32String("id1")); project = await registerProject(owner, projectToken, controller, aUSD); - expect(await controller.canInvestInStakersPool(alice.address)).to.be - .false; + expect( + await controller.canInvestInStakersPool(alice.address, aliceMerkleProof) + ).to.be.false; }); it("is false if the user does not have staked tokens", async () => { - await registry.addUserAddress(alice.address, formatBytes32String("id1")); await citizend.transfer(alice.address, 1000); project = await registerProject(owner, projectToken, controller, aUSD); - expect(await controller.canInvestInStakersPool(alice.address)).to.be - .false; + expect( + await controller.canInvestInStakersPool(alice.address, aliceMerkleProof) + ).to.be.false; }); }); describe("canInvestInPeoplesPool", () => { it("is true if the user meets all the requirements", async () => { - await registry.addUserAddress(alice.address, formatBytes32String("id1")); await citizend.transfer(alice.address, 1000); project = await registerProject(owner, projectToken, controller, aUSD); await makeProjectReady(project, projectToken); const batch: Batch = await setUpBatch(controller, [project], owner); - await batch.connect(alice).vote(project.address); + await batch.connect(alice).vote(project.address, aliceMerkleProof); expect( - await controller.canInvestInPeoplesPool(project.address, alice.address) + await controller.canInvestInPeoplesPool( + project.address, + alice.address, + aliceMerkleProof + ) ).to.be.true; }); @@ -132,30 +140,40 @@ describe("Controller", () => { const batch: Batch = await setUpBatch(controller, [project], owner); expect( - await controller.canInvestInPeoplesPool(project.address, alice.address) + await controller.canInvestInPeoplesPool( + project.address, + alice.address, + aliceMerkleProof + ) ).to.be.false; }); it("is false if the user does not belong to the DAO", async () => { - await registry.addUserAddress(alice.address, formatBytes32String("id1")); project = await registerProject(owner, projectToken, controller, aUSD); await makeProjectReady(project, projectToken); const batch: Batch = await setUpBatch(controller, [project], owner); expect( - await controller.canInvestInPeoplesPool(project.address, alice.address) + await controller.canInvestInPeoplesPool( + project.address, + alice.address, + aliceMerkleProof + ) ).to.be.false; }); it("is false if the user hasn't voted in the project", async () => { await citizend.transfer(alice.address, 1000); - await registry.addUserAddress(alice.address, formatBytes32String("id1")); project = await registerProject(owner, projectToken, controller, aUSD); await makeProjectReady(project, projectToken); await setUpBatch(controller, [project], owner); expect( - await controller.canInvestInPeoplesPool(project.address, alice.address) + await controller.canInvestInPeoplesPool( + project.address, + alice.address, + aliceMerkleProof + ) ).to.be.false; }); }); diff --git a/packages/contracts/test/contracts/discovery/pool/Pool.ts b/packages/contracts/test/contracts/discovery/pool/Pool.ts index 5f7e40d7..b81adeb5 100644 --- a/packages/contracts/test/contracts/discovery/pool/Pool.ts +++ b/packages/contracts/test/contracts/discovery/pool/Pool.ts @@ -31,7 +31,7 @@ describe("Pool", () => { [owner, alice, bob] = await ethers.getSigners(); merkleProof = [ - "0x00314e565e0574cb412563df634608d76f5c59d9f817e85966100ec1d48005c0", + "0xe9707d0e6171f728f7473c24cc0432a9b07eaaf1efed6a137a4a8c12c79552d9", "0x347dce04eb339ca70588960730ef0cada966bb1d5e10a9b9489a3e0ba47dc1b6", ];