Skip to content

Commit

Permalink
feat: starting to look at rewards distribution for L2
Browse files Browse the repository at this point in the history
  • Loading branch information
pcarranzav committed May 17, 2022
1 parent 1576535 commit 34f4083
Show file tree
Hide file tree
Showing 8 changed files with 299 additions and 26 deletions.
54 changes: 54 additions & 0 deletions contracts/l2/reservoir/L2Reservoir.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
// SPDX-License-Identifier: GPL-2.0-or-later

pragma solidity ^0.7.6;
pragma abicoder v2;

import "@openzeppelin/contracts/math/SafeMath.sol";

import "../../arbitrum/L2ArbitrumMessenger.sol";

import "../../reservoir/IReservoir.sol";
import "./L2ReservoirStorage.sol";

/**
* @title L2 Rewards Reservoir
* @dev TODO
*/
contract L2Reservoir is L2ReservoirV1Storage, Reservoir {
modifier onlyL2Gateway() {
require(msg.sender == _resolveContract(keccak256("GraphTokenGateway")), "ONLY_GATEWAY");
_;
}

/**
* @dev Initialize this contract.
* The contract will be paused.
* @param _controller Address of the Controller that manages this contract
*/
function initialize(address _controller) external onlyImpl {
Managed._initialize(_controller);
}

function getAccumulatedRewards(uint256 blocknum) external override returns (uint256) {
// R(t) = R(t0) + (DeltaR2(t, t0))
// (deltaRewards implicitly uses lambda because it computes using normalizedTokenSupply)
return accumulatedLayerRewards + deltaRewards(blocknum, lastRewardsUpdateBlock);
}

function deltaRewards(uint256 t1, uint256 t0) public returns (uint256) {
if (issuanceRate <= MIN_ISSUANCE_RATE) {
return 0;
}
return
normalizedTokenSupplyCache.mul(_pow(issuanceRate, t1.sub(t0), TOKEN_DECIMALS)).div(
TOKEN_DECIMALS
);
}

function receiveDrip(uint256 _normalizedTokenSupply) external onlyL2Gateway {
if (_normalizedTokenSupply > normalizedTokenSupplyCache) {
normalizedTokenSupplyCache = _normalizedTokenSupply;
lastRewardsUpdateBlock = block.number;
}
}
}
11 changes: 11 additions & 0 deletions contracts/l2/reservoir/L2ReservoirStorage.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
// SPDX-License-Identifier: GPL-2.0-or-later

pragma solidity ^0.7.6;

import "../../reservoir/IReservoir.sol";
import "../../governance/Managed.sol";

contract L2ReservoirV1Storage is Managed {
uint256 public normalizedTokenSupplyCache;
uint256 public lastRewardsUpdateBlock;
}
7 changes: 7 additions & 0 deletions contracts/reservoir/IReservoir.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
// SPDX-License-Identifier: GPL-2.0-or-later

pragma solidity ^0.7.6;

interface IReservoir {
function getAccumulatedRewards(uint256 blocknum) external;
}
109 changes: 109 additions & 0 deletions contracts/reservoir/L1Reservoir.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,109 @@
// SPDX-License-Identifier: GPL-2.0-or-later

pragma solidity ^0.7.6;
pragma abicoder v2;

import "@openzeppelin/contracts/math/SafeMath.sol";

import "../upgrades/GraphUpgradeable.sol";

import "./ReservoirStorage.sol";
import "./IReservoir.sol";

