Skip to content
Closed
Show file tree
Hide file tree
Changes from 1 commit
Commits
Show all changes
47 commits
Select commit Hold shift + click to select a range
c04c236
feat: implement distribution of rewards to L1 and L2 using a Reservoir
pcarranzav May 17, 2022
bef792e
fix: document potential drip reverts if issuance rate is updated [L-01]
pcarranzav Jul 11, 2022
a34e508
fix: document drip revert when l2RewardsFraction changed [L-02]
pcarranzav Jul 11, 2022
d2e53fd
fix: rename variables related to supply to issuanceBase to make it cl…
pcarranzav Jul 11, 2022
4ad867a
fix: use issuanceBase check to prevent calling initialSnapshot twice …
pcarranzav Jul 11, 2022
a8144df
test: fix tests after not allowing initialSnapshot to be called twice
pcarranzav Jul 12, 2022
5c6802f
fix: document the need for drip after a param update [L-06]
pcarranzav Jul 13, 2022
b6e1497
fix: validate L2Reservoir address on L1Reservoir [L-07]
pcarranzav Jul 12, 2022
ad09ca0
fix: rename normalizedSupply to l2IssuanceBase in L2 message to avoid…
pcarranzav Jul 13, 2022
8cd3270
fix: remove silent failure if rewardsManager is not set [L-12]
pcarranzav Jul 13, 2022
19dd310
fix: general code improvements [N-05]
pcarranzav Jul 13, 2022
6a50012
fix: use internal function to consistently set dripInterval [N-07]
pcarranzav Jul 13, 2022
965dbb3
fix: remove named returns [N-08]
pcarranzav Jul 13, 2022
f4bc6f9
fix: update some outdated docstrings and comments [N-09]
pcarranzav Jul 13, 2022
2c89ea7
fix: document why some variables are not set during initialization [N…
pcarranzav Jul 13, 2022
c1336ef
fix: add missing getters to Managed [N-11]
pcarranzav Jul 13, 2022
86cddb8
fix: separate contracts into different files [N-13]
pcarranzav Jul 14, 2022
d327b36
fix: rename some variables for clarity [N-14]
pcarranzav Jul 14, 2022
af81533
fix: document the need to retry tickets if drip is received out-of-or…
pcarranzav Jul 14, 2022
f20124a
fix: various typos [N-16]
pcarranzav Jul 14, 2022
1d1e319
fix: replace MAX_UINT256 with type().max [N-18] [N-20]
pcarranzav Jul 14, 2022
9031651
fix: remove an unused import [N-21]
pcarranzav Jul 14, 2022
e2e08d1
test: remove repeated addToCallhookWhitelist
pcarranzav Jul 15, 2022
bbb3fad
fix: use SafeMath more consistently
pcarranzav Jul 15, 2022
e2766b3
feat: keeper reward for reservoir drip through token issuance
pcarranzav Jun 5, 2022
fc42c3f
fix: don't use tx.origin as it will not work
pcarranzav Jun 8, 2022
e69c095
fix: only allow indexers, their operators, or whitelisted addresses t…
pcarranzav Jun 21, 2022
c274636
test: fix rewards and reservoir tests after restricting drip callers
pcarranzav Jun 21, 2022
51db5f9
test: add a test for the keeper reward delivery in L2
pcarranzav Jun 23, 2022
a54a629
fix: provide part of the keeper reward to L2 redeemer
pcarranzav Jun 27, 2022
e275908
fix: clean up comments about redeemer
pcarranzav Jul 15, 2022
6d0c39d
fix: more documentation details
pcarranzav Jul 15, 2022
e727133
fix: use safe math for minDripInterval
pcarranzav Jul 15, 2022
c4d583a
fix: validate input when granting/revoking drip permission
pcarranzav Jul 15, 2022
304d055
fix: docs and inheritance for IArbTxWithRedeemer
pcarranzav Jul 28, 2022
a570c8c
fix: remove minDripInterval from the drip keeper reward calculation […
pcarranzav Aug 5, 2022
2432862
fix: use L2 alias of l1ReservoirAddress when comparing getCurrentRede…
pcarranzav Aug 8, 2022
f1c9530
fix: don't include keeper reward twice when computing what to send to…
pcarranzav Aug 17, 2022
26a4922
test: add test to ensure no DoS if l2RewardsFraction is zeroed [H-04]
pcarranzav Aug 22, 2022
8869e69
test: optimize functions to advance blocks and fix some race conditions
pcarranzav Aug 22, 2022
7207a23
fix: add some missing validation on reservoirs [M-01]
pcarranzav Aug 22, 2022
117cb4a
fix: add some missing docstrings [L-04]
pcarranzav Aug 22, 2022
f2e1f81
fix: use a single-condition requires for the drip auth check [L-05]
pcarranzav Aug 22, 2022
33f7ec2
fix: add indexed params to dripper change events [N-01]
pcarranzav Aug 22, 2022
fb3ed11
fix: use explicit imports in relevant reservoir contracts [N-02]
pcarranzav Aug 22, 2022
53e0a80
test: fix call in l2Reservoir test
pcarranzav Sep 1, 2022
559ea00
fix: adjust gre, e2e and configs to account for reservoirs
pcarranzav Sep 27, 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
Prev Previous commit
Next Next commit
fix: provide part of the keeper reward to L2 redeemer
  • Loading branch information
pcarranzav committed Sep 28, 2022
commit a54a629c47ce8cd5d95967cc062cb9aa173f476c
58 changes: 50 additions & 8 deletions contracts/l2/reservoir/L2Reservoir.sol
Original file line number Diff line number Diff line change
Expand Up @@ -4,12 +4,22 @@ pragma solidity ^0.7.6;
pragma abicoder v2;

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

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

interface IArbTxWithRedeemer {
/**
* @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.
Expand All @@ -19,8 +29,12 @@ import "./L2ReservoirStorage.sol";
contract L2Reservoir is L2ReservoirV2Storage, Reservoir, IL2Reservoir {
using SafeMath for uint256;

address public constant ARB_TX_ADDRESS = 0x000000000000000000000000000000000000006E;

event DripReceived(uint256 _issuanceBase);
event NextDripNonceUpdated(uint256 _nonce);
event L1ReservoirAddressUpdated(address _l1ReservoirAddress);
event L2KeeperRewardFractionUpdated(uint256 _l2KeeperRewardFraction);

/**
* @dev Checks that the sender is the L2GraphTokenGateway as configured on the Controller.
Expand Down Expand Up @@ -52,6 +66,29 @@ contract L2Reservoir is L2ReservoirV2Storage, 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 {
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 @@ -115,14 +152,19 @@ contract L2Reservoir is L2ReservoirV2Storage, Reservoir, IL2Reservoir {
}
issuanceBase = _issuanceBase;
IGraphToken grt = graphToken();
// We'd like to reward the keeper that redeemed the tx in L2
// but this won't work right now as tx.origin will actually be the L1 sender.
// uint256 _l2KeeperReward = _keeperReward.mul(l2KeeperRewardFraction).div(TOKEN_DECIMALS);
// // solhint-disable-next-line avoid-tx-origin
// grt.transfer(tx.origin, _l2KeeperReward);
// grt.transfer(_l1Keeper, _keeperReward.sub(_l2KeeperReward));
// So for now we just send all the rewards to teh L1 keeper:
grt.transfer(_l1Keeper, _keeperReward);

// 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 != 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 teh L1 keeper:
grt.transfer(_l1Keeper, _keeperReward);
}

emit DripReceived(issuanceBase);
}

Expand Down
2 changes: 2 additions & 0 deletions contracts/l2/reservoir/L2ReservoirStorage.sol
Original file line number Diff line number Diff line change
Expand Up @@ -13,4 +13,6 @@ contract L2ReservoirV1Storage {
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;
}
73 changes: 71 additions & 2 deletions test/l2/l2Reservoir.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import { BigNumber, constants, ContractTransaction, utils } from 'ethers'
import { L2FixtureContracts, NetworkFixture } from '../lib/fixtures'

import { BigNumber as BN } from 'bignumber.js'
import { FakeContract, smock } from '@defi-wonderland/smock'

import {
advanceBlocks,
Expand All @@ -30,11 +31,13 @@ const dripIssuanceRate = toBN('1000000023206889619')
describe('L2Reservoir', () => {
let governor: Account
let testAccount1: Account
let testAccount2: Account
let mockRouter: Account
let mockL1GRT: Account
let mockL1Gateway: Account
let mockL1Reservoir: Account
let fixture: NetworkFixture
let arbTxMock: FakeContract

let grt: L2GraphToken
let l2Reservoir: L2Reservoir
Expand Down Expand Up @@ -122,7 +125,7 @@ describe('L2Reservoir', () => {
}

before(async function () {
;[governor, testAccount1, mockRouter, mockL1GRT, mockL1Gateway, mockL1Reservoir] =
;[governor, testAccount1, mockRouter, mockL1GRT, mockL1Gateway, mockL1Reservoir, testAccount2] =
await getAccounts()

fixture = new NetworkFixture()
Expand All @@ -136,6 +139,11 @@ describe('L2Reservoir', () => {
mockL1Gateway.address,
mockL1Reservoir.address,
)

arbTxMock = await smock.fake('IArbTxWithRedeemer', {
address: '0x000000000000000000000000000000000000006E',
})
arbTxMock.getCurrentRedeemer.returns(mockL1Reservoir.address)
})

beforeEach(async function () {
Expand All @@ -157,7 +165,42 @@ describe('L2Reservoir', () => {
await expect(await l2Reservoir.nextDripNonce()).to.eq(toBN('10'))
})
})

describe('setL1ReservoirAddress', async function () {
it('rejects unauthorized calls', async function () {
const tx = l2Reservoir
.connect(testAccount1.signer)
.setL1ReservoirAddress(testAccount1.address)
await expect(tx).revertedWith('Caller must be Controller governor')
})
it('sets the L1Reservoir address', async function () {
const tx = l2Reservoir.connect(governor.signer).setL1ReservoirAddress(testAccount1.address)
await expect(tx).emit(l2Reservoir, 'L1ReservoirAddressUpdated').withArgs(testAccount1.address)
await expect(await l2Reservoir.l1ReservoirAddress()).to.eq(testAccount1.address)
})
})

describe('setL2KeeperRewardFraction', async function () {
it('rejects unauthorized calls', async function () {
const tx = l2Reservoir.connect(testAccount1.signer).setL2KeeperRewardFraction(toBN(1))
await expect(tx).revertedWith('Caller must be Controller governor')
})
it('rejects invalid values (> 1)', async function () {
const tx = l2Reservoir.connect(governor.signer).setL2KeeperRewardFraction(toGRT('1.000001'))
await expect(tx).revertedWith('INVALID_VALUE')
})
it('sets the L1Reservoir address', async function () {
const tx = l2Reservoir.connect(governor.signer).setL2KeeperRewardFraction(toGRT('0.999'))
await expect(tx).emit(l2Reservoir, 'L2KeeperRewardFractionUpdated').withArgs(toGRT('0.999'))
await expect(await l2Reservoir.l2KeeperRewardFraction()).to.eq(toGRT('0.999'))
})
})

describe('receiveDrip', async function () {
beforeEach(async function () {
await l2Reservoir.connect(governor.signer).setL2KeeperRewardFraction(toGRT('0.2'))
await l2Reservoir.connect(governor.signer).setL1ReservoirAddress(mockL1Reservoir.address)
})
it('rejects the call when not called by the gateway', async function () {
const tx = l2Reservoir
.connect(governor.signer)
Expand Down Expand Up @@ -224,14 +267,40 @@ describe('L2Reservoir', () => {
)
const tx = await validGatewayFinalizeTransfer(receiveDripTx.data, reward)
dripBlock = await latestBlock()
await expect(await l2Reservoir.normalizedTokenSupplyCache()).to.eq(dripNormalizedSupply)
await expect(await l2Reservoir.issuanceBase()).to.eq(dripNormalizedSupply)
await expect(await l2Reservoir.issuanceRate()).to.eq(dripIssuanceRate)
await expect(tx).emit(l2Reservoir, 'DripReceived').withArgs(dripNormalizedSupply)
await expect(tx)
.emit(grt, 'Transfer')
.withArgs(l2Reservoir.address, testAccount1.address, reward)
await expect(await grt.balanceOf(testAccount1.address)).to.eq(reward)
})
it('delivers part of the keeper reward to the L2 redeemer', async function () {
arbTxMock.getCurrentRedeemer.returns(testAccount2.address)
await l2Reservoir.connect(governor.signer).setL2KeeperRewardFraction(toGRT('0.25'))
normalizedSupply = dripNormalizedSupply
const reward = toGRT('16')
const receiveDripTx = await l2Reservoir.populateTransaction.receiveDrip(
dripNormalizedSupply,
dripIssuanceRate,
toBN('0'),
reward,
testAccount1.address,
)
const tx = await validGatewayFinalizeTransfer(receiveDripTx.data, reward)
dripBlock = await latestBlock()
await expect(await l2Reservoir.issuanceBase()).to.eq(dripNormalizedSupply)
await expect(await l2Reservoir.issuanceRate()).to.eq(dripIssuanceRate)
await expect(tx).emit(l2Reservoir, 'DripReceived').withArgs(dripNormalizedSupply)
await expect(tx)
.emit(grt, 'Transfer')
.withArgs(l2Reservoir.address, testAccount1.address, toGRT('12'))
await expect(tx)
.emit(grt, 'Transfer')
.withArgs(l2Reservoir.address, testAccount2.address, toGRT('4'))
await expect(await grt.balanceOf(testAccount1.address)).to.eq(toGRT('12'))
await expect(await grt.balanceOf(testAccount2.address)).to.eq(toGRT('4'))
})
it('updates the normalized supply cache and issuance rate', async function () {
normalizedSupply = dripNormalizedSupply
let receiveDripTx = await l2Reservoir.populateTransaction.receiveDrip(
Expand Down