Skip to content

Commit 74030db

Browse files
committed
feat: keeper reward for reservoir drip through token issuance
1 parent 403be94 commit 74030db

File tree

8 files changed

+253
-54
lines changed

8 files changed

+253
-54
lines changed

contracts/l2/reservoir/L2Reservoir.sol

Lines changed: 13 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ import "./L2ReservoirStorage.sol";
1515
* It receives tokens for rewards from L1, and provides functions to compute accumulated and new
1616
* total rewards at a particular block number.
1717
*/
18-
contract L2Reservoir is L2ReservoirV1Storage, Reservoir, IL2Reservoir {
18+
contract L2Reservoir is L2ReservoirV2Storage, Reservoir, IL2Reservoir {
1919
using SafeMath for uint256;
2020

2121
event DripReceived(uint256 _normalizedTokenSupply);
@@ -80,14 +80,20 @@ contract L2Reservoir is L2ReservoirV1Storage, Reservoir, IL2Reservoir {
8080
* updates the normalizedTokenSupplyCache and issuanceRate,
8181
* and snapshots the accumulated rewards. If issuanceRate changes,
8282
* it also triggers a snapshot of rewards per signal on the RewardsManager.
83+
* A keeper reward will be sent to the keeper that dripped on L1, and part of it
84+
* to whoever redeemed the current retryable ticket (tx.origin)
8385
* @param _normalizedTokenSupply Snapshot of total GRT supply multiplied by L2 rewards fraction
8486
* @param _issuanceRate Rewards issuance rate, using fixed point at 1e18, and including a +1
8587
* @param _nonce Incrementing nonce to ensure messages are received in order
88+
* @param _keeperReward Keeper reward to distribute between keeper that called drip and keeper that redeemed the retryable tx
89+
* @param _l1Keeper Address of the keeper that called drip in L1
8690
*/
8791
function receiveDrip(
8892
uint256 _normalizedTokenSupply,
8993
uint256 _issuanceRate,
90-
uint256 _nonce
94+
uint256 _nonce,
95+
uint256 _keeperReward,
96+
address _l1Keeper
9197
) external override onlyL2Gateway {
9298
require(_nonce == nextDripNonce, "INVALID_NONCE");
9399
nextDripNonce = nextDripNonce.add(1);
@@ -100,6 +106,11 @@ contract L2Reservoir is L2ReservoirV1Storage, Reservoir, IL2Reservoir {
100106
snapshotAccumulatedRewards();
101107
}
102108
normalizedTokenSupplyCache = _normalizedTokenSupply;
109+
uint256 _l2KeeperReward = _keeperReward.mul(l2KeeperRewardFraction).div(TOKEN_DECIMALS);
110+
IGraphToken grt = graphToken();
111+
// solhint-disable-next-line avoid-tx-origin
112+
grt.transfer(tx.origin, _l2KeeperReward);
113+
grt.transfer(_l1Keeper, _keeperReward.sub(_l2KeeperReward));
103114
emit DripReceived(normalizedTokenSupplyCache);
104115
}
105116

contracts/l2/reservoir/L2ReservoirStorage.sol

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,3 +11,8 @@ contract L2ReservoirV1Storage {
1111
// Expected nonce value for the next drip hook
1212
uint256 public nextDripNonce;
1313
}
14+
15+
contract L2ReservoirV2Storage is L2ReservoirV1Storage {
16+
// Fraction of the keeper reward to send to the retryable tx redeemer in L2 (fixed point 1e18)
17+
uint256 public l2KeeperRewardFraction;
18+
}

contracts/reservoir/IReservoir.sol

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -49,10 +49,14 @@ interface IL2Reservoir is IReservoir {
4949
* @param _normalizedTokenSupply Snapshot of total GRT supply multiplied by L2 rewards fraction
5050
* @param _issuanceRate Rewards issuance rate, using fixed point at 1e18, and including a +1
5151
* @param _nonce Incrementing nonce to ensure messages are received in order
52+
* @param _keeperReward Keeper reward to distribute between keeper that called drip and keeper that redeemed the retryable tx
53+
* @param _l1Keeper Address of the keeper that called drip in L1
5254
*/
5355
function receiveDrip(
5456
uint256 _normalizedTokenSupply,
5557
uint256 _issuanceRate,
56-
uint256 _nonce
58+
uint256 _nonce,
59+
uint256 _keeperReward,
60+
address _l1Keeper
5761
) external;
5862
}

contracts/reservoir/L1Reservoir.sol

Lines changed: 54 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ import "./L1ReservoirStorage.sol";
1717
* It provides a function to periodically drip rewards, and functions to compute accumulated and new
1818
* total rewards at a particular block number.
1919
*/
20-
contract L1Reservoir is L1ReservoirV1Storage, Reservoir {
20+
contract L1Reservoir is L1ReservoirV2Storage, Reservoir {
2121
using SafeMath for uint256;
2222

2323
// Emitted when the initial supply snapshot is taken after contract deployment
@@ -38,6 +38,10 @@ contract L1Reservoir is L1ReservoirV1Storage, Reservoir {
3838
event RewardsDripped(uint256 _totalMinted, uint256 _sentToL2, uint256 _nextDeadline);
3939
// Emitted when the address for the L2Reservoir is updated
4040
event L2ReservoirAddressUpdated(address _l2ReservoirAddress);
41+
// Emitted when drip reward per block is updated
42+
event DripRewardPerBlockUpdated(uint256 _dripRewardPerBlock);
43+
// Emitted when minDripInterval is updated
44+
event MinDripIntervalUpdated(uint256 _minDripInterval);
4145

4246
/**
4347
* @dev Initialize this contract.
@@ -90,6 +94,26 @@ contract L1Reservoir is L1ReservoirV1Storage, Reservoir {
9094
emit L2RewardsFractionStaged(_l2RewardsFraction);
9195
}
9296

97+
/**
98+
* @dev Sets the drip reward per block
99+
* This is the reward in GRT provided to the keeper that calls drip()
100+
* @param _dripRewardPerBlock GRT accrued for each block after the threshold
101+
*/
102+
function setDripRewardPerBlock(uint256 _dripRewardPerBlock) external onlyGovernor {
103+
dripRewardPerBlock = _dripRewardPerBlock;
104+
emit DripRewardPerBlockUpdated(_dripRewardPerBlock);
105+
}
106+
107+
/**
108+
* @dev Sets the minimum drip interval
109+
* This is the minimum number of blocks between two successful drips
110+
* @param _minDripInterval Minimum number of blocks since last drip for drip to be allowed
111+
*/
112+
function setMinDripInterval(uint256 _minDripInterval) external onlyGovernor {
113+
minDripInterval = _minDripInterval;
114+
emit MinDripIntervalUpdated(_minDripInterval);
115+
}
116+
93117
/**
94118
* @dev Sets the L2 Reservoir address
95119
* This is the address on L2 to which we send tokens for rewards.
@@ -128,16 +152,23 @@ contract L1Reservoir is L1ReservoirV1Storage, Reservoir {
128152
* @param l2MaxGas Max gas for the L2 retryable ticket, only needed if L2RewardsFraction is > 0
129153
* @param l2GasPriceBid Gas price for the L2 retryable ticket, only needed if L2RewardsFraction is > 0
130154
* @param l2MaxSubmissionCost Max submission price for the L2 retryable ticket, only needed if L2RewardsFraction is > 0
155+
* @param keeperRewardBeneficiary Address to which to credit keeper reward (will be redeemed in L2 if l2RewardsFraction is nonzero)
131156
*/
132157
function drip(
133158
uint256 l2MaxGas,
134159
uint256 l2GasPriceBid,
135-
uint256 l2MaxSubmissionCost
160+
uint256 l2MaxSubmissionCost,
161+
address keeperRewardBeneficiary
136162
) external payable notPaused {
163+
require(block.number > lastRewardsUpdateBlock + minDripInterval, "WAIT_FOR_MIN_INTERVAL");
164+
137165
uint256 mintedRewardsTotal = getNewGlobalRewards(rewardsMintedUntilBlock);
138166
uint256 mintedRewardsActual = getNewGlobalRewards(block.number);
139167
// eps = (signed int) mintedRewardsTotal - mintedRewardsActual
140168

169+
uint256 keeperReward = dripRewardPerBlock.mul(
170+
block.number.sub(lastRewardsUpdateBlock).sub(minDripInterval)
171+
);
141172
if (nextIssuanceRate != issuanceRate) {
142173
rewardsManager().updateAccRewardsPerSignal();
143174
snapshotAccumulatedRewards(mintedRewardsActual); // This updates lastRewardsUpdateBlock
@@ -150,13 +181,15 @@ contract L1Reservoir is L1ReservoirV1Storage, Reservoir {
150181
rewardsMintedUntilBlock = block.number.add(dripInterval);
151182
// n = deltaR(t1, t0)
152183
uint256 newRewardsToDistribute = getNewGlobalRewards(rewardsMintedUntilBlock);
153-
// N = n - eps
154-
uint256 tokensToMint = newRewardsToDistribute.add(mintedRewardsActual).sub(
155-
mintedRewardsTotal
156-
);
184+
// N = n - eps ( + reward)
185+
uint256 tokensToMint = newRewardsToDistribute
186+
.add(mintedRewardsActual)
187+
.add(keeperReward)
188+
.sub(mintedRewardsTotal);
157189

190+
IGraphToken grt = graphToken();
158191
if (tokensToMint > 0) {
159-
graphToken().mint(address(this), tokensToMint);
192+
grt.mint(address(this), tokensToMint);
160193
}
161194

162195
uint256 tokensToSendToL2 = 0;
@@ -187,20 +220,26 @@ contract L1Reservoir is L1ReservoirV1Storage, Reservoir {
187220
tokensToSendToL2,
188221
l2MaxGas,
189222
l2GasPriceBid,
190-
l2MaxSubmissionCost
223+
l2MaxSubmissionCost,
224+
keeperReward,
225+
keeperRewardBeneficiary
191226
);
192227
} else if (l2RewardsFraction > 0) {
193228
tokensToSendToL2 = tokensToMint.mul(l2RewardsFraction).div(TOKEN_DECIMALS);
194229
_sendNewTokensAndStateToL2(
195230
tokensToSendToL2,
196231
l2MaxGas,
197232
l2GasPriceBid,
198-
l2MaxSubmissionCost
233+
l2MaxSubmissionCost,
234+
keeperReward,
235+
keeperRewardBeneficiary
199236
);
200237
} else {
201238
// Avoid locking funds in this contract if we don't need to
202239
// send a message to L2.
203240
require(msg.value == 0, "No eth value needed");
241+
// If we don't send rewards to L2, pay the keeper reward in L1
242+
grt.transfer(keeperRewardBeneficiary, keeperReward);
204243
}
205244
emit RewardsDripped(tokensToMint, tokensToSendToL2, rewardsMintedUntilBlock);
206245
}
@@ -234,14 +273,18 @@ contract L1Reservoir is L1ReservoirV1Storage, Reservoir {
234273
uint256 nTokens,
235274
uint256 maxGas,
236275
uint256 gasPriceBid,
237-
uint256 maxSubmissionCost
276+
uint256 maxSubmissionCost,
277+
uint256 keeperReward,
278+
address keeper
238279
) internal {
239280
uint256 normalizedSupply = l2RewardsFraction.mul(tokenSupplyCache).div(TOKEN_DECIMALS);
240281
bytes memory extraData = abi.encodeWithSelector(
241282
IL2Reservoir.receiveDrip.selector,
242283
normalizedSupply,
243284
issuanceRate,
244-
nextDripNonce
285+
nextDripNonce,
286+
keeperReward,
287+
keeper
245288
);
246289
nextDripNonce = nextDripNonce.add(1);
247290
bytes memory data = abi.encode(maxSubmissionCost, extraData);

contracts/reservoir/L1ReservoirStorage.sol

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,3 +23,10 @@ contract L1ReservoirV1Storage {
2323
// Auto-incrementing nonce that will be used when sending rewards to L2, to ensure ordering
2424
uint256 public nextDripNonce;
2525
}
26+
27+
contract L1ReservoirV2Storage is L1ReservoirV1Storage {
28+
// Minimum number of blocks since last drip for a new drip to be allowed
29+
uint256 public minDripInterval;
30+
// Drip reward in GRT for each block since lastRewardsUpdateBlock + dripRewardThreshold
31+
uint256 public dripRewardPerBlock;
32+
}

test/l2/l2Reservoir.test.ts

Lines changed: 27 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -160,7 +160,13 @@ describe('L2Reservoir', () => {
160160
it('rejects the call when not called by the gateway', async function () {
161161
const tx = l2Reservoir
162162
.connect(governor.signer)
163-
.receiveDrip(dripNormalizedSupply, dripIssuanceRate, toBN('0'))
163+
.receiveDrip(
164+
dripNormalizedSupply,
165+
dripIssuanceRate,
166+
toBN('0'),
167+
toBN('0'),
168+
testAccount1.address,
169+
)
164170
await expect(tx).revertedWith('ONLY_GATEWAY')
165171
})
166172
it('rejects the call when received out of order', async function () {
@@ -169,6 +175,8 @@ describe('L2Reservoir', () => {
169175
dripNormalizedSupply,
170176
dripIssuanceRate,
171177
toBN('0'),
178+
toBN('0'),
179+
testAccount1.address,
172180
)
173181
const tx = await validGatewayFinalizeTransfer(receiveDripTx.data)
174182
dripBlock = await latestBlock()
@@ -181,6 +189,8 @@ describe('L2Reservoir', () => {
181189
dripNormalizedSupply.add(1),
182190
dripIssuanceRate.add(1),
183191
toBN('2'),
192+
toBN('0'),
193+
testAccount1.address,
184194
)
185195
const tx2 = gatewayFinalizeTransfer(receiveDripTx.data)
186196
dripBlock = await latestBlock()
@@ -192,6 +202,8 @@ describe('L2Reservoir', () => {
192202
dripNormalizedSupply,
193203
dripIssuanceRate,
194204
toBN('0'),
205+
toBN('0'),
206+
testAccount1.address,
195207
)
196208
const tx = await validGatewayFinalizeTransfer(receiveDripTx.data)
197209
dripBlock = await latestBlock()
@@ -205,6 +217,8 @@ describe('L2Reservoir', () => {
205217
dripNormalizedSupply,
206218
dripIssuanceRate,
207219
toBN('0'),
220+
toBN('0'),
221+
testAccount1.address,
208222
)
209223
let tx = await validGatewayFinalizeTransfer(receiveDripTx.data)
210224
dripBlock = await latestBlock()
@@ -216,6 +230,8 @@ describe('L2Reservoir', () => {
216230
dripNormalizedSupply.add(1),
217231
dripIssuanceRate.add(1),
218232
toBN('1'),
233+
toBN('0'),
234+
testAccount1.address,
219235
)
220236
tx = await gatewayFinalizeTransfer(receiveDripTx.data)
221237
dripBlock = await latestBlock()
@@ -232,6 +248,8 @@ describe('L2Reservoir', () => {
232248
dripNormalizedSupply,
233249
dripIssuanceRate,
234250
toBN('0'),
251+
toBN('0'),
252+
testAccount1.address,
235253
)
236254
let tx = await validGatewayFinalizeTransfer(receiveDripTx.data)
237255
dripBlock = await latestBlock()
@@ -243,6 +261,8 @@ describe('L2Reservoir', () => {
243261
dripNormalizedSupply.add(1),
244262
dripIssuanceRate,
245263
toBN('1'),
264+
toBN('0'),
265+
testAccount1.address,
246266
)
247267
tx = await gatewayFinalizeTransfer(receiveDripTx.data)
248268
dripBlock = await latestBlock()
@@ -259,6 +279,8 @@ describe('L2Reservoir', () => {
259279
dripNormalizedSupply,
260280
dripIssuanceRate,
261281
toBN('0'),
282+
toBN('0'),
283+
testAccount1.address,
262284
)
263285
let tx = await validGatewayFinalizeTransfer(receiveDripTx.data)
264286
dripBlock = await latestBlock()
@@ -271,6 +293,8 @@ describe('L2Reservoir', () => {
271293
dripNormalizedSupply.add(1),
272294
dripIssuanceRate,
273295
toBN('2'),
296+
toBN('0'),
297+
testAccount1.address,
274298
)
275299
tx = await gatewayFinalizeTransfer(receiveDripTx.data)
276300
dripBlock = await latestBlock()
@@ -291,6 +315,8 @@ describe('L2Reservoir', () => {
291315
dripNormalizedSupply,
292316
ISSUANCE_RATE_PER_BLOCK,
293317
toBN('0'),
318+
toBN('0'),
319+
testAccount1.address,
294320
)
295321
await validGatewayFinalizeTransfer(receiveDripTx.data)
296322
dripBlock = await latestBlock()

0 commit comments

Comments
 (0)