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

feat: keeper reward for reservoir drip through token issuance #582

Merged
merged 21 commits into from
Sep 2, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
21 commits
Select commit Hold shift + click to select a range
4b9491f
feat: keeper reward for reservoir drip through token issuance
pcarranzav Jun 5, 2022
56de47a
fix: don't use tx.origin as it will not work
pcarranzav Jun 8, 2022
a33bd30
fix: only allow indexers, their operators, or whitelisted addresses t…
pcarranzav Jun 21, 2022
435abc2
test: fix rewards and reservoir tests after restricting drip callers
pcarranzav Jun 21, 2022
c4af2f6
test: add a test for the keeper reward delivery in L2
pcarranzav Jun 23, 2022
873b600
fix: provide part of the keeper reward to L2 redeemer
pcarranzav Jun 27, 2022
634477a
fix: clean up comments about redeemer
pcarranzav Jul 15, 2022
b542943
fix: more documentation details
pcarranzav Jul 15, 2022
6ef2faa
fix: use safe math for minDripInterval
pcarranzav Jul 15, 2022
1a6df5d
fix: validate input when granting/revoking drip permission
pcarranzav Jul 15, 2022
120753f
fix: docs and inheritance for IArbTxWithRedeemer
pcarranzav Jul 28, 2022
00d4547
fix: remove minDripInterval from the drip keeper reward calculation […
pcarranzav Aug 5, 2022
d36aab9
fix: use L2 alias of l1ReservoirAddress when comparing getCurrentRede…
pcarranzav Aug 8, 2022
2eea58c
fix: don't include keeper reward twice when computing what to send to…
pcarranzav Aug 17, 2022
448f506
test: add test to ensure no DoS if l2RewardsFraction is zeroed [H-04]
pcarranzav Aug 22, 2022
cc51cb7
test: optimize functions to advance blocks and fix some race conditions
pcarranzav Aug 22, 2022
0f8fb06
fix: add some missing validation on reservoirs [M-01]
pcarranzav Aug 22, 2022
365e307
fix: add some missing docstrings [L-04]
pcarranzav Aug 22, 2022
91a0219
fix: use a single-condition requires for the drip auth check [L-05]
pcarranzav Aug 22, 2022
80cc2f2
fix: add indexed params to dripper change events [N-01]
pcarranzav Aug 22, 2022
b99d31f
fix: use explicit imports in relevant reservoir contracts [N-02]
pcarranzav Aug 22, 2022
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 12 additions & 2 deletions contracts/l2/reservoir/IL2Reservoir.sol
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

pragma solidity ^0.7.6;

import "../../reservoir/IReservoir.sol";
import { IReservoir } from "../../reservoir/IReservoir.sol";

/**
* @title Interface for the L2 Rewards Reservoir
Expand All @@ -18,13 +18,23 @@ interface IL2Reservoir is IReservoir {
* updates the issuanceBase and issuanceRate,
* and snapshots the accumulated rewards. If issuanceRate changes,
* it also triggers a snapshot of rewards per signal on the RewardsManager.
* Note that the transaction might revert if it's received out-of-order,
* because it checks an incrementing nonce. If that is the case, the retryable ticket can be redeemed
* again once the ticket for previous drip has been redeemed.
* A keeper reward will be sent to the keeper that dripped on L1, and part of it
* to whoever redeemed the current retryable ticket (as reported by ArbRetryableTx.getCurrentRedeemer) if
* the ticket is not auto-redeemed.
* @param _issuanceBase Base value for token issuance (approximation for token supply times L2 rewards fraction)
* @param _issuanceRate Rewards issuance rate, using fixed point at 1e18, and including a +1
* @param _nonce Incrementing nonce to ensure messages are received in order
* @param _keeperReward Keeper reward to distribute between keeper that called drip and keeper that redeemed the retryable tx
* @param _l1Keeper Address of the keeper that called drip in L1
*/
function receiveDrip(
uint256 _issuanceBase,
uint256 _issuanceRate,
uint256 _nonce
uint256 _nonce,
uint256 _keeperReward,
address _l1Keeper
) external;
}
96 changes: 87 additions & 9 deletions contracts/l2/reservoir/L2Reservoir.sol
Original file line number Diff line number Diff line change
Expand Up @@ -3,24 +3,51 @@
pragma solidity ^0.7.6;
pragma abicoder v2;

