From 3a1303279f46e3642c6be3e137467b0183008f8d Mon Sep 17 00:00:00 2001 From: rocketman <20303031+rocketman-21@users.noreply.github.com> Date: Mon, 1 Apr 2024 18:46:00 -0700 Subject: [PATCH] BaseContest improvements (#148) * let contest receive funds * add payout splits helper functions * add start time (does nothing) to contest * test for timing * Update .storage-layout * new contest builder deployer --- .changeset/chilly-bats-dance.md | 5 +++ packages/revolution/.storage-layout | 17 ++++---- .../revolution/deploys/contests/84532.txt | 10 ++--- .../contests/DeployContestBuilder.s.sol | 5 +++ .../culture-index/contests/BaseContest.sol | 24 +++++++++++ .../culture-index/contests/IBaseContest.sol | 10 +++++ .../test/contests/ContestBuilder.t.sol | 5 ++- .../test/contests/ContestOwnerControl.t.sol | 2 +- .../test/contests/ContestPayouts.t.sol | 10 ++--- .../test/contests/ContestsCreation.t.sol | 41 ++++++++++++++++++- 10 files changed, 108 insertions(+), 21 deletions(-) create mode 100644 .changeset/chilly-bats-dance.md diff --git a/.changeset/chilly-bats-dance.md b/.changeset/chilly-bats-dance.md new file mode 100644 index 00000000..5d080e45 --- /dev/null +++ b/.changeset/chilly-bats-dance.md @@ -0,0 +1,5 @@ +--- +"@cobuild/revolution": patch +--- + +Make BaseContest payable and add payout splits helper functions diff --git a/packages/revolution/.storage-layout b/packages/revolution/.storage-layout index bd035cd8..7077b515 100644 --- a/packages/revolution/.storage-layout +++ b/packages/revolution/.storage-layout @@ -183,14 +183,15 @@ | WETH | address | 0 | 0 | 20 | src/culture-index/contests/BaseContest.sol:BaseContest | | entropyRate | uint256 | 1 | 0 | 32 | src/culture-index/contests/BaseContest.sol:BaseContest | | builderReward | address | 2 | 0 | 20 | src/culture-index/contests/BaseContest.sol:BaseContest | -| endTime | uint256 | 3 | 0 | 32 | src/culture-index/contests/BaseContest.sol:BaseContest | -| paidOut | bool | 4 | 0 | 1 | src/culture-index/contests/BaseContest.sol:BaseContest | -| payoutIndex | uint256 | 5 | 0 | 32 | src/culture-index/contests/BaseContest.sol:BaseContest | -| initialPayoutBalance | uint256 | 6 | 0 | 32 | src/culture-index/contests/BaseContest.sol:BaseContest | -| splitMain | contract ISplitMain | 7 | 0 | 20 | src/culture-index/contests/BaseContest.sol:BaseContest | -| cultureIndex | contract ICultureIndex | 8 | 0 | 20 | src/culture-index/contests/BaseContest.sol:BaseContest | -| payoutSplits | uint256[] | 9 | 0 | 32 | src/culture-index/contests/BaseContest.sol:BaseContest | -| payoutSplitAccounts | mapping(uint256 => address) | 10 | 0 | 32 | src/culture-index/contests/BaseContest.sol:BaseContest | +| startTime | uint256 | 3 | 0 | 32 | src/culture-index/contests/BaseContest.sol:BaseContest | +| endTime | uint256 | 4 | 0 | 32 | src/culture-index/contests/BaseContest.sol:BaseContest | +| paidOut | bool | 5 | 0 | 1 | src/culture-index/contests/BaseContest.sol:BaseContest | +| payoutIndex | uint256 | 6 | 0 | 32 | src/culture-index/contests/BaseContest.sol:BaseContest | +| initialPayoutBalance | uint256 | 7 | 0 | 32 | src/culture-index/contests/BaseContest.sol:BaseContest | +| splitMain | contract ISplitMain | 8 | 0 | 20 | src/culture-index/contests/BaseContest.sol:BaseContest | +| cultureIndex | contract ICultureIndex | 9 | 0 | 20 | src/culture-index/contests/BaseContest.sol:BaseContest | +| payoutSplits | uint256[] | 10 | 0 | 32 | src/culture-index/contests/BaseContest.sol:BaseContest | +| payoutSplitAccounts | mapping(uint256 => address) | 11 | 0 | 32 | src/culture-index/contests/BaseContest.sol:BaseContest | ======================= ➡ ContestBuilder diff --git a/packages/revolution/deploys/contests/84532.txt b/packages/revolution/deploys/contests/84532.txt index 11105d81..80f9034e 100644 --- a/packages/revolution/deploys/contests/84532.txt +++ b/packages/revolution/deploys/contests/84532.txt @@ -1,5 +1,5 @@ -Contest Builder: 0xd6e6635bb9dfd3a4bad698ea554bfbb373ad0fa6 -Contest Builder implementation: 0xb50506c979bac7bcda3d27998e1c2faccc8942e0 -Culture Index implementation: 0xa47e3accb7b271dc135e7490cf6dcd9de243499e -Max Heap implementation: 0xdfe840daddc5f00f5aaefd591938ad2cb90ef54a -Base Contest implementation: 0x1d3f513b0d4b9b6c0dbabdcfc74c392e5f8d1fd9 +Contest Builder: 0x05a3910e27720aa220ddbcea8ab3fcdb2c66b768 +Contest Builder implementation: 0xabbcb17afe77c694ec6739f0368f9b247da9963a +Culture Index implementation: 0x48121ad0212ee5c1b4885247910676a4ee5a500c +Max Heap implementation: 0x7582440dcf3e03c3d79a58bce1e99558125024f6 +Base Contest implementation: 0x60f65632e4a448e33a07a4fa4b03f6daf5ec51e3 diff --git a/packages/revolution/script/contests/DeployContestBuilder.s.sol b/packages/revolution/script/contests/DeployContestBuilder.s.sol index b1b94303..8e937e11 100644 --- a/packages/revolution/script/contests/DeployContestBuilder.s.sol +++ b/packages/revolution/script/contests/DeployContestBuilder.s.sol @@ -127,6 +127,11 @@ contract DeployContestBuilder is Script { abi.encodePacked("Base Contest implementation: ", addressToString(deployedContracts.baseContestImpl)) ) ); + // write protocol rewards address + vm.writeLine( + filePath, + string(abi.encodePacked("Protocol Rewards: ", addressToString(vm.envAddress("PROTOCOL_REWARDS")))) + ); console2.log("~~~~~~~~~~ MANAGER IMPL 0 ~~~~~~~~~~~"); console2.logAddress(deployedContracts.contestBuilderImpl0); diff --git a/packages/revolution/src/culture-index/contests/BaseContest.sol b/packages/revolution/src/culture-index/contests/BaseContest.sol index a3986b0b..3a04fc1d 100644 --- a/packages/revolution/src/culture-index/contests/BaseContest.sol +++ b/packages/revolution/src/culture-index/contests/BaseContest.sol @@ -56,6 +56,9 @@ contract BaseContest is // The address of th account to receive builder rewards address public builderReward; + // The start time of the contest + uint256 public startTime; + // The end time of the contest uint256 public endTime; @@ -158,6 +161,7 @@ contract BaseContest is // set creator payout params entropyRate = _baseContestParams.entropyRate; + startTime = _baseContestParams.startTime; endTime = _baseContestParams.endTime; payoutSplits = _baseContestParams.payoutSplits; } @@ -319,6 +323,22 @@ contract BaseContest is return cultureIndex.topVotedPieceMeetsQuorum(); } + /** + * @notice Returns the payout splits of the contest + * @return The payout splits of the contest + */ + function getPayoutSplits() external view returns (uint256[] memory) { + return payoutSplits; + } + + /** + * @notice Returns the payout splits count + * @return The payout splits count + */ + function getPayoutSplitsCount() external view returns (uint256) { + return payoutSplits.length; + } + /** * @notice Pay out the contest winners * @param _payoutCount The number of winners to pay out. Needs to be adjusted based on gas requirements. @@ -383,6 +403,10 @@ contract BaseContest is } } + receive() external payable {} + + fallback() external payable {} + /// /// /// BASE CONTEST UPGRADE /// /// /// diff --git a/packages/revolution/src/culture-index/contests/IBaseContest.sol b/packages/revolution/src/culture-index/contests/IBaseContest.sol index f146cf55..fa7069a4 100644 --- a/packages/revolution/src/culture-index/contests/IBaseContest.sol +++ b/packages/revolution/src/culture-index/contests/IBaseContest.sol @@ -80,12 +80,22 @@ interface IBaseContest is IBaseContestEvents { function pause() external; + function getPayoutSplits() external view returns (uint256[] memory); + + function getPayoutSplitsCount() external view returns (uint256); + + function startTime() external view returns (uint256); + + function endTime() external view returns (uint256); + /// @notice The contest parameters /// @param entropyRate The entropy rate of each contest - the portion of the creator's share that is directly sent to the creator in ETH /// @param endTime The end time of the contest. + /// @param startTime The start time of the contest. /// @param payoutSplits How to split the prize pool between the winners struct BaseContestParams { uint256 entropyRate; + uint256 startTime; uint256 endTime; uint256[] payoutSplits; } diff --git a/packages/revolution/test/contests/ContestBuilder.t.sol b/packages/revolution/test/contests/ContestBuilder.t.sol index 53e39d48..1769b958 100644 --- a/packages/revolution/test/contests/ContestBuilder.t.sol +++ b/packages/revolution/test/contests/ContestBuilder.t.sol @@ -136,6 +136,7 @@ contract ContestBuilderTest is RevolutionBuilderTest { payoutSplits[0] = 1e6; baseContestParams = IBaseContest.BaseContestParams({ entropyRate: 100, + startTime: block.timestamp, // 1 week endTime: block.timestamp + 60 * 60 * 24 * 7, payoutSplits: payoutSplits @@ -144,11 +145,13 @@ contract ContestBuilderTest is RevolutionBuilderTest { function setBaseContestParams( uint256 _entropyRate, + uint256 _startTime, uint256 _endTime, uint256[] memory _payoutSplits ) internal virtual { baseContestParams = IBaseContest.BaseContestParams({ entropyRate: _entropyRate, + startTime: _startTime, endTime: _endTime, payoutSplits: _payoutSplits }); @@ -191,7 +194,7 @@ contract ContestBuilderTest is RevolutionBuilderTest { _baseContestParams ); - baseContest = BaseContest(baseContestAddr); + baseContest = BaseContest(payable(baseContestAddr)); contest_CultureIndex = CultureIndex(address(baseContest.cultureIndex())); vm.label(address(baseContest), "BASE_CONTEST"); diff --git a/packages/revolution/test/contests/ContestOwnerControl.t.sol b/packages/revolution/test/contests/ContestOwnerControl.t.sol index acbe9051..5e060a05 100644 --- a/packages/revolution/test/contests/ContestOwnerControl.t.sol +++ b/packages/revolution/test/contests/ContestOwnerControl.t.sol @@ -42,7 +42,7 @@ contract ContestOwnerControl is ContestBuilderTest { ); // ensure founder can set entropyRateBps and that it is updated - BaseContest baseContest = BaseContest(contest); + BaseContest baseContest = BaseContest(payable(contest)); uint256 newEntropyRate = 51000; // Example new entropy rate to test with // Ensure only the owner can set the entropy rate diff --git a/packages/revolution/test/contests/ContestPayouts.t.sol b/packages/revolution/test/contests/ContestPayouts.t.sol index be3df823..8e3730b3 100644 --- a/packages/revolution/test/contests/ContestPayouts.t.sol +++ b/packages/revolution/test/contests/ContestPayouts.t.sol @@ -111,7 +111,7 @@ contract ContestOwnerControl is ContestBuilderTest { payoutSplits[1] = 300000; // 30% payoutSplits[2] = 200000; // 20% - super.setBaseContestParams(500000, block.timestamp + 60 * 60 * 24 * 7, payoutSplits); + super.setBaseContestParams(500000, block.timestamp, block.timestamp + 60 * 60 * 24 * 7, payoutSplits); super.deployContestMock(); vm.stopPrank(); @@ -174,7 +174,7 @@ contract ContestOwnerControl is ContestBuilderTest { payoutSplits[1] = 300000; // 30% payoutSplits[2] = 200000; // 20% - super.setBaseContestParams(500000, block.timestamp + 60 * 60 * 24 * 7, payoutSplits); + super.setBaseContestParams(500000, block.timestamp, block.timestamp + 60 * 60 * 24 * 7, payoutSplits); super.deployContestMock(); vm.stopPrank(); @@ -323,7 +323,7 @@ contract ContestOwnerControl is ContestBuilderTest { payoutSplits[1] = 300000; // 30% payoutSplits[2] = 200000; // 20% - super.setBaseContestParams(500000, block.timestamp + 60 * 60 * 24 * 7, payoutSplits); + super.setBaseContestParams(500000, block.timestamp, block.timestamp + 60 * 60 * 24 * 7, payoutSplits); super.deployContestMock(); vm.stopPrank(); @@ -358,7 +358,7 @@ contract ContestOwnerControl is ContestBuilderTest { // Scaled by 1e6 payoutSplits[0] = 1e6; // 50% - super.setBaseContestParams(500000, block.timestamp + 60 * 60 * 24 * 7, payoutSplits); + super.setBaseContestParams(500000, block.timestamp, block.timestamp + 60 * 60 * 24 * 7, payoutSplits); super.deployContestMock(); vm.stopPrank(); @@ -421,7 +421,7 @@ contract ContestOwnerControl is ContestBuilderTest { payoutSplits[i] = 100000; // 10% for each } - super.setBaseContestParams(500000, block.timestamp + 60 * 60 * 24 * 7, payoutSplits); + super.setBaseContestParams(500000, block.timestamp, block.timestamp + 60 * 60 * 24 * 7, payoutSplits); super.deployContestMock(); diff --git a/packages/revolution/test/contests/ContestsCreation.t.sol b/packages/revolution/test/contests/ContestsCreation.t.sol index 1e9d0783..01490b8a 100644 --- a/packages/revolution/test/contests/ContestsCreation.t.sol +++ b/packages/revolution/test/contests/ContestsCreation.t.sol @@ -22,6 +22,22 @@ contract ContestsCreationTest is ContestBuilderTest { super.deployContestMock(); } + /** + * @dev Ensure the contest can be sent funds + */ + function test__SendFundsToContest() public { + // Send funds to the contest + uint256 amount = 1 ether; + vm.deal(address(this), amount); + vm.prank(address(this)); + payable(address(baseContest)).call{ value: amount }(new bytes(0)); + + // Verify the balance of the contest + uint256 expectedBalance = amount; + uint256 actualBalance = address(baseContest).balance; + assertEq(actualBalance, expectedBalance, "Balance mismatch"); + } + /** * @dev Use the builder to create a contest and test the fields */ @@ -38,7 +54,7 @@ contract ContestsCreationTest is ContestBuilderTest { ); // verify contest fields - BaseContest baseContest = BaseContest(contest); + BaseContest baseContest = BaseContest(payable(contest)); assertEq(baseContest.owner(), founder, "Owner mismatch"); assertEq(baseContest.WETH(), weth, "WETH mismatch"); assertEq(address(baseContest.splitMain()), address(splitMain), "Split main mismatch"); @@ -82,6 +98,23 @@ contract ContestsCreationTest is ContestBuilderTest { contest_CultureIndexParams.description, "CultureIndex description mismatch" ); + + // ensure start time is set + uint256 expectedStartTime = baseContestParams.startTime; + uint256 actualStartTime = baseContest.startTime(); + assertEq(actualStartTime, expectedStartTime, "Start time mismatch"); + + // ensure getPayoutSplitsCount returns the correct value + uint256 expectedPayoutSplitsCount = baseContestParams.payoutSplits.length; + uint256 actualPayoutSplitsCount = baseContest.getPayoutSplitsCount(); + assertEq(actualPayoutSplitsCount, expectedPayoutSplitsCount, "Payout splits count mismatch"); + + // ensure getPayoutSplits returns the correct values + uint256[] memory expectedPayoutSplits = baseContestParams.payoutSplits; + uint256[] memory actualPayoutSplits = baseContest.getPayoutSplits(); + for (uint256 i = 0; i < expectedPayoutSplits.length; i++) { + assertEq(actualPayoutSplits[i], expectedPayoutSplits[i], "Payout splits mismatch at index"); + } } /** @@ -107,6 +140,12 @@ contract ContestsCreationTest is ContestBuilderTest { uint256 expectedEndTime = baseContestParams.endTime; uint256 actualEndTime = baseContest.endTime(); assertTrue(actualEndTime == expectedEndTime, "End time mismatch"); + + // Verify the startTime of the deployed contest + uint256 expectedStartTime = baseContestParams.startTime; + uint256 actualStartTime = baseContest.startTime(); + assertTrue(actualStartTime == expectedStartTime, "Start time mismatch"); + // Verify the payoutSplits of the deployed contest uint256[] memory expectedPayoutSplits = baseContestParams.payoutSplits; for (uint256 i = 0; i < expectedPayoutSplits.length; i++) {