/**
* @title Rewards Reservoir
* @dev TODO
*/
contract L1Reservoir is ReservoirV1Storage, Reservoir {
using SafeMath for uint256;

function getAccumulatedRewards(uint256 blocknum) external override returns (uint256) {
// R(t) = R(t0) + (1-lambda) * (DeltaR(t, t0))
return
accumulatedLayerRewards +
deltaRewards(blocknum, lastRewardsUpdateBlock)
.mul(TOKEN_DECIMALS.sub(l2RewardsFraction))
.div(TOKEN_DECIMALS);
}

function drip(
uint256 l2MaxGas,
uint256 l2GasPriceBid,
uint256 l2MaxSubmissionCost
) external payable {
uint256 mintedRewardsTotal = deltaRewards(rewardsMintedUntilBlock, lastRewardsUpdateBlock);
uint256 mintedRewardsActual = deltaRewards(block.number, lastRewardsUpdateBlock);
// eps = (signed int) mintedRewardsTotal - mintedRewardsActual

lastRewardsUpdateBlock = block.number;
rewardsMintedUntilBlock = block.number.add(MINT_SUPPLY_PERIOD);
// n:
uint256 newRewardsToDistribute = deltaRewards(
rewardsMintedUntilBlock,
lastRewardsUpdateBlock
);
// N = n - eps
uint256 tokensToMint = newRewardsToDistribute.add(mintedRewardsActual).sub(
mintedRewardsTotal
);

if (tokensToMint > 0) {
graphToken().mint(address(this), tokensToMint);
}

accumulatedLayerRewards = getAccumulatedRewards(block.number);

tokenSupplyCache = graphToken().totalSupply();

uint256 tokensToSendToL2 = l2RewardsFraction.mul(newRewardsToDistribute).div(
TOKEN_DECIMALS
);
if (l2RewardsFraction != lastL2RewardsFraction) {
if (mintedRewardsTotal > mintedRewardsActual) {
// eps > 0, i.e. t < t1_old
tokensToSendToL2 = tokensToSendToL2.sub(
lastL2RewardsFraction.mul(mintedRewardsTotal.sub(mintedRewardsActual))
);
} else {
tokensToSendToL2 = tokensToSendToL2.add(
lastL2RewardsFraction.mul(mintedRewardsActual.sub(mintedRewardsTotal))
);
}
lastL2RewardsFraction = l2RewardsFraction;
}
_sendNewTokensAndStateToL2(tokensToSendToL2, l2MaxSubmissionCost, l2GasPriceBid, l2MaxGas);
}

function _sendNewTokensAndStateToL2(
uint256 nTokens,
uint256 maxGas,
uint256 gasPriceBid,
uint256 maxSubmissionCost
) internal {
uint256 normalizedSupply = l2RewardsFraction * tokenSupplyCache;
bytes memory extraData = abi.encodeWithSelector(
L2Reservoir.receiveDrip.selector,
normalizedSupply
);
bytes memory data = abi.encode(maxSubmissionCost, extraData);
ITokenGateway gateway = ITokenGateway(_resolveContract(keccak256("GraphTokenGateway")));
gateway.outboundTransfer{ value: msg.value }(
address(graphToken()),
l2ReservoirAddress,
nTokens,
maxGas,
gasPriceBid,
data
);
}

function deltaRewards(uint256 t1, uint256 t0) public returns (uint256) {
if (issuanceRate <= MIN_ISSUANCE_RATE) {
return 0;
}
return
tokenSupplyCache.mul(_pow(issuanceRate, t1.sub(t0), TOKEN_DECIMALS)).div(
TOKEN_DECIMALS
);
}
}
79 changes: 79 additions & 0 deletions contracts/reservoir/Reservoir.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
// SPDX-License-Identifier: GPL-2.0-or-later

pragma solidity ^0.7.6;
pragma abicoder v2;

import "@openzeppelin/contracts/math/SafeMath.sol";

import "../upgrades/GraphUpgradeable.sol";

import "./ReservoirStorage.sol";
import "./IReservoir.sol";

/**
* @title Rewards Reservoir
* @dev TODO
*/
abstract contract Reservoir is GraphUpgradeable, Managed, IReservoir {
using SafeMath for uint256;

uint256 internal constant TOKEN_DECIMALS = 1e18;
uint256 internal constant MIN_ISSUANCE_RATE = 1e18;
uint256 internal constant MINT_SUPPLY_PERIOD = 45815; // ~1 week in blocks

function _pow(
uint256 x,
uint256 n,
uint256 base
) private pure returns (uint256 z) {
// solhint-disable-next-line no-inline-assembly
assembly {
switch x
case 0 {
switch n
case 0 {
z := base
}
default {
z := 0
}
}
default {
switch mod(n, 2)
case 0 {
z := base
}
default {
z := x
}
let half := div(base, 2) // for rounding.
for {
n := div(n, 2)
} n {
n := div(n, 2)
} {
let xx := mul(x, x)
if iszero(eq(div(xx, x), x)) {
revert(0, 0)
}
let xxRound := add(xx, half)
if lt(xxRound, xx) {
revert(0, 0)
}
x := div(xxRound, base)
if mod(n, 2) {
let zx := mul(z, x)
if and(iszero(iszero(x)), iszero(eq(div(zx, x), z))) {
revert(0, 0)
}
let zxRound := add(zx, half)
if lt(zxRound, zx) {
revert(0, 0)
}
z := div(zxRound, base)
}
}
}
}
}
}
18 changes: 18 additions & 0 deletions contracts/reservoir/ReservoirStorage.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
// SPDX-License-Identifier: GPL-2.0-or-later

pragma solidity ^0.7.6;

import "./IReservoir.sol";
import "../governance/Managed.sol";