import "@openzeppelin/contracts/math/SafeMath.sol";
import { SafeMath } from "@openzeppelin/contracts/math/SafeMath.sol";
import { ArbRetryableTx } from "arbos-precompiles/arbos/builtin/ArbRetryableTx.sol";

import "../../reservoir/IReservoir.sol";
import "../../reservoir/Reservoir.sol";
import "./IL2Reservoir.sol";
import "./L2ReservoirStorage.sol";
import { Managed } from "../../governance/Managed.sol";
import { IGraphToken } from "../../token/IGraphToken.sol";
import { AddressAliasHelper } from "../../arbitrum/AddressAliasHelper.sol";
import { IReservoir } from "../../reservoir/IReservoir.sol";
import { Reservoir } from "../../reservoir/Reservoir.sol";
import { IL2Reservoir } from "./IL2Reservoir.sol";
import { L2ReservoirV2Storage } from "./L2ReservoirStorage.sol";

/**
* @dev ArbRetryableTx with additional interface to query the current redeemer.
* This is being added by the Arbitrum team but hasn't made it into the arbos-precompiles
* package yet.
*/
interface IArbTxWithRedeemer is ArbRetryableTx {
/**
* @notice Gets the redeemer of the current retryable redeem attempt.
* Returns the zero address if the current transaction is not a retryable redeem attempt.
* If this is an auto-redeem, returns the fee refund address of the retryable.
*/
function getCurrentRedeemer() external view returns (address);
}

