-
Notifications
You must be signed in to change notification settings - Fork 9
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Voting power refactor with offsets (y-intercept approach) #97
Conversation
int88[] memory _absoluteLQTYVotes, | ||
int88[] memory absoluteLQTYVetos | ||
int256[] memory _absoluteLQTYVotes, | ||
int256[] memory absoluteLQTYVetos |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Can’t these be unsigned ints?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Hmm yes. We'd need different conversion later on though, and I just kept everything the same on this front (including the requireNoNegatives
check, which I suppose is silly for these quantities, but it was the original logic)
src/Governance.sol
Outdated
UserState memory userState = userStates[msg.sender]; | ||
require(userState.allocatedLQTY == 0, "Governance: must-be-zero-allocation"); | ||
require(userState.unallocatedLQTY == 0, "Governance: must-be-zero-allocation"); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I don’t think I understand this change.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Right, this was a typo - fixed.
src/Governance.sol
Outdated
@@ -739,93 +722,65 @@ contract Governance is Multicall, UserProxyFactory, ReentrancyGuard, Ownable, IG | |||
// deep copy of the initiative's state before the allocation | |||
InitiativeState memory prevInitiativeState = InitiativeState( | |||
initiativeState.voteLQTY, | |||
initiativeState.voteOffset, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We can pass a forge fmt
at the end 😉
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Sure!
src/Governance.sol
Outdated
// allocate the voting and vetoing LQTY to the initiative | ||
initiativeState.voteLQTY = add(initiativeState.voteLQTY, deltaLQTYVotes); | ||
initiativeState.vetoLQTY = add(initiativeState.vetoLQTY, deltaLQTYVetos); | ||
|
||
// Update the initiative's vote and veto offsets | ||
initiativeState.voteOffset = add(initiativeState.voteOffset, deltaOffsetVotes); | ||
initiativeState.vetoOffset = add(initiativeState.vetoOffset, deltaOffsetVetos); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If we can make all uint
s, we’ll also get rid of this add
here.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The deltas can be positive or negative though, since this internal function _allocateLQTY
is used twice: first to deallocate everything to 0, secondly to reallocate to the chosen allocations. So the deltas are ints.
src/BribeInitiative.sol
Outdated
uint256 scaledEpochEnd = ( | ||
governance.EPOCH_START() + _epoch * governance.EPOCH_DURATION() | ||
) * TIMESTAMP_PRECISION; | ||
uint256 scaledEpochEnd = governance.EPOCH_START() + _epoch * governance.EPOCH_DURATION(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
No big deal, but maybe we can get rid of the scaled
part of the name?
@@ -452,34 +448,19 @@ contract Governance is Multicall, UserProxyFactory, ReentrancyGuard, Ownable, IG | |||
// == Rewards Conditions (votes can be zero, logic is the same) == // | |||
|
|||
// By definition if _votesForInitiativeSnapshot.votes > 0 then _votesSnapshot.votes > 0 | |||
if (_votesForInitiativeSnapshot.votes > votingTheshold && _votesForInitiativeSnapshot.votes > _votesForInitiativeSnapshot.vetos) | |||
{ | |||
uint256 claim = _votesForInitiativeSnapshot.votes * boldAccrued / _votesSnapshot.votes; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Nice simplification!
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Did we have a test case for what this custom precision was trying to solve? Are we sure we are not re-introducing any important rounding error?
Hopefully not thanks to the y-intercept refactor, because code looks much better now.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Good question. Will ask Alex which test exactly is relevant here.
@@ -99,28 +97,18 @@ contract BribeInitiative is IInitiative, IBribeInitiative { | |||
"BribeInitiative: invalid-prev-total-lqty-allocation-epoch" | |||
); | |||
|
|||
(uint88 totalLQTY, uint120 totalAverageTimestamp) = _decodeLQTYAllocation(totalLQTYAllocation.value); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Can we remove now utils/EncodingDecodingLib.sol
?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes - done
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I left some minor comments inline, but this looks very good to me.
Maybe the most important one is the one about int -> uint. I think it would make code nicer, specially for those “absolute” values, where it makes little sense to allow for negatives.
) * uint120(TIMESTAMP_PRECISION); | ||
|
||
/// @audit User Invariant | ||
assert(totalAverageTimestamp <= scaledEpochEnd); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Not sure if needed, or if we should move it to invariant tests, but I guess we could translate this into something like:
assert(totalLQTYAllocation.lqty * scaledEpochEnd - totalLQTYAllocation.offset >= 0);
(which is equivalent to assert(governance.lqtyToVotes(totalLQTYAllocation.lqty, scaledEpochEnd, totalLQTYAllocation.offset) >= 0);
)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@danielattilasimon do you think we could add this to invariant tests? So we don’t pollute contracts with asserts?
|
||
/// @audit Governance Invariant | ||
assert(averageTimestamp <= scaledEpochEnd); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Same as previous assert comment.
// check that user has reset before changing lqty balance | ||
UserState storage userState = userStates[msg.sender]; | ||
require(userState.allocatedLQTY == 0, "Governance: must-allocate-zero"); | ||
|
||
UserProxy userProxy = UserProxy(payable(deriveUserProxyAddress(msg.sender))); | ||
require(address(userProxy).code.length != 0, "Governance: user-proxy-not-deployed"); | ||
|
||
uint88 lqtyStaked = uint88(stakingV1.stakes(address(userProxy))); | ||
// Update the offset tracker | ||
if (_lqtyAmount < userState.unallocatedLQTY) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Are we checking that the input parameter is <= unallocatedLQTY
? Otherwise it would revert below when trying to subtract.
Maybe we can update userState.unallocatedLQTY
inside the if
clause, zeroing it in the else
branch, so we would be effectively clamping the input amount to the max.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Right, but if they try to withdraw more than their unallocatedLQTY, isn't revert the right behavior? But I'm fine to clamp to the max too
@@ -542,17 +484,20 @@ contract Governance is Multicall, UserProxyFactory, ReentrancyGuard, Ownable, IG | |||
// an initiative can be registered if the registrant has more voting power (LQTY * age) | |||
// than the registration threshold derived from the previous epoch's total global votes | |||
|
|||
uint256 upscaledSnapshotVotes = uint256(snapshot.votes); | |||
uint256 upscaledSnapshotVotes = snapshot.votes; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Maybe we can get rid of the upscale
part of the name.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done!
2d088c7
to
d6cf089
Compare
It was happening when evaluating a user's voting power at a timestamp _before_ the first time they staked.
14549ee
to
3ac5be9
Compare
uint256
have been converted touint256
int256
converted toint256
https://docs.google.com/document/d/1nPdD-w1n_0KIzAgi3Y5c8h2t2wkFiFD5YDHKsc6KxH8/edit?usp=sharing
Here's an example (see final allocate->deallocate actions by B for path independence):
https://docs.google.com/spreadsheets/d/1MSOsrIR-3qIwKPBlXkUTsQcfg7wwjfegCW8VD7QZWn8/edit?usp=sharing
TODO: