From aeeda9d4c69e447c2b40f2e224d0074f8da119c8 Mon Sep 17 00:00:00 2001 From: Arr00 <13561405+arr00@users.noreply.github.com> Date: Tue, 26 Dec 2023 15:42:44 -0500 Subject: [PATCH 1/3] chore: Set additional authorities on creation manual party (#369) --- contracts/crowdfund/AtomicManualParty.sol | 38 ++++-- test/crowdfund/AtomicManualParty.t.sol | 150 +++++++++++++++++++++- 2 files changed, 169 insertions(+), 19 deletions(-) diff --git a/contracts/crowdfund/AtomicManualParty.sol b/contracts/crowdfund/AtomicManualParty.sol index fd60e6e7e..247e21eaa 100644 --- a/contracts/crowdfund/AtomicManualParty.sol +++ b/contracts/crowdfund/AtomicManualParty.sol @@ -44,21 +44,28 @@ contract AtomicManualParty { uint256[] memory preciousTokenIds, uint40 rageQuitTimestamp, address[] memory partyMembers, - uint96[] memory partyMemberVotingPowers + uint96[] memory partyMemberVotingPowers, + address[] memory authorities ) public returns (Party party) { uint96 totalVotingPower = _validateAtomicManualPartyArrays( partyMembers, partyMemberVotingPowers ); - - address[] memory authorities = new address[](1); - authorities[0] = address(this); - opts.governance.totalVotingPower = totalVotingPower; + address[] memory authorities_; + { + uint256 authoritiesLength = authorities.length + 1; + authorities_ = new address[](authoritiesLength); + for (uint i = 0; i < authoritiesLength - 1; ++i) { + authorities_[i] = authorities[i]; + } + authorities_[authoritiesLength - 1] = address(this); + } + party = PARTY_FACTORY.createParty( partyImpl, - authorities, + authorities_, opts, preciousTokens, preciousTokenIds, @@ -89,21 +96,28 @@ contract AtomicManualParty { MetadataProvider provider, bytes memory metadata, address[] memory partyMembers, - uint96[] memory partyMemberVotingPowers + uint96[] memory partyMemberVotingPowers, + address[] memory authorities ) external returns (Party party) { uint96 totalVotingPower = _validateAtomicManualPartyArrays( partyMembers, partyMemberVotingPowers ); - - address[] memory authorities = new address[](1); - authorities[0] = address(this); - opts.governance.totalVotingPower = totalVotingPower; + address[] memory authorities_; + { + uint256 authoritiesLength = authorities.length + 1; + authorities_ = new address[](authoritiesLength); + for (uint i = 0; i < authoritiesLength - 1; ++i) { + authorities_[i] = authorities[i]; + } + authorities_[authoritiesLength - 1] = address(this); + } + party = PARTY_FACTORY.createPartyWithMetadata( partyImpl, - authorities, + authorities_, opts, preciousTokens, preciousTokenIds, diff --git a/test/crowdfund/AtomicManualParty.t.sol b/test/crowdfund/AtomicManualParty.t.sol index af40afac1..efd59f864 100644 --- a/test/crowdfund/AtomicManualParty.t.sol +++ b/test/crowdfund/AtomicManualParty.t.sol @@ -85,7 +85,8 @@ contract AtomicManualPartyTest is SetupPartyHelper { preciousTokenIds, 0, partyMembers, - partyMemberVotingPower + partyMemberVotingPower, + new address[](0) ); // Ensure `atomicManualParty` is not an authority after creation @@ -135,7 +136,8 @@ contract AtomicManualPartyTest is SetupPartyHelper { MetadataProvider(address(0)), "", partyMembers, - partyMemberVotingPower + partyMemberVotingPower, + new address[](0) ); // Ensure `atomicManualParty` is not an authority after creation @@ -174,7 +176,8 @@ contract AtomicManualPartyTest is SetupPartyHelper { preciousTokenIds, 0, partyMembers, - partyMemberVotingPower + partyMemberVotingPower, + new address[](0) ); } @@ -199,7 +202,8 @@ contract AtomicManualPartyTest is SetupPartyHelper { preciousTokenIds, 0, partyMembers, - partyMemberVotingPower + partyMemberVotingPower, + new address[](0) ); } @@ -239,7 +243,8 @@ contract AtomicManualPartyTest is SetupPartyHelper { preciousTokenIds, 0, partyMembers, - partyMemberVotingPower + partyMemberVotingPower, + new address[](0) ); // Ensure `atomicManualParty` is not an authority after creation @@ -282,7 +287,8 @@ contract AtomicManualPartyTest is SetupPartyHelper { preciousTokenIds, 0, partyMembers, - partyMemberVotingPower + partyMemberVotingPower, + new address[](0) ); } @@ -314,7 +320,137 @@ contract AtomicManualPartyTest is SetupPartyHelper { preciousTokenIds, 0, partyMembers, - partyMemberVotingPower + partyMemberVotingPower, + new address[](0) + ); + } + + function test_atomicManualParty_additionalAuthorities() public { + Party.PartyOptions memory opts; + opts.name = "PARTY"; + opts.symbol = "PR-T"; + opts.governance.voteDuration = 99; + opts.governance.executionDelay = _EXECUTION_DELAY; + opts.governance.passThresholdBps = 1000; + opts.governance.totalVotingPower = 180; + + address[] memory partyMembers = new address[](2); + uint96[] memory partyMemberVotingPower = new uint96[](2); + + partyMembers[0] = john; + partyMembers[1] = danny; + + partyMemberVotingPower[0] = 100; + partyMemberVotingPower[1] = 80; + + // Not checking address of the party + vm.expectEmit(false, true, true, true); + emit PartyCreated( + Party(payable(0)), + opts, + preciousTokens, + preciousTokenIds, + address(atomicManualParty) + ); + vm.expectEmit(false, true, true, true); + emit AtomicManualPartyCreated( + Party(payable(0)), + partyMembers, + partyMemberVotingPower, + opts, + preciousTokens, + preciousTokenIds, + 0, + address(this) + ); + + address[] memory authorities = new address[](2); + authorities[0] = _randomAddress(); + authorities[1] = _randomAddress(); + + // total voting power ignored + opts.governance.totalVotingPower = 100; + Party atomicParty = atomicManualParty.createParty( + Party(payable(address(Proxy(payable(address(party))).IMPL()))), + opts, + preciousTokens, + preciousTokenIds, + 0, + partyMembers, + partyMemberVotingPower, + authorities + ); + + // Ensure `atomicManualParty` is not an authority after creation + // Ensure `atomicManualParty` is not an authority after creation + assertFalse(atomicParty.isAuthority(address(atomicManualParty))); + // Ensure authorities passed are authorities + assertTrue(atomicParty.isAuthority(authorities[0])); + assertTrue(atomicParty.isAuthority(authorities[1])); + + assertEq(atomicParty.getGovernanceValues().totalVotingPower, 180); + + // Ensure holders match input + assertEq(atomicParty.getVotingPowerAt(john, uint40(block.timestamp)), 100); + assertEq(atomicParty.getVotingPowerAt(danny, uint40(block.timestamp)), 80); + } + + function test_createAtomicManualPartyWithMetadata_additionalAuthorities() public { + Party.PartyOptions memory opts; + opts.name = "PARTY"; + opts.symbol = "PR-T"; + opts.governance.voteDuration = 99; + opts.governance.executionDelay = _EXECUTION_DELAY; + opts.governance.passThresholdBps = 1000; + opts.governance.totalVotingPower = 180; + + address[] memory partyMembers = new address[](2); + uint96[] memory partyMemberVotingPower = new uint96[](2); + + partyMembers[0] = john; + partyMembers[1] = danny; + + partyMemberVotingPower[0] = 100; + partyMemberVotingPower[1] = 80; + + address[] memory authorities = new address[](2); + authorities[0] = _randomAddress(); + authorities[1] = _randomAddress(); + + // Not checking address of the party + vm.expectEmit(false, true, true, true); + emit PartyCreated( + Party(payable(0)), + opts, + preciousTokens, + preciousTokenIds, + address(atomicManualParty) + ); + vm.expectEmit(false, true, true, true); + emit ProviderSet(address(0), IMetadataProvider(address(0))); + Party atomicParty = atomicManualParty.createPartyWithMetadata( + Party(payable(address(Proxy(payable(address(party))).IMPL()))), + opts, + preciousTokens, + preciousTokenIds, + 0, + MetadataProvider(address(0)), + "", + partyMembers, + partyMemberVotingPower, + authorities ); + + // Ensure `atomicManualParty` is not an authority after creation + assertFalse(party.isAuthority(address(atomicManualParty))); + // Ensure authorities passed are authorities + assertTrue(atomicParty.isAuthority(authorities[0])); + assertTrue(atomicParty.isAuthority(authorities[1])); + + assertEq(atomicParty.getGovernanceValues().totalVotingPower, 180); + + // Ensure holders match input + assertEq(atomicParty.getVotingPowerAt(john, uint40(block.timestamp)), 100); + assertEq(atomicParty.getVotingPowerAt(danny, uint40(block.timestamp)), 80); } } From d4bf036ebd22c2b92574595e6cb179f51670c939 Mon Sep 17 00:00:00 2001 From: Brian Le Date: Wed, 3 Jan 2024 10:31:05 -0800 Subject: [PATCH 2/3] chore(deploy): fix incorrect handling of string constructor args (#370) --- utils/verify.ts | 29 ++++++++++++++++++++--------- 1 file changed, 20 insertions(+), 9 deletions(-) diff --git a/utils/verify.ts b/utils/verify.ts index 1284a0350..63b8ae826 100644 --- a/utils/verify.ts +++ b/utils/verify.ts @@ -51,26 +51,31 @@ const getEncodedConstructorArgs = (args: any[], types: string[]) => { let arg = args[i]; const type = types[i]; if (type.startsWith("uint")) { + // Format uint types arg = parseInt(arg.match(/\d+/)[0]); } else if (type.includes("[]")) { + // Handle array types if (arg !== "[]") { arg = arg.slice(1, -1).split(", "); } else { arg = []; } + } else if (type === "string") { + // Remove quotes from start and end of string (if present) + arg = arg.replace(/^['"](.*)['"]$/, "$1"); } constructorArgs.push(arg); } - return ethers.AbiCoder.defaultAbiCoder().encode(types, constructorArgs).slice(2); + return ethers.AbiCoder.defaultAbiCoder().encode(types, constructorArgs); }; const generateStandardJson = ( chain: string, contractName: string, contractAddress: string, - constructorArgs: string, + constructorArgsEncoded: string, optimizerRuns: number, compilerVersion: string, evmVersion: string, @@ -78,7 +83,7 @@ const generateStandardJson = ( ) => { let cmd = `forge verify-contract ${contractAddress} ${contractName} --chain-id ${getChainId( chain, - )} --optimizer-runs ${optimizerRuns} --constructor-args '${constructorArgs}' --compiler-version ${compilerVersion}`; + )} --optimizer-runs ${optimizerRuns} --constructor-args '${constructorArgsEncoded}' --compiler-version ${compilerVersion}`; if (libraries.length > 0) { cmd += ` --libraries ${libraries.join(" ")}`; @@ -131,6 +136,11 @@ const uploadToBlockExplorer = async ( evmVersion: string, apiKey: string, ) => { + // Remove 0x at the beginning of the encoded constructor args, if present. Etherscan doesn't like it. + if (constructorArgsEncoded.startsWith("0x")) { + constructorArgsEncoded = constructorArgsEncoded.slice(2); + } + const response = await axios.post( getBlockExplorerApiEndpoint(chain), { @@ -258,7 +268,11 @@ export const verify = async (chain: string, skip: boolean) => { }); const contractAddress = contract["contractAddress"]; - const constructorArgs = getConstructorArgs(contract["arguments"]); + const constructorArgsEncoded = getEncodedConstructorArgs( + contract["arguments"], + contractTypes[contractName], + ); + const optimizerRuns = cacheData["files"][contractFilePath]["solcConfig"]["settings"]["optimizer"]["runs"]; const evmVersion = cacheData["files"][contractFilePath]["solcConfig"]["settings"]["evmVersion"]; @@ -267,16 +281,13 @@ export const verify = async (chain: string, skip: boolean) => { chain, contractName, contractAddress, - constructorArgs, + constructorArgsEncoded, optimizerRuns, getContractVersion(contractName), evmVersion, runLatestData["libraries"], ); - const constructorArgsEncoded = getEncodedConstructorArgs( - contract["arguments"], - contractTypes[contractName], - ); + const response = await uploadToBlockExplorer( chain, jsonData, From d2856e2d20b815da9b52c1d38b2dfaa4bf3530bc Mon Sep 17 00:00:00 2001 From: Arr00 <13561405+arr00@users.noreply.github.com> Date: Fri, 5 Jan 2024 15:20:23 -0500 Subject: [PATCH 3/3] chore: Deploy to sepolia and base-sepolia (#363) --- deploy/BaseSepolia.s.sol | 15 ++++++++ deploy/Deploy.s.sol | 3 ++ deploy/LibDeployConstants.sol | 68 +++++++++++++++++++++++++++++++++++ deploy/Sepolia.s.sol | 15 ++++++++ package.json | 2 ++ utils/deploy.ts | 10 +++++- utils/verify.ts | 6 ++++ 7 files changed, 118 insertions(+), 1 deletion(-) create mode 100644 deploy/BaseSepolia.s.sol create mode 100644 deploy/Sepolia.s.sol diff --git a/deploy/BaseSepolia.s.sol b/deploy/BaseSepolia.s.sol new file mode 100644 index 000000000..a0b1fc8bd --- /dev/null +++ b/deploy/BaseSepolia.s.sol @@ -0,0 +1,15 @@ +// SPDX-License-Identifier: Apache-2.0 +pragma solidity ^0.8; + +import "./Deploy.s.sol"; +import "./LibDeployConstants.sol"; + +contract SepoliaDeploy is DeployScript { + function _run() internal override { + console.log("Starting base sepolia deploy script."); + + deploy(LibDeployConstants.baseSepolia(this.getDeployer())); + + console.log("Ending base sepolia deploy script."); + } +} diff --git a/deploy/Deploy.s.sol b/deploy/Deploy.s.sol index 7346b5570..3d880b694 100644 --- a/deploy/Deploy.s.sol +++ b/deploy/Deploy.s.sol @@ -112,6 +112,9 @@ abstract contract Deploy { console.log(""); console.log("### ProposalExecutionEngine"); console.log(" Deploying - ProposalExecutionEngine"); + if (deployConstants.zoraReserveAuctionCoreEth == address(0)) { + revert("zoraReserveAuctionCoreEth address cannot be 0"); + } IReserveAuctionCoreEth zora = IReserveAuctionCoreEth( deployConstants.zoraReserveAuctionCoreEth ); diff --git a/deploy/LibDeployConstants.sol b/deploy/LibDeployConstants.sol index aa39864b8..5c806fb8c 100644 --- a/deploy/LibDeployConstants.sol +++ b/deploy/LibDeployConstants.sol @@ -32,6 +32,74 @@ library LibDeployConstants { string baseExternalURL; } + function sepolia(address multisig) internal pure returns (DeployConstants memory) { + address[] memory allowedERC20SwapOperatorTargets = new address[](0); + + DeployConstants memory deployConstants = DeployConstants({ + seaportExchangeAddress: 0x00000000000000ADc04C56Bf30aC9d3c0aAF14dC, + osZoraAuctionDuration: 2 minutes, + osZoraAuctionTimeout: 2 minutes, + osMinOrderDuration: 2 minutes, + osMaxOrderDuration: 14 days, + zoraMinAuctionDuration: 2 minutes, + zoraMaxAuctionDuration: 10 days, + zoraMaxAuctionTimeout: 7 days, + minCancelDelay: 5 minutes, + maxCancelDelay: 1 days, + distributorEmergencyActionAllowedDuration: 365 days, + partyDaoMultisig: multisig, + allowedERC20SwapOperatorTargets: allowedERC20SwapOperatorTargets, + osZone: 0x0000000000000000000000000000000000000000, + osConduitKey: 0xf984c55ca75735630c1c27d3d06969c1aa6af1df86d22ddc0e3a978ad6138e9f, + osConduitController: 0x00000000F9490004C11Cef243f5400493c00Ad63, + fractionalVaultFactory: 0x0000000000000000000000000000000000000000, + nounsAuctionHouse: 0x0000000000000000000000000000000000000000, + zoraReserveAuctionCoreEth: 0x0000000000000000000000000000000000000000, + networkName: "sepolia", + deployedNounsMarketWrapper: 0x0000000000000000000000000000000000000000, + contributionRouterInitialFee: 0.00055 ether, + tokenDistributorV1: 0x0000000000000000000000000000000000000000, + tokenDistributorV2: 0x0000000000000000000000000000000000000000, + baseExternalURL: "https://party.app/party/" + }); + + return deployConstants; + } + + function baseSepolia(address multisig) internal pure returns (DeployConstants memory) { + address[] memory allowedERC20SwapOperatorTargets = new address[](0); + + DeployConstants memory deployConstants = DeployConstants({ + seaportExchangeAddress: 0x00000000000000ADc04C56Bf30aC9d3c0aAF14dC, + osZoraAuctionDuration: 2 minutes, + osZoraAuctionTimeout: 2 minutes, + osMinOrderDuration: 2 minutes, + osMaxOrderDuration: 14 days, + zoraMinAuctionDuration: 2 minutes, + zoraMaxAuctionDuration: 10 days, + zoraMaxAuctionTimeout: 7 days, + minCancelDelay: 5 minutes, + maxCancelDelay: 1 days, + distributorEmergencyActionAllowedDuration: 365 days, + partyDaoMultisig: multisig, + allowedERC20SwapOperatorTargets: allowedERC20SwapOperatorTargets, + osZone: 0x0000000000000000000000000000000000000000, + osConduitKey: 0xf984c55ca75735630c1c27d3d06969c1aa6af1df86d22ddc0e3a978ad6138e9f, + osConduitController: 0x00000000F9490004C11Cef243f5400493c00Ad63, + fractionalVaultFactory: 0x0000000000000000000000000000000000000000, + nounsAuctionHouse: 0x0000000000000000000000000000000000000000, + zoraReserveAuctionCoreEth: 0x0000000000000000000000000000000000000000, + networkName: "base-sepolia", + deployedNounsMarketWrapper: 0x0000000000000000000000000000000000000000, + contributionRouterInitialFee: 0.00055 ether, + tokenDistributorV1: 0x0000000000000000000000000000000000000000, + tokenDistributorV2: 0x0000000000000000000000000000000000000000, + baseExternalURL: "https://party.app/party/" + }); + + return deployConstants; + } + function goerli(address multisig) internal pure returns (DeployConstants memory) { address[] memory allowedERC20SwapOperatorTargets = new address[](1); allowedERC20SwapOperatorTargets[0] = 0xF91bB752490473B8342a3E964E855b9f9a2A668e; // 0x Swap Aggregator diff --git a/deploy/Sepolia.s.sol b/deploy/Sepolia.s.sol new file mode 100644 index 000000000..657a580e2 --- /dev/null +++ b/deploy/Sepolia.s.sol @@ -0,0 +1,15 @@ +// SPDX-License-Identifier: Apache-2.0 +pragma solidity ^0.8; + +import "./Deploy.s.sol"; +import "./LibDeployConstants.sol"; + +contract SepoliaDeploy is DeployScript { + function _run() internal override { + console.log("Starting sepolia deploy script."); + + deploy(LibDeployConstants.sepolia(this.getDeployer())); + + console.log("Ending sepolia deploy script."); + } +} diff --git a/package.json b/package.json index 6a23391df..ad65ab205 100644 --- a/package.json +++ b/package.json @@ -15,6 +15,8 @@ "deploy": "node js/deploy.js", "deploy:goerli": "DRY_RUN=0 forge script ./deploy/Goerli.s.sol -vvv --rpc-url $GOERLI_RPC_URL --broadcast --etherscan-api-key $ETHERSCAN_API_KEY --via-ir --skip test --optimize --optimizer-runs 95 --ffi --slow", "deploy:goerli:dry": "DRY_RUN=1 forge script ./deploy/Goerli.s.sol -vvv --rpc-url $GOERLI_RPC_URL --via-ir --skip test --optimize --optimizer-runs 95 --ffi", + "deploy:sepolia": "DRY_RUN=0 forge script ./deploy/Sepolia.s.sol -vvv --rpc-url $SEPOLIA_RPC_URL --broadcast --etherscan-api-key $ETHERSCAN_API_KEY --via-ir --skip test --optimize --optimizer-runs 95 --ffi --slow", + "deploy:sepolia:dry": "DRY_RUN=1 forge script ./deploy/Sepolia.s.sol -vvv --rpc-url $SEPOLIA_RPC_URL --via-ir --skip test --optimize --optimizer-runs 95 --ffi", "deploy:mainnet": "DRY_RUN=0 forge script ./deploy/Mainnet.s.sol -vvv --rpc-url $ETH_RPC_URL --broadcast --etherscan-api-key $ETHERSCAN_API_KEY --via-ir --skip test --optimize --optimizer-runs 95 --ffi --slow", "deploy:mainnet:dry": "DRY_RUN=1 forge script ./deploy/Mainnet.s.sol -vvv --rpc-url $ETH_RPC_URL --via-ir --skip test --optimize --optimizer-runs 95 --ffi", "deploy:base": "DRY_RUN=0 forge script ./deploy/Base.s.sol -vvv --rpc-url $BASE_RPC_URL --via-ir --broadcast --etherscan-api-key $BASESCAN_API_KEY --evm-version paris --skip test --optimize --optimizer-runs 0 --ffi --slow", diff --git a/utils/deploy.ts b/utils/deploy.ts index aaa460f9c..481289e66 100644 --- a/utils/deploy.ts +++ b/utils/deploy.ts @@ -359,7 +359,15 @@ async function updateHeadJson(chain: string, releaseName: string) { async function main() { const chain = process.argv[2]; - const validChains = ["mainnet", "goerli", "base", "base-goerli", "zora"]; + const validChains = [ + "mainnet", + "goerli", + "base", + "base-goerli", + "zora", + "sepolia", + "base-sepolia", + ]; if (!chain) { console.error(`Missing chain argument. Valid chains are: ${validChains.join(", ")}`); diff --git a/utils/verify.ts b/utils/verify.ts index 63b8ae826..108e8c2e0 100644 --- a/utils/verify.ts +++ b/utils/verify.ts @@ -14,6 +14,8 @@ export const getBlockExplorerApiEndpoint = (chain: string) => { return "https://api-goerli.basescan.org/api"; } else if (chain === "zora") { return "https://api.routescan.io/v2/network/mainnet/evm/7777777/etherscan/api"; + } else if (chain === "base-sepolia") { + return "https://api-sepolia.basescan.org/api"; } else { return `https://api-${chain}.etherscan.io/api`; } @@ -222,6 +224,10 @@ const getChainId = (chain: string) => { return 84531; } else if (chain === "zora") { return 7777777; + } else if (chain === "sepolia") { + return 11155111; + } else if (chain === "base-sepolia") { + return 84532; } else { throw new Error(`Unknown chain ID for "${chain}". Please add to getChainId() function.`); }