/**
* @title L2 Rewards Reservoir
* @dev This contract acts as a reservoir/vault for the rewards to be distributed on Layer 2.
* It receives tokens for rewards from L1, and provides functions to compute accumulated and new
* total rewards at a particular block number.
*/
contract L2Reservoir is L2ReservoirV1Storage, Reservoir, IL2Reservoir {
contract L2Reservoir is L2ReservoirV2Storage, Reservoir, IL2Reservoir {
using SafeMath for uint256;

event DripReceived(uint256 _issuanceBase);
event NextDripNonceUpdated(uint256 _nonce);
// Address for the ArbRetryableTx interface provided by Arbitrum
address public constant ARB_TX_ADDRESS = 0x000000000000000000000000000000000000006E;

// Emitted when a rewards drip is received from L1
event DripReceived(uint256 issuanceBase);
// Emitted when the next drip nonce is manually updated by governance
event NextDripNonceUpdated(uint256 nonce);
// Emitted when the L1Reservoir's address is updated
event L1ReservoirAddressUpdated(address l1ReservoirAddress);
// Emitted when the L2 keeper reward fraction is updated
event L2KeeperRewardFractionUpdated(uint256 l2KeeperRewardFraction);

/**
* @dev Checks that the sender is the L2GraphTokenGateway as configured on the Controller.
Expand All @@ -36,6 +63,10 @@ contract L2Reservoir is L2ReservoirV1Storage, Reservoir, IL2Reservoir {
* are not set here because they are set from L1 through the drip function.
* The RewardsManager's address might also not be available in the controller at initialization
* time, so approveRewardsManager() must be called separately.
* The l1ReservoirAddress must also be set separately through setL1ReservoirAddress
* for the same reason.
* In the same vein, the l2KeeperRewardFraction is assumed to be zero at initialization,
* so it must be set through setL2KeeperRewardFraction.
* @param _controller Address of the Controller that manages this contract
*/
function initialize(address _controller) external onlyImpl {
Expand All @@ -52,6 +83,30 @@ contract L2Reservoir is L2ReservoirV1Storage, Reservoir, IL2Reservoir {
emit NextDripNonceUpdated(_nonce);
}

/**
* @dev Sets the L1 Reservoir address
* This is the address on L1 that will appear as redeemer when a ticket
* was auto-redeemed.
* @param _l1ReservoirAddress New address for the L1Reservoir on L1
*/
function setL1ReservoirAddress(address _l1ReservoirAddress) external onlyGovernor {
require(_l1ReservoirAddress != address(0), "INVALID_L1_RESERVOIR");
l1ReservoirAddress = _l1ReservoirAddress;
emit L1ReservoirAddressUpdated(_l1ReservoirAddress);
}

/**
* @dev Sets the L2 keeper reward fraction
* This is the fraction of the keeper reward that will be sent to the redeemer on L2
* if the retryable ticket is not auto-redeemed
* @param _l2KeeperRewardFraction New value for the fraction, with fixed point at 1e18
*/
function setL2KeeperRewardFraction(uint256 _l2KeeperRewardFraction) external onlyGovernor {
require(_l2KeeperRewardFraction <= FIXED_POINT_SCALING_FACTOR, "INVALID_VALUE");
l2KeeperRewardFraction = _l2KeeperRewardFraction;
emit L2KeeperRewardFractionUpdated(_l2KeeperRewardFraction);
}

/**
* @dev Get new total rewards accumulated since the last drip.
* This is deltaR = p * r ^ (blocknum - t0) - p, where:
Expand Down Expand Up @@ -88,14 +143,21 @@ contract L2Reservoir is L2ReservoirV1Storage, Reservoir, IL2Reservoir {
* Note that the transaction might revert if it's received out-of-order,
* because it checks an incrementing nonce. If that is the case, the retryable ticket can be redeemed
* again once the ticket for previous drip has been redeemed.
* A keeper reward will be sent to the keeper that dripped on L1, and part of it
* to whoever redeemed the current retryable ticket (as reported by ArbRetryableTx.getCurrentRedeemer) if
* the ticket is not auto-redeemed.
* @param _issuanceBase Base value for token issuance (approximation for token supply times L2 rewards fraction)
* @param _issuanceRate Rewards issuance rate, using fixed point at 1e18, and including a +1
* @param _nonce Incrementing nonce to ensure messages are received in order
* @param _keeperReward Keeper reward to distribute between keeper that called drip and keeper that redeemed the retryable tx
* @param _l1Keeper Address of the keeper that called drip in L1
*/
function receiveDrip(
uint256 _issuanceBase,
uint256 _issuanceRate,
uint256 _nonce
uint256 _nonce,
uint256 _keeperReward,
address _l1Keeper
) external override onlyL2Gateway {
require(_nonce == nextDripNonce, "INVALID_NONCE");
nextDripNonce = nextDripNonce.add(1);
Expand All @@ -108,6 +170,22 @@ contract L2Reservoir is L2ReservoirV1Storage, Reservoir, IL2Reservoir {
snapshotAccumulatedRewards();
}
issuanceBase = _issuanceBase;
IGraphToken grt = graphToken();

// Part of the reward always goes to whoever redeemed the ticket in L2,
// unless this was an autoredeem, in which case the "redeemer" is the sender, i.e. L1Reservoir
address redeemer = IArbTxWithRedeemer(ARB_TX_ADDRESS).getCurrentRedeemer();
if (redeemer != AddressAliasHelper.applyL1ToL2Alias(l1ReservoirAddress)) {
uint256 _l2KeeperReward = _keeperReward.mul(l2KeeperRewardFraction).div(
FIXED_POINT_SCALING_FACTOR
);
grt.transfer(redeemer, _l2KeeperReward);
grt.transfer(_l1Keeper, _keeperReward.sub(_l2KeeperReward));
} else {
// In an auto-redeem, we just send all the rewards to the L1 keeper:
grt.transfer(_l1Keeper, _keeperReward);
}

emit DripReceived(issuanceBase);
}

Expand Down
13 changes: 12 additions & 1 deletion contracts/l2/reservoir/L2ReservoirStorage.sol
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,20 @@
pragma solidity ^0.7.6;

/**
* @dev Storage variables for the L2Reservoir
* @dev Storage variables for the L2Reservoir, version 1
*/
contract L2ReservoirV1Storage {
// Expected nonce value for the next drip hook
uint256 public nextDripNonce;
}

/**
* @dev Storage variables for the L2Reservoir, version 2
* This version adds some variables needed when introducing the keeper reward.
*/
contract L2ReservoirV2Storage is L2ReservoirV1Storage {
// Fraction of the keeper reward to send to the retryable tx redeemer in L2 (fixed point 1e18)
uint256 public l2KeeperRewardFraction;
// Address of the L1Reservoir on L1, used to check if a ticket was auto-redeemed
address public l1ReservoirAddress;
}
Loading