diff --git a/package-lock.json b/package-lock.json new file mode 100644 index 0000000..48e341a --- /dev/null +++ b/package-lock.json @@ -0,0 +1,3 @@ +{ + "lockfileVersion": 1 +} diff --git a/smart_contracts/common/errors.py b/smart_contracts/common/errors.py index 23bb79b..a4248ee 100644 --- a/smart_contracts/common/errors.py +++ b/smart_contracts/common/errors.py @@ -79,4 +79,7 @@ ERROR_NOT_OWNER = "NOT_OWNER" # The requested operation could not be completed because not enough value is vested -ERROR_NOT_VESTED = "NOT_VESTED" \ No newline at end of file +ERROR_NOT_VESTED = "NOT_VESTED" + +# A given parameter was an unacceptable value +ERROR_BAD_DAO_PARAM = "ERROR_BAD_DAO_PARAM" diff --git a/smart_contracts/dao.py b/smart_contracts/dao.py index 16675dc..645e470 100644 --- a/smart_contracts/dao.py +++ b/smart_contracts/dao.py @@ -489,6 +489,20 @@ def setParameters(self, newGovernanceParameters): # Only the DAO can change its own parameters. sp.verify(sp.sender == sp.self_address, Errors.ERROR_NOT_DAO) + # Validate that upper quorum cap is less than 100. Values greater than 100 are unachievable + # and will result in the DAO being unable to pass proposals. + # NOTE: Lower quorum cap can't be less than 0, because the Michelson `nat` type ensures numbers are + # always positive + sp.verify(newGovernanceParameters.quorumCap.upper <= 100, Errors.ERROR_BAD_DAO_PARAM) + + # Validate that percentages for super majority is less than 100. Values greater than + # 100 are unachievable and will result in the DAO being unable to pass proposals. + sp.verify(newGovernanceParameters.percentageForSuperMajority <= 100, Errors.ERROR_BAD_DAO_PARAM) + + # Validate that the percentage of yay votes for escrow return is less than 100. Values greater than + # 100 are unachievable, and will result in Escrow always being confiscated. + sp.verify(newGovernanceParameters.minYayVotesPercentForEscrowReturn <= 100, Errors.ERROR_BAD_DAO_PARAM) + # Update parameters. self.data.governanceParameters = newGovernanceParameters @@ -2867,4 +2881,179 @@ def test(): valid = False ) + @sp.add_test(name="setParameters - fails if upper quorum cap is above 100") + def test(): + scenario = sp.test_scenario() + + # Given governance parameters with a quorum cap greater than 100 + escrowAmount = sp.nat(10) + voteDelayBlocks = sp.nat(1) + voteLengthBlocks = sp.nat(10) + minYayVotesPercentForEscrowReturn = sp.nat(20) + blocksInTimelockForExecution = sp.nat(30) + blocksInTimelockForCancellation = sp.nat(40) + percentageForSuperMajority = sp.nat(80) + quorumCap = sp.record(lower = 1, upper = 99) + governanceParameters = sp.record( + escrowAmount = escrowAmount, + voteDelayBlocks = voteDelayBlocks, + voteLengthBlocks = voteLengthBlocks, + minYayVotesPercentForEscrowReturn = minYayVotesPercentForEscrowReturn, + blocksInTimelockForExecution = blocksInTimelockForExecution, + blocksInTimelockForCancellation = blocksInTimelockForCancellation, + percentageForSuperMajority = percentageForSuperMajority, + quorumCap = quorumCap + ) + + # AND a dao contract. + dao = DaoContract( + governanceParameters = governanceParameters, + ) + scenario += dao + + # WHEN the DAO contract tries to rotate the parameters to have a quorum cap + # that is above 100. + newQuorumCap = sp.record(lower = 2, upper = 105) + + newEscrowAmount = sp.nat(12) + newVoteDelayBlocks = sp.nat(2) + newVoteLengthBlocks = sp.nat(11) + newminYayVotesPercentForEscrowReturn = sp.nat(21) + newblocksInTimelockForExecution = sp.nat(31) + newblocksInTimelockForCancellation = sp.nat(41) + newPercentageForSuperMajority = sp.nat(81) + newGovernanceParameters = sp.record( + escrowAmount = newEscrowAmount, + voteDelayBlocks = newVoteDelayBlocks, + voteLengthBlocks = newVoteLengthBlocks, + minYayVotesPercentForEscrowReturn = newminYayVotesPercentForEscrowReturn, + blocksInTimelockForExecution = newblocksInTimelockForExecution, + blocksInTimelockForCancellation = newblocksInTimelockForCancellation, + percentageForSuperMajority = newPercentageForSuperMajority, + quorumCap = newQuorumCap + ) + + # THEN the call fails + scenario += dao.setParameters(newGovernanceParameters).run( + sender = dao.address, + valid = False + ) + + @sp.add_test(name="setParameters - fails if super majority is above 100") + def test(): + scenario = sp.test_scenario() + + # Given governance parameters with a quorum cap greater than 100 + escrowAmount = sp.nat(10) + voteDelayBlocks = sp.nat(1) + voteLengthBlocks = sp.nat(10) + minYayVotesPercentForEscrowReturn = sp.nat(20) + blocksInTimelockForExecution = sp.nat(30) + blocksInTimelockForCancellation = sp.nat(40) + percentageForSuperMajority = sp.nat(80) + quorumCap = sp.record(lower = 1, upper = 99) + governanceParameters = sp.record( + escrowAmount = escrowAmount, + voteDelayBlocks = voteDelayBlocks, + voteLengthBlocks = voteLengthBlocks, + minYayVotesPercentForEscrowReturn = minYayVotesPercentForEscrowReturn, + blocksInTimelockForExecution = blocksInTimelockForExecution, + blocksInTimelockForCancellation = blocksInTimelockForCancellation, + percentageForSuperMajority = percentageForSuperMajority, + quorumCap = quorumCap + ) + + # AND a dao contract. + dao = DaoContract( + governanceParameters = governanceParameters, + ) + scenario += dao + + # WHEN the DAO contract tries to rotate the parameters to have a super majority + # that is above 100. + newPercentageForSuperMajority = sp.nat(105) + + newEscrowAmount = sp.nat(12) + newVoteDelayBlocks = sp.nat(2) + newVoteLengthBlocks = sp.nat(11) + newminYayVotesPercentForEscrowReturn = sp.nat(21) + newblocksInTimelockForExecution = sp.nat(31) + newblocksInTimelockForCancellation = sp.nat(41) + newQuorumCap = sp.record(lower = 2, upper = 105) + newGovernanceParameters = sp.record( + escrowAmount = newEscrowAmount, + voteDelayBlocks = newVoteDelayBlocks, + voteLengthBlocks = newVoteLengthBlocks, + minYayVotesPercentForEscrowReturn = newminYayVotesPercentForEscrowReturn, + blocksInTimelockForExecution = newblocksInTimelockForExecution, + blocksInTimelockForCancellation = newblocksInTimelockForCancellation, + percentageForSuperMajority = newPercentageForSuperMajority, + quorumCap = newQuorumCap + ) + + # THEN the call fails + scenario += dao.setParameters(newGovernanceParameters).run( + sender = dao.address, + valid = False + ) + + @sp.add_test(name="setParameters - fails if min yay votes for escrow return is above 100") + def test(): + scenario = sp.test_scenario() + + # Given governance parameters with a quorum cap greater than 100 + escrowAmount = sp.nat(10) + voteDelayBlocks = sp.nat(1) + voteLengthBlocks = sp.nat(10) + minYayVotesPercentForEscrowReturn = sp.nat(20) + blocksInTimelockForExecution = sp.nat(30) + blocksInTimelockForCancellation = sp.nat(40) + percentageForSuperMajority = sp.nat(80) + quorumCap = sp.record(lower = 1, upper = 99) + governanceParameters = sp.record( + escrowAmount = escrowAmount, + voteDelayBlocks = voteDelayBlocks, + voteLengthBlocks = voteLengthBlocks, + minYayVotesPercentForEscrowReturn = minYayVotesPercentForEscrowReturn, + blocksInTimelockForExecution = blocksInTimelockForExecution, + blocksInTimelockForCancellation = blocksInTimelockForCancellation, + percentageForSuperMajority = percentageForSuperMajority, + quorumCap = quorumCap + ) + + # AND a dao contract. + dao = DaoContract( + governanceParameters = governanceParameters, + ) + scenario += dao + + # WHEN the DAO contract tries to rotate the parameters to have a min yay votes + # for escrow return that is above 100. + newminYayVotesPercentForEscrowReturn = sp.nat(105) + + newEscrowAmount = sp.nat(12) + newVoteDelayBlocks = sp.nat(2) + newVoteLengthBlocks = sp.nat(11) + newblocksInTimelockForExecution = sp.nat(31) + newblocksInTimelockForCancellation = sp.nat(41) + newPercentageForSuperMajority = sp.nat(105) + newQuorumCap = sp.record(lower = 2, upper = 105) + newGovernanceParameters = sp.record( + escrowAmount = newEscrowAmount, + voteDelayBlocks = newVoteDelayBlocks, + voteLengthBlocks = newVoteLengthBlocks, + minYayVotesPercentForEscrowReturn = newminYayVotesPercentForEscrowReturn, + blocksInTimelockForExecution = newblocksInTimelockForExecution, + blocksInTimelockForCancellation = newblocksInTimelockForCancellation, + percentageForSuperMajority = newPercentageForSuperMajority, + quorumCap = newQuorumCap + ) + + # THEN the call fails + scenario += dao.setParameters(newGovernanceParameters).run( + sender = dao.address, + valid = False + ) + + sp.add_compilation_target("dao", DaoContract()) diff --git a/smart_contracts/dao.tz b/smart_contracts/dao.tz index 689d2dc..bc069c0 100644 --- a/smart_contracts/dao.tz +++ b/smart_contracts/dao.tz @@ -773,6 +773,45 @@ code PUSH string "NOT_DAO"; # string : @parameter%setParameters : @storage FAILWITH; # FAILED }; # @parameter%setParameters : @storage + # sp.verify(params.quorumCap.upper <= 100, message = 'ERROR_BAD_DAO_PARAM') # @parameter%setParameters : @storage + DUP; # @parameter%setParameters : @parameter%setParameters : @storage + GET 16; # nat : @parameter%setParameters : @storage + PUSH nat 100; # nat : nat : @parameter%setParameters : @storage + SWAP; # nat : nat : @parameter%setParameters : @storage + COMPARE; # int : @parameter%setParameters : @storage + LE; # bool : @parameter%setParameters : @storage + IF + {} + { + PUSH string "ERROR_BAD_DAO_PARAM"; # string : @parameter%setParameters : @storage + FAILWITH; # FAILED + }; # @parameter%setParameters : @storage + # sp.verify(params.percentageForSuperMajority <= 100, message = 'ERROR_BAD_DAO_PARAM') # @parameter%setParameters : @storage + DUP; # @parameter%setParameters : @parameter%setParameters : @storage + GET 13; # nat : @parameter%setParameters : @storage + PUSH nat 100; # nat : nat : @parameter%setParameters : @storage + SWAP; # nat : nat : @parameter%setParameters : @storage + COMPARE; # int : @parameter%setParameters : @storage + LE; # bool : @parameter%setParameters : @storage + IF + {} + { + PUSH string "ERROR_BAD_DAO_PARAM"; # string : @parameter%setParameters : @storage + FAILWITH; # FAILED + }; # @parameter%setParameters : @storage + # sp.verify(params.minYayVotesPercentForEscrowReturn <= 100, message = 'ERROR_BAD_DAO_PARAM') # @parameter%setParameters : @storage + DUP; # @parameter%setParameters : @parameter%setParameters : @storage + GET 7; # nat : @parameter%setParameters : @storage + PUSH nat 100; # nat : nat : @parameter%setParameters : @storage + SWAP; # nat : nat : @parameter%setParameters : @storage + COMPARE; # int : @parameter%setParameters : @storage + LE; # bool : @parameter%setParameters : @storage + IF + {} + { + PUSH string "ERROR_BAD_DAO_PARAM"; # string : @parameter%setParameters : @storage + FAILWITH; # FAILED + }; # @parameter%setParameters : @storage SWAP; # @storage : @parameter%setParameters # self.data.governanceParameters = params # @storage : @parameter%setParameters UNPAIR; # pair (pair (address %communityFundAddress) (pair %governanceParameters (nat %escrowAmount) (pair (nat %voteDelayBlocks) (pair (nat %voteLengthBlocks) (pair (nat %minYayVotesPercentForEscrowReturn) (pair (nat %blocksInTimelockForExecution) (pair (nat %blocksInTimelockForCancellation) (pair (nat %percentageForSuperMajority) (pair %quorumCap (nat %lower) (nat %upper)))))))))) (pair (big_map %metadata string bytes) (pair (nat %nextProposalId) (big_map %outcomes nat (pair (nat %outcome) (pair %poll (nat %id) (pair (pair %proposal (string %title) (pair (string %descriptionLink) (pair (string %descriptionHash) (lambda %proposalLambda unit (list operation))))) (pair (nat %votingStartBlock) (pair (nat %votingEndBlock) (pair (nat %yayVotes) (pair (nat %nayVotes) (pair (nat %abstainVotes) (pair (nat %totalVotes) (pair (map %voters address (pair (nat %voteValue) (pair (nat %level) (nat %votes)))) (pair (address %author) (pair (nat %escrowAmount) (pair (nat %quorum) (pair %quorumCap (nat %lower) (nat %upper)))))))))))))))))) : pair (pair (option %poll (pair (nat %id) (pair (pair %proposal (string %title) (pair (string %descriptionLink) (pair (string %descriptionHash) (lambda %proposalLambda unit (list operation))))) (pair (nat %votingStartBlock) (pair (nat %votingEndBlock) (pair (nat %yayVotes) (pair (nat %nayVotes) (pair (nat %abstainVotes) (pair (nat %totalVotes) (pair (map %voters address (pair (nat %voteValue) (pair (nat %level) (nat %votes)))) (pair (address %author) (pair (nat %escrowAmount) (pair (nat %quorum) (pair %quorumCap (nat %lower) (nat %upper))))))))))))))) (pair (nat %quorum) (nat %state))) (pair (option %timelockItem (pair (nat %id) (pair (pair %proposal (string %title) (pair (string %descriptionLink) (pair (string %descriptionHash) (lambda %proposalLambda unit (list operation))))) (pair (nat %endBlock) (pair (nat %cancelBlock) (address %author)))))) (pair (address %tokenContractAddress) (option %votingState (pair (nat %voteValue) (pair (address %address) (nat %level)))))) : @parameter%setParameters