contract ReservoirV1Storage is Managed {
uint256 public l2RewardsFraction; // Expressed in base 1e18
uint256 public lastL2RewardsFraction;
address public l2ReservoirAddress;
uint256 public lastRewardsUpdateBlock;
uint256 public rewardsMintedUntilBlock;
uint256 public accumulatedGlobalRewards;
uint256 public accumulatedLayerRewards;
uint256 public tokenSupplyCache;
uint256 public issuanceRate;
}
42 changes: 16 additions & 26 deletions contracts/rewards/RewardsManager.sol
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,8 @@ import "../upgrades/GraphUpgradeable.sol";
import "./RewardsManagerStorage.sol";
import "./IRewardsManager.sol";

import "../reservoir/IReservoir.sol";

/**
* @title Rewards Manager Contract
* @dev Tracks how inflationary GRT rewards should be handed out. Relies on the Curation contract
Expand All @@ -18,7 +20,7 @@ import "./IRewardsManager.sol";
* total rewards for the Subgraph are split up for each Indexer based on much they have Staked on
* that Subgraph.
*/
contract RewardsManager is RewardsManagerV2Storage, GraphUpgradeable, IRewardsManager {
contract RewardsManager is RewardsManagerV3Storage, GraphUpgradeable, IRewardsManager {
using SafeMath for uint256;

uint256 private constant TOKEN_DECIMALS = 1e18;
Expand Down Expand Up @@ -155,27 +157,13 @@ contract RewardsManager is RewardsManagerV2Storage, GraphUpgradeable, IRewardsMa
/**
* @dev Gets the issuance of rewards per signal since last updated.
*
* Compound interest formula: `a = p(1 + r/n)^nt`
* The formula is simplified with `n = 1` as we apply the interest once every time step.
* The `r` is passed with +1 included. So for 10% instead of 0.1 it is 1.1
* The simplified formula is `a = p * r^t`
*
* Notation:
* t: time steps are in blocks since last updated
* p: total supply of GRT tokens
* a: inflated amount of total supply for the period `t` when interest `r` is applied
* x: newly accrued rewards token for the period `t`
* The compound interest formula is applied in the Reservoir contract.
* This function will compare accumulated rewards at the current block
* with the value that was cached at accRewardsPerSignalLastBlockUpdated.
*
* @return newly accrued rewards per signal since last update
*/
function getNewRewardsPerSignal() public view override returns (uint256) {
// Calculate time steps
uint256 t = block.number.sub(accRewardsPerSignalLastBlockUpdated);
// Optimization to skip calculations if zero time steps elapsed
if (t == 0) {
return 0;
}

// Zero issuance under a rate of 1.0
if (issuanceRate <= MIN_ISSUANCE_RATE) {
return 0;
Expand All @@ -188,16 +176,14 @@ contract RewardsManager is RewardsManagerV2Storage, GraphUpgradeable, IRewardsMa
return 0;
}

uint256 r = issuanceRate;
uint256 p = tokenSupplySnapshot;
uint256 a = p.mul(_pow(r, t, TOKEN_DECIMALS)).div(TOKEN_DECIMALS);

// New issuance of tokens during time steps
uint256 x = a.sub(p);
uint256 accRewardsNow = reservoir().getAccumulatedRewards(block.number);

// Get the new issuance per signalled token
// We multiply the decimals to keep the precision as fixed-point number
return x.mul(TOKEN_DECIMALS).div(signalledTokens);
return
(accRewardsNow.sub(accRewardsOnLastSignalUpdate)).mul(TOKEN_DECIMALS).div(
signalledTokens
);
}

/**
Expand Down Expand Up @@ -272,7 +258,7 @@ contract RewardsManager is RewardsManagerV2Storage, GraphUpgradeable, IRewardsMa
function updateAccRewardsPerSignal() public override returns (uint256) {
accRewardsPerSignal = getAccRewardsPerSignal();
accRewardsPerSignalLastBlockUpdated = block.number;
tokenSupplySnapshot = graphToken().totalSupply();
accRewardsOnLastSignalUpdate = reservoir().getAccumulatedRewards(block.number);
return accRewardsPerSignal;
}

Expand Down Expand Up @@ -459,4 +445,8 @@ contract RewardsManager is RewardsManagerV2Storage, GraphUpgradeable, IRewardsMa
}
}
}

function reservoir() private returns (IReservoir) {
return IReservoir(_resolveContract(keccak256("Reservoir")));
}
}
5 changes: 5 additions & 0 deletions contracts/rewards/RewardsManagerStorage.sol
Original file line number Diff line number Diff line change
Expand Up @@ -26,3 +26,8 @@ contract RewardsManagerV2Storage is RewardsManagerV1Storage {
// Snapshot of the total supply of GRT when accRewardsPerSignal was last updated
uint256 public tokenSupplySnapshot;
}

contract RewardsManagerV3Storage is RewardsManagerV2Storage {
// Accumulated rewards at accRewardsPerSignalLastBlockUpdated
uint256 public accRewardsOnLastSignalUpdate;
}

0 comments on commit 34f4083

Please sign in to comment.