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

Code review via comments for YieldDistributor.sol #65

Closed
wants to merge 1 commit into from

Conversation

ShaNagbaImuru
Copy link

No description provided.

- Even better IMO would be a `mapping(address => uint256)` to track distributions per project.
That would allow a voting interface that passes votes by address rather than array index, which
is pretty hard to debug after the fact (e.g. through a chain explorer). The current setup is more
vulnerable to either of an evil frontend or MEV. Neither of those is a proximate issue, but still.
Copy link
Author

Choose a reason for hiding this comment

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

It occurs to me that more probable than an evil frontend is a bug in the real frontend that causes the displayed project order to mismatch with the contract project order. Protecting against that seems worthwhile.

Comment on lines +135 to +136
- This returns the voting power based on the last period, which is intended but may be
surprising. It would be good to comment this behavior.
Copy link
Collaborator

Choose a reason for hiding this comment

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

Voting power at the time of distribution required us to iterate over all the voters, introducing an attack vector. This prompted us to record voting power at the time of voting and not distribution. This would, however, introduce an advantage to "late voters", so we opted to calculate it at a set delay.

Copy link
Author

Choose a reason for hiding this comment

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

Makes sense, just think it deserves a comment.

Comment on lines +139 to +140
choice it should be explicitly called out, but I think it might make more sense to track the
last two distribution blocks, rather than just `lastClaimedBlockNumber` here.
Copy link
Collaborator

Choose a reason for hiding this comment

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

I do not understand, the suggestion is to define an interval as a tuple of two blocks, rather than using lastClaimedBlockNumber? What are the benefits in this approach?

Copy link
Author

Choose a reason for hiding this comment

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

The only benefit is that it will measure the power accumulated over the previous period between distributions, rather than the cycleLength before the previous distribution. I believe that in normal operation, these are expected to be very close. There is only a difference when distributions are not occurring on a cycleLength candence.

Consider the following scenario:

cycleLength == 100
Block 100: distribution
Block 200: no distribution (because the mechanism is broken or for some other reason)
Block 300: distribution
Block 400: distribution

IIUC, the distribution in block 400 will use the voting power accumulated between blocks 200 and 300. I'm proposing that it may be more fair to use the voting power accumulated between blocks 100 and 300 in this scenario.

Copy link
Collaborator

Choose a reason for hiding this comment

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

@bagelface thoughts?

Copy link
Contributor

Choose a reason for hiding this comment

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

I agree with @ShaNagbaImuru on this. cycleLength would be more accurately considered the "minimum cycle length" before which a distribution can't happen. Assuming the suggested changes don't have any major tradeoffs, I think it makes for a better algorithm.

src/YieldDistributor.sol Show resolved Hide resolved
@@ -236,6 +259,8 @@ contract YieldDistributor is OwnableUpgradeable {
/**
* @notice Internal function for casting votes for a specified user
* @param _account Address of user to cast votes for
* /* Unless I'm missing something, calling these basis points is pretty confusing, since that term
* usually refers to 1/100th of a percent, which these are not.
Copy link
Collaborator

Choose a reason for hiding this comment

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

these points can be up to 10000, does that make sense?

Copy link
Author

Choose a reason for hiding this comment

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

That's param-controlled and changeable, right? So they happen to be basis points currently but that is contingent. That does explain it though.

Copy link
Collaborator

@RonTuretzky RonTuretzky left a comment

Choose a reason for hiding this comment

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

Added documentation suggestions to #36 , opened #81 #82 for implementation change suggestions, awaiting feedback for L191-L193 and then will close PR

@ShaNagbaImuru

Comment on lines +191 to +193
/* It really feels like L170-190 could be unified into a single loop rather than a separate
step for calculating just the first chunk of voting power.
*/
Copy link
Collaborator

Choose a reason for hiding this comment

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

Suggestion re: this

function getVotingPowerForPeriod(uint256 _start, uint256 _end, address _account) external view returns (uint256) {
    if (_start >= _end) revert StartMustBeBeforeEnd();
    if (_end > block.number) revert EndAfterCurrentBlock();

    uint32 checkpointCount = BREAD.numCheckpoints(_account);
    if (checkpointCount == 0) return 0;

    Checkpoints.Checkpoint208 memory checkpoint = BREAD.checkpoints(_account, 0);
    if (checkpoint._key > _end) return 0;

    uint256 totalVotingPower = 0;
    for (uint32 i = checkpointCount; i > 0;) {
        checkpoint = BREAD.checkpoints(_account, --i);

        if (checkpoint._key <= _end) {
            uint48 effectiveStart = checkpoint._key < _start ? uint48(_start) : checkpoint._key;
            totalVotingPower += checkpoint._value * (_end - effectiveStart);

            if (checkpoint._key <= _start) {
                totalVotingPower -= checkpoint._value * (_start - checkpoint._key);
                break;
            }
            _end = checkpoint._key;
        }
    }

    return totalVotingPower;
}

Thoughts @ShaNagbaImuru ?

Copy link
Author

Choose a reason for hiding this comment

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

  1. It might be cleaner to use a continue when the checkpoint._key > end, to reduce the indent required.
  2. I don't think I understand why you need both of calculating and using effectiveStart and subtracting voting power when checkpoint._key <= _start. That seems redundant unless I'm missing something. (If it is redundant, I prefer the first approach for clarity.)
  3. I think there's probably already unit testing for this code, but if not there should be, to ensure that this change in logic doesn't change the results.

Copy link
Collaborator

Choose a reason for hiding this comment

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

Ok, opened #84 for this, will get back in touch regarding this

@RonTuretzky
Copy link
Collaborator

We have opened issues #84 #81 #82 and have included things in #36 to address all suggestions brought up in this pr, closing as they will be addressed in those issues

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