diff --git a/contracts/distribution/TokenDistributor.sol b/contracts/distribution/TokenDistributor.sol index f6bfd2526..a2041412c 100644 --- a/contracts/distribution/TokenDistributor.sol +++ b/contracts/distribution/TokenDistributor.sol @@ -1,6 +1,7 @@ // SPDX-License-Identifier: GPL-3.0 pragma solidity 0.8.20; +import "openzeppelin/contracts/security/ReentrancyGuard.sol"; import "../globals/IGlobals.sol"; import "../globals/LibGlobals.sol"; import "../tokens/IERC20.sol"; @@ -12,7 +13,7 @@ import "../utils/LibSafeCast.sol"; import "./ITokenDistributor.sol"; /// @notice Creates token distributions for parties. -contract TokenDistributor is ITokenDistributor { +contract TokenDistributor is ITokenDistributor, ReentrancyGuard { using LibAddress for address payable; using LibERC20Compat for IERC20; using LibRawResult for bytes; @@ -105,7 +106,7 @@ contract TokenDistributor is ITokenDistributor { Party party, address payable feeRecipient, uint16 feeBps - ) external payable returns (DistributionInfo memory info) { + ) external payable nonReentrant returns (DistributionInfo memory info) { info = _createDistribution( CreateDistributionArgs({ party: party, @@ -124,7 +125,7 @@ contract TokenDistributor is ITokenDistributor { Party party, address payable feeRecipient, uint16 feeBps - ) external returns (DistributionInfo memory info) { + ) external nonReentrant returns (DistributionInfo memory info) { info = _createDistribution( CreateDistributionArgs({ party: party, @@ -141,7 +142,7 @@ contract TokenDistributor is ITokenDistributor { function claim( DistributionInfo calldata info, uint256 partyTokenId - ) public returns (uint128 amountClaimed) { + ) public nonReentrant returns (uint128 amountClaimed) { // Caller must own the party token. { address ownerOfPartyToken = info.party.ownerOf(partyTokenId); @@ -185,7 +186,10 @@ contract TokenDistributor is ITokenDistributor { } /// @inheritdoc ITokenDistributor - function claimFee(DistributionInfo calldata info, address payable recipient) public { + function claimFee( + DistributionInfo calldata info, + address payable recipient + ) public nonReentrant { // DistributionInfo must be correct for this distribution ID. DistributionState storage state = _distributionStates[info.party][info.distributionId]; if (state.distributionHash != _getDistributionHash(info)) { @@ -364,25 +368,13 @@ contract TokenDistributor is ITokenDistributor { uint256 amount ) private { bytes32 balanceId = _getBalanceId(tokenType, token); - // Reduce stored token balance. - uint256 storedBalance = _storedBalances[balanceId] - amount; - // Temporarily set to max as a reentrancy guard. An interesing attack - // could occur if we didn't do this where an attacker could `claim()` and - // reenter upon transfer (e.g. in the `tokensToSend` hook of an ERC777) to - // `createERC20Distribution()`. Since the `balanceOf(address(this))` - // would not of been updated yet, the supply would be miscalculated and - // the attacker would create a distribution that essentially steals from - // the last distribution they were claiming from. Here, we prevent that - // by causing an arithmetic underflow with the supply calculation if - // this were to be attempted. - _storedBalances[balanceId] = type(uint256).max; + _storedBalances[balanceId] -= amount; if (tokenType == TokenType.Native) { recipient.transferEth(amount); } else { assert(tokenType == TokenType.Erc20); IERC20(token).compatTransfer(recipient, amount); } - _storedBalances[balanceId] = storedBalance; } function _getDistributionHash( diff --git a/contracts/renderers/PartyNFTRenderer.sol b/contracts/renderers/PartyNFTRenderer.sol index 585d2cc25..76c37aa00 100644 --- a/contracts/renderers/PartyNFTRenderer.sol +++ b/contracts/renderers/PartyNFTRenderer.sol @@ -76,8 +76,10 @@ contract PartyNFTRenderer is RendererBase { IMetadataRegistry1_1 constant OLD_METADATA_REGISTRY = IMetadataRegistry1_1(0x175487875F0318EdbAB54BBA442fF53b36e96015); /// @notice The old token distributor contract address. + /// @dev Cannot store immutable arrays... address immutable TOKEN_DISTRIBUTOR_V1; address immutable TOKEN_DISTRIBUTOR_V2; + address immutable TOKEN_DISTRIBUTOR_V3; /// @notice The base url for external URLs. External URL is BASE_EXTERNAL_URL + PARTY_ADDRESS /// @dev First byte is the size of the data, the rest is the data (starting from MSB) @@ -89,11 +91,13 @@ contract PartyNFTRenderer is RendererBase { IFont font, address tokenDistributionV1, address tokenDistributionV2, + address tokenDistributionV3, string memory baseExternalURL ) RendererBase(globals, rendererStorage, font) { IMPL = address(this); TOKEN_DISTRIBUTOR_V1 = tokenDistributionV1; TOKEN_DISTRIBUTOR_V2 = tokenDistributionV2; + TOKEN_DISTRIBUTOR_V3 = tokenDistributionV3; bytes memory baseExternalURLBytes = bytes(baseExternalURL); if (baseExternalURLBytes.length > 31) { @@ -662,11 +666,14 @@ contract PartyNFTRenderer is RendererBase { if (address(this) == IMPL) return false; // There will only be one distributor if old token distributor is not set - TokenDistributor[] memory distributors = new TokenDistributor[](3); + TokenDistributor[] memory distributors = new TokenDistributor[](4); distributors[0] = TokenDistributor( _GLOBALS.getAddress(LibGlobals.GLOBAL_TOKEN_DISTRIBUTOR) ); uint256 l = 1; + if (TOKEN_DISTRIBUTOR_V3 != address(0)) { + distributors[l++] = TokenDistributor(TOKEN_DISTRIBUTOR_V3); + } if (TOKEN_DISTRIBUTOR_V2 != address(0)) { distributors[l++] = TokenDistributor(TOKEN_DISTRIBUTOR_V2); } diff --git a/deploy/BaseGoerli.s.sol b/deploy/BaseGoerli.s.sol deleted file mode 100644 index dbae8030d..000000000 --- a/deploy/BaseGoerli.s.sol +++ /dev/null @@ -1,15 +0,0 @@ -// SPDX-License-Identifier: Apache-2.0 -pragma solidity ^0.8; - -import "./Deploy.s.sol"; -import "./LibDeployConstants.sol"; - -contract BaseGoerliDeploy is DeployScript { - function _run() internal override { - console.log("Starting base goerli deploy script."); - - deploy(LibDeployConstants.baseGoerli(this.getDeployer())); - - console.log("Ending base goerli deploy script."); - } -} diff --git a/deploy/Deploy.s.sol b/deploy/Deploy.s.sol index 68a559f9b..ea56c7727 100644 --- a/deploy/Deploy.s.sol +++ b/deploy/Deploy.s.sol @@ -330,6 +330,7 @@ abstract contract Deploy { IFont(address(pixeldroidConsoleFont)), deployConstants.tokenDistributorV1, deployConstants.tokenDistributorV2, + deployConstants.tokenDistributorV3, deployConstants.baseExternalURL ); _trackDeployerGasAfter(); diff --git a/deploy/Goerli.s.sol b/deploy/Goerli.s.sol deleted file mode 100644 index 311b64a10..000000000 --- a/deploy/Goerli.s.sol +++ /dev/null @@ -1,15 +0,0 @@ -// SPDX-License-Identifier: Apache-2.0 -pragma solidity ^0.8; - -import "./Deploy.s.sol"; -import "./LibDeployConstants.sol"; - -contract GoerliDeploy is DeployScript { - function _run() internal override { - console.log("Starting goerli deploy script."); - - deploy(LibDeployConstants.goerli(this.getDeployer())); - - console.log("Ending goerli deploy script."); - } -} diff --git a/deploy/LibDeployConstants.sol b/deploy/LibDeployConstants.sol index 5c806fb8c..6122f2e6f 100644 --- a/deploy/LibDeployConstants.sol +++ b/deploy/LibDeployConstants.sol @@ -29,6 +29,7 @@ library LibDeployConstants { uint96 contributionRouterInitialFee; address tokenDistributorV1; address tokenDistributorV2; + address tokenDistributorV3; string baseExternalURL; } @@ -60,6 +61,7 @@ library LibDeployConstants { contributionRouterInitialFee: 0.00055 ether, tokenDistributorV1: 0x0000000000000000000000000000000000000000, tokenDistributorV2: 0x0000000000000000000000000000000000000000, + tokenDistributorV3: 0xf0560F963538017CAA5081D96f839FE5D265acCB, baseExternalURL: "https://party.app/party/" }); @@ -94,82 +96,13 @@ library LibDeployConstants { contributionRouterInitialFee: 0.00055 ether, tokenDistributorV1: 0x0000000000000000000000000000000000000000, tokenDistributorV2: 0x0000000000000000000000000000000000000000, + tokenDistributorV3: 0x2d451d8317feF4f3fB8798815520202195FE8C7C, 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 - - 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: 0x014850E83d9D0D1BB0c8624035F09626b967B81c, - nounsAuctionHouse: 0x7295e70f2B26986Ba108bD1Bf9E349a181F4a6Ea, - zoraReserveAuctionCoreEth: 0x2506D9F5A2b0E1A2619bCCe01CD3e7C289A13163, - networkName: "goerli", - deployedNounsMarketWrapper: 0x0000000000000000000000000000000000000000, - contributionRouterInitialFee: 0.00055 ether, - tokenDistributorV1: 0xE6F58B31344404E3479d81fB8f9dD592feB37965, - tokenDistributorV2: 0x8714EA9C2BC5a8f2d26D7c3F86558331c16145B5, - baseExternalURL: "https://party.app/party/" - }); - - return deployConstants; - } - - function baseGoerli(address multisig) internal pure returns (DeployConstants memory) { - address[] memory allowedERC20SwapOperatorTargets = new address[](1); - allowedERC20SwapOperatorTargets[0] = 0xF91bB752490473B8342a3E964E855b9f9a2A668e; // 0x Swap Aggregator - - 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-goerli", - deployedNounsMarketWrapper: 0x0000000000000000000000000000000000000000, - contributionRouterInitialFee: 0.00055 ether, - tokenDistributorV1: address(0), - tokenDistributorV2: 0x55D2463cf5b6743F279Fe9BcbF32415f575B953d, - baseExternalURL: "https://base.party.app/party/" - }); - - return deployConstants; - } - function mainnet() internal pure returns (DeployConstants memory) { address[] memory allowedERC20SwapOperatorTargets = new address[](1); allowedERC20SwapOperatorTargets[0] = 0xDef1C0ded9bec7F1a1670819833240f027b25EfF; // 0x Swap Aggregator @@ -199,6 +132,7 @@ library LibDeployConstants { contributionRouterInitialFee: 0.00055 ether, tokenDistributorV1: 0x1CA2007a81F8A7491BB6E11D8e357FD810896454, tokenDistributorV2: 0x49a3caab781f711aD74C9d2F34c3cbD835d6A608, + tokenDistributorV3: 0x0b7b86DCEAa8015CeD8F625d3b7A961b31fB05FE, baseExternalURL: "https://party.app/party/" }); @@ -234,6 +168,7 @@ library LibDeployConstants { contributionRouterInitialFee: 0.00055 ether, tokenDistributorV1: address(0), tokenDistributorV2: 0xf0560F963538017CAA5081D96f839FE5D265acCB, + tokenDistributorV3: 0x65778953D291DD1e3a97c6b4d8BEea188B650077, baseExternalURL: "https://base.party.app/party/" }); @@ -268,6 +203,7 @@ library LibDeployConstants { contributionRouterInitialFee: 0.00055 ether, tokenDistributorV1: address(0), tokenDistributorV2: address(0), + tokenDistributorV3: 0x5B19016a409a888326b05949391EB8797dD5F75B, baseExternalURL: "https://zora.party.app/party/" }); diff --git a/lib/forge-std b/lib/forge-std index 2f43c7e69..2f6762e4f 160000 --- a/lib/forge-std +++ b/lib/forge-std @@ -1 +1 @@ -Subproject commit 2f43c7e69b820910e9d4f3b8cc8d3b4e6382786e +Subproject commit 2f6762e4f73f3d835457c220b5f62dfeeb6f6341 diff --git a/lib/party-addresses b/lib/party-addresses index 9e3ee3c4f..eeab8a7c9 160000 --- a/lib/party-addresses +++ b/lib/party-addresses @@ -1 +1 @@ -Subproject commit 9e3ee3c4fef00420388e37bbb98167cc6d4fd497 +Subproject commit eeab8a7c92e24e9d985a835d2b24f599d0465968 diff --git a/package.json b/package.json index 1629bae64..d05e71292 100644 --- a/package.json +++ b/package.json @@ -13,8 +13,6 @@ "test:fork": "yarn test --fork-url $ETH_RPC_URL", "test:gas": "forge test --ffi --mc GasBenchmarks -vv", "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 50 --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 50 --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 50 --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 50 --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 50 --ffi --slow", @@ -23,8 +21,6 @@ "deploy:base:dry": "DRY_RUN=1 forge script ./deploy/Base.s.sol -vvv --rpc-url $BASE_RPC_URL --via-ir --skip test --optimize --optimizer-runs 50 --ffi", "deploy:base-sepolia": "DRY_RUN=0 forge script ./deploy/BaseSepolia.s.sol -vvv --rpc-url $BASE_SEPOLIA_RPC_URL --via-ir --broadcast --etherscan-api-key $BASESCAN_API_KEY --skip test --optimize --optimizer-runs 50 --ffi --slow", "deploy:base-sepolia:dry": "DRY_RUN=1 forge script ./deploy/BaseSepolia.s.sol -vvv --rpc-url $BASE_SEPOLIA_RPC_URL --via-ir --skip test --optimize --optimizer-runs 50 --ffi", - "deploy:base-goerli": "DRY_RUN=0 forge script ./deploy/BaseGoerli.s.sol -vvv --rpc-url $BASE_GOERLI_RPC_URL --via-ir --broadcast --etherscan-api-key $BASESCAN_API_KEY --skip test --optimize --optimizer-runs 50 --ffi --slow", - "deploy:base-goerli:dry": "DRY_RUN=1 forge script ./deploy/BaseGoerli.s.sol -vvv --rpc-url $BASE_GOERLI_RPC_URL --via-ir --skip test --optimize --optimizer-runs 50 --ffi", "deploy:zora:dry": "DRY_RUN=1 forge script ./deploy/Zora.s.sol -vvv --rpc-url $ZORA_RPC_URL --via-ir --skip test --optimize --optimizer-runs 50 --ffi --priority-gas-price 1", "deploy:zora": "DRY_RUN=0 forge script ./deploy/Zora.s.sol -vvv --rpc-url $ZORA_RPC_URL --broadcast --via-ir --skip test --optimize --optimizer-runs 50 --ffi --slow --priority-gas-price 1", "decode-revert": "node js/decode-revert.js", diff --git a/test/crowdfund/InitialETHCrowdfund.t.sol b/test/crowdfund/InitialETHCrowdfund.t.sol index 5a9176492..e9fe135c4 100644 --- a/test/crowdfund/InitialETHCrowdfund.t.sol +++ b/test/crowdfund/InitialETHCrowdfund.t.sol @@ -60,6 +60,7 @@ contract InitialETHCrowdfundTestBase is LintJSON, TestUtils, ERC721Receiver { font, address(0), address(0), + address(0), "https://party.app/party/" ); tokenDistributor = new TokenDistributor(globals, 0); diff --git a/test/party/PartyGovernanceNFT.t.sol b/test/party/PartyGovernanceNFT.t.sol index d6429a02f..ab187bd7c 100644 --- a/test/party/PartyGovernanceNFT.t.sol +++ b/test/party/PartyGovernanceNFT.t.sol @@ -78,6 +78,7 @@ contract PartyGovernanceNFTTestBase is LintJSON, TestUtils { font, address(0), address(0), + address(0), "https://party.app/party/" ); globalsAdmin.setGovernanceNftRendererAddress(address(nftRenderer)); diff --git a/test/utils/PartyGovernanceHelpers.t.sol b/test/utils/PartyGovernanceHelpers.t.sol index 70f7ab20f..b38c6c51f 100644 --- a/test/utils/PartyGovernanceHelpers.t.sol +++ b/test/utils/PartyGovernanceHelpers.t.sol @@ -44,6 +44,7 @@ contract PartyGovernanceHelpersTest is Test, TestUtils { IFont(address(0)), address(0), address(0), + address(0), "https://party.app/party/" ); globalsAdmin.setGovernanceNftRendererAddress(address(nftRenderer)); diff --git a/utils/deploy.ts b/utils/deploy.ts index 481289e66..3a9e39ac0 100644 --- a/utils/deploy.ts +++ b/utils/deploy.ts @@ -359,15 +359,7 @@ async function updateHeadJson(chain: string, releaseName: string) { async function main() { const chain = process.argv[2]; - const validChains = [ - "mainnet", - "goerli", - "base", - "base-goerli", - "zora", - "sepolia", - "base-sepolia", - ]; + const validChains = ["mainnet", "base", "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 108e8c2e0..ac82d7046 100644 --- a/utils/verify.ts +++ b/utils/verify.ts @@ -10,8 +10,6 @@ export const getBlockExplorerApiEndpoint = (chain: string) => { return "https://api.etherscan.io/api"; } else if (chain === "base") { return "https://api.basescan.org/api"; - } else if (chain === "base-goerli") { - 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") { @@ -216,12 +214,8 @@ const getContractNames = (chain: string, libraries: string[]) => { const getChainId = (chain: string) => { if (chain === "mainnet") { return 1; - } else if (chain === "goerli") { - return 5; } else if (chain === "base") { return 8453; - } else if (chain === "base-goerli") { - return 84531; } else if (chain === "zora") { return 7777777; } else if (chain === "sepolia") {