Skip to content
This repository has been archived by the owner on Jul 9, 2021. It is now read-only.

Mutli-Block Finalization #2155

Merged
merged 52 commits into from
Sep 23, 2019
Merged

Mutli-Block Finalization #2155

merged 52 commits into from
Sep 23, 2019

Conversation

dorothy-zbornak
Copy link
Contributor

@dorothy-zbornak dorothy-zbornak commented Sep 13, 2019

Description

blocks-in-yo-face

At long last, multi-block finalization is here!

Overview

Multi-Block finalization provides an asynchronous method for finalizing pool rewards that can be done over the span of multiple transactions, without halting the next epoch.

  1. Every time a protocol fee is paid, the pool associated with it is credited, tracked, and emitted in an event.
  2. When a keeper calls endEpoch(), the epoch will immediately advance and finalization begins.
    • During this time, the next epoch continues to accrue fees, as normal.
  3. Keepers gather up all the pool IDs that were emitted during the previous epoch and pass them into finalizePools()-- potentially over several transactions.
    • Each time a pool is finalized, the rewards owed to that pool are paid to pool members (as a group) and to operators (individually).
    • If a delegator touches their stake in a pool that has yet to be finalized, it will be finalized on the spot.

Notes

  • An epoch cannot be ended until the previous epoch has completed finalization.
  • In contrast to the original, synchronous finalization, epoch rewards are now paid in the context of the new epoch, not the epoch in which rewards were earned in. This turned out to be easier said than done and required significant adjustments to the stake accounting:
    • Prior, we maintained "cumulative reward" balances for currentEpoch and currentEpoch-1. Now we maintain them for currentEpoch and currentEpoch+1 (I know, it's weird).
    • The behavior of reference counting had to be altered to properly accommodate this, and you'll witness this in their totally different tests.
    • We have to take care to factor in unfinalized rewards as well as rewards from previous epochs when computing a delegator's balance.
  • There is a new mixin, MixinFinalizer, which contains the bulk of the finalization logic. Notable functions include:
    • endEpoch(), which anyone can call to end an epoch and begin the finalization process.
    • finalizePools(), which finalizes multiple pools at once.
    • _finalizePool(), an internal function that finalizes a single pool. Called by MixinStakingPoolRewards when syncing stake.
    • _getUnfinalizedPoolRewards(), an internal function that calculates the unfinalized rewards owed to a pool. Also called by MixinStakingPoolRewards in order to return accurate balances while pools are still being finalized.
  • MixinExchangeFees has been pretty heavily modified to integrate the tracking we need.
  • This PR comes with fresh unit tests for delegator/operator rewards, finalization, and protocol fees, but I'm still missing ones for the new logic added to payProtocolFee(). Any takers?
  • A notable change was made to EthVault and StakingPoolRewardVault to support efficient batching of finalization:
    • We're back to the recordDepositFor() pattern and just bulk transferring funds via their fallbacks.
  • The FinalizerActor has been updated to accurately and more robustly compute finalization balances.
  • There is now a minimum cap on stake for a pool to be considered active. It was a whopping 1 wei. I think this makes more sense than edge casing pools with zero stake.
  • The cobbDouglas() function is now in its own library named, surprise, LibCobbDouglas.
  • I added some dynamic scaling of values to LibFractions to fix precision issues I was running into.
  • I clip cobb douglas results to the remaining reward balance in _getUnfinalizedPoolRewards() of MixinFinalizer.
  • My tests are ugly. Brace yourselves.

Further Reading

That oooold quip doc I created on MBF still largely applies, if you want a natural language description of the finalization algorithm and state variables associated with it. Ignore the one titled "Marrying Delayed Staking with MBF." That was the work of a lunatic.

Testing instructions

Types of changes

  • Breaking change (fix or feature that would cause existing functionality to change)

Checklist:

  • Prefix PR title with [WIP] if necessary.
  • Add tests to cover changes as needed.
  • Update documentation as needed.
  • Add new entries to the relevant CHANGELOG.jsons.

@coveralls
Copy link

coveralls commented Sep 17, 2019

Coverage Status

Coverage remained the same at 75.804% when pulling 3883297 on feat/staking/hurts-like-a-mbf into 29f4d69 on 3.0.

/**
* _.zip() that clips to the shortest array.
*/
export function shortZip<T1, T2>(a: T1[], b: T2[]): Array<[T1, T2]> {
Copy link
Contributor Author

Choose a reason for hiding this comment

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

This is better than _.zip() because the return type isn't some weird Array<[T1 | undefined, T2 | undefined]>, which makes _.zip() pretty inconvenient to use in TS.

@dorothy-zbornak dorothy-zbornak changed the title [WIP] Mutli-Block Finalization Mutli-Block Finalization Sep 19, 2019
@dorothy-zbornak dorothy-zbornak marked this pull request as ready for review September 19, 2019 11:35
Copy link
Member

@abandeali1 abandeali1 left a comment

Choose a reason for hiding this comment

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

I'm not finished with my review but wanted to get something in ASAP. I will continue later today!

MixinStakeStorage,
MixinStakingPoolMakers,
MixinStakeBalances,
MixinCumulativeRewards,
Copy link
Member

Choose a reason for hiding this comment

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

I see you got a hold of Greg's linearization script :trollface:

Copy link
Member

Choose a reason for hiding this comment

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

I suppose we can give imports another pass after all big features are merged.

Copy link
Contributor

Choose a reason for hiding this comment

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

Come to the dark side, Amir. These aren't the imports you're looking for.

contracts/staking/contracts/src/ReadOnlyProxy.sol Outdated Show resolved Hide resolved
contracts/staking/contracts/src/fees/MixinExchangeFees.sol Outdated Show resolved Hide resolved
contracts/staking/contracts/src/fees/MixinExchangeFees.sol Outdated Show resolved Hide resolved
contracts/staking/contracts/src/sys/MixinParams.sol Outdated Show resolved Hide resolved
Copy link
Contributor

@moodlezoup moodlezoup left a comment

Choose a reason for hiding this comment

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

Awesome job overall! Not done reading the tests, but here are some comments in the meantime

contracts/staking/contracts/src/sys/MixinAbstract.sol Outdated Show resolved Hide resolved
contracts/staking/test/utils/api_wrapper.ts Show resolved Hide resolved
contracts/staking/test/rewards_test.ts Outdated Show resolved Hide resolved
contracts/staking/test/rewards_test.ts Outdated Show resolved Hide resolved
contracts/staking/test/rewards_test.ts Outdated Show resolved Hide resolved
contracts/staking/test/rewards_test.ts Show resolved Hide resolved
Copy link
Contributor

@hysz hysz left a comment

Choose a reason for hiding this comment

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

I'm partway through my review. I think the overall approach looks good, and I like that there's relatively little logic outside of finalization had to change +1.

contracts/staking/contracts/src/fees/MixinExchangeFees.sol Outdated Show resolved Hide resolved
internal
/// @dev Returns the total balance of this contract, including WETH.
/// @return totalBalance Total balance.
function getTotalBalance()
Copy link
Contributor

Choose a reason for hiding this comment

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

Could we add that this is the fee balance? This will add context to the greater staking API.

Copy link
Contributor Author

@dorothy-zbornak dorothy-zbornak Sep 20, 2019

Choose a reason for hiding this comment

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

As in, add it to totalFeesCollected? idk, aren't they two different things? The contract balance could include subsidies, remaining rewards, etc.

Edit: misread

Copy link
Contributor

Choose a reason for hiding this comment

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

Yeah I guess they are. Basically, just a name to disambiguate between this balance and the stake balances. Maybe getTotalPayableBalance or something?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I could just remove this function. All it's doing is suming ETH and WETH balance, and we could use the space.

contracts/staking/contracts/src/immutable/MixinStorage.sol Outdated Show resolved Hide resolved

/// @dev Exposes some internal functions from various contracts to avoid
/// cyclical dependencies.
contract MixinAbstract {
Copy link
Contributor

Choose a reason for hiding this comment

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

I guess I don't have full context, but I feel like this could be solved more cleanly by reorganizing the mixins.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

idk how to fix the cyclical deps without introducing something like this or an interface, and we don't use the interface convention for mixins in this package so I went with this. 🤔

Copy link
Contributor

Choose a reason for hiding this comment

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

Kk I'll look into it a bit more during the final pass. If not then could be good to make this an interface since it's declarations only.

contracts/staking/contracts/src/sys/MixinFinalizer.sol Outdated Show resolved Hide resolved
Copy link
Member

@abandeali1 abandeali1 left a comment

Choose a reason for hiding this comment

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

Added a review for the remaining Solidity!

Copy link
Contributor

@xianny xianny left a comment

Choose a reason for hiding this comment

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

I only looked over changes to base-contract and order-utils. Those look good to me :)

@dorothy-zbornak dorothy-zbornak force-pushed the feat/staking/hurts-like-a-mbf branch 2 times, most recently from 5be0eb0 to dde306a Compare September 22, 2019 03:51
merklejerk and others added 18 commits September 22, 2019 12:11
…actorings.

`@0x/contracts-staking`: Add finalization-related protocol fees unit tests.
`@0x/contracts-staking`: Convert all rewards to WETH.
`@0x/contracts-staking`: Style changes.
`@0x/contracts-staking`: Address misc. review comments.
`@0x/contracts-staking`: Make `LibFractions` scaling a separate step.
Copy link
Member

@abandeali1 abandeali1 left a comment

Choose a reason for hiding this comment

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

This looks good to go. I did not leave any comments on any code related to the EthVault or StakingPoolRewardsVault because of #2186 . Please do not make any further changes to these contracts, since they will be removed.

Copy link
Contributor

@jalextowle jalextowle left a comment

Choose a reason for hiding this comment

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

Nice work on this one! I had a couple of nits, but it looks good to go.

Copy link
Contributor

@hysz hysz left a comment

Choose a reason for hiding this comment

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

This is looking really solid! I left four comments; three of which we can address in the final pass. Although one regarding reward intervals should be addressed before we merge.


// Emit an event so keepers know what pools to pass into
// `finalize()`.
emit StakingPoolActivated(currentEpoch_, poolId);
Copy link
Contributor

Choose a reason for hiding this comment

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

I feel like there's more information we may want to communicate in this function. What would you think of emitting a single event that communicates: the epoch, pool id, whether payments was in ETH or WETH, and whether or not the fee was attributed to the pool?

internal
/// @dev Returns the total balance of this contract, including WETH.
/// @return totalBalance Total balance.
function getTotalBalance()
Copy link
Contributor

Choose a reason for hiding this comment

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

Yeah I guess they are. Basically, just a name to disambiguate between this balance and the stake balances. Maybe getTotalPayableBalance or something?


/// @dev Exposes some internal functions from various contracts to avoid
/// cyclical dependencies.
contract MixinAbstract {
Copy link
Contributor

Choose a reason for hiding this comment

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

Kk I'll look into it a bit more during the final pass. If not then could be good to make this an interface since it's declarations only.

if (unsyncedStake.currentEpoch >= lastRewardEpoch) {
return reward;
}
// From here we know: `unsyncedStake.currentEpoch < currentEpoch > 0`.
Copy link
Contributor

Choose a reason for hiding this comment

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

It looks like the intervals have been shifted. Is this on purpose?

# Previously the ranges were:
[unsyncedStake.currentEpoch-1 .. unsyncedStake.currentEpoch]
[unsyncedStake.currentEpoch .. lastRewardEpoch]

# Now the ranges are:
[unsyncedStake.currentEpoch .. unsyncedStake.currentEpoch + 1]
[unsyncedStake.currentEpoch + 1 .. lastRewardEpoch + 1]

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Yup. This is from crediting rewards at the top of an epoch rather than at the end, and folding in unfinalized rewards.

Copy link
Contributor

@moodlezoup moodlezoup left a comment

Choose a reason for hiding this comment

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

LGTM modulo nits! 🚀 🚀 🚀

`@0x/contracts-staking`: Fix linter errors.
@dorothy-zbornak dorothy-zbornak merged commit 8ddcf88 into 3.0 Sep 23, 2019
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

8 participants