Skip to content
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

Merged
merged 24 commits into from
Dec 14, 2024

Conversation

RickGriff
Copy link
Contributor

@RickGriff RickGriff commented Dec 4, 2024

TODO:

  • Fix unit tests so they run!
  • Fix merge conflicts
  • "Custom precision" has not been fixed - AFAICS, can just be removed now

int88[] memory _absoluteLQTYVotes,
int88[] memory absoluteLQTYVetos
int256[] memory _absoluteLQTYVotes,
int256[] memory absoluteLQTYVetos
Copy link
Contributor

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?

Copy link
Contributor Author

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)

UserState memory userState = userStates[msg.sender];
require(userState.allocatedLQTY == 0, "Governance: must-be-zero-allocation");
require(userState.unallocatedLQTY == 0, "Governance: must-be-zero-allocation");
Copy link
Contributor

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.

Copy link
Contributor Author

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.

@@ -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,
Copy link
Contributor

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 😉

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sure!

// 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);
Copy link
Contributor

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 uints, we’ll also get rid of this add here.

Copy link
Contributor Author

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.

@bingen bingen mentioned this pull request Dec 11, 2024
uint256 scaledEpochEnd = (
governance.EPOCH_START() + _epoch * governance.EPOCH_DURATION()
) * TIMESTAMP_PRECISION;
uint256 scaledEpochEnd = governance.EPOCH_START() + _epoch * governance.EPOCH_DURATION();
Copy link
Contributor

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;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nice simplification!

Copy link
Contributor

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.

Copy link
Contributor Author

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);
Copy link
Contributor

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?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes - done

Copy link
Contributor

@bingen bingen left a 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);
Copy link
Contributor

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);)

Copy link
Contributor

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);
Copy link
Contributor

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) {
Copy link
Contributor

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.

Copy link
Contributor Author

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;
Copy link
Contributor

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.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done!

@RickGriff RickGriff force-pushed the refactor_voting_power branch from 2d088c7 to d6cf089 Compare December 12, 2024 16:06
@RickGriff RickGriff force-pushed the refactor_voting_power branch from 14549ee to 3ac5be9 Compare December 14, 2024 15:55
@bingen bingen merged commit d11e15a into main Dec 14, 2024
3 checks passed
@danielattilasimon danielattilasimon deleted the refactor_voting_power branch January 19, 2025 04:41
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants