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

bonding: Implement active stake checkpointing #614

Merged
merged 17 commits into from
Aug 25, 2023

Conversation

victorges
Copy link
Member

@victorges victorges commented Jun 17, 2023

What does this pull request do? Explain your changes. (required)
This is to introduce a checkpointing logic in bonding manager so that we can lookup historical information about any point in time. We need this in order to support an on-chain governance system with support for OpenZeppelin Governor abstractions. That is why it also implements the IERC5808 interface to plug into OZ Governor framework.

It is implemented as "external" to BondingManager as possible in a separate BondingVotes contract, but there were a couple places where BondingManager needed changes to start checkpointing its state.

Specific updates (required)

  • Implement historical active stake snapshotting logic in the BondingVotes contract
  • Make BondingManager call it to checkpoint the state where it needs to
  • Tests for the checkpointing logic (unit / integration / fork)

How did you test each of these updates (required)

  • Unit and integration tests with yarn test
  • Fork test with forge
  • Devnet on Goerli

Does this pull request close any open issues?
Implements PRO-28

Checklist:

  • README and other documentation updated
  • All tests using yarn test pass

@victorges victorges force-pushed the vg/feat/bonding-checkpoints branch 6 times, most recently from b6de577 to 91547a8 Compare June 17, 2023 01:28
@victorges victorges force-pushed the vg/feat/bonding-checkpoints branch 2 times, most recently from b82f36d to 3b49303 Compare June 20, 2023 15:29
@victorges victorges marked this pull request as ready for review June 20, 2023 16:48
@victorges victorges marked this pull request as draft June 28, 2023 23:11
Will be required for new checkpoints code and later governor
implementation.
Used for checkpointing logic in bonding state checkpoints
Handles historic checkpointing ("snapshotting") and lookup
of the bonding state.
@victorges victorges force-pushed the vg/feat/bonding-checkpoints branch 2 times, most recently from 4915ecb to 09a2d51 Compare July 13, 2023 05:02
 - unit tests
 - integration tests
 - gas-report
 - fork test for upgrade
@victorges victorges force-pushed the vg/feat/bonding-checkpoints branch from 09a2d51 to 078f348 Compare July 14, 2023 06:48
@victorges victorges marked this pull request as ready for review July 14, 2023 06:48
@victorges
Copy link
Member Author

@yondonfu @0xcadams Ok I think NOW this one is ready to go! :)

@victorges victorges force-pushed the vg/feat/bonding-checkpoints branch from bc23af0 to 22ee2db Compare July 17, 2023 03:18
This will provide a cleaner experience using governance
tools, since users that don't have any stake won't get
errors when querying their voting power. For users that
do have stake, we will make sure to checkpoint them on
first deploy.
@victorges
Copy link
Member Author

victorges commented Jul 17, 2023

Ran the gas report on confluence branch (current) compared to this (checkpoints). Here are the reports, it seems to have increased around 200k gas on the bond/unbond functions due to the checkpoints that have been made. The checkpoint initialization itself also costs around 170k gas, which should still be relatively cheap on Arbitrum, but I haven't found a way to reliably calculate how it would cost considering the calldata cost on L1.

gas-report-checkpoints.txt
gas-report-current.txt

@victorges victorges mentioned this pull request Jul 20, 2023
2 tasks
Copy link
Contributor

@0xcadams 0xcadams left a comment

Choose a reason for hiding this comment

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

Preliminary review of code quality & gas improvements, no deep checks of logic

contracts/bonding/BondingManager.sol Outdated Show resolved Hide resolved
contracts/bonding/BondingCheckpoints.sol Outdated Show resolved Hide resolved
contracts/bonding/libraries/SortedArrays.sol Show resolved Hide resolved
contracts/bonding/BondingCheckpoints.sol Outdated Show resolved Hide resolved
contracts/bonding/BondingCheckpoints.sol Outdated Show resolved Hide resolved
contracts/bonding/BondingCheckpoints.sol Outdated Show resolved Hide resolved
contracts/bonding/BondingCheckpoints.sol Outdated Show resolved Hide resolved
contracts/bonding/BondingManager.sol Outdated Show resolved Hide resolved
contracts/bonding/BondingCheckpoints.sol Outdated Show resolved Hide resolved
contracts/bonding/BondingCheckpoints.sol Outdated Show resolved Hide resolved
contracts/bonding/BondingCheckpoints.sol Outdated Show resolved Hide resolved
contracts/bonding/BondingManager.sol Outdated Show resolved Hide resolved
contracts/bonding/BondingCheckpoints.sol Outdated Show resolved Hide resolved
contracts/bonding/BondingCheckpoints.sol Outdated Show resolved Hide resolved
contracts/bonding/BondingManager.sol Outdated Show resolved Hide resolved
contracts/bonding/BondingCheckpoints.sol Outdated Show resolved Hide resolved
Meaning the start of next round instead of the
current round. This is more compatible with the
way OZ expects the timepoints to work in the clock
and snapshots on the Governor framework.
This changes the BondingVotes implementation to
stop having so many reverts on supposedly invalid states.

Instead, redefine the voting power (and checkpointed stake)
as being zero before the first round to be checkpointed.

This had other implications in the code like removing
changes in BondingManager to make the state complete
(always having an earnings pool on lastClaimRound).
Instead, the BondingVotes implementaiton is resilient
to that as well and just assumes reward() had never
been called.

This also fixed the redundant reverts between BondingVotes
and SortedArrays, as now there are almost no reverts.
@victorges
Copy link
Member Author

OK @yondonfu @0xcadams I've addressed the feedback on your comments and this is ready for another review round. I have included some other changes as well that seemed necessary after the rounds of testing with the testnet and some discussions.

I recommend reviewing commit by commit, I avoided making a force push here not to lose history, and also isolated the less trivial changes in their own commits. As an overview of each:

  • 0c41f78: This addresses the comments about multiple checkpoints being made for the same account in the same transaction. It makes sure we only call it once in the end of all mutations instead on all code paths.
  • d518cd6: This merges the BondingCheckpointsVotes contract from treasury: Delta Governor v1 #615 into the BondingCheckpoints contract here (renamed to just BondingVotes). This is mainly to allow it to send the ERC5805 events on changes.
  • ccc3ddf: This is a change on the way that we assign voting power to accounts, to use their active stake at the end of the round instead of start. I didn't change any of the core checkpointing logic, just changed the voting power functions to read state from the next round instead, also using the nextRoundTotalActiveStake when querying for the next round to the current one.
    • After further testing with the OZ governor, I came to understand that their definition of "voting power at a timepoint" actually refers to the voting power at the end of that timepoint. This makes sense if you think about using block numbers as the clock, and is also reflected in the way that the OZ Governor works for some things like vote snapshotting: voting starts only after the "snapshot round", not on it.
    • A practical effect of this is that, in the previous implementation, if we had a votingDelay of 1 round the voting would be delayed in 1-2 rounds, but users would only really have 0-1 rounds to make any staking changes. Any staking changes made on the round before the voting actually starts would not be effective, since we would take the active stake from the start of that round for the voting power. Now we take it from the end (start of next round), and things work a bit more as expected.
  • d5062fe: This changes the behavior of most of the "read" logic in the BondingVotes contract to avoid reverting queries that could simply return a 0 value. There is only 1 specific case now where we will revert instead of returning zero, which is when there is no earnings pool on a transcoder's last reward round, which is an illegal state from the way that BondingManager works. This also fixed a lot of the redundant reverts with the SortedArrays implementation.
  • 292f954: This one addresses the other review comments which were simpler so I just combined them in this commit.

Just for consistency with other contracts.
Some docs as a bonus
This is to increase compatibility with some tools
out there like Tally, which require only these
functions from the ERC20 spec, not the full implementation.

So we can have these from the get-go to make things
easier if we want to make something with them.
Copy link
Member

@yondonfu yondonfu left a comment

Choose a reason for hiding this comment

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

Still need to do the follow up for BondingVotes.sol.

contracts/bonding/BondingManager.sol Outdated Show resolved Hide resolved
contracts/bonding/BondingManager.sol Outdated Show resolved Hide resolved
contracts/bonding/BondingManager.sol Outdated Show resolved Hide resolved
contracts/bonding/BondingManager.sol Outdated Show resolved Hide resolved
contracts/bonding/BondingManager.sol Outdated Show resolved Hide resolved
contracts/bonding/BondingVotes.sol Outdated Show resolved Hide resolved
contracts/bonding/BondingVotes.sol Outdated Show resolved Hide resolved
contracts/bonding/BondingVotes.sol Outdated Show resolved Hide resolved
contracts/bonding/BondingVotes.sol Outdated Show resolved Hide resolved
contracts/bonding/BondingVotes.sol Outdated Show resolved Hide resolved
contracts/bonding/BondingVotes.sol Outdated Show resolved Hide resolved
contracts/bonding/BondingVotes.sol Outdated Show resolved Hide resolved
contracts/bonding/BondingVotes.sol Outdated Show resolved Hide resolved
contracts/bonding/BondingVotes.sol Show resolved Hide resolved
contracts/bonding/BondingVotes.sol Outdated Show resolved Hide resolved
Copy link
Member

@yondonfu yondonfu left a comment

Choose a reason for hiding this comment

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

LGTM

@victorges victorges changed the base branch from confluence to delta August 25, 2023 21:39
@victorges victorges merged commit 4d720f0 into delta Aug 25, 2023
2 checks passed
@victorges victorges deleted the vg/feat/bonding-checkpoints branch August 25, 2023 22:08
victorges added a commit that referenced this pull request Oct 11, 2023
* package.json: Update @OpenZeppelin libraries

Will be required for new checkpoints code and later governor
implementation.

* bonding: Create SortedArrays library

Used for checkpointing logic in bonding state checkpoints

* bonding: Create BondingCheckpoints contract

Handles historic checkpointing ("snapshotting") and lookup
of the bonding state.

* bonding: Checkpoint bonding state on changes

* test/bonding: Test BondingManager and Checkpoints

 - unit tests
 - integration tests
 - gas-report
 - fork test for upgrade

* bonding: Migrate to custom error types

* bonding: Allow querying unbonded+uncheckpointed accounts

This will provide a cleaner experience using governance
tools, since users that don't have any stake won't get
errors when querying their voting power. For users that
do have stake, we will make sure to checkpoint them on
first deploy.

* bonding: Make sure we checkpoint up to once per op

* bonding: Make bonding checkpoints implement IVotes

* bonding: Read votes from the end of the round

Meaning the start of next round instead of the
current round. This is more compatible with the
way OZ expects the timepoints to work in the clock
and snapshots on the Governor framework.

* bonding: Make checkpoint reading revert-free

This changes the BondingVotes implementation to
stop having so many reverts on supposedly invalid states.

Instead, redefine the voting power (and checkpointed stake)
as being zero before the first round to be checkpointed.

This had other implications in the code like removing
changes in BondingManager to make the state complete
(always having an earnings pool on lastClaimRound).
Instead, the BondingVotes implementaiton is resilient
to that as well and just assumes reward() had never
been called.

This also fixed the redundant reverts between BondingVotes
and SortedArrays, as now there are almost no reverts.

* bonding: Address minor code review comments

* bonding: Move constructor to after modifiers

Just for consistency with other contracts.
Some docs as a bonus

* bonding: Implement ERC20 metadata on votes

This is to increase compatibility with some tools
out there like Tally, which require only these
functions from the ERC20 spec, not the full implementation.

So we can have these from the get-go to make things
easier if we want to make something with them.

* bonding: Address PR comments

* bonding: Address BondingVotes review comments

* test/bonding: Document IVotes layer tests
victorges added a commit that referenced this pull request Oct 11, 2023
* package.json: Update @OpenZeppelin libraries

Will be required for new checkpoints code and later governor
implementation.

* bonding: Create SortedArrays library

Used for checkpointing logic in bonding state checkpoints

* bonding: Create BondingCheckpoints contract

Handles historic checkpointing ("snapshotting") and lookup
of the bonding state.

* bonding: Checkpoint bonding state on changes

* test/bonding: Test BondingManager and Checkpoints

 - unit tests
 - integration tests
 - gas-report
 - fork test for upgrade

* bonding: Migrate to custom error types

* bonding: Allow querying unbonded+uncheckpointed accounts

This will provide a cleaner experience using governance
tools, since users that don't have any stake won't get
errors when querying their voting power. For users that
do have stake, we will make sure to checkpoint them on
first deploy.

* bonding: Make sure we checkpoint up to once per op

* bonding: Make bonding checkpoints implement IVotes

* bonding: Read votes from the end of the round

Meaning the start of next round instead of the
current round. This is more compatible with the
way OZ expects the timepoints to work in the clock
and snapshots on the Governor framework.

* bonding: Make checkpoint reading revert-free

This changes the BondingVotes implementation to
stop having so many reverts on supposedly invalid states.

Instead, redefine the voting power (and checkpointed stake)
as being zero before the first round to be checkpointed.

This had other implications in the code like removing
changes in BondingManager to make the state complete
(always having an earnings pool on lastClaimRound).
Instead, the BondingVotes implementaiton is resilient
to that as well and just assumes reward() had never
been called.

This also fixed the redundant reverts between BondingVotes
and SortedArrays, as now there are almost no reverts.

* bonding: Address minor code review comments

* bonding: Move constructor to after modifiers

Just for consistency with other contracts.
Some docs as a bonus

* bonding: Implement ERC20 metadata on votes

This is to increase compatibility with some tools
out there like Tally, which require only these
functions from the ERC20 spec, not the full implementation.

So we can have these from the get-go to make things
easier if we want to make something with them.

* bonding: Address PR comments

* bonding: Address BondingVotes review comments

* test/bonding: Document IVotes layer tests
victorges added a commit that referenced this pull request Oct 11, 2023
* package.json: Update @OpenZeppelin libraries

Will be required for new checkpoints code and later governor
implementation.

* bonding: Create SortedArrays library

Used for checkpointing logic in bonding state checkpoints

* bonding: Create BondingCheckpoints contract

Handles historic checkpointing ("snapshotting") and lookup
of the bonding state.

* bonding: Checkpoint bonding state on changes

* test/bonding: Test BondingManager and Checkpoints

 - unit tests
 - integration tests
 - gas-report
 - fork test for upgrade

* bonding: Migrate to custom error types

* bonding: Allow querying unbonded+uncheckpointed accounts

This will provide a cleaner experience using governance
tools, since users that don't have any stake won't get
errors when querying their voting power. For users that
do have stake, we will make sure to checkpoint them on
first deploy.

* bonding: Make sure we checkpoint up to once per op

* bonding: Make bonding checkpoints implement IVotes

* bonding: Read votes from the end of the round

Meaning the start of next round instead of the
current round. This is more compatible with the
way OZ expects the timepoints to work in the clock
and snapshots on the Governor framework.

* bonding: Make checkpoint reading revert-free

This changes the BondingVotes implementation to
stop having so many reverts on supposedly invalid states.

Instead, redefine the voting power (and checkpointed stake)
as being zero before the first round to be checkpointed.

This had other implications in the code like removing
changes in BondingManager to make the state complete
(always having an earnings pool on lastClaimRound).
Instead, the BondingVotes implementaiton is resilient
to that as well and just assumes reward() had never
been called.

This also fixed the redundant reverts between BondingVotes
and SortedArrays, as now there are almost no reverts.

* bonding: Address minor code review comments

* bonding: Move constructor to after modifiers

Just for consistency with other contracts.
Some docs as a bonus

* bonding: Implement ERC20 metadata on votes

This is to increase compatibility with some tools
out there like Tally, which require only these
functions from the ERC20 spec, not the full implementation.

So we can have these from the get-go to make things
easier if we want to make something with them.

* bonding: Address PR comments

* bonding: Address BondingVotes review comments

* test/bonding: Document IVotes layer tests
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